test(bruno): Add API tests for Kobo, Koreader, OPDS, collections

- Add Kobo bookmark/markup sync tests
- Add Koreader progress/bookmark sync tests
- Add OPDS feed tests
- Add collection tests
- Add sync tests for book linking
This commit is contained in:
2026-01-31 22:32:56 -05:00
parent 0a97b679a6
commit d6d208c3d1
33 changed files with 936 additions and 0 deletions
@@ -0,0 +1,25 @@
meta {
name: Add Books to Collection
type: http
seq: 6
}
post {
url: {{base_url}}/api/collections/{{collection_id}}/books
body: json
auth: bearer
}
auth:bearer {
token: {{token}}
}
body:json {
{
"book_ids": [
"{{book_id_1}}",
"{{book_id_2}}",
"{{book_id_3}}"
]
}
}
+37
View File
@@ -0,0 +1,37 @@
meta {
name: Create Collection
type: http
seq: 1
}
post {
url: {{base_url}}/api/collections
body: json
auth: bearer
}
auth:bearer {
token: {{token}}
}
body:json {
{
"name": "Science Fiction",
"description": "My favorite sci-fi books",
"color": "#ff0000",
"icon": "🚀",
"auto_assign_rules": [
{
"id": "rule-1",
"field": "genre",
"operator": "equals",
"value": "Science Fiction",
"priority": 8
}
],
"view_settings": {
"sort_by": "title",
"view_mode": "grid"
}
}
}
@@ -0,0 +1,23 @@
meta {
name: Create Device Mapping
type: http
seq: 9
}
post {
url: {{base_url}}/api/devices/{{device_id}}/collections
body: json
auth: bearer
}
auth:bearer {
token: {{token}}
}
body:json {
{
"collection_id": "{{collection_id}}",
"device_shelf_name": "Sci-Fi",
"sync_direction": "bidirectional"
}
}
+14
View File
@@ -0,0 +1,14 @@
meta {
name: Delete Collection
type: http
seq: 5
}
delete {
url: {{base_url}}/api/collections/{{collection_id}}
auth: bearer
}
auth:bearer {
token: {{token}}
}
@@ -0,0 +1,14 @@
meta {
name: Delete Device Mapping
type: http
seq: 11
}
delete {
url: {{base_url}}/api/devices/{{device_id}}/collections/{{mapping_id}}
auth: bearer
}
auth:bearer {
token: {{token}}
}
@@ -0,0 +1,14 @@
meta {
name: Get Book Collections
type: http
seq: 12
}
get {
url: {{base_url}}/api/collections/books/{{book_id}}
auth: bearer
}
auth:bearer {
token: {{token}}
}
+14
View File
@@ -0,0 +1,14 @@
meta {
name: Get Collection
type: http
seq: 3
}
get {
url: {{base_url}}/api/collections/{{collection_id}}
auth: bearer
}
auth:bearer {
token: {{token}}
}
+14
View File
@@ -0,0 +1,14 @@
meta {
name: Get Collections
type: http
seq: 2
}
get {
url: {{base_url}}/api/collections?include_auto=true&sort_by=name
auth: bearer
}
auth:bearer {
token: {{token}}
}
+14
View File
@@ -0,0 +1,14 @@
meta {
name: Get Device Mappings
type: http
seq: 8
}
get {
url: {{base_url}}/api/devices/{{device_id}}/collections
auth: bearer
}
auth:bearer {
token: {{token}}
}
@@ -0,0 +1,14 @@
meta {
name: Remove Book from Collection
type: http
seq: 7
}
delete {
url: {{base_url}}/api/collections/{{collection_id}}/books/{{book_id}}
auth: bearer
}
auth:bearer {
token: {{token}}
}
+37
View File
@@ -0,0 +1,37 @@
meta {
name: Update Collection
type: http
seq: 4
}
put {
url: {{base_url}}/api/collections/{{collection_id}}
body: json
auth: bearer
}
auth:bearer {
token: {{token}}
}
body:json {
{
"name": "Sci-Fi Favorites",
"description": "Updated description",
"color": "#00ff00",
"icon": "⭐",
"auto_assign_rules": [
{
"id": "rule-2",
"field": "series",
"operator": "equals",
"value": "Foundation",
"priority": 9
}
],
"view_settings": {
"sort_by": "author",
"view_mode": "list"
}
}
}
@@ -0,0 +1,22 @@
meta {
name: Update Device Mapping
type: http
seq: 10
}
put {
url: {{base_url}}/api/devices/{{device_id}}/collections/{{mapping_id}}
body: json
auth: bearer
}
auth:bearer {
token: {{token}}
}
body:json {
{
"device_shelf_name": "Science Fiction",
"sync_direction": "book_to_device"
}
}
+30
View File
@@ -0,0 +1,30 @@
meta {
name: "Kobo Bookmark Sync - Enhanced with ContentId Mapping"
type: http
seq: 3
}
post {
url: {{base_url}}/api/v1/kobo/bookmark
body: json({
"BookmarkSync": [
{
"BookmarkId": "bookmark_2",
"ContentId": "kobo_xyz789",
"BookmarkText": "Important note",
"BookmarkType": "bookmark",
"DateCreated": "2026-01-31T12:00:00Z"
}
]
})
auth: {
type: bearer
bearer: {{device_token}}
}
}
assert {
response.status == 200
response.body.Status == "Success"
response.body.BookmarksSynced >= 0
}
+21
View File
@@ -0,0 +1,21 @@
meta {
name: "Kobo Initialization - Enhanced with ContentId Mapping"
type: http
seq: 1
}
get {
url: {{base_url}}/api/v1/kobo/initialization
body: none
auth: {
type: bearer
bearer: {{device_token}}
}
}
assert {
response.status == 200
response.body.ContentId exists()
response.body.Categories exists()
response.body.BookmannUUID exists()
}
+41
View File
@@ -0,0 +1,41 @@
meta {
name: "Kobo Markup Sync - Enhanced with ContentId Mapping"
type: http
seq: 2
}
post {
url: {{base_url}}/api/v1/kobo/markup
body: json({
"ReadingSync": [
{
"ContentId": "kobo_abc123def456",
"PercentRead": 60.0,
"RemainingTimeMin": 120,
"ReadingEvent": "BookRead",
"LastModified": "2026-01-31T12:00:00Z"
}
],
"BookmarkSync": [
{
"BookmarkId": "bookmark_1",
"ContentId": "kobo_abc123def456",
"BookmarkText": "Great quote",
"BookmarkType": "annotation",
"DateCreated": "2026-01-31T12:00:00Z"
}
],
"Metadata": true
})
auth: {
type: bearer
bearer: {{device_token}}
}
}
assert {
response.status == 200
response.body.Status == "Success" || response.body.Status == "Partial"
response.body.MarkupsSynced >= 0
response.body.BookmarksSynced >= 0
}
@@ -0,0 +1,39 @@
meta {
name: "KOReader Sync Annotations - Per-Annotation SHA-256"
type: http
seq: 4
}
post {
url: {{base_url}}/api/v1/koreader/sync/bookmarks
body: json({
"book_uuid": "{{book_uuid}}",
"highlights": [
{
"text": "Quote from book 1",
"pos0": "/6/4[chap1ref]!/4/2/1:0",
"pos1": "/6/4[chap1ref]!/4/2/1:50",
"color": "#ffff00",
"page": 10,
"book_sha256": "{{book_sha256}}"
},
{
"text": "Quote from book 2 (different book)",
"pos0": "/6/4[chap1ref]!/4/2/1:0",
"pos1": "/6/4[chap1ref]!/4/2/1:50",
"color": "#00ff00",
"page": 15,
"book_sha256": "{{another_book_sha256}}"
}
]
})
auth: {
type: bearer
bearer: {{koreader_device_token}}
}
}
assert {
response.status == 200
response.body.highlights_synced >= 0
}
@@ -0,0 +1,47 @@
meta {
name: "KOReader Sync Bookmarks - Enhanced with SHA-256"
type: http
seq: 3
}
post {
url: {{base_url}}/api/v1/koreader/sync/bookmarks
body: json({
"book_sha256": "{{book_sha256}}",
"bookmarks": [
{
"text": "Important passage about chapter 3",
"pos0": "/6/4[chap3ref]!/4/2/1:0",
"page": 45,
"type": "bookmark"
}
],
"notes": [
{
"notes": "My note about this section",
"pos0": "/6/4[chap3ref]!/4/2/1:100",
"page": 47,
"text": "Quoted text from the book"
}
],
"highlights": [
{
"text": "This is highlighted text",
"pos0": "/6/4[chap3ref]!/4/2/1:50",
"pos1": "/6/4[chap3ref]!/4/2/1:100",
"color": "#ffff00",
"page": 50
}
]
})
auth: {
type: bearer
bearer: {{koreader_device_token}}
}
}
assert {
response.status == 200
response.body.sync_status == "completed"
response.body.total_synced >= 0
}
@@ -0,0 +1,29 @@
meta {
name: "KOReader Sync Progress - Priority Matching (SHA-256 only)"
type: http
seq: 2
}
post {
url: {{base_url}}/api/v1/koreader/sync/progress
body: json({
"sync_mode": "immediate",
"books": [
{
"sha256": "{{book_sha256}}",
"file_path": "/mnt/onboard/Unknown%20Book.epub",
"percentage": 0.45,
"page": 89,
"total_pages": 200
}
]
})
auth: {
type: bearer
bearer: {{koreader_device_token}}
}
}
assert {
response.status == 200 || response.status == 202
}
@@ -0,0 +1,37 @@
meta {
name: "KOReader Sync Progress - Enhanced with SHA-256"
type: http
seq: 1
}
post {
url: {{base_url}}/api/v1/koreader/sync/progress
body: json({
"sync_mode": "immediate",
"books": [
{
"uuid": "{{book_uuid}}",
"sha256": "{{book_sha256}}",
"file_path": "/mnt/onboard/The%20Hobbit.epub",
"percentage": 0.65,
"chapter": 5,
"page": 142,
"total_pages": 310,
"epubcfi": "/6/4[chap1ref]!/4/2/1:0",
"last_read": "2026-01-31T12:00:00Z",
"title": "The Hobbit",
"authors": ["J.R.R. Tolkien"]
}
]
})
auth: {
type: bearer
bearer: {{koreader_device_token}}
}
}
assert {
response.status == 200 || response.status == 202
response.body.sync_status exists()
response.body.books_synced >= 0
}
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Download Book (EPUB)
type: http
seq: 3
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/download/{{book_id}}
}
vars:device_id, book_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Download Book (KEPUB)
type: http
seq: 4
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/download/{{book_id}}?format=kepub
}
vars:device_id, book_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Get Cover Image
type: http
seq: 5
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/cover/{{book_id}}
}
vars:device_id, book_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Get Device Catalog
type: http
seq: 1
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/catalog?page=1&per_page=50
}
vars:device_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Get Device Navigation
type: http
seq: 6
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/nav
}
vars:device_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: List Formats
type: http
seq: 7
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/formats/{{book_id}}
}
vars:device_id, book_id, opds_base_url
+11
View File
@@ -0,0 +1,11 @@
meta {
name: Search Device Catalog
type: http
seq: 2
}
get {
url: {{opds_base_url}}/devices/{{device_id}}/search?q=hobbit
}
vars:device_id, opds_base_url
@@ -0,0 +1,19 @@
meta {
name: "Download Device Sidecar File"
type: http
seq: 2
}
get {
url: {{base_url}}/api/devices/{{device_id}}/sidecar/download
auth: {
type: bearer
bearer: {{user_token}}
}
}
assert {
response.status == 200
response.headers["Content-Type"] contains "application/json"
response.headers["Content-Disposition"] contains ".bookmann.json"
}
@@ -0,0 +1,21 @@
meta {
name: "Get Device Sidecar Config"
type: http
seq: 1
}
get {
url: {{base_url}}/api/devices/{{device_id}}/sidecar
auth: {
type: bearer
bearer: {{user_token}}
}
}
assert {
response.status == 200
response.body.version == "1.0"
response.body.bookmann exists()
response.body.books exists()
response.body.collections exists()
}
@@ -0,0 +1,20 @@
meta {
name: "Get System Configuration"
type: http
seq: 3
}
get {
url: {{base_url}}/api/system/config
auth: {
type: bearer
bearer: {{admin_token}}
}
}
assert {
response.status == 200
response.body.base_url exists()
response.body.opds_base_url exists()
response.body.api_base_url exists()
}
+243
View File
@@ -0,0 +1,243 @@
# Phase 8: Sidecar Configuration System - API Documentation
## Overview
This document describes the sidecar configuration system that enables easy device setup for Kobo and KOReader devices.
## What is a Sidecar File?
A sidecar file (`.bookmann.json`) is a configuration file that contains all the information a device needs to connect to Bookmann, including:
- OPDS catalog URL for wireless book browsing
- Sync API endpoints for progress sync
- Book inventory with SHA-256 hashes
- Collection metadata with shelf mappings
## Authentication
All endpoints require authentication:
- User endpoints: Use user JWT token (Authorization: Bearer {{user_token}})
- Admin endpoints: Use admin JWT token (Authorization: Bearer {{admin_token}})
## Endpoints
### 1. Get Device Sidecar Config
**Endpoint**: `GET /api/devices/:device_id/sidecar`
**Description**: Returns sidecar configuration for a device as JSON.
**Request Headers**:
```
Authorization: Bearer {{user_token}}
```
**Response** (200 OK):
```json
{
"version": "1.0",
"bookmann": {
"opds_catalog": "http://192.168.1.100:8765/opds/devices/kobo-id/catalog",
"sync_api": "http://192.168.1.100:8765/api/sync/kobo",
"opds_base_url": "http://192.168.1.100:8765/opds",
"api_base_url": "http://192.168.1.100:8765/api",
"device_id": "kobo-device-uuid",
"device_token": "dev_xxxxx..."
},
"books": {
"sha256:abc123...": {
"bookmann_uuid": "uuid-123",
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"available_formats": ["epub", "kepub"],
"sha256": "abc123...",
"file_path": "/path/to/book.epub"
}
},
"collections": [
{
"name": "Sci-Fi",
"shelf_mapping": "Science Fiction",
"book_ids": ["uuid-1", "uuid-2", "uuid-3"]
}
],
"opds_enabled": true,
"sidecar_enabled": true,
"last_updated": "2026-01-31T12:00:00Z"
}
```
### 2. Download Device Sidecar File
**Endpoint**: `GET /api/devices/:device_id/sidecar/download`
**Description**: Downloads a `.bookmann.json` configuration file for device setup.
**Request Headers**:
```
Authorization: Bearer {{user_token}}
```
**Response** (200 OK):
```
Content-Type: application/json
Content-Disposition: attachment; filename="MyKoboClara.bookmann.json"
```
File contains formatted JSON (pretty-printed) suitable for:
- Manual device configuration
- Backup and restore
- Transfer via USB
### 3. Get System Configuration
**Endpoint**: `GET /api/system/config`
**Description**: Returns system-wide configuration settings (admin only).
**Request Headers**:
```
Authorization: Bearer {{admin_token}}
```
**Response** (200 OK):
```json
{
"base_url": "https://bookmann.example.com",
"opds_base_url": "https://bookmann.example.com/opds",
"api_base_url": "https://bookmann.example.com/api"
}
```
### 4. Update System Configuration
**Endpoint**: `PUT /api/system/config`
**Description**: Updates system-wide configuration settings (admin only).
**Request Headers**:
```
Authorization: Bearer {{admin_token}}
Content-Type: application/json
```
**Request Body**:
```json
{
"base_url": "https://bookmann.example.com",
"opds_base_url": "https://bookmann.example.com/opds",
"api_base_url": "https://bookmann.example.com/api"
}
```
**Response** (200 OK):
```json
{
"status": "success",
"message": "System configuration updated"
}
```
## Sidecar File Format
### Version
Always "1.0" - enables future format changes
### Bookmann Section
Contains device connection information:
- **opds_catalog**: Full URL to device's OPDS catalog
- **sync_api**: Sync API endpoint
- **opds_base_url**: Base URL for all OPDS operations
- **api_base_url**: Base URL for all API operations
- **device_id**: Device's unique identifier
- **device_token**: Device authentication token
### Books Section
Map of book identifiers to book metadata:
- **Key**: SHA-256 hash (preferred) or Bookmann UUID
- **bookmann_uuid**: Canonical Bookmann UUID
- **title**: Book title
- **author**: Book author
- **available_formats**: Array of formats ("epub", "kepub")
- **sha256**: SHA-256 hash of book file
- **file_path**: Original file path
### Collections Section
Array of collection definitions:
- **name**: Collection name in Bookmann
- **shelf_mapping**: Device-specific shelf name (e.g., "Science Fiction")
- **book_ids**: Array of Bookmann UUIDs in collection
## Device Setup Workflow
### Kobo E-Reader
1. **Download Configuration**
- Log into Bookmann web UI
- Navigate to Device Management
- Click "Download Configuration" for your Kobo device
- File saves as `MyKoboClara.bookmann.json`
2. **Manual Configuration** (if needed)
- Copy `.bookmann.json` to Kobo device
- Kobo can import configuration automatically
3. **OPDS Setup** (Recommended)
- Use `opds_catalog` URL from sidecar
- Add as new content catalog in Kobo settings
- Browse and download books wirelessly
### KOReader
1. **Download Configuration**
- Same process as Kobo above
2. **Configure Wireless Sync**
- In KOReader, set Calibre wireless URL to `sync_api` from sidecar
- Enable password and use `device_token`
- Set sync frequency to desired interval
## Environment Variables
Set these in your Bruno collection:
```json
{
"base_url": "http://localhost:8765/api",
"user_token": "your-user-jwt-token",
"admin_token": "your-admin-jwt-token",
"device_id": "uuid-of-device"
}
```
## Error Codes
- `200`: Success
- `400`: Bad Request (invalid device ID, invalid JSON)
- `401`: Unauthorized (missing or invalid token)
- `403`: Forbidden (admin access required)
- `404`: Not Found (device not found)
- `500`: Internal Server Error (database error, generation failure)
## Benefits
1. **Easy Setup**: One file contains all configuration
2. **No Manual Entry**: URLs and tokens pre-populated
3. **SHA-256 Matching**: Reliable book identification
4. **Collection Sync**: Shelf mappings included
5. **Backup/Restore**: Save and transfer device configs
6. **Offline Configuration**: Configure devices without network access initially
## Testing Scenarios
### Scenario 1: New Kobo Device
1. Register Kobo device in Bookmann
2. Download sidecar configuration
3. Add OPDS catalog URL from sidecar to Kobo
4. Browse and download books wirelessly
5. Progress syncs automatically
### Scenario 2: Device Re-configuration
1. Download current sidecar file
2. Update system configuration if needed
3. Re-download sidecar with new settings
4. Re-configure device with updated file
### Scenario 3: Collection Management
1. Create collections in Bookmann
2. Set up device-specific shelf mappings
3. Sidecar automatically includes collection info
4. Device shelves reflect collection structure
@@ -0,0 +1,23 @@
meta {
name: "Update System Configuration"
type: http
seq: 4
}
put {
url: {{base_url}}/api/system/config
body: json({
"base_url": "https://bookmann.example.com",
"opds_base_url": "https://bookmann.example.com/opds",
"api_base_url": "https://bookmann.example.com/api"
})
auth: {
type: bearer
bearer: {{admin_token}}
}
}
assert {
response.status == 200
response.body.status == "success"
}
+21
View File
@@ -0,0 +1,21 @@
meta {
name: "Get Unlinked Books - User View"
type: http
seq: 4
}
get {
url: {{base_url}}/api/sync/unlinked-books
body: none
auth: {
type: bearer
bearer: {{user_token}}
}
}
assert {
response.status == 200
response.body.unlinked exists()
response.body.total exists()
response.body.total >= 0
}
+26
View File
@@ -0,0 +1,26 @@
meta {
name: "Link Unlinked Book - Manual Resolution"
type: http
seq: 5
}
post {
url: {{base_url}}/api/sync/link-book
body: json({
"unlinked_book_id": "{{unlinked_book_id}}",
"media_item_id": "{{media_item_id}}",
"confidence_score": 1.0
})
auth: {
type: bearer
bearer: {{user_token}}
}
}
assert {
response.status == 200
response.body.status == "linked"
response.body.unlinked_book_id exists()
response.body.media_item_id exists()
response.body.message exists()
}