Eric Bower
·
2025-12-15
gen_dir_listing_test.go
1package pgs
2
3import (
4 "os"
5 "strings"
6 "testing"
7 "time"
8
9 sst "github.com/picosh/pico/pkg/pobj/storage"
10 "github.com/picosh/pico/pkg/send/utils"
11)
12
13func TestGenerateDirectoryHTML(t *testing.T) {
14 fixtures := []struct {
15 Name string
16 Path string
17 Entries []os.FileInfo
18 Contains []string
19 }{
20 {
21 Name: "empty-directory",
22 Path: "/",
23 Entries: []os.FileInfo{},
24 Contains: []string{
25 "<title>Index of /</title>",
26 "Index of /",
27 },
28 },
29 {
30 Name: "single-file",
31 Path: "/",
32 Entries: []os.FileInfo{
33 &utils.VirtualFile{FName: "hello.txt", FSize: 1024, FIsDir: false, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
34 },
35 Contains: []string{
36 "<title>Index of /</title>",
37 `href="hello.txt"`,
38 "hello.txt",
39 "1.0 KB",
40 },
41 },
42 {
43 Name: "single-folder",
44 Path: "/",
45 Entries: []os.FileInfo{
46 &utils.VirtualFile{FName: "docs", FSize: 0, FIsDir: true, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
47 },
48 Contains: []string{
49 `href="docs/"`,
50 "docs/",
51 },
52 },
53 {
54 Name: "mixed-entries",
55 Path: "/assets/",
56 Entries: []os.FileInfo{
57 &utils.VirtualFile{FName: "images", FSize: 0, FIsDir: true, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
58 &utils.VirtualFile{FName: "style.css", FSize: 2048, FIsDir: false, FModTime: time.Date(2025, 1, 14, 8, 0, 0, 0, time.UTC)},
59 &utils.VirtualFile{FName: "app.js", FSize: 512, FIsDir: false, FModTime: time.Date(2025, 1, 13, 12, 0, 0, 0, time.UTC)},
60 },
61 Contains: []string{
62 "<title>Index of /assets/</title>",
63 `href="images/"`,
64 `href="style.css"`,
65 `href="app.js"`,
66 "images/",
67 "2.0 KB",
68 },
69 },
70 {
71 Name: "subdirectory-with-parent-link",
72 Path: "/docs/api/",
73 Entries: []os.FileInfo{
74 &utils.VirtualFile{FName: "readme.md", FSize: 256, FIsDir: false, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
75 },
76 Contains: []string{
77 "<title>Index of /docs/api/</title>",
78 `href="../"`,
79 "../",
80 },
81 },
82 }
83
84 for _, fixture := range fixtures {
85 t.Run(fixture.Name, func(t *testing.T) {
86 html := generateDirectoryHTML(fixture.Path, fixture.Entries)
87
88 for _, expected := range fixture.Contains {
89 if !strings.Contains(html, expected) {
90 t.Errorf("expected HTML to contain %q, got:\n%s", expected, html)
91 }
92 }
93 })
94 }
95}
96
97func TestHideDotFiles(t *testing.T) {
98 entries := []os.FileInfo{
99 &utils.VirtualFile{FName: ".hidden", FIsDir: false, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
100 &utils.VirtualFile{FName: ".git", FIsDir: true, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
101 &utils.VirtualFile{FName: "visible.txt", FIsDir: false, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
102 &utils.VirtualFile{FName: "docs", FIsDir: true, FModTime: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)},
103 }
104
105 displayEntries := toDisplayEntries(entries)
106
107 if len(displayEntries) != 2 {
108 t.Errorf("expected 2 entries, got %d", len(displayEntries))
109 }
110
111 for _, entry := range displayEntries {
112 if strings.HasPrefix(entry.Display, ".") {
113 t.Errorf("dot file should be hidden: %s", entry.Display)
114 }
115 }
116}
117
118func TestSortEntries(t *testing.T) {
119 entries := []os.FileInfo{
120 &utils.VirtualFile{FName: "zebra.txt", FIsDir: false},
121 &utils.VirtualFile{FName: "alpha", FIsDir: true},
122 &utils.VirtualFile{FName: "beta.md", FIsDir: false},
123 &utils.VirtualFile{FName: "zulu", FIsDir: true},
124 &utils.VirtualFile{FName: "apple.js", FIsDir: false},
125 }
126
127 sortEntries(entries)
128
129 expected := []string{"alpha", "zulu", "apple.js", "beta.md", "zebra.txt"}
130 for i, entry := range entries {
131 if entry.Name() != expected[i] {
132 t.Errorf("position %d: expected %q, got %q", i, expected[i], entry.Name())
133 }
134 }
135}
136
137func TestShouldGenerateListing(t *testing.T) {
138 fixtures := []struct {
139 Name string
140 Path string
141 Storage map[string]map[string]string
142 Expected bool
143 }{
144 {
145 Name: "directory-with-index-html",
146 Path: "/docs/",
147 Storage: map[string]map[string]string{
148 "testbucket": {
149 "/project/docs/index.html": "<html>hello</html>",
150 },
151 },
152 Expected: false,
153 },
154 {
155 Name: "directory-without-index-html",
156 Path: "/docs/",
157 Storage: map[string]map[string]string{
158 "testbucket": {
159 "/project/docs/readme.md": "# Readme",
160 "/project/docs/guide.md": "# Guide",
161 },
162 },
163 Expected: true,
164 },
165 {
166 Name: "empty-directory",
167 Path: "/empty/",
168 Storage: map[string]map[string]string{
169 "testbucket": {
170 "/project/other/file.txt": "content",
171 },
172 },
173 Expected: false,
174 },
175 {
176 Name: "root-directory-without-index",
177 Path: "/",
178 Storage: map[string]map[string]string{
179 "testbucket": {
180 "/project/style.css": "body {}",
181 "/project/app.js": "console.log('hi')",
182 },
183 },
184 Expected: true,
185 },
186 {
187 Name: "root-directory-with-index",
188 Path: "/",
189 Storage: map[string]map[string]string{
190 "testbucket": {
191 "/project/index.html": "<html>home</html>",
192 },
193 },
194 Expected: false,
195 },
196 {
197 Name: "nested-directory-without-index",
198 Path: "/assets/images/",
199 Storage: map[string]map[string]string{
200 "testbucket": {
201 "/project/assets/images/logo.png": "png data",
202 "/project/assets/images/banner.jpg": "jpg data",
203 },
204 },
205 Expected: true,
206 },
207 }
208
209 for _, fixture := range fixtures {
210 t.Run(fixture.Name, func(t *testing.T) {
211 st, _ := sst.NewStorageMemory(fixture.Storage)
212 bucket := sst.Bucket{Name: "testbucket", Path: "testbucket"}
213
214 result := shouldGenerateListing(st, bucket, "project", fixture.Path)
215
216 if result != fixture.Expected {
217 t.Errorf("shouldGenerateListing(%q) = %v, want %v", fixture.Path, result, fixture.Expected)
218 }
219 })
220 }
221}