mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 06:11:27 -07:00
184 lines
4.8 KiB
Markdown
184 lines
4.8 KiB
Markdown
# DO Storage Testing
|
|
|
|
Testing Durable Objects with storage using `vitest-pool-workers`.
|
|
|
|
## Setup
|
|
|
|
**vitest.config.ts:**
|
|
```typescript
|
|
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
|
|
|
|
export default defineWorkersConfig({
|
|
test: {
|
|
poolOptions: {
|
|
workers: { wrangler: { configPath: "./wrangler.toml" } }
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
**package.json:** Add `@cloudflare/vitest-pool-workers` and `vitest` to devDependencies
|
|
|
|
## Basic Testing
|
|
|
|
```typescript
|
|
import { env, runInDurableObject } from "cloudflare:test";
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
describe("Counter DO", () => {
|
|
it("increments counter", async () => {
|
|
const id = env.COUNTER.idFromName("test");
|
|
const result = await runInDurableObject(env.COUNTER, id, async (instance, state) => {
|
|
const val1 = await instance.increment();
|
|
const val2 = await instance.increment();
|
|
return { val1, val2 };
|
|
});
|
|
expect(result.val1).toBe(1);
|
|
expect(result.val2).toBe(2);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Testing SQL Storage
|
|
|
|
```typescript
|
|
it("creates and queries users", async () => {
|
|
const id = env.USER_MANAGER.idFromName("test");
|
|
await runInDurableObject(env.USER_MANAGER, id, async (instance, state) => {
|
|
await instance.createUser("alice@example.com", "Alice");
|
|
const user = await instance.getUser("alice@example.com");
|
|
expect(user).toEqual({ email: "alice@example.com", name: "Alice" });
|
|
});
|
|
});
|
|
|
|
it("handles schema migrations", async () => {
|
|
const id = env.USER_MANAGER.idFromName("migration-test");
|
|
await runInDurableObject(env.USER_MANAGER, id, async (instance, state) => {
|
|
const version = state.storage.sql.exec(
|
|
"SELECT value FROM _meta WHERE key = 'schema_version'"
|
|
).one()?.value;
|
|
expect(version).toBe("1");
|
|
});
|
|
});
|
|
```
|
|
|
|
## Testing Alarms
|
|
|
|
```typescript
|
|
import { runDurableObjectAlarm } from "cloudflare:test";
|
|
|
|
it("processes batch on alarm", async () => {
|
|
const id = env.BATCH_PROCESSOR.idFromName("test");
|
|
|
|
// Add items
|
|
await runInDurableObject(env.BATCH_PROCESSOR, id, async (instance) => {
|
|
await instance.addItem("item1");
|
|
await instance.addItem("item2");
|
|
});
|
|
|
|
// Trigger alarm
|
|
await runDurableObjectAlarm(env.BATCH_PROCESSOR, id);
|
|
|
|
// Verify processed
|
|
await runInDurableObject(env.BATCH_PROCESSOR, id, async (instance, state) => {
|
|
const count = state.storage.sql.exec(
|
|
"SELECT COUNT(*) as count FROM processed_items"
|
|
).one().count;
|
|
expect(count).toBe(2);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Testing Concurrency
|
|
|
|
```typescript
|
|
it("handles concurrent increments safely", async () => {
|
|
const id = env.COUNTER.idFromName("concurrent-test");
|
|
|
|
// Parallel increments
|
|
const results = await Promise.all([
|
|
runInDurableObject(env.COUNTER, id, (i) => i.increment()),
|
|
runInDurableObject(env.COUNTER, id, (i) => i.increment()),
|
|
runInDurableObject(env.COUNTER, id, (i) => i.increment())
|
|
]);
|
|
|
|
// All should get unique values
|
|
expect(new Set(results).size).toBe(3);
|
|
expect(Math.max(...results)).toBe(3);
|
|
});
|
|
```
|
|
|
|
## Test Isolation
|
|
|
|
```typescript
|
|
// Per-test unique IDs
|
|
let testId: string;
|
|
beforeEach(() => { testId = crypto.randomUUID(); });
|
|
|
|
it("isolated test", async () => {
|
|
const id = env.MY_DO.idFromName(testId);
|
|
// Uses unique DO instance
|
|
});
|
|
|
|
// Cleanup pattern
|
|
it("with cleanup", async () => {
|
|
const id = env.MY_DO.idFromName("cleanup-test");
|
|
try {
|
|
await runInDurableObject(env.MY_DO, id, async (instance) => {});
|
|
} finally {
|
|
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
|
|
await state.storage.deleteAll();
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
## Testing PITR
|
|
|
|
```typescript
|
|
it("restores from bookmark", async () => {
|
|
const id = env.MY_DO.idFromName("pitr-test");
|
|
|
|
// Create checkpoint
|
|
const bookmark = await runInDurableObject(env.MY_DO, id, async (instance, state) => {
|
|
await state.storage.put("value", 1);
|
|
return await state.storage.getCurrentBookmark();
|
|
});
|
|
|
|
// Modify and restore
|
|
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
|
|
await state.storage.put("value", 2);
|
|
await state.storage.onNextSessionRestoreBookmark(bookmark);
|
|
state.abort();
|
|
});
|
|
|
|
// Verify restored
|
|
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
|
|
const value = await state.storage.get("value");
|
|
expect(value).toBe(1);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Testing Transactions
|
|
|
|
```typescript
|
|
it("rolls back on error", async () => {
|
|
const id = env.BANK.idFromName("transaction-test");
|
|
|
|
await runInDurableObject(env.BANK, id, async (instance, state) => {
|
|
await state.storage.put("balance", 100);
|
|
|
|
await expect(
|
|
state.storage.transaction(async () => {
|
|
await state.storage.put("balance", 50);
|
|
throw new Error("Cancel");
|
|
})
|
|
).rejects.toThrow("Cancel");
|
|
|
|
const balance = await state.storage.get("balance");
|
|
expect(balance).toBe(100); // Rolled back
|
|
});
|
|
});
|
|
```
|