Test your integration

Test your agent's workspace integration with the TypeScript and Python SDK fakes, then run the same checks against Shardflux.

Use your application code in tests

The SDK testing helpers run your workspace calls in memory. Your tests use the same workspace handles, refs and errors as your application, with command results and workspace state that you control. Available in @shardflux/sdk 0.16.0+ and Python shardflux 0.12.0+.

TypeScript

TypeScript
import assert from 'node:assert/strict';
import { createFakeShardflux } from '@shardflux/sdk/testing';

const cloud = createFakeShardflux({
  onExec: (request) => {
    assert.deepEqual(request.argv, ['python3', '-V']);
    return { stdout: 'Python 3.12.3\n', exitCode: 0 };
  },
});
const ref = cloud.workspace('acme/test', { template: 'python-node-browser' });
const result = await ref.exec(['python3', '-V'], { sessionId: 'version-check' });
assert.equal(result.exitCode, 0);
await ref.files.write('/home/user/version.txt', result.stdout);
assert.equal(await ref.files.readText('/home/user/version.txt'), 'Python 3.12.3\n');

Python

Python
from shardflux.testing import create_fake_shardflux

def command(request, workspace):
    assert request['argv'] == ['python3', '-V']
    return {'stdout': 'Python 3.12.3\n', 'exit_code': 0}

with create_fake_shardflux(on_exec=command) as cloud:
    ref = cloud.workspace('acme/test', template='python-node-browser')
    result = ref.exec(['python3', '-V'], session_id='version-check')
    assert result.exit_code == 0
    ref.files.write('/home/user/version.txt', result.stdout)
    assert ref.files.read_text('/home/user/version.txt') == 'Python 3.12.3\n'

Exercise your recovery paths

Get the workspace with await ref.open() (TypeScript) or ref.open() (Python). Set a fault by its ID:

Scenario TypeScript Python
Command activity cloud.testing.setFaults(ws.id, { busy: true }) cloud.testing.set_faults(ws.id, busy=True)
Workspace deleted cloud.testing.setFaults(ws.id, { deleted: true }) cloud.testing.set_faults(ws.id, deleted=True)
Failed state cloud.testing.setFaults(ws.id, { failed: true }) cloud.testing.set_faults(ws.id, failed=True)
Size applies at the next start cloud.testing.setFaults(ws.id, { legacyLayout: true }) cloud.testing.set_faults(ws.id, legacy_layout=True)
Desktop unavailable cloud.testing.setFaults(ws.id, { computerUnavailable: true }) cloud.testing.set_faults(ws.id, computer_unavailable=True)
Disk shrink refused cloud.testing.setFaults(ws.id, { resizeRefusal: 'shrink_not_supported' }) cloud.testing.set_faults(ws.id, resize_refusal='shrink_not_supported')
Plan bounds a size cloud.testing.setFaults(ws.id, { memoryLimitMib: 2048 }) cloud.testing.set_faults(ws.id, memory_limit_mib=2048)

Use quota: 'retained_state' or 'concurrent_workspaces' when creating the TypeScript fake, and quota='retained_state' or 'concurrent_workspaces' in Python. The thrown errors are ShardfluxApiError with the same status, source, code and details as the API. Your application's normal error handling runs in the test.

For a command that stays running, return null from onExec or None from on_exec and start it through ws.cell().exec.start(...) or ws.cell().exec_start(...). Inspect ws.idle() while it runs. End it with cloud.testing.completeCommand(ws.id, sessionId, { exitCode: 0 }) or cloud.testing.complete_command(ws.id, session_id, exit_code=0).

cloud.testing.advance(milliseconds) advances the idle clock. Use latencyMs in TypeScript or latency_ms in Python to exercise request delays; the optional sleep callback integrates with your test runner's clock. A fresh fake starts with fresh state. Explicit exec session IDs make command snapshots repeatable.

Run the same checks against Shardflux

Pass a client into your integration scenario functions. Fast tests pass the fake; an integration job passes a Shardflux client with your project API key. Keep the assertions the same: open the same key twice, reuse it after deleting, filter by labels, check command exit codes, read a missing file and inspect resize results.

Use session workspaces for disposable integration runs and delete them in a finally block. Sessions end instead of suspending, so a session's suspendWhenIdle({ afterSeconds: 0 }) or suspend_when_idle(after_seconds=0) returns 409 conflict with reason session_lifetime. Test successful idle suspension with a persistent workspace, and check that a running command or keepalive postpones it. See workspace lifecycle for the lifecycle calls and errors for handling each refusal.

View this page as Markdown