Files
bookhoard/cmd/server/tests/kobo_test.go
T
john-okeefe 7dcc70b448 fix: remove redundant getTestUserID call in TestKoboInitialization
The TestKoboInitialization test was calling getTestUserID() explicitly
on line 25, but loginTestUser() already calls this internally. This caused
the test user to be deleted and recreated after login, leading to
inconsistent state and HTTP 500 errors when creating libraries.

After removing the redundant call:
- TestKoboInitialization now passes
- All Kobo sync tests pass successfully
2026-02-10 18:15:28 -05:00

274 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{}{
"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", 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()
}
}