test(static-plugin): cover pure helpers, build helpers, and state/env paths
Build / build (push) Successful in 11m3s
Build / build (push) Successful in 11m3s
Bring the previously-untested internal/workload/plugin/source/static/ package from 0% to 23.6% coverage with three new test files: helpers_test.go (20 cases) - idShort/containerNameFor/imageTagFor/ siteVolumeKey shape + same-name-workload collision avoidance; sanitizeError newline collapse, empty-token no-op, 240-byte cap, and multi-byte UTF-8 validity at the cap; containerRowID determinism; lockFor map semantics (same lock for same workload, distinct locks for different workloads, real serialization under contention, safe concurrent insertion); runtimeStateKeys exactly equals the JSON-tag key set. build_test.go (8 cases) - copyDir copies files + subdirs and preserves modes on Unix; verifyDownloadInsideRoot accepts clean trees and surfaces ErrNotExist for missing roots; both functions reject symlinks (skipped cleanly on Windows non-admin where the SeCreateSymbolicLink privilege is absent); prepareStaticBuild writes the Dockerfile even for an empty source. state_integration_test.go (12 cases) - loadState/saveState round- trip on an in-memory SQLite store, including: unknown extra_json keys (future writers) survive a save; clearing a typed field drops the key; malformed extra_json is recovered from rather than panicked on; concurrent writers exercise the per-workload mutex by accumulating into state.LastError - the test verified to fail loudly (15+ lost markers) when the mutex is disabled. buildEnv returns plain values, decrypts encrypted ones, skips rows that fail to decrypt without leaking ciphertext, and returns empty on store failure without panicking. Review followups from go-reviewer pass applied inline: H1 rewrite to exercise actual lost-update race (verified against disabled mutex), H2 workload-ID scoping by t.Name() so the package-global saveLocks map cannot bleed across tests or -count=N runs, set- based env-assertions, JSON tag-set equality check, multi-byte truncation case, valid-JSON-on-recovery assertion, unique-keys in concurrent map test, double-close cleanup.
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
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("<h1>hi</h1>"), 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) != "<h1>hi</h1>" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user