package scanner import ( "os" "path/filepath" "strings" "testing" "time" ) func testScanner(t *testing.T) (*Scanner, string) { t.Helper() root := t.TempDir() s := New(Config{ StorageRoot: root, IncomingDir: "incoming", ProcessingDir: "processing", FailedDir: "failed", StableWindow: time.Millisecond, StableChecks: 1, }) if err := s.EnsureDirs(); err != nil { t.Fatalf("EnsureDirs: %v", err) } return s, root } func TestEnsureDirs(t *testing.T) { _, root := testScanner(t) for _, dir := range []string{"incoming", "processing", "failed"} { p := filepath.Join(root, dir) if st, err := os.Stat(p); err != nil || !st.IsDir() { t.Fatalf("dir %s missing: %v", dir, err) } } } func TestScanOnceClaimsStableAudio(t *testing.T) { s, root := testScanner(t) incoming := filepath.Join(root, "incoming") if err := os.WriteFile(filepath.Join(incoming, "call.wav"), []byte("audio"), 0o644); err != nil { t.Fatal(err) } claimed, err := s.ScanOnce() if err != nil { t.Fatal(err) } if len(claimed) != 1 { t.Fatalf("claimed %d files, want 1", len(claimed)) } cf := claimed[0] if cf.Filename != "call.wav" { t.Fatalf("filename: got %q", cf.Filename) } if cf.TaskID == "" { t.Fatal("empty task_id") } if !strings.HasPrefix(cf.FilePath, filepath.Join(root, "processing")) { t.Fatalf("unexpected path: %s", cf.FilePath) } if _, err := os.Stat(cf.FilePath); err != nil { t.Fatalf("claimed file missing: %v", err) } if _, err := os.Stat(filepath.Join(incoming, "call.wav")); !os.IsNotExist(err) { t.Fatal("source file should be moved") } } func TestScanOnceSkipsUnsupportedAndHidden(t *testing.T) { s, root := testScanner(t) incoming := filepath.Join(root, "incoming") files := map[string][]byte{ ".hidden.wav": []byte("x"), "notes.txt": []byte("x"), "upload.tmp": []byte("x"), "valid.mp3": []byte("audio"), } for name, data := range files { if err := os.WriteFile(filepath.Join(incoming, name), data, 0o644); err != nil { t.Fatal(err) } } claimed, err := s.ScanOnce() if err != nil { t.Fatal(err) } if len(claimed) != 1 { t.Fatalf("claimed %d files, want 1 (valid.mp3)", len(claimed)) } if claimed[0].Filename != "valid.mp3" { t.Fatalf("filename: got %q", claimed[0].Filename) } } func TestScanOnceEmptyIncoming(t *testing.T) { s, _ := testScanner(t) claimed, err := s.ScanOnce() if err != nil { t.Fatal(err) } if len(claimed) != 0 { t.Fatalf("expected 0 claimed, got %d", len(claimed)) } } func TestRollbackToIncoming(t *testing.T) { s, root := testScanner(t) processing := filepath.Join(root, "processing") incoming := filepath.Join(root, "incoming") processingFile := filepath.Join(processing, "01TASK.wav") if err := os.WriteFile(processingFile, []byte("data"), 0o644); err != nil { t.Fatal(err) } if err := s.RollbackToIncoming(processingFile, "call.wav"); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(incoming, "call.wav")); err != nil { t.Fatalf("file not restored to incoming: %v", err) } if _, err := os.Stat(processingFile); !os.IsNotExist(err) { t.Fatal("processing file should be gone") } } func TestMoveToFailed(t *testing.T) { s, root := testScanner(t) processing := filepath.Join(root, "processing") processingFile := filepath.Join(processing, "broken.wav") if err := os.WriteFile(processingFile, []byte("data"), 0o644); err != nil { t.Fatal(err) } if err := s.MoveToFailed(processingFile, "broken.wav"); err != nil { t.Fatal(err) } failedPath := filepath.Join(root, "failed", "broken.wav") if _, err := os.Stat(failedPath); err != nil { t.Fatalf("file not in failed: %v", err) } } func TestIsStableStableFile(t *testing.T) { s, root := testScanner(t) path := filepath.Join(root, "incoming", "stable.wav") if err := os.WriteFile(path, []byte("fixed-content"), 0o644); err != nil { t.Fatal(err) } if !s.isStable(path) { t.Fatal("expected stable file") } } func TestIsStableMissingFile(t *testing.T) { s, root := testScanner(t) path := filepath.Join(root, "incoming", "missing.wav") if s.isStable(path) { t.Fatal("missing file should not be stable") } }