- Add KOReader sync endpoints to main application router - Create Bruno API collection for testing KOReader endpoints - Add integration tests for KOReader functionality - Include comprehensive README with setup instructions - Test coverage for progress, metadata, library, and bookmarks sync - Part of Phase 3 KOReader Integration implementation
298 lines
7.4 KiB
Go
298 lines
7.4 KiB
Go
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"])
|
|
})
|
|
}
|