package main import ( "bookhoard/internal/handlers" "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestKoboInitialization(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts, db, _ := setupTestServer(t) defer closeTestServer(t, ts, db) token := loginTestUser(t, ts, db) _ = getTestUserID(t, db) _ = createTestMediaItemID(t, ts, token) t.Run("successful initialization", func(t *testing.T) { req, _ := http.NewRequest("GET", ts.URL+"/api/sync/kobo/test-token/v1/initialization", nil) req.Header.Set("Authorization", "Bearer test-auth-token") client := &http.Client{} resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) }) } func TestKoboLibrarySync(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts, db, _ := setupTestServer(t) defer closeTestServer(t, ts, db) token := loginTestUser(t, ts, db) _ = createTestMediaItemID(t, ts, token) t.Run("successful library sync", func(t *testing.T) { req, _ := http.NewRequest("GET", ts.URL+"/api/sync/kobo/test-token/v1/initialization", nil) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) }) } func TestKoboMarkupSync(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts, db, _ := setupTestServer(t) defer closeTestServer(t, ts, db) token := loginTestUser(t, ts, db) mediaItemID := createTestMediaItemID(t, ts, token) t.Run("successful markup sync with annotations and bookmarks", func(t *testing.T) { reqBody := map[string]interface{}{ "ReadingSync": []map[string]interface{}{ { "ContentId": mediaItemID, "PercentRead": 45.6, "EntitlementId": "ent-123", "RemainingTimeMinutes": 120, "LastModified": "2026-01-30T20:00:00Z", }, }, "BookmarkSync": []map[string]interface{}{ { "BookmarkId": "bookmark-1", "ContentId": mediaItemID, "BookmarkText": "This is highlighted text", "BookmarkType": "annotation", "BookmarkTitle": "Chapter 3", }, { "BookmarkId": "bookmark-2", "ContentId": mediaItemID, "BookmarkText": "This is my note abouts book", "BookmarkType": "bookmark", }, }, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", ts.URL+"/api/sync/kobo/markup", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-kobo-device", `{"DeviceId":"kobo-clara-test","Model":"Kobo Clara","SerialNumber":"N123456789"}`) client := &http.Client{} resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) assert.Contains(t, result, "Status") }) } func TestKoboBookmarkSync(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts, db, _ := setupTestServer(t) defer closeTestServer(t, ts, db) token := loginTestUser(t, ts, db) mediaItemID := createTestMediaItemID(t, ts, token) t.Run("successful bookmark sync", func(t *testing.T) { reqBody := map[string]interface{}{ "BookmarkSync": []map[string]interface{}{ { "BookmarkId": "bookmark-3", "ContentId": mediaItemID, "BookmarkText": "Important note abouts book", "BookmarkType": "bookmark", "DateCreated": "2026-01-30T19:55:00Z", }, }, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", ts.URL+"/api/sync/kobo/bookmark", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-kobo-device", `{"DeviceId":"kobo-clara-test","Model":"Kobo Clara","SerialNumber":"N123456789"}`) client := &http.Client{} resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) assert.Contains(t, result, "Status") }) } func TestKoboAnalyticsGettests(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } ts, db, _ := setupTestServer(t) defer closeTestServer(t, ts, db) token := loginTestUser(t, ts, db) mediaItemID := createTestMediaItemID(t, ts, token) t.Run("successful analytics tests", func(t *testing.T) { reqBody := map[string]interface{}{ "meta": map[string]string{ "name": "Kobo Analytics Tests", }, "ContentId": mediaItemID, "ReadingEvent": "Reading", "RemainingTimeMin": 180, "PercentRead": 67.8, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", ts.URL+"/api/sync/kobo/v1/analytics/gettests", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-kobo-device", `{"DeviceId":"kobo-clara-test","Model":"Kobo Clara","SerialNumber":"N123456789"}`) client := &http.Client{} resp, err := client.Do(req) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) assert.Contains(t, result, "Status") }) } func TestKoboDeviceHeaderParsing(t *testing.T) { t.Run("valid device header", func(t *testing.T) { reqBody := map[string]interface{}{ "DeviceId": "kobo-clara-test", "Model": "Kobo Clara", "SerialNumber": "N123456789", "Firmware": "4.38.23555", } jsonData, _ := json.Marshal(reqBody) var device handlers.KoboDeviceInfo err := json.Unmarshal(jsonData, &device) require.NoError(t, err) assert.Equal(t, "kobo-clara-test", device.DeviceID) assert.Equal(t, "Kobo Clara", device.Model) assert.Equal(t, "N123456789", device.SerialNumber) assert.Equal(t, "4.38.23555", device.Firmware) }) } func closeTestServer(t *testing.T, ts interface{}, db interface{}) { if ts, ok := ts.(*httptest.Server); ok { ts.Close() } }