Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 121 additions & 10 deletions .claude/skills/swamp-extension-datastore/references/testing.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Testing Datastore Extensions

The `@systeminit/swamp-testing` package provides conformance suites and test
doubles for datastore extensions.
The `@systeminit/swamp-testing` package provides conformance suites, mock
primitives, and test doubles for datastore extensions.

Install: `deno add jsr:@systeminit/swamp-testing`

Expand Down Expand Up @@ -60,21 +60,131 @@ Deno.test("verifier contract", async () => {
Validates: verify() returns a result with healthy (boolean), message (string),
latencyMs (non-negative number), datastoreType (string).

## Mocking External Calls

Test the exact production code path by intercepting at the runtime boundary.

### S3/AWS-based datastores

Datastores that accept an `endpoint` config can point at a local mock server to
test verifier and sync behavior without real AWS credentials:

```typescript
import { assertVerifierConformance } from "@systeminit/swamp-testing";
import { datastore } from "./s3.ts";

Deno.test({
name: "s3 verifier reports healthy",
sanitizeResources: false,
fn: async () => {
const server = Deno.serve({ port: 0, onListen() {} }, (req) => {
if (req.method === "HEAD") return new Response(null, { status: 200 });
return new Response(null, { status: 404 });
});

const addr = server.addr as Deno.NetAddr;
Deno.env.set("AWS_ACCESS_KEY_ID", "test");
Deno.env.set("AWS_SECRET_ACCESS_KEY", "test");

try {
const provider = datastore.createProvider({
bucket: "my-bucket",
region: "us-east-1",
endpoint: `http://localhost:${addr.port}`,
forcePathStyle: true,
});
const verifier = provider.createVerifier();
await assertVerifierConformance(verifier);
} finally {
Deno.env.delete("AWS_ACCESS_KEY_ID");
Deno.env.delete("AWS_SECRET_ACCESS_KEY");
await server.shutdown();
}
},
});
```

### Lock testing with mock clients

For testing lock semantics, create an in-memory mock of your storage client and
pass it to your lock implementation. The S3 datastore uses this pattern:

```typescript
import { assertLockConformance } from "@systeminit/swamp-testing";
import { S3Lock } from "./_lib/s3_lock.ts";

function createMockS3Client() {
const storage = new Map<string, Uint8Array>();
return {
storage,
putObjectConditional(key, body) {
if (storage.has(key)) return Promise.resolve(false);
storage.set(key, body);
return Promise.resolve(true);
},
getObject(key) {
const data = storage.get(key);
if (!data) return Promise.reject(new Error("NoSuchKey"));
return Promise.resolve(data);
},
deleteObject(key) {
storage.delete(key);
return Promise.resolve();
},
// ... other methods
};
}

Deno.test("S3Lock passes conformance", async () => {
const mock = createMockS3Client();
const lock = new S3Lock(mock, { ttlMs: 5000 });
await assertLockConformance(lock);
});
```

### CLI-based datastores

Use `withMockedCommand` for datastores that shell out to CLI tools:

```typescript
import { withMockedCommand } from "@systeminit/swamp-testing";

await withMockedCommand((cmd, args) => {
if (cmd === "rclone" && args.includes("sync")) {
return { stdout: "Transferred: 3 files", code: 0 };
}
return { stdout: "", code: 1 };
}, async () => {
const sync = provider.createSyncService!("/repo", "/cache");
await sync.pullChanged();
});
```

### REST API-based datastores

Use `withMockedFetch` for datastores that call `fetch()` directly:

```typescript
import { withMockedFetch } from "@systeminit/swamp-testing";

await withMockedFetch((req) => {
if (req.method === "HEAD") return new Response(null, { status: 200 });
return new Response(null, { status: 404 });
}, async () => {
const verifier = provider.createVerifier();
const result = await verifier.verify();
assertEquals(result.healthy, true);
});
```

## In-Memory Test Double

For testing code that _consumes_ a datastore (not the datastore itself):

```typescript
import { createDatastoreTestContext } from "@systeminit/swamp-testing";

Deno.test("lock behavior", async () => {
const { provider, isLockHeld } = createDatastoreTestContext();
const lock = provider.createLock("/ds");

await lock.acquire();
assertEquals(isLockHeld(), true);
await lock.release();
});
const { provider, isLockHeld } = createDatastoreTestContext();
```

| Option | Default | Description |
Expand All @@ -93,5 +203,6 @@ Import directly from the testing package source:
import {
assertDatastoreExportConformance,
assertLockConformance,
withMockedCommand,
} from "../../packages/testing/mod.ts";
```
138 changes: 99 additions & 39 deletions .claude/skills/swamp-extension-driver/references/testing.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
# Unit Testing Execution Drivers
# Testing Execution Drivers

The `@systeminit/swamp-testing` package provides `createDriverTestContext()` for
unit testing execution driver implementations without running real model
methods.
The `@systeminit/swamp-testing` package provides test context factories and mock
primitives for execution driver extensions.

Install: `deno add jsr:@systeminit/swamp-testing`

## createDriverTestContext Options
## createDriverTestContext

Builds a well-formed `ExecutionRequest` and callbacks that capture events:

```typescript
import { createDriverTestContext } from "@systeminit/swamp-testing";
import { assertEquals } from "@std/assert";
import { driver } from "./my_driver.ts";

Deno.test("driver executes method", async () => {
const myDriver = driver.createDriver({});
const { request, callbacks, getCapturedLogs } = createDriverTestContext({
methodName: "run",
globalArgs: { region: "us-east-1" },
});

const result = await myDriver.execute(request, callbacks);
assertEquals(result.status, "success");
});
```

| Option | Default | Description |
| ----------------- | -------------- | ----------------------------- |
Expand All @@ -33,54 +51,92 @@ const {
} = createDriverTestContext();
```

## Testing a Driver Implementation
## Mocking External Calls

```typescript
import { createDriverTestContext } from "@systeminit/swamp-testing";
import { assertEquals } from "@std/assert";
import { driver } from "./my_driver.ts";
Drivers that call external services (HTTP APIs, CLI tools) can be tested using
mock primitives that intercept at the runtime boundary.

Deno.test("driver executes method successfully", async () => {
const myDriver = driver.createDriver({});
const { request, callbacks, getCapturedLogs } = createDriverTestContext({
methodName: "run",
globalArgs: { region: "us-east-1" },
});
### Drivers that call `fetch()`

const result = await myDriver.execute(request, callbacks);
Use `withMockedFetch` for drivers that make HTTP requests via `fetch()`:

assertEquals(result.status, "success");
assertEquals(result.outputs.length > 0, true);
```typescript
import {
createDriverTestContext,
withMockedFetch,
} from "@systeminit/swamp-testing";

Deno.test("driver calls remote API", async () => {
const { calls } = await withMockedFetch((req) => {
if (req.url.includes("/execute")) {
return Response.json({ status: "ok", output: "result" });
}
return Response.json({ error: "not found" }, { status: 404 });
}, async () => {
const myDriver = driver.createDriver({ endpoint: "https://api.test" });
const { request, callbacks } = createDriverTestContext();
await myDriver.execute(request, callbacks);
});

assertEquals(calls.length, 1);
assertEquals(calls[0].url, "https://api.test/execute");
});
```

## Testing Callback Events
### Drivers that shell out to CLI tools

```typescript
Deno.test("driver emits log lines", async () => {
const myDriver = driver.createDriver({});
const { request, callbacks, getCapturedLogs } = createDriverTestContext();
Use `withMockedCommand` for drivers that use `Deno.Command`:

await myDriver.execute(request, callbacks);
```typescript
import {
createDriverTestContext,
withMockedCommand,
} from "@systeminit/swamp-testing";

Deno.test("driver runs subprocess", async () => {
const { result } = await withMockedCommand((cmd, args) => {
if (cmd === "docker" && args.includes("run")) {
return { stdout: '{"status":"success"}', code: 0 };
}
return { stdout: "", stderr: "not found", code: 1 };
}, async () => {
const myDriver = driver.createDriver({});
const { request, callbacks } = createDriverTestContext();
return await myDriver.execute(request, callbacks);
});

const logs = getCapturedLogs();
assertEquals(logs.length > 0, true);
assertEquals(typeof logs[0].line, "string");
assertEquals(result.status, "success");
});
```

## Testing Error Handling
### Drivers that use AWS SDK

```typescript
Deno.test("driver reports errors gracefully", async () => {
const myDriver = driver.createDriver({});
const { request, callbacks } = createDriverTestContext({
methodName: "nonexistent-method",
});
AWS SDK uses `node:https` internally. Use a local mock server with
`AWS_ENDPOINT_URL`:

const result = await myDriver.execute(request, callbacks);
assertEquals(result.status, "error");
assertEquals(typeof result.error, "string");
```typescript
Deno.test({
name: "driver calls AWS",
sanitizeResources: false,
fn: async () => {
const server = Deno.serve({ port: 0, onListen() {} }, async (req) => {
return Response.json({ result: "ok" });
});
const addr = server.addr as Deno.NetAddr;
Deno.env.set("AWS_ENDPOINT_URL", `http://localhost:${addr.port}`);
Deno.env.set("AWS_ACCESS_KEY_ID", "test");
Deno.env.set("AWS_SECRET_ACCESS_KEY", "test");

try {
const myDriver = driver.createDriver({ region: "us-east-1" });
const { request, callbacks } = createDriverTestContext();
const result = await myDriver.execute(request, callbacks);
assertEquals(result.status, "success");
} finally {
Deno.env.delete("AWS_ENDPOINT_URL");
await server.shutdown();
}
},
});
```

Expand All @@ -89,5 +145,9 @@ Deno.test("driver reports errors gracefully", async () => {
Import directly from the testing package source:

```typescript
import { createDriverTestContext } from "../../packages/testing/mod.ts";
import {
createDriverTestContext,
withMockedCommand,
withMockedFetch,
} from "../../packages/testing/mod.ts";
```
Loading
Loading