Files
bookhoard/internal/services/media_scanner_hash_test.go
T
john-okeefe d61afbef50 test(services): rename test files for MediaScanner
- Rename ebook_scanner_library_type_test.go -> media_scanner_library_type_test.go
- Rename ebook_scanner_comic_test.go -> media_scanner_comic_test.go
- Rename ebook_scanner_hash_test.go -> media_scanner_hash_test.go
- Update test function names (TestEbookScanner* -> TestMediaScanner*)
- Update ExampleEbookScanner_calculateFileSHA256 -> ExampleMediaScanner_calculateFileSHA256
- Update all EbookScanner references to MediaScanner in tests
2026-02-08 14:30:43 -05:00

437 lines
10 KiB
Go

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: "<html><body>Test content</body></html>",
},
}
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)
}