- Add book matching service for intelligent book deduplication - Add collection service for collection management - Add test files for book matching and collections
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: Bookmann 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)
|
|
}
|