241 lines
5.9 KiB
Go
241 lines
5.9 KiB
Go
package extraction
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func tempQueue(t *testing.T) *Queue {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
q, err := NewQueue(dir, 3)
|
|
if err != nil {
|
|
t.Fatalf("NewQueue: %v", err)
|
|
}
|
|
return q
|
|
}
|
|
|
|
func TestEnqueue_CreatesStatusFile(t *testing.T) {
|
|
q := tempQueue(t)
|
|
|
|
err := q.Enqueue("att-123", "files/ch/att-123_doc.pdf", "application/pdf", "doc.pdf")
|
|
if err != nil {
|
|
t.Fatalf("Enqueue: %v", err)
|
|
}
|
|
|
|
item, err := q.GetStatus("att-123")
|
|
if err != nil {
|
|
t.Fatalf("GetStatus: %v", err)
|
|
}
|
|
if item == nil {
|
|
t.Fatal("expected non-nil item")
|
|
}
|
|
if item.Status != StatusPending {
|
|
t.Errorf("status = %q, want %q", item.Status, StatusPending)
|
|
}
|
|
if item.FileID != "att-123" {
|
|
t.Errorf("file_id = %q, want att-123", item.FileID)
|
|
}
|
|
if item.ContentType != "application/pdf" {
|
|
t.Errorf("content_type = %q, want application/pdf", item.ContentType)
|
|
}
|
|
}
|
|
|
|
func TestGetStatus_NotQueued(t *testing.T) {
|
|
q := tempQueue(t)
|
|
|
|
item, err := q.GetStatus("nonexistent")
|
|
if err != nil {
|
|
t.Fatalf("GetStatus: %v", err)
|
|
}
|
|
if item != nil {
|
|
t.Error("expected nil for non-queued item")
|
|
}
|
|
}
|
|
|
|
func TestMarkComplete(t *testing.T) {
|
|
q := tempQueue(t)
|
|
q.Enqueue("att-456", "key", "application/pdf", "test.pdf")
|
|
|
|
err := q.MarkComplete("att-456", "processing/att-456/extracted.txt")
|
|
if err != nil {
|
|
t.Fatalf("MarkComplete: %v", err)
|
|
}
|
|
|
|
item, _ := q.GetStatus("att-456")
|
|
if item.Status != StatusComplete {
|
|
t.Errorf("status = %q, want %q", item.Status, StatusComplete)
|
|
}
|
|
if item.OutputKey != "processing/att-456/extracted.txt" {
|
|
t.Errorf("output_key = %q", item.OutputKey)
|
|
}
|
|
if item.CompletedAt == "" {
|
|
t.Error("completed_at should be set")
|
|
}
|
|
}
|
|
|
|
func TestMarkFailed(t *testing.T) {
|
|
q := tempQueue(t)
|
|
q.Enqueue("att-789", "key", "application/pdf", "test.pdf")
|
|
|
|
err := q.MarkFailed("att-789", "libreoffice crashed")
|
|
if err != nil {
|
|
t.Fatalf("MarkFailed: %v", err)
|
|
}
|
|
|
|
item, _ := q.GetStatus("att-789")
|
|
if item.Status != StatusFailed {
|
|
t.Errorf("status = %q, want %q", item.Status, StatusFailed)
|
|
}
|
|
if item.Error != "libreoffice crashed" {
|
|
t.Errorf("error = %q", item.Error)
|
|
}
|
|
}
|
|
|
|
func TestCleanup_RemovesDirectory(t *testing.T) {
|
|
q := tempQueue(t)
|
|
q.Enqueue("att-cleanup", "key", "application/pdf", "test.pdf")
|
|
|
|
// Verify directory exists
|
|
dir := filepath.Join(q.storagePath, "processing", "att-cleanup")
|
|
if _, err := os.Stat(dir); err != nil {
|
|
t.Fatal("expected processing dir to exist before cleanup")
|
|
}
|
|
|
|
if err := q.Cleanup("att-cleanup"); err != nil {
|
|
t.Fatalf("Cleanup: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
|
t.Error("expected processing dir to be removed after cleanup")
|
|
}
|
|
}
|
|
|
|
func TestListPending(t *testing.T) {
|
|
q := tempQueue(t)
|
|
q.Enqueue("att-a", "key-a", "application/pdf", "a.pdf")
|
|
q.Enqueue("att-b", "key-b", "application/pdf", "b.pdf")
|
|
q.MarkComplete("att-b", "out")
|
|
|
|
pending, err := q.ListPending()
|
|
if err != nil {
|
|
t.Fatalf("ListPending: %v", err)
|
|
}
|
|
if len(pending) != 1 {
|
|
t.Fatalf("expected 1 pending, got %d", len(pending))
|
|
}
|
|
if pending[0].FileID != "att-a" {
|
|
t.Errorf("pending[0].FileID = %q, want att-a", pending[0].FileID)
|
|
}
|
|
}
|
|
|
|
func TestListAll(t *testing.T) {
|
|
q := tempQueue(t)
|
|
q.Enqueue("att-1", "key-1", "application/pdf", "1.pdf")
|
|
q.Enqueue("att-2", "key-2", "application/pdf", "2.pdf")
|
|
q.Enqueue("att-3", "key-3", "application/pdf", "3.pdf")
|
|
|
|
all, err := q.ListAll()
|
|
if err != nil {
|
|
t.Fatalf("ListAll: %v", err)
|
|
}
|
|
if len(all) != 3 {
|
|
t.Errorf("expected 3 items, got %d", len(all))
|
|
}
|
|
}
|
|
|
|
func TestRecoverStale(t *testing.T) {
|
|
q := tempQueue(t)
|
|
|
|
// Create item and manually set it to "processing" with old timestamp
|
|
q.Enqueue("att-stale", "key", "application/pdf", "stale.pdf")
|
|
item, _ := q.GetStatus("att-stale")
|
|
item.Status = StatusProcessing
|
|
item.StartedAt = time.Now().Add(-2 * time.Hour).UTC().Format(time.RFC3339)
|
|
q.mu.Lock()
|
|
q.writeStatus("att-stale", item)
|
|
q.mu.Unlock()
|
|
|
|
// Recover items stale for > 1 hour
|
|
recovered, err := q.RecoverStale(1 * time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("RecoverStale: %v", err)
|
|
}
|
|
if recovered != 1 {
|
|
t.Errorf("recovered = %d, want 1", recovered)
|
|
}
|
|
|
|
// Verify it's back to pending
|
|
item, _ = q.GetStatus("att-stale")
|
|
if item.Status != StatusPending {
|
|
t.Errorf("status = %q, want %q", item.Status, StatusPending)
|
|
}
|
|
}
|
|
|
|
func TestRecoverStale_SkipsRecent(t *testing.T) {
|
|
q := tempQueue(t)
|
|
|
|
// Create item "processing" but recent (not stale)
|
|
q.Enqueue("att-recent", "key", "application/pdf", "recent.pdf")
|
|
item, _ := q.GetStatus("att-recent")
|
|
item.Status = StatusProcessing
|
|
item.StartedAt = time.Now().Add(-5 * time.Minute).UTC().Format(time.RFC3339)
|
|
q.mu.Lock()
|
|
q.writeStatus("att-recent", item)
|
|
q.mu.Unlock()
|
|
|
|
recovered, err := q.RecoverStale(1 * time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("RecoverStale: %v", err)
|
|
}
|
|
if recovered != 0 {
|
|
t.Errorf("recovered = %d, want 0 (item is recent)", recovered)
|
|
}
|
|
}
|
|
|
|
func TestIsExtractable(t *testing.T) {
|
|
tests := []struct {
|
|
contentType string
|
|
want bool
|
|
}{
|
|
{"application/pdf", true},
|
|
{"application/vnd.openxmlformats-officedocument.wordprocessingml.document", true},
|
|
{"image/png", false},
|
|
{"text/plain", false},
|
|
{"image/jpeg", false},
|
|
{"application/rtf", true},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := IsExtractable(tc.contentType)
|
|
if got != tc.want {
|
|
t.Errorf("IsExtractable(%q) = %v, want %v", tc.contentType, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite(t *testing.T) {
|
|
q := tempQueue(t)
|
|
|
|
// Enqueue and verify the file is valid JSON
|
|
q.Enqueue("att-atomic", "key", "application/pdf", "test.pdf")
|
|
|
|
statusPath := filepath.Join(q.storagePath, "processing", "att-atomic", "status.json")
|
|
data, err := os.ReadFile(statusPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile: %v", err)
|
|
}
|
|
|
|
// Verify no .tmp file left behind
|
|
tmpPath := statusPath + ".tmp"
|
|
if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
|
|
t.Error("temp file should not exist after atomic write")
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
t.Error("status.json should not be empty")
|
|
}
|
|
}
|