- Rename phase1_integration_test.go to universal_progress_integration_test.go (tests universal reading progress feature) - Rename ebook_scanner_phase2_test.go to ebook_scanner_hash_test.go (tests hash calculation and file identification utilities) These renames make the test suite more maintainable and self-documenting.
437 lines
10 KiB
Go
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 := &EbookScanner{}
|
|
|
|
// 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 := &EbookScanner{}
|
|
|
|
// 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 := &EbookScanner{}
|
|
|
|
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 := &EbookScanner{}
|
|
|
|
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 := &EbookScanner{}
|
|
|
|
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 := &EbookScanner{}
|
|
|
|
// 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 := &EbookScanner{}
|
|
|
|
// 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 := &EbookScanner{}
|
|
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 ExampleEbookScanner_calculateFileSHA256() {
|
|
scanner := &EbookScanner{}
|
|
|
|
// 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)
|
|
}
|