rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
110 lines (109 loc) • 4.63 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("cloudflare:workers", () => {
class DurableObject {
}
return { DurableObject, env: {} };
});
vi.mock("capnweb", () => ({
RpcTarget: class RpcTarget {
},
newWorkersRpcResponse: vi.fn(),
}));
vi.mock("../../runtime/entries/router", () => ({
route: vi.fn((path, handler) => ({ path, handler })),
}));
vi.mock("../../runtime/requestInfo/worker", () => ({
runWithRequestInfo: (_requestInfo, fn) => fn(),
}));
import { newWorkersRpcResponse } from "capnweb";
import { syncedStateRoutes, SyncedStateServer } from "../worker.mjs";
describe("SyncedStateProxy", () => {
let mockCoordinator;
beforeEach(() => {
mockCoordinator = new SyncedStateServer({}, {});
});
const mockStub = {};
afterEach(() => {
vi.mocked(newWorkersRpcResponse).mockReset();
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
});
it("transforms keys before calling coordinator methods when handler is registered", async () => {
const handler = async (key, stub) => `transformed:${key}`;
SyncedStateServer.registerKeyHandler(handler);
const transformedKey = await handler("counter", mockStub);
expect(transformedKey).toBe("transformed:counter");
mockCoordinator.setState(5, transformedKey);
const value = mockCoordinator.getState(transformedKey);
expect(value).toBe(5);
});
it("does not transform keys when no handler is registered", () => {
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
const handler = SyncedStateServer.getKeyHandler();
expect(handler).not.toBeNull();
});
it("passes through original key when handler returns it unchanged", async () => {
const handler = async (key, stub) => key;
SyncedStateServer.registerKeyHandler(handler);
const result = await handler("counter", mockStub);
expect(result).toBe("counter");
});
it("handler can scope keys per user", async () => {
const handler = async (key, stub) => {
const userId = "user123";
return `user:${userId}:${key}`;
};
SyncedStateServer.registerKeyHandler(handler);
const result = await handler("settings", mockStub);
expect(result).toBe("user:user123:settings");
});
it("allows errors from handler to propagate", async () => {
const handler = async (_key, stub) => {
throw new Error("Handler error");
};
SyncedStateServer.registerKeyHandler(handler);
await expect(handler("test", mockStub)).rejects.toThrow("Handler error");
});
it("uses the subscribed duplicated RPC client when unsubscribing (#1207)", async () => {
const coordinator = {
_setStub: vi.fn(),
subscribe: vi.fn().mockResolvedValue(undefined),
unsubscribe: vi.fn().mockResolvedValue(undefined),
};
const namespace = {
idFromName: vi.fn(() => "synced-state-id"),
get: vi.fn(() => coordinator),
};
let proxy;
vi.mocked(newWorkersRpcResponse).mockImplementation(async (_request, api) => {
proxy = api;
return new Response(null, { status: 204 });
});
SyncedStateServer.registerKeyHandler(async (key) => key);
const [baseRoute] = syncedStateRoutes(() => namespace);
await baseRoute.handler({
request: new Request("https://example.com/__synced-state"),
params: {},
});
const duplicatedClient = Object.assign(vi.fn(), {
dup: vi.fn(() => duplicatedClient),
});
const originalClient = Object.assign(vi.fn(), {
dup: vi.fn(() => duplicatedClient),
});
await proxy.subscribe("notifications", originalClient);
await proxy.unsubscribe("notifications", originalClient);
const subscribeClient = coordinator.subscribe.mock.calls[0][1];
const unsubscribeClient = coordinator.unsubscribe.mock.calls[0][1];
expect(subscribeClient).not.toBe(originalClient);
expect(unsubscribeClient).toBe(subscribeClient);
});
it("handles async operations in handler", async () => {
const handler = async (key, stub) => {
await new Promise((resolve) => setTimeout(resolve, 5));
return `async:${key}`;
};
SyncedStateServer.registerKeyHandler(handler);
const result = await handler("data", mockStub);
expect(result).toBe("async:data");
});
});