[READ-ONLY] Mirror of https://github.com/darccio/zas. Most simple static website generator in Golang.
go static-site-generation static-site-generator
Something went wrong. Try again.
zas walk_test.go
9.0 kB · 270 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271package zas
import ( "errors" "os" "path/filepath" "strings" "testing")
// walk/reaper must return the incoming filepath.Walk error immediately,// instead of touching a possibly-nil FileInfo first.
func TestWalkPropagatesErr(t *testing.T) { gen := &Generator{} wantErr := errors.New("permission denied") if err := gen.walk("noperm", nil, wantErr); !errors.Is(err, wantErr) { t.Fatalf("walk() error = %v, want %v", err, wantErr) }}
func TestReaperPropagatesErr(t *testing.T) { gen := &Generator{} wantErr := errors.New("permission denied") if err := gen.reaper("noperm", nil, wantErr); !errors.Is(err, wantErr) { t.Fatalf("reaper() error = %v, want %v", err, wantErr) }}
func TestWalkSkipsHiddenDirectory(t *testing.T) { gen := &Generator{} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := filepath.Join("sect", ".hidden") if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
// TestWalkAllowedDotDirNotSkipped is a regression test for the "no escape// hatch" gap: a top-level dot-directory explicitly named in the zas// section's allowed_dotdirs config must not be pruned, unlike every other// dot-prefixed directory.func TestWalkAllowedDotDirNotSkipped(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{".well-known"}}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := ".well-known" if err := gen.walk(path, info, nil); err != nil { t.Fatalf("walk(%q) error = %v, want nil", path, err) }}
// TestWalkUnlistedDotDirStillSkipped confirms the allowlist is exact: with// ".well-known" allowed, an unrelated top-level dot-directory like// ".secrets" must still be pruned.func TestWalkUnlistedDotDirStillSkipped(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{".well-known"}}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := ".secrets" if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
// TestWalkNestedDotDirStillSkippedEvenIfAllowedAtTopLevel confirms the// allowlist only ever applies to a top-level directory: a dot-directory// nested under an allowed one (or anywhere else) still gets pruned, so// naming a directory in allowed_dotdirs can't be used to reach an// arbitrarily deep hidden directory that happens to share its name.func TestWalkNestedDotDirStillSkippedEvenIfAllowedAtTopLevel(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{".well-known"}}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := filepath.Join(".well-known", ".well-known") if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
// TestWalkGitNeverAllowlistable confirms .git stays pruned unconditionally// even if a config file explicitly lists it in allowed_dotdirs.func TestWalkGitNeverAllowlistable(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{".git"}}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := ".git" if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
// TestWalkZasDirNeverAllowlistable confirms Dir (".zas") stays pruned// unconditionally even if a config file explicitly lists it in// allowed_dotdirs.func TestWalkZasDirNeverAllowlistable(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{Dir}}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } if err := gen.walk(Dir, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", Dir, err, filepath.SkipDir) }}
// TestWalkAllowedDotDirRequiresDirectory confirms the allowlist only ever// opts in a directory, not a file that happens to share an allowlisted// name.func TestWalkAllowedDotDirRequiresDirectory(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"allowed_dotdirs": []interface{}{".well-known"}}}} file := filepath.Join(t.TempDir(), "x") if err := os.WriteFile(file, nil, 0o644); err != nil { t.Fatal(err) } info, err := os.Stat(file) if err != nil { t.Fatal(err) } path := ".well-known" if err := gen.walk(path, info, nil); err != nil { t.Fatalf("walk(%q) error = %v, want nil", path, err) }}
func TestWalkSkipsDeployDirectory(t *testing.T) { gen := &Generator{} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := filepath.Join("public", Dir, "deploy") if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
func TestWalkSkipsConfiguredDeployPathOutsideZasDir(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"deploy": "public"}}} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } path := "public" if err := gen.walk(path, info, nil); !errors.Is(err, filepath.SkipDir) { t.Fatalf("walk(%q) error = %v, want %v", path, err, filepath.SkipDir) }}
func TestWalkSkipsConfiguredLayoutPathOutsideZasDir(t *testing.T) { gen := &Generator{Config: ConfigSection{Name: ConfigSection{"layout": "mylayout.html"}}} file := filepath.Join(t.TempDir(), "x") if err := os.WriteFile(file, nil, 0o644); err != nil { t.Fatal(err) } info, err := os.Stat(file) if err != nil { t.Fatal(err) } path := "mylayout.html" if err := gen.walk(path, info, nil); err != nil { t.Fatalf("walk(%q) error = %v, want nil", path, err) }}
func TestWalkSkipsHiddenFileWithoutSkipDir(t *testing.T) { gen := &Generator{} file := filepath.Join(t.TempDir(), "x") if err := os.WriteFile(file, nil, 0o644); err != nil { t.Fatal(err) } info, err := os.Stat(file) if err != nil { t.Fatal(err) } path := filepath.Join("sect", ".gitignore") if err := gen.walk(path, info, nil); err != nil { t.Fatalf("walk(%q) error = %v, want nil", path, err) }}
func TestWalkDoesNotSkipDirAtRoot(t *testing.T) { gen := &Generator{} info, err := os.Stat(t.TempDir()) if err != nil { t.Fatal(err) } if err := gen.walk(".", info, nil); err != nil { t.Fatalf(`walk(".") error = %v, want nil`, err) }}
// TestWalkDoesNotSkipDirEndingInZasDir is a regression test: a directory// whose name merely ends in Dir (".zas"), such as "docs.zas", must not// be treated as the real ".zas" directory.func TestWalkDoesNotSkipDirEndingInZasDir(t *testing.T) { t.Chdir(t.TempDir()) gen := &Generator{} if err := os.Mkdir("docs.zas", 0o755); err != nil { t.Fatal(err) } info, err := os.Stat("docs.zas") if err != nil { t.Fatal(err) } if err := gen.walk("docs.zas", info, nil); err != nil { t.Fatalf(`walk("docs.zas") error = %v, want nil`, err) }}
// TestWalkRecordsCollisionAndSkipsSecondGoroutine is a regression test: a// source file whose deploy output path was already claimed by another// source (e.g. foo.html claiming foo.html before foo.md tries to, since// swapExtension maps both to the same output) must not get a second// renderAsync goroutine spawned for it - that's what let two goroutines// race to write the same output file. It must instead record an error// naming both files and return without touching gen.wg.func TestWalkRecordsCollisionAndSkipsSecondGoroutine(t *testing.T) { t.Chdir(t.TempDir()) gen := &Generator{claimedOutputs: map[string]string{"foo.html": "foo.html"}} if err := os.WriteFile("foo.md", nil, 0o644); err != nil { t.Fatal(err) } info, err := os.Stat("foo.md") if err != nil { t.Fatal(err) } if err := gen.walk("foo.md", info, nil); err != nil { t.Fatalf("walk(%q) error = %v, want nil", "foo.md", err) } gen.wg.Wait() if len(gen.errs) != 1 { t.Fatalf("errs = %v, want exactly 1 collision error", gen.errs) } if msg := gen.errs[0].Error(); !strings.Contains(msg, "foo.md") || !strings.Contains(msg, "foo.html") { t.Fatalf("errs[0] = %q, want it to mention both foo.md and foo.html", msg) }}
func TestPathHasComponent(t *testing.T) { tests := []struct { path string component string want bool }{ {Dir, Dir, true}, {filepath.Join("sect", Dir), Dir, true}, {filepath.Join(Dir, "config.yml"), Dir, true}, {"docs.zas", Dir, false}, {filepath.Join("docs.zas", "page.md"), Dir, false}, {"myzasdir", Dir, false}, {"foo.zaster", Dir, false}, } for _, tt := range tests { if got := pathHasComponent(tt.path, tt.component); got != tt.want { t.Errorf("pathHasComponent(%q, %q) = %v, want %v", tt.path, tt.component, got, tt.want) } }}