Testing
Testing your engine
Test engine logic without the platform — unit-test your tool code, run the container against a mock context, and validate your manifest before you publish.
You don't need a running platform to build an engine with confidence. Test at three levels — your tool logic, the packaged container, and the manifest — then do one live run.
1. Unit-test your tool logic
Keep the actual work in plain functions and have your entrypoint call them. Pure functions test without Docker, the platform, or a token:
# transform.py
def summarise(rows: list) -> dict:
return {"rows": len(rows), "first": rows[0] if rows else None}
# tests/test_transform.py
from transform import summarise
def test_summarise():
assert summarise([1, 2, 3]) == {"rows": 3, "first": 1}
When a test needs the run context (but not the network), build one with VerentisEngine.initialize
— no /verentis mount required:
from verentis import VerentisEngine
def test_reads_parameters():
engine = VerentisEngine.initialize(
api_url="https://api.localtest.me:6500",
access_token="test-token",
workspace_id="ws-test",
file_path="/scripts/transform.py",
)
assert engine.context.file.path == "/scripts/transform.py"
Avoid calling engine.files / engine.http in unit tests — those hit the gateway. Test the logic
in isolation; cover file/HTTP interaction with the mock-context container run below, or a live run.
2. Run the container with a mock context
The platform mounts /verentis/context.json, your engine runs, and it writes /verentis/result.json
(and optional progress.json). Reproduce that locally by mounting a /verentis directory:
mkdir -p .local/verentis
cp examples/context.json .local/verentis/context.json # edit to taste
docker run --rm -v "$PWD/.local/verentis:/verentis" my-engine:dev
cat .local/verentis/result.json
A minimal mock context.json (see the Python SDK reference for every
field):
{
"version": 1,
"execution": { "id": "exec-local", "mode": "request-response", "tool": "execute", "trigger": "user", "timeout": 300 },
"workspace": { "id": "ws-local", "name": "Local" },
"file": { "path": "/examples/input.txt", "name": "input.txt", "mimeType": "text/plain", "branch": "main" },
"input": { "arguments": [], "environment": {}, "parameters": { "message": "hello" } },
"platform": { "apiUrl": "http://localhost:5000", "token": { "accessToken": "local-dev-token", "scopes": ["node.file.read"] } }
}
File and HTTP calls in a mock run target platform.apiUrl with platform.token.accessToken. Point them
at a real (or dev) gateway with a valid token to exercise engine.files / engine.http end-to-end;
otherwise keep the run to logic that doesn't touch the platform.
3. Validate your manifest
Before publishing, sanity-check engine.yaml. A quick structural check catches the common mistakes:
# validate_manifest.py — usage: python validate_manifest.py engine.yaml
import sys, yaml
REQUIRED_META = ("name", "display-name", "version")
def validate(path: str) -> list[str]:
doc = yaml.safe_load(open(path))
errors = []
if doc.get("kind") != "ExecutionEngine":
errors.append("kind must be 'ExecutionEngine'")
meta = doc.get("metadata") or {}
errors += [f"metadata.{k} is required" for k in REQUIRED_META if not meta.get(k)]
spec = doc.get("spec") or {}
if not (spec.get("runtimes") or {}):
errors.append("spec.runtimes must declare at least one runtime (docker and/or wasm)")
if not (spec.get("file-types") or []):
errors.append("spec.file-types should declare at least one pattern")
for tool in spec.get("tools") or []:
if not tool.get("name"):
errors.append("every spec.tools[] entry needs a name")
return errors
if __name__ == "__main__":
problems = validate(sys.argv[1])
print("\n".join(problems) if problems else "OK")
sys.exit(1 if problems else 0)
Checklist the platform enforces at registration time:
A resolvable runtimespec.runtimes.docker.image (or a wasm module) that the platform can actually pull/load.
Permissions match what you callReads need node.file.read; writing/deleting files needs the VFS authoring cascade. Request only what
you use — see Scopes.
Tools dispatch cleanlyEach spec.tools[].name must match a branch your entrypoint handles on
engine.context.execution.tool.
4. Debug against a REAL workspace with the CLI
Mock-context runs cover logic, but the fastest way to develop platform-touching code is the CLI's dev
loop: run your script locally — same interpreter, your editor, your breakpoints — while
engine.files / engine.http hit a real workspace with a scoped token identical to a platform run.
cd my-engine-project # contains engine.yaml (spec.permissions drives the token scopes)
# One-shot run with real workspace access:
verentis dev run transform.py --workspace <id> --params '{"message":"hello"}'
# Attach a debugger — the script waits for you before the first line:
verentis dev run transform.py --debug # debugpy on :5678 with --wait-for-client
verentis dev vscode # writes .vscode/launch.json (attach + launch configs)
dev run mints a workspace-bound access token (scopes from your manifest's spec.permissions, or
--scopes / --boundary /some/dir for a node-bounded token like platform runs get), writes a real
.verentis/context.json matching the execution contract, and spawns
your script with the full VERENTIS_* environment — the SDK auto-loads it with zero code changes.
Prefer the building blocks directly?
verentis dev token --workspace <id> # print a scoped token
verentis dev context --file /scripts/transform.py # write .verentis/context.json
export VERENTIS_CONTEXT_PATH=$PWD/.verentis/context.json
python -m debugpy --listen 5678 --wait-for-client transform.py
.verentis/context.json contains a live (short-lived) access token — add .verentis/ to your
.gitignore. Tokens expire; re-run verentis dev run/dev context when you see a 401.
Validate your manifest with the CLI instead of hand-rolled scripts: verentis validate engine.yaml.
5. Test the in-browser (WASM) path
If your engine ships a client runtime (spec.runtimes.wasm.adapter), verify it in the browser too:
Build and upload the worker
Build your adapter and upload it to the VFS path your manifest declares (e.g.
/applications/<name>/runner.js), alongside the manifest.
Run from a directory
Client runs get a scoped token only when the platform can bind one to a launch node — run a directory program (or a file with a parent folder), not a lone root-level file.
Verify the SDK
Confirm engine.files / engine.http work in-browser exactly as they do server-side — same API, backed
by the scoped token.
Next
Push your image, upload the manifest, and make your engine available.