package services import ( "bufio" "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "strings" "testing" ) // TestCalculateFileSHA256 tests the SHA-256 calculation with streaming func TestCalculateFileSHA256(t *testing.T) { scanner := &MediaScanner{} // Create a temporary test file tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test.txt") testContent := "The quick brown fox jumps over the lazy dog" if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } // Calculate expected hash hasher := sha256.New() hasher.Write([]byte(testContent)) expectedHash := hex.EncodeToString(hasher.Sum(nil)) // Test the function calculatedHash, err := scanner.calculateFileSHA256(testFile) if err != nil { t.Fatalf("calculateFileSHA256 failed: %v", err) } if calculatedHash != expectedHash { t.Errorf("Expected hash %s, got %s", expectedHash, calculatedHash) } t.Logf("SHA-256 hash calculation successful: %s", calculatedHash) } // TestCalculateFileSHA256LargeFile tests streaming with large file func TestCalculateFileSHA256LargeFile(t *testing.T) { scanner := &MediaScanner{} // Create a temporary test file with larger content tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "large_test.txt") // Create a 10MB file file, err := os.Create(testFile) if err != nil { t.Fatalf("Failed to create test file: %v", err) } defer file.Close() writer := bufio.NewWriter(file) testLine := strings.Repeat("This is a test line for SHA-256 calculation\n", 100) for i := 0; i < 1000; i++ { if _, err := writer.WriteString(testLine); err != nil { t.Fatalf("Failed to write to test file: %v", err) } } writer.Flush() // Calculate expected hash file.Seek(0, 0) hasher := sha256.New() buf := make([]byte, 4096) for { n, err := file.Read(buf) if err != nil && err != bufio.ErrBufferFull { if err == io.EOF { break } t.Fatalf("Failed to read file for expected hash: %v", err) } hasher.Write(buf[:n]) if err == io.EOF { break } } expectedHash := hex.EncodeToString(hasher.Sum(nil)) // Test the function calculatedHash, err := scanner.calculateFileSHA256(testFile) if err != nil { t.Fatalf("calculateFileSHA256 failed for large file: %v", err) } if calculatedHash != expectedHash { t.Errorf("Expected hash %s, got %s", expectedHash, calculatedHash) } t.Logf("Large file SHA-256 hash calculation successful") } // TestExtractISBNFromIdentifier tests ISBN extraction func TestExtractISBNFromIdentifier(t *testing.T) { scanner := &MediaScanner{} tests := []struct { name string input string expected string }{ { name: "ISBN with prefix", input: "isbn:978-3-16-148410-0", expected: "9783161484100", }, { name: "ISBN with hyphens", input: "978-3-16-148410-0", expected: "9783161484100", }, { name: "ISBN with spaces", input: "978 3 16 148410 0", expected: "9783161484100", }, { name: "ISBN-10", input: "isbn:0-306-40615-2", expected: "0306406152", }, { name: "Clean ISBN-13", input: "9783161484100", expected: "9783161484100", }, { name: "Invalid identifier", input: "not-an-isbn", expected: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scanner.extractISBNFromIdentifier(tt.input) if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } } // TestIsValidUUID tests UUID validation func TestIsValidUUID(t *testing.T) { tests := []struct { name string input string expected bool }{ { name: "Valid UUID v4", input: "550e8400-e29b-41d4-a716-446655440000", expected: true, }, { name: "Valid UUID with uppercase", input: "550E8400-E29B-41D4-A716-446655440000", expected: true, }, { name: "Invalid UUID - missing dashes", input: "550e8400e29b41d4a716446655440000", expected: false, }, { name: "Invalid UUID - wrong format", input: "not-a-uuid", expected: false, }, { name: "Empty string", input: "", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isValidUUID(tt.input) if result != tt.expected { t.Errorf("Expected %v, got %v for input %s", tt.expected, result, tt.input) } }) } } // TestDetermineHashConfidence tests confidence level determination func TestDetermineHashConfidence(t *testing.T) { scanner := &MediaScanner{} tests := []struct { name string uuid string identifier string expectedConf string }{ { name: "High confidence - valid UUID", uuid: "550e8400-e29b-41d4-a716-446655440000", identifier: "", expectedConf: "high", }, { name: "Medium confidence - ISBN", uuid: "", identifier: "isbn:978-3-16-148410-0", expectedConf: "medium", }, { name: "Medium confidence - long identifier", uuid: "", identifier: "some-long-identifier-string", expectedConf: "medium", }, { name: "Low confidence - no identifiers", uuid: "", identifier: "", expectedConf: "low", }, { name: "Low confidence - short identifier", uuid: "", identifier: "abc", expectedConf: "low", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scanner.determineHashConfidence(tt.uuid, tt.identifier) if result != tt.expectedConf { t.Errorf("Expected confidence %s, got %s", tt.expectedConf, result) } }) } } // TestDetectFormatType tests format type detection func TestDetectFormatType(t *testing.T) { scanner := &MediaScanner{} tests := []struct { name string filePath string expectedType string }{ { name: "EPUB file", filePath: "/path/to/book.epub", expectedType: "epub", }, { name: "KEPUB file", filePath: "/path/to/book.kepub.epub", expectedType: "kepub", }, { name: "PDF file", filePath: "/path/to/document.pdf", expectedType: "pdf", }, { name: "CBZ file", filePath: "/path/to/comic.cbz", expectedType: "comic_archive", }, { name: "MOBI file", filePath: "/path/to/book.mobi", expectedType: "mobi", }, { name: "TXT file", filePath: "/path/to/book.txt", expectedType: "txt", }, { name: "Unknown format", filePath: "/path/to/book.xyz", expectedType: "unknown", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scanner.detectFormatType(tt.filePath) if result != tt.expectedType { t.Errorf("Expected format type %s, got %s", tt.expectedType, result) } }) } } // TestExtractHashInfo tests the complete hash extraction flow func TestExtractHashInfo(t *testing.T) { scanner := &MediaScanner{} // Create a temporary test file tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test_book.txt") testContent := "Test book content for hash extraction" if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } // Test hash extraction hashInfo, formatInfo, err := scanner.extractHashInfo(testFile) if err != nil { t.Fatalf("extractHashInfo failed: %v", err) } // Verify hash info if hashInfo == nil { t.Fatal("hashInfo is nil") } if hashInfo.FileSHA256 == "" { t.Error("FileSHA256 is empty") } // Verify format info if formatInfo == nil { t.Fatal("formatInfo is nil") } if formatInfo.FormatType != "txt" { t.Errorf("Expected format type 'txt', got %s", formatInfo.FormatType) } if formatInfo.FileSHA256 != hashInfo.FileSHA256 { t.Error("FormatInfo.FileSHA256 doesn't match HashInfo.FileSHA256") } t.Logf("Hash extraction test passed: SHA256=%s, Format=%s, Confidence=%s", hashInfo.FileSHA256, formatInfo.FormatType, hashInfo.HashConfidence) } // BenchmarkCalculateFileSHA256 benchmarks SHA-256 calculation func BenchmarkCalculateFileSHA256(b *testing.B) { scanner := &MediaScanner{} // Create a temporary test file tmpDir := b.TempDir() testFile := filepath.Join(tmpDir, "bench_test.txt") testContent := strings.Repeat("Benchmark test content for SHA-256 calculation\n", 10000) if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { b.Fatalf("Failed to create test file: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = scanner.calculateFileSHA256(testFile) } } // TestExtractHashInfoIntegration is an integration test that tests a realistic scenario func TestExtractHashInfoIntegration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } scanner := &MediaScanner{} tmpDir := t.TempDir() // Test multiple file types testFiles := []struct { name string filename string content string }{ { name: "Text file", filename: "test.txt", content: "Plain text file", }, { name: "HTML file (simulating EPUB content)", filename: "test.html", content: "Test content", }, } for _, tf := range testFiles { t.Run(tf.name, func(t *testing.T) { testFile := filepath.Join(tmpDir, tf.filename) if err := os.WriteFile(testFile, []byte(tf.content), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } hashInfo, formatInfo, err := scanner.extractHashInfo(testFile) if err != nil { t.Fatalf("extractHashInfo failed: %v", err) } // Verify SHA-256 is calculated if hashInfo.FileSHA256 == "" { t.Error("SHA-256 hash not calculated") } // Verify format detection if formatInfo.FormatType == "unknown" { t.Logf("Warning: Format detected as 'unknown' for %s", tf.filename) } t.Logf("Integration test passed for %s: SHA256=%s, Format=%s", tf.name, hashInfo.FileSHA256, formatInfo.FormatType) }) } } // Example usage func ExampleMediaScanner_calculateFileSHA256() { scanner := &MediaScanner{} // Calculate SHA-256 hash of a file hash, err := scanner.calculateFileSHA256("/path/to/ebook.epub") if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("SHA-256: %s\n", hash) }