Files
john-okeefe 5b9f21a592 Final cleanup: Update remaining comments and variable names
Changes:
- Update comments: "Bookmann UUID" → "Bookhoard UUID"
- Rename sidecar struct field: Bookmann → Bookhoard
- Update type names: SidecarBookmannConfig → SidecarBookhoardConfig
- Fix test database name in queue_test.go
- Fix uppercase env var examples in KOBO_SETUP.md

Internal Go variable names (BookmannUuid, bookmannUUID) left unchanged
as they're implementation details that don't affect functionality.

Part of project rename to Bookhoard.
2026-02-01 16:24:58 -05:00

367 lines
8.4 KiB
Go

package services
import (
"testing"
"github.com/google/uuid"
)
// TestBookMatchingService_QueryBooks tests the book query functionality
func TestBookMatchingService_QueryBooks(t *testing.T) {
// This is a placeholder test that would need a mock database
// For now, we'll test the matching priority logic structure
t.Run("Priority 1: Bookhoard UUID match", func(t *testing.T) {
// Test that UUID matching returns highest confidence
req := &BookQueryRequest{
Identifiers: []string{"uuid:550e8400-e29b-41d4-a716-446655440000"},
}
// Would need mock DB to test actual matching
_ = req
t.Log("UUID matching test placeholder - requires mock database")
})
t.Run("Priority 2: OPF UUID match", func(t *testing.T) {
req := &BookQueryRequest{
Identifiers: []string{"opf_uuid:550e8400-e29b-41d4-a716-446655440000"},
}
_ = req
t.Log("OPF UUID matching test placeholder - requires mock database")
})
t.Run("Priority 3: SHA-256 match", func(t *testing.T) {
req := &BookQueryRequest{
SHA256: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
}
_ = req
t.Log("SHA-256 matching test placeholder - requires mock database")
})
t.Run("Priority 7: Title + author + size match", func(t *testing.T) {
req := &BookQueryRequest{
Title: "Test Book",
Author: "Test Author",
FileSize: 1024000,
}
_ = req
t.Log("Title+author+size matching test placeholder - requires mock database")
})
}
// TestBookMatch_ConfidenceLevels tests confidence scoring
func TestBookMatch_ConfidenceLevels(t *testing.T) {
tests := []struct {
name string
match BookMatch
confidence float64
method string
}{
{
name: "UUID match - highest confidence",
match: BookMatch{
Confidence: 1.0,
MatchMethod: "uuid_match",
},
confidence: 1.0,
method: "uuid_match",
},
{
name: "SHA-256 match - high confidence",
match: BookMatch{
Confidence: 0.9,
MatchMethod: "sha256_match",
},
confidence: 0.9,
method: "sha256_match",
},
{
name: "ISBN match - medium confidence",
match: BookMatch{
Confidence: 0.8,
MatchMethod: "isbn_match",
},
confidence: 0.8,
method: "isbn_match",
},
{
name: "Title+author+size match - lower confidence",
match: BookMatch{
Confidence: 0.5,
MatchMethod: "title_author_size_match",
},
confidence: 0.5,
method: "title_author_size_match",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.match.Confidence != tt.confidence {
t.Errorf("Expected confidence %f, got %f", tt.confidence, tt.match.Confidence)
}
if tt.match.MatchMethod != tt.method {
t.Errorf("Expected method %s, got %s", tt.method, tt.match.MatchMethod)
}
})
}
}
// TestBookQueryResponse_ActionDetermination tests action logic
func TestBookQueryResponse_ActionDetermination(t *testing.T) {
tests := []struct {
name string
response BookQueryResponse
action string
}{
{
name: "No matches",
response: BookQueryResponse{
Matches: []BookMatch{},
},
action: "no_match",
},
{
name: "Single high-confidence match",
response: BookQueryResponse{
Matches: []BookMatch{
{Confidence: 0.9, MatchMethod: "sha256_match"},
},
},
action: "auto_link",
},
{
name: "Single low-confidence match",
response: BookQueryResponse{
Matches: []BookMatch{
{Confidence: 0.5, MatchMethod: "title_author_size_match"},
},
},
action: "multiple_matches",
},
{
name: "Multiple matches",
response: BookQueryResponse{
Matches: []BookMatch{
{Confidence: 0.8, MatchMethod: "isbn_match"},
{Confidence: 0.5, MatchMethod: "title_match"},
},
},
action: "multiple_matches",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Determine action
var action string
if len(tt.response.Matches) == 0 {
action = "no_match"
} else if len(tt.response.Matches) == 1 && tt.response.Matches[0].Confidence > 0.7 {
action = "auto_link"
} else {
action = "multiple_matches"
}
if action != tt.action {
t.Errorf("Expected action %s, got %s", tt.action, action)
}
})
}
}
// TestDetermineAction tests the action determination logic
func TestDetermineAction(t *testing.T) {
tests := []struct {
name string
matches []BookMatch
expected string
}{
{
name: "No matches",
matches: []BookMatch{},
expected: "no_match",
},
{
name: "Single high-confidence match",
matches: []BookMatch{
{Confidence: 0.9, MatchMethod: "sha256_match"},
},
expected: "auto_link",
},
{
name: "Single low-confidence match",
matches: []BookMatch{
{Confidence: 0.5, MatchMethod: "title_match"},
},
expected: "multiple_matches",
},
{
name: "Multiple matches",
matches: []BookMatch{
{Confidence: 0.8, MatchMethod: "isbn_match"},
{Confidence: 0.7, MatchMethod: "asin_match"},
},
expected: "multiple_matches",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
service := &BookMatchingService{}
result := service.determineAction(tt.matches)
if result != tt.expected {
t.Errorf("Expected action %s, got %s", tt.expected, result)
}
})
}
}
// TestBookQueryRequest_Validation tests request validation
func TestBookQueryRequest_Validation(t *testing.T) {
tests := []struct {
name string
req BookQueryRequest
valid bool
reason string
}{
{
name: "Valid UUID identifier",
req: BookQueryRequest{
Identifiers: []string{"uuid:550e8400-e29b-41d4-a716-446655440000"},
},
valid: true,
reason: "",
},
{
name: "Valid ISBN identifier",
req: BookQueryRequest{
Identifiers: []string{"isbn:9783161484100"},
},
valid: true,
reason: "",
},
{
name: "Valid SHA-256",
req: BookQueryRequest{
SHA256: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
},
valid: true,
reason: "",
},
{
name: "Valid title+author+size",
req: BookQueryRequest{
Title: "Test Book",
Author: "Test Author",
FileSize: 1024000,
},
valid: true,
reason: "",
},
{
name: "Empty request",
req: BookQueryRequest{},
valid: false,
reason: "No identifiers provided",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Basic validation logic
valid := len(tt.req.Identifiers) > 0 ||
tt.req.SHA256 != "" ||
(tt.req.Title != "" && tt.req.Author != "")
if valid != tt.valid {
t.Errorf("Expected valid=%v, got valid=%v. Reason: %s", tt.valid, valid, tt.reason)
}
})
}
}
// TestLinkBookRequest_Validation tests linking request validation
func TestLinkBookRequest_Validation(t *testing.T) {
tests := []struct {
name string
req LinkBookRequest
valid bool
reason string
}{
{
name: "Valid link request",
req: LinkBookRequest{
MediaItemID: uuid.New(),
ConfidenceScore: 1.0,
DeviceFile: struct {
FilePath string `json:"file_path"`
SHA256 string `json:"sha256"`
Title string `json:"title"`
}{
FilePath: "/path/to/book.epub",
SHA256: "abc123",
Title: "Test Book",
},
},
valid: true,
reason: "",
},
{
name: "Missing media item ID",
req: LinkBookRequest{
ConfidenceScore: 1.0,
},
valid: false,
reason: "MediaItemID is required",
},
{
name: "Invalid confidence score",
req: LinkBookRequest{
MediaItemID: uuid.New(),
ConfidenceScore: 2.0, // Invalid, should be 0-1
},
valid: false,
reason: "Confidence score must be between 0 and 1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Check if MediaItemID is valid
valid := tt.req.MediaItemID != uuid.Nil &&
tt.req.ConfidenceScore >= 0 &&
tt.req.ConfidenceScore <= 1
if valid != tt.valid {
t.Errorf("Expected valid=%v, got valid=%v. Reason: %s", tt.valid, valid, tt.reason)
}
})
}
}
// Example usage
func ExampleBookMatchingService_QueryBooks() {
service := NewBookMatchingService(nil) // Would need real DB
req := &BookQueryRequest{
Identifiers: []string{
"uuid:550e8400-e29b-41d4-a716-446655440000",
"isbn:9783161484100",
},
SHA256: "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
Title: "Test Book",
Author: "Test Author",
FileSize: 1024000,
}
_ = service
_ = req
// response, err := service.QueryBooks(context.Background(), req)
// fmt.Printf("Matches: %d, Action: %s\n", len(response.Matches), response.Action)
}