ZDDC/zddc/internal/fs/resolve_test.go
2026-06-11 13:32:31 -05:00

156 lines
4.3 KiB
Go

package fs
import (
"os"
"path/filepath"
"runtime"
"testing"
)
func mkdir(t *testing.T, parts ...string) {
t.Helper()
if err := os.MkdirAll(filepath.Join(parts...), 0o755); err != nil {
t.Fatal(err)
}
}
func TestResolveCanonical_RootAndEmpty(t *testing.T) {
root := t.TempDir()
for _, in := range []string{"/", "", "//"} {
abs, url, ok := ResolveCanonical(root, in)
if !ok {
t.Fatalf("%q: ok=false", in)
}
if abs != root || url != "/" {
t.Fatalf("%q: abs=%q url=%q", in, abs, url)
}
}
}
func TestResolveCanonical_ExactCase(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive", "incoming")
abs, url, ok := ResolveCanonical(root, "/archive/incoming")
if !ok || url != "/archive/incoming" {
t.Fatalf("ok=%v url=%q", ok, url)
}
if abs != filepath.Join(root, "archive", "incoming") {
t.Fatalf("abs=%q", abs)
}
}
func TestResolveCanonical_MixedCaseURLLowercaseOnDisk(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive", "incoming")
abs, url, ok := ResolveCanonical(root, "/Archive/Incoming")
if !ok || url != "/archive/incoming" {
t.Fatalf("ok=%v url=%q", ok, url)
}
if abs != filepath.Join(root, "archive", "incoming") {
t.Fatalf("abs=%q", abs)
}
}
func TestResolveCanonical_OnlyMixedCaseExists(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "Archive", "Incoming")
abs, url, ok := ResolveCanonical(root, "/archive/incoming")
if !ok || url != "/Archive/Incoming" {
t.Fatalf("ok=%v url=%q", ok, url)
}
if abs != filepath.Join(root, "Archive", "Incoming") {
t.Fatalf("abs=%q", abs)
}
}
func TestResolveCanonical_BothCasesExistLowercaseWins(t *testing.T) {
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
t.Skip("filesystem may be case-insensitive; tiebreak only meaningful on case-sensitive FS")
}
root := t.TempDir()
mkdir(t, root, "Archive")
mkdir(t, root, "archive")
if err := os.WriteFile(filepath.Join(root, "Archive", "marker"), []byte("upper"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "archive", "marker"), []byte("lower"), 0o644); err != nil {
t.Fatal(err)
}
for _, in := range []string{"/Archive/marker", "/archive/marker", "/aRcHiVe/marker"} {
abs, url, ok := ResolveCanonical(root, in)
if !ok {
t.Fatalf("%q: ok=false", in)
}
if url != "/archive/marker" {
t.Fatalf("%q: url=%q (want /archive/marker)", in, url)
}
body, err := os.ReadFile(abs)
if err != nil {
t.Fatalf("%q: read %s: %v", in, abs, err)
}
if string(body) != "lower" {
t.Fatalf("%q: body=%q (want \"lower\" — lowercase variant must win)", in, body)
}
}
}
func TestResolveCanonical_NonexistentSegmentPreservesRemainder(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive")
abs, url, ok := ResolveCanonical(root, "/Archive/.archive/TR-001.html")
if !ok {
t.Fatal("ok=false")
}
// Walk canonicalizes "Archive" to "archive"; the virtual ".archive"
// segment doesn't exist on disk, so the remainder passes through
// unchanged so the dispatcher's virtual-prefix routing still fires.
if url != "/archive/.archive/TR-001.html" {
t.Fatalf("url=%q", url)
}
if abs != filepath.Join(root, "archive", ".archive", "TR-001.html") {
t.Fatalf("abs=%q", abs)
}
}
func TestResolveCanonical_FileSegmentTerminatesWalk(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive")
if err := os.WriteFile(filepath.Join(root, "archive", "Doc.PDF"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
abs, url, ok := ResolveCanonical(root, "/Archive/doc.pdf")
if !ok {
t.Fatal("ok=false")
}
// On Linux Doc.PDF exists but doc.pdf does not — exact-case tier
// finds Doc.PDF and uses it.
if url != "/archive/Doc.PDF" {
t.Fatalf("url=%q", url)
}
_ = abs
}
func TestResolveCanonical_RejectsEscape(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive")
// filepath.Clean reduces "/archive/../.." to "/.."; Resolve sees
// segments that don't exist on disk and walks them verbatim. The
// final containment check must reject the result.
_, _, ok := ResolveCanonical(root, "/archive/../../etc")
if ok {
t.Fatal("expected ok=false for escape path")
}
}
func TestResolveCanonical_TrailingSlashesNormalized(t *testing.T) {
root := t.TempDir()
mkdir(t, root, "archive", "incoming")
_, url, ok := ResolveCanonical(root, "/Archive/Incoming/")
if !ok {
t.Fatal("ok=false")
}
if url != "/archive/incoming" {
t.Fatalf("url=%q", url)
}
}