Files
bookhoard/cmd/server/tests/kobo_test.go
T
john-okeefe a3aa9f67ac feat: add Kobo device sync support and fix device route protection
- Add Kobo sync handler with markup, bookmark, analytics, and initialization endpoints
- Add Kobo integration tests and Bruno API test collection
- Move device approve/reject routes from public to protected routes
- Enhance test infrastructure with DATABASE_URL support and helper functions
- Fix device GetDevice handler nil pointer handling
- Clean up test reports and session files
2026-01-30 23:58:34 -05:00

227 lines
6.2 KiB
Go

package main
import (
"bookmann/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)
_ = createTestEbookID(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)
_ = createTestEbookID(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)
ebookID := createTestEbookID(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": ebookID,
"PercentRead": 45.6,
"EntitlementId": "ent-123",
"RemainingTimeMinutes": 120,
"LastModified": "2026-01-30T20:00:00Z",
},
},
"BookmarkSync": []map[string]interface{}{
{
"BookmarkId": "bookmark-1",
"ContentId": ebookID,
"BookmarkText": "This is highlighted text",
"BookmarkType": "annotation",
"BookmarkTitle": "Chapter 3",
},
{
"BookmarkId": "bookmark-2",
"ContentId": ebookID,
"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)
ebookID := createTestEbookID(t, ts, token)
t.Run("successful bookmark sync", func(t *testing.T) {
reqBody := map[string]interface{}{
"BookmarkSync": []map[string]interface{}{
{
"BookmarkId": "bookmark-3",
"ContentId": ebookID,
"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)
ebookID := createTestEbookID(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": ebookID,
"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()
}
}