package browser import ( "fmt" "os" "path/filepath" "testing" "time" ) // --- Fixture setup --- type testEntry struct { name string isDir bool size int64 } func setupTestDir(t *testing.T, entries []testEntry) string { t.Helper() dir := t.TempDir() for _, e := range entries { path := filepath.Join(dir, e.name) if e.isDir { err := os.Mkdir(path, 0755) if err != nil { t.Fatalf("failed to create dir %s: %v", e.name, err) } } else { err := os.WriteFile(path, make([]byte, e.size), 0644) if err != nil { t.Fatalf("failed to create file %s: %v", e.name, err) } } } return dir } // --- Test: Build Index - Empty Dir --- func TestBuildIndex_EmptyDir(t *testing.T) { dir := setupTestDir(t, nil) idx, err := buildIndex(dir) if err != nil { t.Fatalf("buildIndex(empty) returned error: %v", err) } if idx.EntryCount != 0 { t.Errorf("expected EntryCount=0, got %d", idx.EntryCount) } if len(idx.Entries) != 0 { t.Errorf("expected 0 entries, got %d", len(idx.Entries)) } } // --- Test: Build Index - Unsorted (raw metadata) --- // Per design: index stores raw metadata; sorting happens at page-load time. func TestBuildIndex_UnsortedRawMetadata(t *testing.T) { dir := setupTestDir(t, []testEntry{ {name: "Zebra.txt", size: 100}, {name: "apple.txt", size: 200}, {name: "Banana.txt", size: 300}, {name: "cherry.txt", size: 400}, }) idx, err := buildIndex(dir) if err != nil { t.Fatalf("buildIndex returned error: %v", err) } // Index should contain all entries (order not guaranteed - raw metadata) if len(idx.Entries) != 4 { t.Fatalf("expected 4 entries, got %d", len(idx.Entries)) } // Verify all entries are present (regardless of order) names := make(map[string]bool) for _, e := range idx.Entries { names[e.Name] = true } for _, want := range []string{"Zebra.txt", "apple.txt", "Banana.txt", "cherry.txt"} { if !names[want] { t.Errorf("expected entry %q not found in index", want) } } } // --- Test: Build Index - Dirs and Files Both Present --- // Per design: index stores raw metadata; dir/file ordering happens at page-load time. func TestBuildIndex_DirsAndFilesPresent(t *testing.T) { dir := setupTestDir(t, []testEntry{ {name: "file.txt", size: 100}, {name: "adir", isDir: true}, {name: "another.txt", size: 200}, {name: "bdir", isDir: true}, }) idx, err := buildIndex(dir) if err != nil { t.Fatalf("buildIndex returned error: %v", err) } // Both dirs and files should be present var gotDirs, gotFiles int for _, e := range idx.Entries { if e.IsDir { gotDirs++ } else { gotFiles++ } } if gotDirs != 2 { t.Errorf("expected 2 directories, got %d", gotDirs) } if gotFiles != 2 { t.Errorf("expected 2 files, got %d", gotFiles) } } // --- Test: Build Index - Cache Written --- func TestBuildIndex_CacheWritten(t *testing.T) { dir := setupTestDir(t, []testEntry{ {name: "test.txt", size: 100}, }) _, err := buildIndex(dir) if err != nil { t.Fatalf("buildIndex returned error: %v", err) } // Verify cache file exists cachePath := getCachePath(dir) if _, err := os.Stat(cachePath); os.IsNotExist(err) { t.Errorf("expected cache file at %s, but it does not exist", cachePath) } } // --- Test: Build Index - Cache Invalidated On Mtime Change --- func TestBuildIndex_CacheInvalidatedOnMtimeChange(t *testing.T) { dir := setupTestDir(t, []testEntry{ {name: "test.txt", size: 100}, }) // First build idx1, err := buildIndex(dir) if err != nil { t.Fatalf("first buildIndex returned error: %v", err) } // Modify directory (add a file) newFile := filepath.Join(dir, "newfile.txt") if err := os.WriteFile(newFile, []byte("new"), 0644); err != nil { t.Fatalf("failed to create new file: %v", err) } // Second build should detect change and rebuild idx2, err := buildIndex(dir) if err != nil { t.Fatalf("second buildIndex returned error: %v", err) } if idx2.EntryCount <= idx1.EntryCount { t.Errorf("expected entry count to increase after adding file; before=%d, after=%d", idx1.EntryCount, idx2.EntryCount) } } // --- Test: Build Index - Cache Reused When Unchanged --- func TestBuildIndex_CacheReusedWhenUnchanged(t *testing.T) { dir := setupTestDir(t, []testEntry{ {name: "test.txt", size: 100}, }) // First build idx1, err := buildIndex(dir) if err != nil { t.Fatalf("first buildIndex returned error: %v", err) } // Second build (no changes) idx2, err := buildIndex(dir) if err != nil { t.Fatalf("second buildIndex returned error: %v", err) } if idx1.EntryCount != idx2.EntryCount { t.Errorf("expected same entry count; first=%d, second=%d", idx1.EntryCount, idx2.EntryCount) } } // --- Test: Build Index - Large Directory --- func TestBuildIndex_LargeDirectory(t *testing.T) { const count = 10000 entries := make([]testEntry, count) for i := 0; i < count; i++ { entries[i] = testEntry{ name: fmt.Sprintf("file_%05d.txt", i), size: int64(i * 10), } } dir := setupTestDir(t, entries) start := time.Now() idx, err := buildIndex(dir) elapsed := time.Since(start) if err != nil { t.Fatalf("buildIndex returned error: %v", err) } if idx.EntryCount != count { t.Errorf("expected %d entries, got %d", count, idx.EntryCount) } // Time budget: 5 seconds for 10k entries if elapsed > 5*time.Second { t.Errorf("buildIndex took %v, expected < 5s", elapsed) } }