/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>
107 lines
4.0 KiB
Python
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"'
|