package static import ( "errors" "os" "path/filepath" "runtime" "strings" "testing" ) func TestCopyDir_CopiesRegularFiles(t *testing.T) { src := t.TempDir() dst := filepath.Join(t.TempDir(), "out") if err := os.MkdirAll(filepath.Join(src, "sub"), 0o755); err != nil { t.Fatalf("mkdir sub: %v", err) } if err := os.WriteFile(filepath.Join(src, "index.html"), []byte("

hi

"), 0o644); err != nil { t.Fatalf("write index: %v", err) } if err := os.WriteFile(filepath.Join(src, "sub", "nested.txt"), []byte("nested"), 0o644); err != nil { t.Fatalf("write nested: %v", err) } if err := copyDir(src, dst); err != nil { t.Fatalf("copyDir: %v", err) } gotIndex, err := os.ReadFile(filepath.Join(dst, "index.html")) if err != nil { t.Fatalf("read index: %v", err) } if string(gotIndex) != "

hi

" { t.Errorf("index content = %q", string(gotIndex)) } gotNested, err := os.ReadFile(filepath.Join(dst, "sub", "nested.txt")) if err != nil { t.Fatalf("read nested: %v", err) } if string(gotNested) != "nested" { t.Errorf("nested content = %q", string(gotNested)) } } func TestCopyDir_PreservesFileMode(t *testing.T) { // File modes are only meaningful outside Windows — Windows reports // 0666 for any writable file regardless of the source mode. if runtime.GOOS == "windows" { t.Skip("file modes are not preserved meaningfully on Windows") } src := t.TempDir() dst := filepath.Join(t.TempDir(), "out") if err := os.WriteFile(filepath.Join(src, "script.sh"), []byte("#!/bin/sh\n"), 0o755); err != nil { t.Fatalf("write: %v", err) } if err := copyDir(src, dst); err != nil { t.Fatalf("copyDir: %v", err) } info, err := os.Stat(filepath.Join(dst, "script.sh")) if err != nil { t.Fatalf("stat: %v", err) } if info.Mode().Perm() != 0o755 { t.Errorf("mode = %v, want 0755", info.Mode().Perm()) } } func TestCopyDir_RejectsSymlinks(t *testing.T) { src := t.TempDir() dst := filepath.Join(t.TempDir(), "out") target := filepath.Join(src, "real.txt") if err := os.WriteFile(target, []byte("real"), 0o644); err != nil { t.Fatalf("write target: %v", err) } if err := os.Symlink(target, filepath.Join(src, "link.txt")); err != nil { // Windows non-admin users cannot create symlinks. The defense // is still valuable on Linux, so just skip when unsupported. t.Skipf("symlink not supported in this environment: %v", err) } err := copyDir(src, dst) if err == nil { t.Fatal("copyDir accepted a symlink; expected refusal") } if !strings.Contains(err.Error(), "non-regular") { t.Errorf("error = %v, want substring \"non-regular\"", err) } } func TestVerifyDownloadInsideRoot_AcceptsCleanTree(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "sub"), 0o755); err != nil { t.Fatalf("mkdir: %v", err) } if err := os.WriteFile(filepath.Join(root, "sub", "file.txt"), []byte("ok"), 0o644); err != nil { t.Fatalf("write: %v", err) } if err := verifyDownloadInsideRoot(root); err != nil { t.Fatalf("verifyDownloadInsideRoot rejected clean tree: %v", err) } } func TestVerifyDownloadInsideRoot_RejectsSymlink(t *testing.T) { root := t.TempDir() target := filepath.Join(t.TempDir(), "outside.txt") if err := os.WriteFile(target, []byte("outside"), 0o644); err != nil { t.Fatalf("write target: %v", err) } if err := os.Symlink(target, filepath.Join(root, "escape.txt")); err != nil { t.Skipf("symlink not supported in this environment: %v", err) } err := verifyDownloadInsideRoot(root) if err == nil { t.Fatal("verifyDownloadInsideRoot accepted symlink; expected refusal") } if !strings.Contains(err.Error(), "non-regular") { t.Errorf("error = %v, want substring \"non-regular\"", err) } } func TestVerifyDownloadInsideRoot_MissingRootSurfacesError(t *testing.T) { err := verifyDownloadInsideRoot(filepath.Join(t.TempDir(), "does-not-exist")) if err == nil { t.Fatal("expected error for missing root, got nil") } if !errors.Is(err, os.ErrNotExist) { t.Errorf("error = %v, want os.ErrNotExist in chain", err) } } func TestPrepareStaticBuild_WritesDockerfileAndCopiesFiles(t *testing.T) { src := t.TempDir() ctxDir := filepath.Join(t.TempDir(), "ctx") if err := os.WriteFile(filepath.Join(src, "index.html"), []byte("hello"), 0o644); err != nil { t.Fatalf("write index: %v", err) } if err := prepareStaticBuild(src, ctxDir); err != nil { t.Fatalf("prepareStaticBuild: %v", err) } if _, err := os.Stat(filepath.Join(ctxDir, "Dockerfile")); err != nil { t.Fatalf("Dockerfile missing: %v", err) } got, err := os.ReadFile(filepath.Join(ctxDir, "index.html")) if err != nil { t.Fatalf("read copied index: %v", err) } if string(got) != "hello" { t.Errorf("copied index content = %q", string(got)) } } func TestPrepareStaticBuild_EmptySrcStillWritesDockerfile(t *testing.T) { // An empty repo folder shouldn't crash the build — nginx will just // serve an empty image. The Dockerfile must still land. src := t.TempDir() ctxDir := filepath.Join(t.TempDir(), "ctx") if err := prepareStaticBuild(src, ctxDir); err != nil { t.Fatalf("prepareStaticBuild: %v", err) } if _, err := os.Stat(filepath.Join(ctxDir, "Dockerfile")); err != nil { t.Fatalf("Dockerfile missing: %v", err) } }