feat(notes): raise annotation text limit from 10,000 to 100,000 chars
The 10k validator cap rejected legitimate long-form notes — a scholarly reading note with quoted passages and footnotes lands around 10.1k chars and failed at save with the raw validator error. Raise the cap to 100,000 on all six annotation text fields in the media handler request structs: - CreateMediaNoteRequest / UpdateMediaNoteRequest Content - CreateMediaHighlightRequest / UpdateMediaHighlightRequest NoteText - CreateMediaBookmarkRequest / UpdateMediaBookmarkRequest Notes No other layer changes: the media_notes / media_highlights columns are unbounded TEXT, and the KOReader + websocket sync paths never had a length cap, so the REST API now matches the rest of the system instead of being the strictest gate. New integration test pins the boundary: 50k and 100k-char notes return 201, 100,001 chars returns 400.
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMediaNoteLengthLimits verifies the note content validator cap
|
||||||
|
// (internal/handlers/media.go): content up to 100,000 chars is accepted,
|
||||||
|
// longer content is rejected with 400.
|
||||||
|
func TestMediaNoteLengthLimits(t *testing.T) {
|
||||||
|
setup := setupTestServer(t)
|
||||||
|
|
||||||
|
token := setup.Token
|
||||||
|
mediaItemID := createTestMediaItemID(t, setup)
|
||||||
|
|
||||||
|
postNote := func(content string) int {
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"content": content,
|
||||||
|
"position": "page:1",
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/"+mediaItemID+"/notes", bytes.NewBuffer(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
_ = Body.Close()
|
||||||
|
}(resp.Body)
|
||||||
|
return resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("50k-char note accepted", func(t *testing.T) {
|
||||||
|
assert.Equal(t, http.StatusCreated, postNote(strings.Repeat("a", 50000)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("100k-char note accepted at boundary", func(t *testing.T) {
|
||||||
|
assert.Equal(t, http.StatusCreated, postNote(strings.Repeat("b", 100000)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("over-limit note rejected", func(t *testing.T) {
|
||||||
|
assert.Equal(t, http.StatusBadRequest, postNote(strings.Repeat("c", 100001)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -102,13 +102,13 @@ type UpdateMediaItemRequest struct {
|
|||||||
|
|
||||||
// CreateMediaNoteRequest represents the request for creating a media note
|
// CreateMediaNoteRequest represents the request for creating a media note
|
||||||
type CreateMediaNoteRequest struct {
|
type CreateMediaNoteRequest struct {
|
||||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
Content string `json:"content" validate:"required,min=1,max=100000"`
|
||||||
Position string `json:"position"`
|
Position string `json:"position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMediaNoteRequest represents the request for updating a media note
|
// UpdateMediaNoteRequest represents the request for updating a media note
|
||||||
type UpdateMediaNoteRequest struct {
|
type UpdateMediaNoteRequest struct {
|
||||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
Content string `json:"content" validate:"required,min=1,max=100000"`
|
||||||
Position string `json:"position"`
|
Position string `json:"position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ type CreateMediaHighlightRequest struct {
|
|||||||
EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"`
|
EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"`
|
||||||
EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"`
|
EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"`
|
||||||
Color string `json:"color" validate:"omitempty,len=7"`
|
Color string `json:"color" validate:"omitempty,len=7"`
|
||||||
NoteText string `json:"note_text" validate:"max=10000"`
|
NoteText string `json:"note_text" validate:"max=100000"`
|
||||||
NoteID string `json:"note_id"`
|
NoteID string `json:"note_id"`
|
||||||
PercentageStart float64 `json:"percentage_start"`
|
PercentageStart float64 `json:"percentage_start"`
|
||||||
PercentageEnd float64 `json:"percentage_end"`
|
PercentageEnd float64 `json:"percentage_end"`
|
||||||
@@ -135,7 +135,7 @@ type UpdateMediaHighlightRequest struct {
|
|||||||
EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"`
|
EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"`
|
||||||
EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"`
|
EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"`
|
||||||
Color string `json:"color" validate:"omitempty,len=7"`
|
Color string `json:"color" validate:"omitempty,len=7"`
|
||||||
NoteText string `json:"note_text" validate:"max=10000"`
|
NoteText string `json:"note_text" validate:"max=100000"`
|
||||||
NoteID string `json:"note_id"`
|
NoteID string `json:"note_id"`
|
||||||
PercentageStart float64 `json:"percentage_start"`
|
PercentageStart float64 `json:"percentage_start"`
|
||||||
PercentageEnd float64 `json:"percentage_end"`
|
PercentageEnd float64 `json:"percentage_end"`
|
||||||
@@ -146,7 +146,7 @@ type UpdateMediaHighlightRequest struct {
|
|||||||
type CreateMediaBookmarkRequest struct {
|
type CreateMediaBookmarkRequest struct {
|
||||||
Title string `json:"title" validate:"required,min=1,max=255"`
|
Title string `json:"title" validate:"required,min=1,max=255"`
|
||||||
Position string `json:"position" validate:"max=100"`
|
Position string `json:"position" validate:"max=100"`
|
||||||
Notes string `json:"notes" validate:"max=10000"`
|
Notes string `json:"notes" validate:"max=100000"`
|
||||||
CfiPosition string `json:"cfi_position" validate:"max=255"`
|
CfiPosition string `json:"cfi_position" validate:"max=255"`
|
||||||
PageNumber int32 `json:"page_number"`
|
PageNumber int32 `json:"page_number"`
|
||||||
ChapterNumber int32 `json:"chapter_number"`
|
ChapterNumber int32 `json:"chapter_number"`
|
||||||
@@ -160,7 +160,7 @@ type CreateMediaBookmarkRequest struct {
|
|||||||
// UpdateMediaBookmarkRequest represents the request for updating a media bookmark
|
// UpdateMediaBookmarkRequest represents the request for updating a media bookmark
|
||||||
type UpdateMediaBookmarkRequest struct {
|
type UpdateMediaBookmarkRequest struct {
|
||||||
Title string `json:"title" validate:"required,min=1,max=255"`
|
Title string `json:"title" validate:"required,min=1,max=255"`
|
||||||
Notes string `json:"notes" validate:"max=10000"`
|
Notes string `json:"notes" validate:"max=100000"`
|
||||||
Position string `json:"position" validate:"max=100"`
|
Position string `json:"position" validate:"max=100"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user