Files
OneCAD-Draft/docs/automation/serve_smoke.py
T
huanldandClaude Opus 5.5 279a87418e feat(automation): entity, block, publishing and REST operations from the fork
The operations docs/automation/API-SPEC.md (PR #1383) describes, brought
over from huaninstratech/OpenCADStudio so the specification no longer runs
ahead of the code:

- entity CRUD: entities_create, entities_delete, entities_transform,
  entities_copy_to, and `where` filters on query
- blocks and export: block_define, block_delete, a normalised wblock
- publishing: layout_create, page_setup_set, plot (per_page included)
- metadata: sysvar, xdata_set / xdata_get, file_identity
- collections: group_create, selection_set_save / load
- control plane: batch, events, view_focus, close
- the REST transport: `--http <port>`, the /api/v1 routes and their
  OpenAPI 3 description
- person-in-the-loop picks: user_select and getpoint, hosted by the GUI
  over the same REST routes, with on-screen pending signals

Taken from the fork's commits b7a7ed80, 69396b42, 64f1cf6a, c3aafc8e,
bb5a7bfa, 52317a5c, 50d293ba, b90e194f, 517c6397, 53a5c4c9, a49ac045,
37de5a46, a78b1822, 6d9aed2a, 33f522da, 5b5cafe2, 02a7e78d, 94df40dc,
fd5bc14d, 485fac61 and 2c5fc994.

Left out on purpose: 37de5a46 also removed the donation surface (the
Patreon fetch, the donation prompt, the Start-page supporters rail, the
DONATE command and its locale strings) and replaced the About dialog's call
to action; that is the fork's own product decision and stays out. The
fork's version bump and its unrelated IFC/BIM work are not part of this.

Adapted while porting: the REST server and the GUI bridge are desktop-only
and a template start is refused in the web build, so the wasm target still
compiles; the two plot tests read the PDF through the shared stream helper,
because exported streams are Flate-compressed; the benches set the new
crosshair field; comments and docs describe each operation in its own
terms rather than by another product's API names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-24 13:41:07 +03:00

104 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Black-box smoke test for the headless automation server (`--serve`).
Exercises the ops an external client needs: state discovery, the wblock
export, the plot-to-PDF export, and the capability advertisement. Uses only
the Python standard library; treats the executable as a black box.
python3 docs/automation/serve_smoke.py target/debug/OpenCADStudio
"""
import json
import subprocess
import sys
import tempfile
from pathlib import Path
class Serve:
def __init__(self, exe: Path):
self.process = subprocess.Popen(
[str(exe), "--serve"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
encoding="utf-8",
)
self.ready = json.loads(self.process.stdout.readline())
assert self.ready["ok"] is True, self.ready
assert self.ready["session_id"], self.ready
def send(self, request: dict) -> dict:
self.process.stdin.write(json.dumps(request) + "\n")
self.process.stdin.flush()
return json.loads(self.process.stdout.readline())
def close(self) -> None:
self.process.stdin.close()
self.process.wait(timeout=30)
def main() -> None:
exe = Path(sys.argv[1] if len(sys.argv) > 1 else "target/debug/OpenCADStudio").resolve()
out_dir = Path(tempfile.mkdtemp(prefix="ocs_serve_smoke_"))
server = Serve(exe)
try:
state = server.send({"protocol": 1, "op": "state"})
document_id = state["document_id"]
# Draw something to export and plot.
server.send({"op": "new"})
for request_id, cmd in [("run-1", "LINE 0,0 100,80"), ("run-2", "CIRCLE 50,40 20")]:
response = server.send({"op": "run", "cmd": cmd})
assert response["ok"] is True, response
# Read-only ops stay on the legacy (non-envelope) shape.
summary = server.send({"op": "entities"})
assert summary["total"] == 2, summary
# wblock: export the two entities to a standalone DXF.
export = out_dir / "wblock.dxf"
response = server.send({
"protocol": 1,
"op": "wblock",
"request_id": "smoke-wblock-1",
"document_id": document_id,
"path": str(export),
"handles": [
entity["handle"]
for entity in server.send({"op": "query", "detail": "summary"})["entities"]
],
})
assert response["ok"] is True, response
assert response["status"] == "completed", response
assert response["result"]["entities"] == 2, response
assert export.exists()
# plot: render model space to a one-page PDF.
pdf = out_dir / "plot.pdf"
response = server.send({
"protocol": 1,
"op": "plot",
"request_id": "smoke-plot-1",
"document_id": document_id,
"path": str(pdf),
})
assert response["ok"] is True, response
assert response["result"]["pages"] == 1, response
assert pdf.read_bytes()[:4] == b"%PDF"
# The file operations are discoverable through capabilities.
capabilities = server.send({"op": "capabilities"})
operations = capabilities["operations"]
assert operations["wblock"] is True, operations
assert operations["plot"] is True, operations
assert operations["embed_image"] is True, operations
print("serve smoke: OK")
finally:
server.close()
if __name__ == "__main__":
main()