Deadcode-verified removals (RTA reachability from main, cross-checked with -test and textual xrefs): Go server: - whole files: middleware/transaction.go, middleware/error_handler.go, middleware/password_validator.go (live registration machinery kept), handlers/sync.go, sync/offline.go - unrouted endpoints: auth UpdateUsername/UpdateEmail, timezone settings handler, OPDS RegisterOPDS, Kobo LibrarySync handler, ReaderHandler.ShowReader, JSON-data twins on collections/library - duplicate book-matching handlers (book_matching.go Handler twins, matching.go MatchingHandler twins), superseded helpers (router.parseUUID, handlers.ValidateDeviceToken, normalizePath, userExists, generateLockID) Templ/client: - never-routed pages: api_explorer.templ, queue.templ and their client modules api-explorer.ts, api-explorer-docs.ts, queue.ts - fully orphaned wrapper modules events.ts, dom.ts - broken clearSyncQueue (called a nonexistent endpoint) and the hidden, never-populated sync-queue section on the devices page - 166+ unused exports demoted to private (knip-verified); ~85 truly dead declarations removed via tsc noUnusedLocals fixpoint - stale 'interactive API Explorer' claims dropped from docs The docs drift-gate package (internal/docs), templ-bound modules (dashboard, stats, header, index, login, profile*, bookshelf, collections, toast-error), sqlc-generated code, and web/static are intentionally untouched.
346 lines
7.8 KiB
Go
346 lines
7.8 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)
|
|
}
|
|
})
|
|
}
|
|
}
|