package services import ( "bookhoard/internal/database" "testing" "github.com/jackc/pgx/v5/pgtype" ) func TestEvaluateRules_Equality(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Genre: pgtype.Text{String: "Science Fiction", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 5, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if !evaluations[0].Matches { t.Errorf("expected rule to match, but it didn't") } if evaluations[0].Confidence <= 0.85 { t.Errorf("expected confidence > 0.85, got %f", evaluations[0].Confidence) } } func TestEvaluateRules_NoMatch(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Genre: pgtype.Text{String: "Fantasy", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 5, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if evaluations[0].Matches { t.Errorf("expected rule not to match, but it did") } if evaluations[0].Confidence != 0 { t.Errorf("expected confidence 0, got %f", evaluations[0].Confidence) } } func TestEvaluateRules_Contains(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Series: pgtype.Text{String: "The Expanse", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "series", Operator: "contains", Value: "Expanse", Priority: 7, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if !evaluations[0].Matches { t.Errorf("expected rule to match, but it didn't") } if evaluations[0].Confidence <= 0.9 { t.Errorf("expected confidence > 0.9 with priority boost, got %f", evaluations[0].Confidence) } } func TestEvaluateRules_PriorityOrder(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Genre: pgtype.Text{String: "Science Fiction", Valid: true}, Series: pgtype.Text{String: "Foundation", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 5, }, { ID: "rule-2", Field: "series", Operator: "equals", Value: "Foundation", Priority: 9, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 2 { t.Fatalf("expected 2 evaluations, got %d", len(evaluations)) } if evaluations[0].RuleID != "rule-2" { t.Errorf("expected rule-2 (higher priority) to be first, got %s", evaluations[0].RuleID) } if evaluations[1].RuleID != "rule-1" { t.Errorf("expected rule-1 (lower priority) to be second, got %s", evaluations[1].RuleID) } if evaluations[0].Confidence <= evaluations[1].Confidence { t.Errorf("expected higher priority rule to have higher confidence") } } func TestEvaluateRules_CopyrightYear(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ CopyrightYear: pgtype.Int4{Int32: 2020, Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "copyright_year", Operator: "greater_than", Value: "2019", Priority: 5, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if !evaluations[0].Matches { t.Errorf("expected rule to match (2020 > 2019), but it didn't") } } func TestEvaluateRules_MissingField(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{} rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 5, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if evaluations[0].Matches { t.Errorf("expected rule not to match when field is missing, but it did") } } func TestEvaluateRules_CaseInsensitive(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Genre: pgtype.Text{String: "science fiction", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "SCIENCE FICTION", Priority: 5, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 1 { t.Fatalf("expected 1 evaluation, got %d", len(evaluations)) } if !evaluations[0].Matches { t.Errorf("expected case-insensitive match, but it didn't match") } } func TestEvaluateRules_PriorityBoost(t *testing.T) { service := &CollectionService{} mediaItem := database.ListMediaItemsRow{ Genre: pgtype.Text{String: "Science Fiction", Valid: true}, } rules := []Rule{ { ID: "rule-1", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 1, }, { ID: "rule-2", Field: "genre", Operator: "equals", Value: "Science Fiction", Priority: 10, }, } evaluations := service.EvaluateRules(mediaItem, rules) if len(evaluations) != 2 { t.Fatalf("expected 2 evaluations, got %d", len(evaluations)) } // Higher priority (10) should give higher confidence than lower priority (1) // Genre base confidence is 0.9 // Priority 1: 0.9 + 0.01 = 0.91 // Priority 10: 0.9 + 0.1 = 1.0 (capped) if evaluations[0].Confidence != 1.0 { t.Errorf("expected higher priority confidence to be 1.0, got %f", evaluations[0].Confidence) } if evaluations[1].Confidence != 0.91 { t.Errorf("expected lower priority confidence to be 0.91, got %f", evaluations[1].Confidence) } // The higher priority rule should come first (sorted by priority descending) if evaluations[0].RuleID != "rule-2" { t.Errorf("expected higher priority rule to be first, got %s", evaluations[0].RuleID) } }