The test was sending a single object with a "meta" field, but the handler expects an array of KoboAnalyticsTest objects (matching other Kobo endpoints). Changed: - Removed unsupported "meta" field - Converted single object to array format - Now matches Kobo protocol pattern used by /markup and /bookmark endpoints All Kobo tests now pass: - TestKoboInitialization ✅ - TestKoboLibrarySync ✅ - TestKoboMarkupSync ✅ - TestKoboBookmarkSync ✅ - TestKoboAnalyticsGettests ✅ - TestKoboDeviceHeaderParsing ✅
273 lines
8.2 KiB
Go
273 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bookhoard/internal/handlers"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"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")
|
|
}
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
_ = createTestMediaItemID(t, setup.Server, token)
|
|
|
|
log.Printf("[DEBUG] Kobo test setup: creating device and media")
|
|
|
|
t.Run("successful initialization", func(t *testing.T) {
|
|
// Create Kobo device using TestDeviceSetup for proper authentication
|
|
deviceSetup := setupDeviceTest(t)
|
|
koboDevice := deviceSetup.CreateDevice(t, "Test Kobo", "kobo", "kobo-clara-test")
|
|
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/kobo/v1/initialization", nil)
|
|
req.Header.Set("Authorization", "Bearer "+koboDevice.AuthToken)
|
|
req.Header.Set("x-kobo-device", fmt.Sprintf(`{"DeviceId":"%s","Model":"Kobo Clara","SerialNumber":"%s"}`,
|
|
koboDevice.ID.String(), koboDevice.Identifier))
|
|
|
|
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")
|
|
}
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
_ = createTestMediaItemID(t, setup.Server, token)
|
|
|
|
log.Printf("[DEBUG] Kobo test setup: creating device and media")
|
|
|
|
t.Run("successful library sync", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
koboDevice := deviceSetup.CreateDevice(t, "Test Kobo", "kobo", "kobo-clara-test")
|
|
|
|
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/kobo/v1/initialization", nil)
|
|
req.Header.Set("Authorization", "Bearer "+koboDevice.AuthToken)
|
|
req.Header.Set("x-kobo-device", fmt.Sprintf(`{"DeviceId":"%s","Model":"Kobo Clara","SerialNumber":"%s"}`,
|
|
koboDevice.ID.String(), koboDevice.Identifier))
|
|
|
|
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")
|
|
}
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
mediaItemID := createTestMediaItemID(t, setup.Server, token)
|
|
|
|
log.Printf("[DEBUG] Kobo test setup: creating device and media")
|
|
|
|
t.Run("successful markup sync with annotations and bookmarks", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
koboDevice := deviceSetup.CreateDevice(t, "Test Kobo", "kobo", "kobo-clara-markup")
|
|
|
|
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 about book",
|
|
"BookmarkType": "bookmark",
|
|
},
|
|
{
|
|
"BookmarkId": "epubcfi(/6/4[chap1]!/4/2/1:0)",
|
|
"ContentId": mediaItemID,
|
|
"BookmarkType": "last-read-place",
|
|
"Hidden": true,
|
|
"Chapter": 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/kobo/markup", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+koboDevice.AuthToken)
|
|
req.Header.Set("x-kobo-device", fmt.Sprintf(`{"DeviceId":"%s","Model":"Kobo Clara","SerialNumber":"%s"}`,
|
|
koboDevice.ID.String(), koboDevice.Identifier))
|
|
|
|
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")
|
|
}
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
mediaItemID := createTestMediaItemID(t, setup.Server, token)
|
|
|
|
log.Printf("[DEBUG] Kobo test setup: creating device and media")
|
|
|
|
t.Run("successful bookmark sync with last-read-place", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
koboDevice := deviceSetup.CreateDevice(t, "Test Kobo", "kobo", "kobo-clara-bookmark")
|
|
|
|
reqBody := map[string]interface{}{
|
|
"BookmarkSync": []map[string]interface{}{
|
|
{
|
|
"BookmarkId": "bookmark-3",
|
|
"ContentId": mediaItemID,
|
|
"BookmarkText": "Important note about book",
|
|
"BookmarkType": "bookmark",
|
|
"DateCreated": "2026-01-30T19:55:00Z",
|
|
},
|
|
{
|
|
"BookmarkId": "epubcfi(/6/4[chap1]!/4/2/1:156)",
|
|
"ContentId": mediaItemID,
|
|
"BookmarkType": "last-read-place",
|
|
"Hidden": true,
|
|
"Chapter": 1,
|
|
"DateCreated": "2026-01-30T19:55:00Z",
|
|
},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/kobo/bookmark", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+koboDevice.AuthToken)
|
|
req.Header.Set("x-kobo-device", fmt.Sprintf(`{"DeviceId":"%s","Model":"Kobo Clara","SerialNumber":"%s"}`,
|
|
koboDevice.ID.String(), koboDevice.Identifier))
|
|
|
|
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")
|
|
}
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
token := loginTestUser(t, setup.Server, setup.DB)
|
|
mediaItemID := createTestMediaItemID(t, setup.Server, token)
|
|
|
|
log.Printf("[DEBUG] Kobo test setup: creating device and media")
|
|
|
|
t.Run("successful analytics tests", func(t *testing.T) {
|
|
deviceSetup := setupDeviceTest(t)
|
|
koboDevice := deviceSetup.CreateDevice(t, "Test Kobo", "kobo", "kobo-clara-analytics")
|
|
|
|
reqBody := []map[string]interface{}{
|
|
{
|
|
"ContentId": mediaItemID,
|
|
"ReadingEvent": "Reading",
|
|
"RemainingTimeMin": 180,
|
|
"PercentRead": 67.8,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/kobo/v1/analytics/gettests", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+koboDevice.AuthToken)
|
|
req.Header.Set("x-kobo-device", fmt.Sprintf(`{"DeviceId":"%s","Model":"Kobo Clara","SerialNumber":"%s"}`,
|
|
koboDevice.ID.String(), koboDevice.Identifier))
|
|
|
|
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()
|
|
}
|
|
}
|