package main import ( "bytes" "encoding/json" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestKOReaderSyncProgress_RequestBody(t *testing.T) { t.Run("valid request body", func(t *testing.T) { reqBody := map[string]interface{}{ "books": []map[string]interface{}{ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "percentage": 0.45, "progress": 0.45, "chapter": 5, }, }, "sync_mode": "immediate", } body, err := json.Marshal(reqBody) assert.NoError(t, err) assert.Contains(t, string(body), "percentage") req := httptest.NewRequest("POST", "/api/sync/koreader/progress", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") assert.Equal(t, "POST", req.Method) assert.Equal(t, "/api/sync/koreader/progress", req.URL.Path) }) t.Run("request with multiple books", func(t *testing.T) { reqBody := map[string]interface{}{ "books": []map[string]interface{}{ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "percentage": 0.25, }, { "uuid": "660e8400-e29b-41d4-a716-446655440001", "percentage": 0.50, }, }, "sync_mode": "checkpoint", } body, err := json.Marshal(reqBody) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) books := parsed["books"].([]interface{}) assert.Len(t, books, 2) }) t.Run("request with highlights and bookmarks", func(t *testing.T) { reqBody := map[string]interface{}{ "book_uuid": "550e8400-e29b-41d4-a716-446655440000", "bookmarks": []map[string]interface{}{ { "chapter": 3, "page": 45, "text": "Bookmarked text", }, }, "highlights": []map[string]interface{}{ { "chapter": 3, "page": 45, "text": "highlighted text", "color": "#ffff00", }, }, } body, err := json.Marshal(reqBody) assert.NoError(t, err) assert.Contains(t, string(body), "bookmarks") assert.Contains(t, string(body), "highlights") }) } func TestKOReaderMetadataParsing(t *testing.T) { t.Run("parse progress data", func(t *testing.T) { progressData := map[string]interface{}{ "percentage": 0.45, "chapter": 5, "epubcfi": "epubcfi(/6/4/2:15)", "character": int64(15432), "page": 89, "total_pages": 200, "chapter_progress": 0.234, } body, err := json.Marshal(progressData) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) assert.Equal(t, 0.45, parsed["percentage"]) assert.Equal(t, "epubcfi(/6/4/2:15)", parsed["epubcfi"]) }) t.Run("parse annotation data", func(t *testing.T) { annotations := map[string]interface{}{ "highlights": []map[string]interface{}{ { "text": "highlighted text", "pos0": "epubcfi(/6/4/2:15)", "pos1": "epubcfi(/6/4/2:20)", "color": "#ffff00", }, }, "notes": []map[string]interface{}{ { "text": "My note", "pos0": "epubcfi(/6/4/2:15)", }, }, } body, err := json.Marshal(annotations) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) highlights := parsed["highlights"].([]interface{}) assert.Len(t, highlights, 1) notes := parsed["notes"].([]interface{}) assert.Len(t, notes, 1) }) } func TestKOReaderResponseFormats(t *testing.T) { t.Run("sync progress response", func(t *testing.T) { response := map[string]interface{}{ "sync_status": "accepted", "books_synced": 1, "conflicts": []interface{}{}, "timestamp": "2026-01-30T20:00:00Z", "device_updated": true, } body, err := json.Marshal(response) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) assert.Equal(t, "accepted", parsed["sync_status"]) assert.Equal(t, float64(1), parsed["books_synced"]) }) t.Run("metadata response", func(t *testing.T) { response := map[string]interface{}{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "title": "Test Book", "authors": []string{"Test Author"}, "progress": map[string]interface{}{ "percentage": 0.45, "chapter": 5, }, "annotations": map[string]interface{}{ "highlights": []interface{}{}, "notes": []interface{}{}, }, } body, err := json.Marshal(response) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) assert.Equal(t, "Test Book", parsed["title"]) assert.NotNil(t, parsed["progress"]) }) t.Run("library response", func(t *testing.T) { response := map[string]interface{}{ "library_sync": []map[string]interface{}{ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "title": "Book 1", "author": "Author 1", "percent_read": 45.0, "pages_remaining": 115, "bookmark_count": 3, }, }, "total_books": 1, "last_sync": "2026-01-30T20:00:00Z", } body, err := json.Marshal(response) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) assert.Equal(t, float64(1), parsed["total_books"]) }) } func TestKOReaderErrorHandling(t *testing.T) { t.Run("invalid UUID format", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/sync/koreader/metadata/invalid-uuid", nil) // This should fail UUID parsing _, err := uuid.Parse(req.URL.Path[len("/api/sync/koreader/metadata/"):]) assert.Error(t, err) }) t.Run("missing authorization header", func(t *testing.T) { req := httptest.NewRequest("POST", "/api/sync/koreader/progress", nil) req.Header.Set("Content-Type", "application/json") authHeader := req.Header.Get("Authorization") assert.Empty(t, authHeader) }) t.Run("invalid percentage value", func(t *testing.T) { progressData := map[string]interface{}{ "percentage": 1.5, // Invalid: > 1.0 } body, err := json.Marshal(progressData) assert.NoError(t, err) var parsed map[string]interface{} err = json.Unmarshal(body, &parsed) assert.NoError(t, err) percentage := parsed["percentage"].(float64) assert.Greater(t, percentage, 1.0) }) } func TestKOReaderDeviceMatching(t *testing.T) { t.Run("match by UUID", func(t *testing.T) { bookUUID := "550e8400-e29b-41d4-a716-446655440000" bookData := map[string]interface{}{ "uuid": bookUUID, "percentage": 0.45, } body, _ := json.Marshal(bookData) var parsed map[string]interface{} json.Unmarshal(body, &parsed) assert.Equal(t, bookUUID, parsed["uuid"]) }) t.Run("match by file path", func(t *testing.T) { bookData := map[string]interface{}{ "file_path": "/path/to/book.epub", "percentage": 0.45, } body, _ := json.Marshal(bookData) var parsed map[string]interface{} json.Unmarshal(body, &parsed) assert.Equal(t, "/path/to/book.epub", parsed["file_path"]) }) t.Run("match by title and author", func(t *testing.T) { bookData := map[string]interface{}{ "title": "Test Book", "authors": []string{"Test Author"}, "percentage": 0.45, } body, _ := json.Marshal(bookData) var parsed map[string]interface{} json.Unmarshal(body, &parsed) assert.Equal(t, "Test Book", parsed["title"]) assert.NotNil(t, parsed["authors"]) }) }