mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 06:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
# 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
|
||||
});
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user