import json from typer.testing import CliRunner from spriteforge.cli import app runner=CliRunner() def make_library(tmp_path): manifest=tmp_path/"assets.yaml" manifest.write_text("""\ floor: backend: procedural generator: floor_tile tags: [environment] params: {width: 24, height: 12, variants: 2, color: '#705840'} decal: backend: procedural generator: decal tags: [effect] params: {width: 20, height: 10, color: '#A03020'} """) output=tmp_path/"library" result=runner.invoke(app,["build",str(manifest),"--output",str(output)]) assert result.exit_code==0,result.output return manifest,output def test_text_introspection_commands(tmp_path): manifest,output=make_library(tmp_path);common=["--build-dir",str(output)] listed=runner.invoke(app,["list","--tag","environment",*common]) assert listed.exit_code==0 and "floor" in listed.stdout and "decal" not in listed.stdout described=runner.invoke(app,["describe","floor",*common]) assert described.exit_code==0 and "backend: procedural" in described.stdout and "spec:" in described.stdout preview=runner.invoke(app,["ascii","floor","--frame","1","--columns","12",*common]) assert preview.exit_code==0 and "size=" in preview.stdout and ("#" in preview.stdout or "+" in preview.stdout) palette=runner.invoke(app,["palette","default",*common]) assert palette.exit_code==0 and "#" in palette.stdout and "transparent" in palette.stdout stats=runner.invoke(app,["stats",*common]) assert stats.exit_code==0 and "assets: 2" in stats.stdout and "frames: 3" in stats.stdout validated=runner.invoke(app,["validate",str(manifest),*common]) assert validated.exit_code==0 and "0 error(s)" in validated.stdout def test_diff_uses_text_snapshots(tmp_path): manifest,output=make_library(tmp_path) old=next(item["hash"] for item in json.loads((output/"index.json").read_text())["assets"] if item["id"]=="floor") text=manifest.read_text().replace("#705840","#406080");manifest.write_text(text) assert runner.invoke(app,["build",str(manifest),"--output",str(output)]).exit_code==0 result=runner.invoke(app,["diff","floor","--against",old[:12],"--build-dir",str(output)]) assert result.exit_code==0 and "changed" in result.stdout and "hash:" in result.stdout def test_validate_reports_manifest_asset_without_file(tmp_path): manifest,output=make_library(tmp_path) with manifest.open("a") as stream: stream.write("missing:\n backend: procedural\n generator: decal\n") result=runner.invoke(app,["validate",str(manifest),"--build-dir",str(output)]) assert result.exit_code==1 and "missing from catalog" in result.stdout def test_contact_sheet_is_human_only_png(tmp_path): _,output=make_library(tmp_path);sheet=tmp_path/"sheet.png" result=runner.invoke(app,["contact-sheet","--build-dir",str(output),"--output",str(sheet)]) assert result.exit_code==0 and "human-only" in result.stdout assert sheet.read_bytes()[:8]==b"\x89PNG\r\n\x1a\n" def test_backend_layer_warning_is_reported_textually(tmp_path): manifest,output=make_library(tmp_path) index=json.loads((output/"index.json").read_text()) index["assets"][0]["warnings"]=["animation=walk direction=2 frame=3: depth ranges overlap; split into passes"] (output/"index.json").write_text(json.dumps(index)) result=runner.invoke(app,["validate","--build-dir",str(output)]) assert result.exit_code==0 and "WARNING" in result.stdout and "frame=3" in result.stdout def test_paperdoll_layer_allows_shared_external_pivot(tmp_path): manifest,output=make_library(tmp_path) index=json.loads((output/"index.json").read_text()) floor=next(item for item in index["assets"] if item["id"]=="floor") floor["tags"].append("paperdoll_layer") (output/"index.json").write_text(json.dumps(index)) result=runner.invoke(app,["validate",str(manifest),"--build-dir",str(output)]) assert result.exit_code==0 and "pivot is far outside" not in result.stdout