fix(review round 2): daemon symlink tars, orphan-safe removes, unique seed names, git ctx, IsErrNotFound, collision-free slugs, branch validation; web first-run WS, draft reset, IME guard, churn fix, test swap repair; 106 daemon + 190 web tests green

This commit is contained in:
Raphael Westphal
2026-08-18 19:13:51 +02:00
parent 95d2b5da4b
commit 66c90e48bb
22 changed files with 1476 additions and 964 deletions
+26 -9
View File
@@ -106,16 +106,33 @@ func TestAPIEventsReplayShape(t *testing.T) {
func TestAPISpawnValidation(t *testing.T) {
ts, _ := newTestServer(t)
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/spawn",
strings.NewReader(`{"repo":"no-slash"}`))
req.Header.Set("Authorization", "Bearer "+testToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("spawn: %v", err)
post := func(body string) int {
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/spawn", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer "+testToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("spawn: %v", err)
}
resp.Body.Close()
return resp.StatusCode
}
resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("bad repo → %d, want 400", resp.StatusCode)
if code := post(`{"repo":"no-slash"}`); code != http.StatusBadRequest {
t.Fatalf("bad repo → %d, want 400", code)
}
// branch is passed to git: anything outside the safe charset → 400
for _, branch := range []string{
"main; rm -rf /",
"feature one", // space
"-oProxyCommand=x", // leading option-ish
"main$(id)",
} {
if code := post(`{"repo":"group/project","branch":"` + branch + `"}`); code != http.StatusBadRequest {
t.Fatalf("branch %q → %d, want 400", branch, code)
}
}
// empty branch (default) stays accepted at this validation layer
if code := post(`{"repo":"group/project","branch":""}`); code == http.StatusBadRequest {
t.Fatal("empty branch must not be rejected as invalid")
}
}