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) }