Files
ai-agent/sidecar/test_scaffold.py
Gabriel Vidal 80726f7b91 feat(sidecar): /scaffold, /scaffold/check and /relocate — the host half of New projects
/scaffold runs the owner's project generator (SIDECAR_SCAFFOLD_CMD, default
the lab's new-project) with a vars JSON and reads its SCAFFOLD_RESULT= line
back (a generated site password stays out of the log); /scaffold/check is its
--check. /relocate lets a session go on in another directory: it stops a live
run and copies the session file (same id) into that cwd's transcript folder,
where the next --resume finds it with its whole history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:42:07 +02:00

107 lines
4.0 KiB
Python

"""New projects, host half: /scaffold(/check) runs the generator and reads its
marker line; /relocate copies a session into another cwd's transcript dir."""
import json
import os
import pathlib
import sys
import time
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
import fork
import scaffold
FAKE_GENERATOR = r"""
import json, sys
args = sys.argv[1:]
vars = json.load(open(args[args.index("--vars") + 1]))
if "--check" in args:
bad = vars["NAME"] == "taken"
print("SCAFFOLD_CHECK=" + json.dumps({"ok": not bad, "problems":
[{"field": "name", "message": "taken"}] if bad else []}))
sys.exit(1 if bad else 0)
print("Scaffolding…")
res = {"dir": "/p/" + vars["NAME"], "flags": [a for a in args if a.startswith("--")]}
if "--password" in args:
res["password"] = "pw-1234"
print("Site password (shown once): pw-1234")
print("SCAFFOLD_RESULT=" + json.dumps(res))
"""
@pytest.fixture()
def client(tmp_path, monkeypatch):
gen = tmp_path / "gen.py"
gen.write_text(FAKE_GENERATOR)
monkeypatch.setattr(scaffold, "SCAFFOLD_CMD", [sys.executable, str(gen)])
monkeypatch.setattr(scaffold, "_auth", lambda h: None)
monkeypatch.setattr(fork, "_auth", lambda h: None)
app = FastAPI()
app.include_router(scaffold.router)
app.include_router(fork.router)
return TestClient(app)
def test_scaffold_reads_the_result_and_hides_the_password(client):
r = client.post("/scaffold", json={"template": "react-vite", "password": True,
"vars": {"NAME": "planning", "TITLE": "P"}})
out = r.json()
assert out["ok"] is True
assert out["result"]["dir"] == "/p/planning"
assert out["result"]["password"] == "pw-1234"
assert "--password" in out["result"]["flags"]
assert "pw-1234" not in out["log"]
def test_check_reports_problems(client):
out = client.post("/scaffold/check", json={"template": "react-vite",
"vars": {"NAME": "taken"}}).json()
assert out["ok"] is False
assert out["result"]["problems"][0]["field"] == "name"
assert client.post("/scaffold/check", json={"template": "react-vite",
"vars": {"NAME": "free"}}).json()["ok"]
@pytest.mark.parametrize("body", [
{"template": "../x", "vars": {"NAME": "a"}},
{"template": "react-vite", "vars": {"name": "a"}},
{"template": "react-vite", "vars": {"TITLE": "no name"}},
])
def test_rejects_bad_input(client, body):
assert client.post("/scaffold", json=body).status_code == 400
def test_relocate_copies_the_newest_copy(client, tmp_path, monkeypatch):
projects = tmp_path / "projects"
monkeypatch.setattr(fork, "_account_projects", lambda account: projects)
monkeypatch.setattr(fork, "_valid_account", lambda a: a or "personal")
monkeypatch.setattr(fork.pids, "_live_record", lambda sid: None)
sid = "11111111-2222-4333-8444-555555555555"
src = projects / "-home-x-homelab" / f"{sid}.jsonl"
src.parent.mkdir(parents=True)
src.write_text('{"sessionId": "%s"}\n' % sid)
dest_cwd = tmp_path / "proj"
dest_cwd.mkdir()
out = client.post("/relocate", json={"sessionId": sid, "cwd": str(dest_cwd)}).json()
dst = projects / fork._cwd_slug(str(dest_cwd)) / f"{sid}.jsonl"
assert out["copied"] is True and pathlib.Path(out["path"]) == dst
assert dst.read_text() == src.read_text()
# again: the copy there is now the newest — nothing to do
time.sleep(0.01)
os.utime(dst)
assert client.post("/relocate", json={"sessionId": sid,
"cwd": str(dest_cwd)}).json()["copied"] is False
assert client.post("/relocate", json={
"sessionId": "99999999-2222-4333-8444-555555555555",
"cwd": str(dest_cwd)}).status_code == 404
def test_cwd_slug_matches_claude_code():
assert fork._cwd_slug("/home/gabrielvidal/projects/my_app.v2") == \
"-home-gabrielvidal-projects-my-app-v2"
assert json.dumps(fork._cwd_slug("/tmp/a b")) == '"-tmp-a-b"'