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.
367 lines
8.4 KiB
Go
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)
|
|
}
|