repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / apps / pgs
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}