nandi/oripublic Fork 0
34e69b510306161654f26903a269247aefa9c94d
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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package files_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"rickub.com/bots-garden/ori/internal/files"
)

// workspace builds a small tree and returns a mux serving the file API on it.
func workspace(t *testing.T) (string, http.Handler) {
	t.Helper()
	root := t.TempDir()
	if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(root, "README.md"), []byte("# hello"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(root, "src", "main.go"), []byte("package main"), 0o644); err != nil {
		t.Fatal(err)
	}

	mux := http.NewServeMux()
	for pattern, handler := range files.New(root).Routes() {
		mux.Handle(pattern, handler)
	}
	return root, mux
}

func do(t *testing.T, h http.Handler, method, target, body string) *httptest.ResponseRecorder {
	t.Helper()
	var req *http.Request
	if body == "" {
		req = httptest.NewRequest(method, target, nil)
	} else {
		req = httptest.NewRequest(method, target, strings.NewReader(body))
	}
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	return rec
}

func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
	t.Helper()
	var payload map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("response is not JSON: %v (%q)", err, rec.Body.String())
	}
	return payload
}

func TestListRootSortsDirectoriesFirst(t *testing.T) {
	_, h := workspace(t)
	rec := do(t, h, http.MethodGet, "/api/files", "")
	if rec.Code != http.StatusOK {
		t.Fatalf("GET /api/files = %d, want 200 (%s)", rec.Code, rec.Body.String())
	}
	payload := decodeBody(t, rec)
	entries := payload["entries"].([]any)
	if len(entries) != 2 {
		t.Fatalf("entries = %d, want 2", len(entries))
	}
	first := entries[0].(map[string]any)
	if first["name"] != "src" || first["isDir"] != true {
		t.Errorf("first entry = %v, want the src directory first", first)
	}
}

func TestListSubdirectoryByRelativePath(t *testing.T) {
	_, h := workspace(t)
	rec := do(t, h, http.MethodGet, "/api/files?path=src", "")
	if rec.Code != http.StatusOK {
		t.Fatalf("GET /api/files?path=src = %d (%s)", rec.Code, rec.Body.String())
	}
	entries := decodeBody(t, rec)["entries"].([]any)
	if len(entries) != 1 || entries[0].(map[string]any)["name"] != "main.go" {
		t.Errorf("entries = %v, want [main.go]", entries)
	}
}

func TestListMissingDirectoryIs404(t *testing.T) {
	_, h := workspace(t)
	rec := do(t, h, http.MethodGet, "/api/files?path=nope", "")
	if rec.Code != http.StatusNotFound {
		t.Errorf("GET missing dir = %d, want 404", rec.Code)
	}
}

func TestReadFileByAbsoluteAndRelativePath(t *testing.T) {
	root, h := workspace(t)

	rec := do(t, h, http.MethodGet, "/api/file?path=README.md", "")
	if rec.Code != http.StatusOK {
		t.Fatalf("GET relative = %d (%s)", rec.Code, rec.Body.String())
	}
	if decodeBody(t, rec)["content"] != "# hello" {
		t.Errorf("relative read content = %v", decodeBody(t, rec)["content"])
	}

	abs := filepath.Join(root, "src", "main.go")
	rec = do(t, h, http.MethodGet, "/api/file?path="+abs, "")
	if rec.Code != http.StatusOK || decodeBody(t, rec)["content"] != "package main" {
		t.Errorf("absolute read = %d %v", rec.Code, decodeBody(t, rec))
	}
}

func TestReadDirectoryIsRejected(t *testing.T) {
	_, h := workspace(t)
	rec := do(t, h, http.MethodGet, "/api/file?path=src", "")
	if rec.Code != http.StatusBadRequest {
		t.Errorf("GET a directory = %d, want 400", rec.Code)
	}
}

func TestReadBinaryFileIs415(t *testing.T) {
	root, h := workspace(t)
	if err := os.WriteFile(filepath.Join(root, "blob.bin"), []byte{0xff, 0xfe, 0x00, 0x80}, 0o644); err != nil {
		t.Fatal(err)
	}
	rec := do(t, h, http.MethodGet, "/api/file?path=blob.bin", "")
	if rec.Code != http.StatusUnsupportedMediaType {
		t.Errorf("GET binary = %d, want 415", rec.Code)
	}
}

func TestReadMissingFileIs404(t *testing.T) {
	_, h := workspace(t)
	rec := do(t, h, http.MethodGet, "/api/file?path=ghost.txt", "")
	if rec.Code != http.StatusNotFound {
		t.Errorf("GET missing file = %d, want 404", rec.Code)
	}
}

func TestWriteRoundTripAndParentCreation(t *testing.T) {
	root, h := workspace(t)

	rec := do(t, h, http.MethodPut, "/api/file?path=deep/dir/new.txt", `{"content":"saved by test"}`)
	if rec.Code != http.StatusOK {
		t.Fatalf("PUT = %d (%s)", rec.Code, rec.Body.String())
	}
	onDisk, err := os.ReadFile(filepath.Join(root, "deep", "dir", "new.txt"))
	if err != nil || string(onDisk) != "saved by test" {
		t.Fatalf("written file = %q, %v", onDisk, err)
	}

	rec = do(t, h, http.MethodGet, "/api/file?path=deep/dir/new.txt", "")
	if decodeBody(t, rec)["content"] != "saved by test" {
		t.Errorf("read-back content = %v", decodeBody(t, rec)["content"])
	}
}

func TestWriteRequiresPathAndValidJSON(t *testing.T) {
	_, h := workspace(t)
	if rec := do(t, h, http.MethodPut, "/api/file", `{"content":"x"}`); rec.Code != http.StatusBadRequest {
		t.Errorf("PUT without path = %d, want 400", rec.Code)
	}
	if rec := do(t, h, http.MethodPut, "/api/file?path=x.txt", `{not json`); rec.Code != http.StatusBadRequest {
		t.Errorf("PUT invalid JSON = %d, want 400", rec.Code)
	}
}