package workspace import ( "os" "path/filepath" "strings" "testing" ) func TestSafePathAcceptsCleanRelativePaths(t *testing.T) { root := t.TempDir() got, err := SafePath(root, "source/manifest.json") if err != nil { t.Fatalf("SafePath: %v", err) } want := filepath.Join(root, "source", "manifest.json") if got != want { t.Fatalf("SafePath = %q, want %q", got, want) } } func TestSafePathRejectsUnsafeNames(t *testing.T) { root := t.TempDir() tests := []struct { name string path string want string }{ {name: "empty", path: " ", want: "empty"}, {name: "absolute", path: filepath.Join(root, "artifact.json"), want: "relative"}, {name: "parent segment", path: "../artifact.json", want: ".."}, {name: "embedded parent", path: "source/../artifact.json", want: ".."}, {name: "backslash", path: `source\artifact.json`, want: "slash-separated"}, {name: "unclean", path: "source//artifact.json", want: "clean"}, {name: "dot", path: ".", want: ".."}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := SafePath(root, tc.path) if err == nil { t.Fatalf("SafePath returned %q, want error", got) } if !strings.Contains(err.Error(), tc.want) { t.Fatalf("SafePath error = %v, want containing %q", err, tc.want) } }) } } func TestSafePathRejectsEmptyRoot(t *testing.T) { got, err := SafePath(" ", "artifact.json") if err == nil { t.Fatalf("SafePath returned %q, want error", got) } if !strings.Contains(err.Error(), "root") { t.Fatalf("SafePath error = %v, want root error", err) } } func TestSafePathDoesNotPermitEscapingRoot(t *testing.T) { root := t.TempDir() for _, name := range []string{ "..", "../outside.json", "nested/../../outside.json", } { t.Run(name, func(t *testing.T) { got, err := SafePath(root, name) if err == nil { t.Fatalf("SafePath returned %q, want error", got) } }) } } func TestWriteJSONWritesIndentedAtomicArtifact(t *testing.T) { root := t.TempDir() err := WriteJSON(root, "source/manifest.json", map[string]any{ "status": "succeeded", "count": 2, }) if err != nil { t.Fatalf("WriteJSON: %v", err) } got := string(readFile(t, filepath.Join(root, "source", "manifest.json"))) if !strings.HasSuffix(got, "\n") { t.Fatalf("expected trailing newline, got %q", got) } if !strings.Contains(got, `"status": "succeeded"`) || !strings.Contains(got, `"count": 2`) { t.Fatalf("unexpected JSON: %s", got) } assertNoTempFiles(t, filepath.Join(root, "source")) } func TestWriteBytesWritesNestedArtifact(t *testing.T) { root := t.TempDir() if err := WriteBytes(root, "chunk/chunks.json", []byte("payload")); err != nil { t.Fatalf("WriteBytes: %v", err) } got := string(readFile(t, filepath.Join(root, "chunk", "chunks.json"))) if got != "payload" { t.Fatalf("bytes = %q, want payload", got) } assertNoTempFiles(t, filepath.Join(root, "chunk")) } func TestWritersRejectUnsafePaths(t *testing.T) { root := t.TempDir() if err := WriteBytes(root, "../outside.json", []byte("payload")); err == nil { t.Fatalf("WriteBytes accepted unsafe path") } if err := WriteJSON(root, `debug\trace.json`, map[string]string{"x": "y"}); err == nil { t.Fatalf("WriteJSON accepted unsafe path") } if _, err := os.Stat(filepath.Join(root, "..", "outside.json")); !os.IsNotExist(err) { t.Fatalf("outside path stat err = %v, want not exist", err) } } func readFile(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatalf("read %q: %v", path, err) } return data } func assertNoTempFiles(t *testing.T, dir string) { t.Helper() entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("read dir %q: %v", dir, err) } for _, entry := range entries { if strings.Contains(entry.Name(), ".tmp-") { t.Fatalf("temporary file was not cleaned up: %s", entry.Name()) } } }