Verentis

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 runtime

spec.runtimes.docker.image (or a wasm module) that the platform can actually pull/load.

Permissions match what you call

Reads need node.file.read; writing/deleting files needs the VFS authoring cascade. Request only what you use — see Scopes.

Tools dispatch cleanly

Each 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.

Need local files instead of a workspace?

verentis dev run always targets a real workspace. For a fully offline process, call engine.configure_local(files_root=..., runtime_dir=..., setting_aliases=...) from a separate local-only runner, then execute that runner with ordinary Python. Do not put this call in the production entry point you package for Verentis. In offline mode engine.files uses the confined local filesystem and engine.settings reads explicitly mapped environment variables. See the Python SDK reference.

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

Publishing & distribution

Push your image, upload the manifest, and make your engine available.