nandi/oripublic Fork 0
7895c1d1c9bb1048807dc04f7246dc456c47e025
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

files_test.go · 166 lines · 5.1 KBGo Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1package files_test
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "rickub.com/bots-garden/ori/internal/files"
13)
14
15// workspace builds a small tree and returns a mux serving the file API on it.
16func workspace(t *testing.T) (string, http.Handler) {
17 t.Helper()
18 root := t.TempDir()
19 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
20 t.Fatal(err)
21 }
22 if err := os.WriteFile(filepath.Join(root, "README.md"), []byte("# hello"), 0o644); err != nil {
23 t.Fatal(err)
24 }
25 if err := os.WriteFile(filepath.Join(root, "src", "main.go"), []byte("package main"), 0o644); err != nil {
26 t.Fatal(err)
27 }
28
29 mux := http.NewServeMux()
30 for pattern, handler := range files.New(root).Routes() {
31 mux.Handle(pattern, handler)
32 }
33 return root, mux
34}
35
36func do(t *testing.T, h http.Handler, method, target, body string) *httptest.ResponseRecorder {
37 t.Helper()
38 var req *http.Request
39 if body == "" {
40 req = httptest.NewRequest(method, target, nil)
41 } else {
42 req = httptest.NewRequest(method, target, strings.NewReader(body))
43 }
44 rec := httptest.NewRecorder()
45 h.ServeHTTP(rec, req)
46 return rec
47}
48
49func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
50 t.Helper()
51 var payload map[string]any
52 if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
53 t.Fatalf("response is not JSON: %v (%q)", err, rec.Body.String())
54 }
55 return payload
56}
57
58func TestListRootSortsDirectoriesFirst(t *testing.T) {
59 _, h := workspace(t)
60 rec := do(t, h, http.MethodGet, "/api/files", "")
61 if rec.Code != http.StatusOK {
62 t.Fatalf("GET /api/files = %d, want 200 (%s)", rec.Code, rec.Body.String())
63 }
64 payload := decodeBody(t, rec)
65 entries := payload["entries"].([]any)
66 if len(entries) != 2 {
67 t.Fatalf("entries = %d, want 2", len(entries))
68 }
69 first := entries[0].(map[string]any)
70 if first["name"] != "src" || first["isDir"] != true {
71 t.Errorf("first entry = %v, want the src directory first", first)
72 }
73}
74
75func TestListSubdirectoryByRelativePath(t *testing.T) {
76 _, h := workspace(t)
77 rec := do(t, h, http.MethodGet, "/api/files?path=src", "")
78 if rec.Code != http.StatusOK {
79 t.Fatalf("GET /api/files?path=src = %d (%s)", rec.Code, rec.Body.String())
80 }
81 entries := decodeBody(t, rec)["entries"].([]any)
82 if len(entries) != 1 || entries[0].(map[string]any)["name"] != "main.go" {
83 t.Errorf("entries = %v, want [main.go]", entries)
84 }
85}
86
87func TestListMissingDirectoryIs404(t *testing.T) {
88 _, h := workspace(t)
89 rec := do(t, h, http.MethodGet, "/api/files?path=nope", "")
90 if rec.Code != http.StatusNotFound {
91 t.Errorf("GET missing dir = %d, want 404", rec.Code)
92 }
93}
94
95func TestReadFileByAbsoluteAndRelativePath(t *testing.T) {
96 root, h := workspace(t)
97
98 rec := do(t, h, http.MethodGet, "/api/file?path=README.md", "")
99 if rec.Code != http.StatusOK {
100 t.Fatalf("GET relative = %d (%s)", rec.Code, rec.Body.String())
101 }
102 if decodeBody(t, rec)["content"] != "# hello" {
103 t.Errorf("relative read content = %v", decodeBody(t, rec)["content"])
104 }
105
106 abs := filepath.Join(root, "src", "main.go")
107 rec = do(t, h, http.MethodGet, "/api/file?path="+abs, "")
108 if rec.Code != http.StatusOK || decodeBody(t, rec)["content"] != "package main" {
109 t.Errorf("absolute read = %d %v", rec.Code, decodeBody(t, rec))
110 }
111}
112
113func TestReadDirectoryIsRejected(t *testing.T) {
114 _, h := workspace(t)
115 rec := do(t, h, http.MethodGet, "/api/file?path=src", "")
116 if rec.Code != http.StatusBadRequest {
117 t.Errorf("GET a directory = %d, want 400", rec.Code)
118 }
119}
120
121func TestReadBinaryFileIs415(t *testing.T) {
122 root, h := workspace(t)
123 if err := os.WriteFile(filepath.Join(root, "blob.bin"), []byte{0xff, 0xfe, 0x00, 0x80}, 0o644); err != nil {
124 t.Fatal(err)
125 }
126 rec := do(t, h, http.MethodGet, "/api/file?path=blob.bin", "")
127 if rec.Code != http.StatusUnsupportedMediaType {
128 t.Errorf("GET binary = %d, want 415", rec.Code)
129 }
130}
131
132func TestReadMissingFileIs404(t *testing.T) {
133 _, h := workspace(t)
134 rec := do(t, h, http.MethodGet, "/api/file?path=ghost.txt", "")
135 if rec.Code != http.StatusNotFound {
136 t.Errorf("GET missing file = %d, want 404", rec.Code)
137 }
138}
139
140func TestWriteRoundTripAndParentCreation(t *testing.T) {
141 root, h := workspace(t)
142
143 rec := do(t, h, http.MethodPut, "/api/file?path=deep/dir/new.txt", `{"content":"saved by test"}`)
144 if rec.Code != http.StatusOK {
145 t.Fatalf("PUT = %d (%s)", rec.Code, rec.Body.String())
146 }
147 onDisk, err := os.ReadFile(filepath.Join(root, "deep", "dir", "new.txt"))
148 if err != nil || string(onDisk) != "saved by test" {
149 t.Fatalf("written file = %q, %v", onDisk, err)
150 }
151
152 rec = do(t, h, http.MethodGet, "/api/file?path=deep/dir/new.txt", "")
153 if decodeBody(t, rec)["content"] != "saved by test" {
154 t.Errorf("read-back content = %v", decodeBody(t, rec)["content"])
155 }
156}
157
158func TestWriteRequiresPathAndValidJSON(t *testing.T) {
159 _, h := workspace(t)
160 if rec := do(t, h, http.MethodPut, "/api/file", `{"content":"x"}`); rec.Code != http.StatusBadRequest {
161 t.Errorf("PUT without path = %d, want 400", rec.Code)
162 }
163 if rec := do(t, h, http.MethodPut, "/api/file?path=x.txt", `{not json`); rec.Code != http.StatusBadRequest {
164 t.Errorf("PUT invalid JSON = %d, want 400", rec.Code)
165 }
166}