From efc54116d720b4710da1ce41c1a88f693064d31e Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 15 Apr 2026 01:27:50 -0700 Subject: [PATCH] chore(rivetkit): remove inline tests --- .../actors/inlineClientActor.ts | 3 - .../driver-test-suite/inline-client.ts | 108 ----- .../driver-test-suite/registry-static.ts | 3 - .../rivetkit/src/driver-test-suite/mod.ts | 138 +++--- .../test-inline-client-driver.ts | 347 -------------- .../tests/actor-inline-client.ts | 163 ------- .../driver-test-suite/tests/actor-queue.ts | 6 +- .../tests/gateway-query-url.ts | 11 +- .../tests/gateway-routing.ts | 19 +- .../tests/hibernatable-websocket-protocol.ts | 326 ++++++------- .../tests/raw-http-direct-registry.ts | 15 +- .../tests/raw-websocket-direct-registry.ts | 13 +- .../driver-test-suite/tests/raw-websocket.ts | 24 +- .../driver-test-suite/tests/request-access.ts | 82 ++-- .../rivetkit/src/driver-test-suite/utils.ts | 41 +- .../rivetkit/src/sandbox/actor.test.ts | 38 -- .../tests/agent-os-session-lifecycle.test.ts | 68 --- .../tests/driver-engine-dynamic.test.ts | 451 ------------------ .../rivetkit/tests/driver-engine-ping.test.ts | 333 ------------- .../rivetkit/tests/driver-engine.test.ts | 1 - .../rivetkit/tests/standalone-driver-test.mts | 126 ----- .../rivetkit/tests/standalone-native-test.mts | 416 ---------------- .../rivetkit/tests/standalone-ws-sqlite.mts | 93 ---- .../rivetkit/tests/standalone-ws-test.mts | 142 ------ 24 files changed, 292 insertions(+), 2675 deletions(-) delete mode 100644 rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/actors/inlineClientActor.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/inline-client.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inline-client.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/src/sandbox/actor.test.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/standalone-driver-test.mts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/standalone-native-test.mts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/standalone-ws-sqlite.mts delete mode 100644 rivetkit-typescript/packages/rivetkit/tests/standalone-ws-test.mts diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/actors/inlineClientActor.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/actors/inlineClientActor.ts deleted file mode 100644 index d28d93e278..0000000000 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/actors/inlineClientActor.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { inlineClientActor } from "../inline-client"; - -export default inlineClientActor; diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/inline-client.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/inline-client.ts deleted file mode 100644 index 24828408f8..0000000000 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/inline-client.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { actor } from "rivetkit"; -import type { registry } from "./registry-static"; - -function isDynamicSandboxRuntime(): boolean { - return process.cwd() === "/root"; -} - -async function waitForConnectionOpen(connection: { - connStatus: string; - onOpen(callback: () => void): () => void; - onError(callback: (error: unknown) => void): () => void; -}) { - if (connection.connStatus === "connected") { - return; - } - - await new Promise((resolve, reject) => { - const unsubscribeOpen = connection.onOpen(() => { - unsubscribeOpen(); - unsubscribeError(); - resolve(); - }); - const unsubscribeError = connection.onError((error) => { - unsubscribeOpen(); - unsubscribeError(); - reject(error); - }); - }); -} - -export const inlineClientActor = actor({ - state: { messages: [] as string[] }, - actions: { - // Action that uses client to call another actor (stateless) - callCounterIncrement: async (c, amount: number) => { - const client = c.client(); - const result = await client.counter - .getOrCreate(["inline-test"]) - .increment(amount); - c.state.messages.push( - `Called counter.increment(${amount}), result: ${result}`, - ); - return result; - }, - - // Action that uses client to get counter state (stateless) - getCounterState: async (c) => { - const client = c.client(); - const count = await client.counter - .getOrCreate(["inline-test"]) - .getCount(); - c.state.messages.push(`Got counter state: ${count}`); - return count; - }, - - // Action that uses client with .connect() for stateful communication - connectToCounterAndIncrement: async (c, amount: number) => { - const client = c.client(); - const handle = client.counter.getOrCreate(["inline-test-stateful"]); - - if (isDynamicSandboxRuntime()) { - const events: number[] = []; - const result1 = await handle.increment(amount); - events.push(result1); - const result2 = await handle.increment(amount * 2); - events.push(result2); - - c.state.messages.push( - `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`, - ); - - return { result1, result2, events }; - } - - await handle.getCount(); - const connection = handle.connect(); - await waitForConnectionOpen(connection); - - // Set up event listener - const events: number[] = []; - connection.on("newCount", (count: number) => { - events.push(count); - }); - - // Perform increments - const result1 = await connection.increment(amount); - const result2 = await connection.increment(amount * 2); - - await connection.dispose(); - - c.state.messages.push( - `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`, - ); - - return { result1, result2, events }; - }, - - // Get all messages from this actor's state - getMessages: (c) => { - return c.state.messages; - }, - - // Clear messages - clearMessages: (c) => { - c.state.messages = []; - }, - }, -}); diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts index dd84a7b591..73d85a1dae 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts @@ -40,7 +40,6 @@ import { hibernationActor, hibernationSleepWindowActor, } from "./hibernation"; -import { inlineClientActor } from "./inline-client"; import { beforeConnectTimeoutActor, beforeConnectRejectActor, @@ -213,8 +212,6 @@ export const registry = setup({ // From error-handling.ts errorHandlingActor, customTimeoutActor, - // From inline-client.ts - inlineClientActor, // From kv.ts kvActor, // From queue.ts diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts index ff6c689967..0b5fa5584b 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts @@ -25,7 +25,6 @@ import { runActorStateTests } from "./tests/actor-state"; import { runActorConnStatusTests } from "./tests/actor-conn-status"; import { runActorErrorHandlingTests } from "./tests/actor-error-handling"; import { runActorHandleTests } from "./tests/actor-handle"; -import { runActorInlineClientTests } from "./tests/actor-inline-client"; import { runActorInspectorTests } from "./tests/actor-inspector"; import { runActorKvTests } from "./tests/actor-kv"; import { runActorMetadataTests } from "./tests/actor-metadata"; @@ -56,7 +55,6 @@ export interface SkipTests { schedule?: boolean; sleep?: boolean; hibernation?: boolean; - inline?: boolean; sandbox?: boolean; agentOs?: boolean; } @@ -85,25 +83,13 @@ export interface DriverTestConfig { /** Restrict which encodings to test. Defaults to all (bare, cbor, json). */ encodings?: Encoding[]; - /** Restrict which client types to test. Defaults to http + inline (unless skip.inline is set). */ - clientTypes?: ClientType[]; - encoding?: Encoding; isDynamic?: boolean; - clientType: ClientType; - cleanup?: () => Promise; } -/** - * The type of client to run the test with. - * - * The logic for HTTP vs inline is very different, so this helps validate all behavior matches. - **/ -type ClientType = "http" | "inline"; - export interface DriverDeployOutput { endpoint: string; namespace: string; @@ -117,107 +103,102 @@ export interface DriverDeployOutput { /** Runs all Vitest tests against the provided drivers. */ export function runDriverTests( - driverTestConfigPartial: Omit, + driverTestConfigPartial: Omit, ) { describe("Driver Tests", () => { - const clientTypes: ClientType[] = driverTestConfigPartial.clientTypes - ?? (driverTestConfigPartial.skip?.inline ? ["http"] : ["http", "inline"]); - for (const clientType of clientTypes) { - describe(`client type (${clientType})`, () => { - const encodings: Encoding[] = driverTestConfigPartial.encodings ?? ["bare", "cbor", "json"]; - - for (const encoding of encodings) { - describe(`encoding (${encoding})`, () => { - const driverTestConfig: DriverTestConfig = { - ...driverTestConfigPartial, - clientType, - encoding, - }; + const encodings: Encoding[] = driverTestConfigPartial.encodings ?? [ + "bare", + "cbor", + "json", + ]; - runActorStateTests(driverTestConfig); - runActorScheduleTests(driverTestConfig); - runActorSleepTests(driverTestConfig); - runActorSleepDbTests(driverTestConfig); - runActorLifecycleTests(driverTestConfig); - runManagerDriverTests(driverTestConfig); + for (const encoding of encodings) { + describe(`encoding (${encoding})`, () => { + const driverTestConfig: DriverTestConfig = { + ...driverTestConfigPartial, + encoding, + }; - runActorConnTests(driverTestConfig); + runActorStateTests(driverTestConfig); + runActorScheduleTests(driverTestConfig); + runActorSleepTests(driverTestConfig); + runActorSleepDbTests(driverTestConfig); + runActorLifecycleTests(driverTestConfig); + runManagerDriverTests(driverTestConfig); - runActorConnStateTests(driverTestConfig); + runActorConnTests(driverTestConfig); - runActorConnHibernationTests(driverTestConfig); + runActorConnStateTests(driverTestConfig); - runActorConnStatusTests(driverTestConfig); + runActorConnHibernationTests(driverTestConfig); - runConnErrorSerializationTests(driverTestConfig); + runActorConnStatusTests(driverTestConfig); - runActorDbTests(driverTestConfig); + runConnErrorSerializationTests(driverTestConfig); - runActorDestroyTests(driverTestConfig); + runActorDbTests(driverTestConfig); - runRequestAccessTests(driverTestConfig); + runActorDestroyTests(driverTestConfig); - runActorHandleTests(driverTestConfig); + runRequestAccessTests(driverTestConfig); - runActionFeaturesTests(driverTestConfig); + runActorHandleTests(driverTestConfig); - runAccessControlTests(driverTestConfig); + runActionFeaturesTests(driverTestConfig); - runActorVarsTests(driverTestConfig); + runAccessControlTests(driverTestConfig); - runActorMetadataTests(driverTestConfig); + runActorVarsTests(driverTestConfig); - runActorOnStateChangeTests(driverTestConfig); + runActorMetadataTests(driverTestConfig); - runActorErrorHandlingTests(driverTestConfig); + runActorOnStateChangeTests(driverTestConfig); - runActorQueueTests(driverTestConfig); + runActorErrorHandlingTests(driverTestConfig); - runActorRunTests(driverTestConfig); + runActorQueueTests(driverTestConfig); - runActorSandboxTests(driverTestConfig); + runActorRunTests(driverTestConfig); - if ( - driverTestConfig.isDynamic && - !driverTestConfig.skip?.sleep - ) { - runDynamicReloadTests(driverTestConfig); - } + runActorSandboxTests(driverTestConfig); - runActorInlineClientTests(driverTestConfig); + if ( + driverTestConfig.isDynamic && + !driverTestConfig.skip?.sleep + ) { + runDynamicReloadTests(driverTestConfig); + } - runActorKvTests(driverTestConfig); + runActorKvTests(driverTestConfig); - runActorWorkflowTests(driverTestConfig); + runActorWorkflowTests(driverTestConfig); - runActorStatelessTests(driverTestConfig); + runActorStatelessTests(driverTestConfig); - runRawHttpTests(driverTestConfig); + runRawHttpTests(driverTestConfig); - runRawHttpRequestPropertiesTests(driverTestConfig); + runRawHttpRequestPropertiesTests(driverTestConfig); - runRawWebSocketTests(driverTestConfig); - runHibernatableWebSocketProtocolTests(driverTestConfig); + runRawWebSocketTests(driverTestConfig); + runHibernatableWebSocketProtocolTests(driverTestConfig); - runRawHttpDirectRegistryTests(driverTestConfig); + runRawHttpDirectRegistryTests(driverTestConfig); - runRawWebSocketDirectRegistryTests(driverTestConfig); + runRawWebSocketDirectRegistryTests(driverTestConfig); - runActorInspectorTests(driverTestConfig); - runGatewayQueryUrlTests(driverTestConfig); - runGatewayRoutingTests(driverTestConfig); + runActorInspectorTests(driverTestConfig); + runGatewayQueryUrlTests(driverTestConfig); + runGatewayRoutingTests(driverTestConfig); - runLifecycleHooksTests(driverTestConfig); + runLifecycleHooksTests(driverTestConfig); - runActorDbRawTests(driverTestConfig); + runActorDbRawTests(driverTestConfig); - runActorDbPragmaMigrationTests(driverTestConfig); + runActorDbPragmaMigrationTests(driverTestConfig); - runActorStateZodCoercionTests(driverTestConfig); + runActorStateZodCoercionTests(driverTestConfig); - runActorAgentOsTests(driverTestConfig); - }); - } + runActorAgentOsTests(driverTestConfig); }); } @@ -225,7 +206,6 @@ export function runDriverTests( // native database behavior. Run once, not per-encoding. runActorDbStressTests({ ...driverTestConfigPartial, - clientType: "http", encoding: "bare", }); }); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts deleted file mode 100644 index ff511c62f4..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts +++ /dev/null @@ -1,347 +0,0 @@ -import * as cbor from "cbor-x"; -import type { Context as HonoContext } from "hono"; -import invariant from "invariant"; -import type { Encoding } from "@/actor/protocol/serde"; -import { assertUnreachable } from "@/actor/utils"; -import { ActorError as ClientActorError } from "@/client/errors"; -import { - WS_PROTOCOL_ACTOR, - WS_PROTOCOL_CONN_PARAMS, - WS_PROTOCOL_ENCODING, - WS_PROTOCOL_STANDARD, - WS_PROTOCOL_TARGET, - WS_TEST_PROTOCOL_PATH, -} from "@/common/actor-router-consts"; -import { type DeconstructedError, noopNext } from "@/common/utils"; -import { importWebSocket } from "@/common/websocket"; -import { - type ActorOutput, - type CreateInput, - type GatewayTarget, - type GetForIdInput, - type GetOrCreateWithKeyInput, - type GetWithKeyInput, - HEADER_ACTOR_ID, - type ListActorsInput, - type RuntimeDisplayInformation, - type EngineControlClient, - resolveGatewayTarget, -} from "@/driver-helpers/mod"; -import type { UniversalWebSocket } from "@/mod"; -import type { GetUpgradeWebSocket } from "@/utils"; -import { logger } from "./log"; - -export interface TestInlineDriverCallRequest { - encoding: Encoding; - method: string; - args: unknown[]; -} - -export type TestInlineDriverCallResponse = - | { - ok: T; - } - | { - err: DeconstructedError; - }; - -/** - * Creates a client driver used for testing the inline client driver. This will send a request to the HTTP server which will then internally call the internal client and return the response. - */ -export function createTestInlineClientDriver( - endpoint: string, - encoding: Encoding, -): EngineControlClient { - let getUpgradeWebSocket: GetUpgradeWebSocket; - const driver: EngineControlClient = { - getForId(input: GetForIdInput): Promise { - return makeInlineRequest(endpoint, encoding, "getForId", [input]); - }, - getWithKey(input: GetWithKeyInput): Promise { - return makeInlineRequest(endpoint, encoding, "getWithKey", [input]); - }, - getOrCreateWithKey( - input: GetOrCreateWithKeyInput, - ): Promise { - return makeInlineRequest(endpoint, encoding, "getOrCreateWithKey", [ - input, - ]); - }, - createActor(input: CreateInput): Promise { - return makeInlineRequest(endpoint, encoding, "createActor", [ - input, - ]); - }, - listActors(input: ListActorsInput): Promise { - return makeInlineRequest(endpoint, encoding, "listActors", [input]); - }, - async sendRequest( - target: GatewayTarget, - actorRequest: Request, - ): Promise { - const actorId = await resolveGatewayTarget(driver, target); - - // Normalize path to match other drivers - const oldUrl = new URL(actorRequest.url); - const normalizedPath = oldUrl.pathname.startsWith("/") - ? oldUrl.pathname.slice(1) - : oldUrl.pathname; - const pathWithQuery = normalizedPath + oldUrl.search; - - logger().debug({ - msg: "sending raw http request via test inline driver", - actorId, - encoding, - path: pathWithQuery, - }); - - // Use the dedicated raw HTTP endpoint - const url = `${endpoint}/.test/inline-driver/send-request/${pathWithQuery}`; - - logger().debug({ - msg: "rewriting http url", - from: oldUrl, - to: url, - }); - - // Merge headers with our metadata - const headers = new Headers(actorRequest.headers); - headers.set(HEADER_ACTOR_ID, actorId); - - // Forward the request directly - const response = await fetch( - new Request(url, { - method: actorRequest.method, - headers, - body: actorRequest.body, - signal: actorRequest.signal, - duplex: "half", - } as RequestInit), - ); - - // Check if it's an error response from our handler - if ( - !response.ok && - response.headers - .get("content-type") - ?.includes("application/json") - ) { - try { - // Clone the response to avoid consuming the body - const clonedResponse = response.clone(); - const errorData = (await clonedResponse.json()) as any; - if (errorData.error) { - // Handle both error formats: - // 1. { error: { code, message, metadata } } - structured format - // 2. { error: "message" } - simple string format (from custom onRequest handlers) - if (typeof errorData.error === "object") { - throw new ClientActorError( - errorData.error.group, - errorData.error.code, - errorData.error.message, - errorData.error.metadata, - ); - } - // For simple string errors, just return the response as-is - // This allows custom onRequest handlers to return their own error formats - } - } catch (e) { - // If it's not our error format, just return the response as-is - if (!(e instanceof ClientActorError)) { - return response; - } - throw e; - } - } - - return response; - }, - async openWebSocket( - path: string, - target: GatewayTarget, - encoding: Encoding, - params: unknown, - ): Promise { - const actorId = await resolveGatewayTarget(driver, target); - const WebSocket = await importWebSocket(); - - // Normalize path to match other drivers - const normalizedPath = path.startsWith("/") ? path.slice(1) : path; - - // Create WebSocket connection to the test endpoint - const wsUrl = new URL( - `${endpoint}/.test/inline-driver/connect-websocket/ws`, - ); - - logger().debug({ - msg: "creating websocket connection via test inline driver", - url: wsUrl.toString(), - }); - - // Convert http/https to ws/wss - const wsProtocol = wsUrl.protocol === "https:" ? "wss:" : "ws:"; - const finalWsUrl = `${wsProtocol}//${wsUrl.host}${wsUrl.pathname}`; - - // Build protocols for the connection - const protocols: string[] = []; - protocols.push(WS_PROTOCOL_STANDARD); - protocols.push(`${WS_PROTOCOL_TARGET}actor`); - protocols.push( - `${WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`, - ); - protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`); - protocols.push( - `${WS_TEST_PROTOCOL_PATH}${encodeURIComponent(normalizedPath)}`, - ); - if (params !== undefined) { - protocols.push( - `${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`, - ); - } - - logger().debug({ - msg: "connecting to websocket", - url: finalWsUrl, - protocols, - }); - - // Create and return the WebSocket - // Node & browser WebSocket types are incompatible - const ws = new WebSocket(finalWsUrl, protocols) as any; - - return ws; - }, - async proxyRequest( - _c: HonoContext, - actorRequest: Request, - actorId: string, - ): Promise { - const url = new URL(actorRequest.url); - const proxyPath = url.pathname.startsWith("/request/") - ? `${url.pathname}${url.search}` - : `/request/${url.pathname.startsWith("/") ? url.pathname.slice(1) : url.pathname}${url.search}`; - const proxyRequest = new Request(`http://actor${proxyPath}`, { - method: actorRequest.method, - headers: actorRequest.headers, - body: actorRequest.body, - signal: actorRequest.signal, - duplex: "half", - } as RequestInit); - - return await this.sendRequest({ directId: actorId }, proxyRequest); - }, - proxyWebSocket( - c: HonoContext, - path: string, - actorId: string, - encoding: Encoding, - params: unknown, - ): Promise { - const upgradeWebSocket = getUpgradeWebSocket?.(); - invariant(upgradeWebSocket, "missing getUpgradeWebSocket"); - - const wsHandler = this.openWebSocket( - path, - { directId: actorId }, - encoding, - params, - ); - return upgradeWebSocket(() => wsHandler)(c, noopNext()); - }, - async buildGatewayUrl(target: GatewayTarget): Promise { - const resolvedActorId = await resolveGatewayTarget(driver, target); - return `${endpoint}/gateway/${resolvedActorId}`; - }, - displayInformation(): RuntimeDisplayInformation { - return { properties: {} }; - }, - setGetUpgradeWebSocket: (getUpgradeWebSocketInner) => { - getUpgradeWebSocket = getUpgradeWebSocketInner; - }, - kvGet: (_actorId: string, _key: Uint8Array) => { - throw new Error("kvGet not implemented on inline client driver"); - }, - kvBatchGet: (_actorId: string, _keys: Uint8Array[]) => { - throw new Error( - "kvBatchGet not implemented on inline client driver", - ); - }, - kvBatchPut: ( - _actorId: string, - _entries: [Uint8Array, Uint8Array][], - ) => { - throw new Error( - "kvBatchPut not implemented on inline client driver", - ); - }, - kvBatchDelete: (_actorId: string, _keys: Uint8Array[]) => { - throw new Error( - "kvBatchDelete not implemented on inline client driver", - ); - }, - kvDeleteRange: ( - _actorId: string, - _start: Uint8Array, - _end: Uint8Array, - ) => { - throw new Error( - "kvDeleteRange not implemented on inline client driver", - ); - }, - } satisfies EngineControlClient; - return driver; -} - -async function makeInlineRequest( - endpoint: string, - encoding: Encoding, - method: string, - args: unknown[], -): Promise { - logger().debug({ - msg: "sending inline request", - encoding, - method, - args, - }); - - // Call driver - const response = await fetch(`${endpoint}/.test/inline-driver/call`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: cbor.encode({ - encoding, - method, - args, - } satisfies TestInlineDriverCallRequest), - duplex: "half", - } as RequestInit); - - if (!response.ok) { - throw new Error( - `Failed to call inline ${method}: ${response.statusText}`, - ); - } - - // Parse response - const buffer = await response.arrayBuffer(); - const callResponse: TestInlineDriverCallResponse = cbor.decode( - new Uint8Array(buffer), - ); - - // Throw or OK - if ("ok" in callResponse) { - return callResponse.ok; - } else if ("err" in callResponse) { - throw new ClientActorError( - callResponse.err.group, - callResponse.err.code, - callResponse.err.message, - callResponse.err.metadata, - ); - } else { - assertUnreachable(callResponse); - } -} diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inline-client.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inline-client.ts deleted file mode 100644 index 9b70db537a..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inline-client.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { describe, expect, test } from "vitest"; -import type { DriverTestConfig } from "../mod"; -import { setupDriverTest } from "../utils"; - -export function runActorInlineClientTests(driverTestConfig: DriverTestConfig) { - describe("Actor Inline Client Tests", () => { - describe("Stateless Client Calls", () => { - test("should make stateless calls to other actors", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Create the inline client actor - const inlineClientHandle = client.inlineClientActor.getOrCreate( - ["inline-client-test"], - ); - - // Test calling counter.increment via inline client - const result = await inlineClientHandle.callCounterIncrement(5); - expect(result).toBe(5); - - // Verify the counter state was actually updated - const counterState = await inlineClientHandle.getCounterState(); - expect(counterState).toBe(5); - - // Check that messages were logged - const messages = await inlineClientHandle.getMessages(); - expect(messages).toHaveLength(2); - expect(messages[0]).toContain( - "Called counter.increment(5), result: 5", - ); - expect(messages[1]).toContain("Got counter state: 5"); - }); - - test("should handle multiple stateless calls", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Create the inline client actor - const inlineClientHandle = client.inlineClientActor.getOrCreate( - ["inline-client-multi"], - ); - - // Clear any existing messages - await inlineClientHandle.clearMessages(); - - // Make multiple calls - const result1 = - await inlineClientHandle.callCounterIncrement(3); - const result2 = - await inlineClientHandle.callCounterIncrement(7); - const finalState = await inlineClientHandle.getCounterState(); - - expect(result1).toBe(3); - expect(result2).toBe(10); // 3 + 7 - expect(finalState).toBe(10); - - // Check messages - const messages = await inlineClientHandle.getMessages(); - expect(messages).toHaveLength(3); - expect(messages[0]).toContain( - "Called counter.increment(3), result: 3", - ); - expect(messages[1]).toContain( - "Called counter.increment(7), result: 10", - ); - expect(messages[2]).toContain("Got counter state: 10"); - }); - }); - - describe("Stateful Client Calls", () => { - test("should connect to other actors and receive events", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Create the inline client actor - const inlineClientHandle = client.inlineClientActor.getOrCreate( - ["inline-client-stateful"], - ); - - // Clear any existing messages - await inlineClientHandle.clearMessages(); - - // Test stateful connection with events - const result = - await inlineClientHandle.connectToCounterAndIncrement(4); - - expect(result.result1).toBe(4); - expect(result.result2).toBe(12); // 4 + 8 - expect(result.events).toEqual([4, 12]); // Should have received both events - - // Check that message was logged - const messages = await inlineClientHandle.getMessages(); - expect(messages).toHaveLength(1); - expect(messages[0]).toContain( - "Connected to counter, incremented by 4 and 8", - ); - expect(messages[0]).toContain("results: 4, 12"); - expect(messages[0]).toContain("events: [4,12]"); - }); - - test("should handle stateful connection independently", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Create the inline client actor - const inlineClientHandle = client.inlineClientActor.getOrCreate( - ["inline-client-independent"], - ); - - // Clear any existing messages - await inlineClientHandle.clearMessages(); - - // Test with different increment values - const result = - await inlineClientHandle.connectToCounterAndIncrement(2); - - expect(result.result1).toBe(2); - expect(result.result2).toBe(6); // 2 + 4 - expect(result.events).toEqual([2, 6]); - - // Verify the state is independent from previous tests - const messages = await inlineClientHandle.getMessages(); - expect(messages).toHaveLength(1); - expect(messages[0]).toContain( - "Connected to counter, incremented by 2 and 4", - ); - }); - }); - - describe("Mixed Client Usage", () => { - test("should handle both stateless and stateful calls", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Create the inline client actor - const inlineClientHandle = client.inlineClientActor.getOrCreate( - ["inline-client-mixed"], - ); - - // Clear any existing messages - await inlineClientHandle.clearMessages(); - - // Start with stateless calls - await inlineClientHandle.callCounterIncrement(1); - const statelessResult = - await inlineClientHandle.getCounterState(); - expect(statelessResult).toBe(1); - - // Then do stateful call - const statefulResult = - await inlineClientHandle.connectToCounterAndIncrement(3); - expect(statefulResult.result1).toBe(3); - expect(statefulResult.result2).toBe(9); // 3 + 6 - - // Check all messages were logged - const messages = await inlineClientHandle.getMessages(); - expect(messages).toHaveLength(3); - expect(messages[0]).toContain( - "Called counter.increment(1), result: 1", - ); - expect(messages[1]).toContain("Got counter state: 1"); - expect(messages[2]).toContain( - "Connected to counter, incremented by 3 and 6", - ); - }); - }); - }); -} diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-queue.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-queue.ts index ee5445957e..56a4d49165 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-queue.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-queue.ts @@ -218,10 +218,8 @@ export function runActorQueueTests(driverTestConfig: DriverTestConfig) { expect((error as Error).message).toContain( "Queue is full. Limit is", ); - if (driverTestConfig.clientType !== "http") { - expect((error as ActorError).group).toBe("queue"); - expect((error as ActorError).code).toBe("full"); - } + expect((error as ActorError).group).toBe("queue"); + expect((error as ActorError).code).toBe("full"); } }); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts index 7c2b425127..a497786315 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts @@ -10,10 +10,7 @@ function buildGatewayInspectorUrl(gatewayUrl: string, path: string): URL { export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { describe("Gateway Query URLs", () => { - const httpOnlyTest = - driverTestConfig.clientType === "http" ? test : test.skip; - - httpOnlyTest( + test( "getOrCreate gateway URLs use rvt-* query params and resolve through the gateway", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -30,7 +27,7 @@ export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildGatewayInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); @@ -42,7 +39,7 @@ export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "get gateway URLs use rvt-* query params and resolve through the gateway", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -61,7 +58,7 @@ export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildGatewayInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts index 2092542a99..d061c53cc2 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts @@ -4,11 +4,8 @@ import { setupDriverTest } from "../utils"; export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { describe("Gateway Routing", () => { - const httpOnlyTest = - driverTestConfig.clientType === "http" ? test : test.skip; - describe("Header-Based Routing", () => { - httpOnlyTest( + test( "routes HTTP request via x-rivet-target and x-rivet-actor headers", async (c) => { const { client, endpoint } = await setupDriverTest( @@ -40,7 +37,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "returns error when x-rivet-actor header is missing", async (c) => { const { endpoint } = await setupDriverTest( @@ -63,7 +60,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }); describe("Query-Based Routing (rvt-* params)", () => { - httpOnlyTest( + test( "routes via rvt-method=getOrCreate with rvt-key", async (c) => { const { client, endpoint } = await setupDriverTest( @@ -100,7 +97,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "routes via rvt-method=get with rvt-key", async (c) => { const { client, endpoint } = await setupDriverTest( @@ -134,7 +131,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "rejects unknown rvt-* params", async (c) => { const { client, endpoint } = await setupDriverTest( @@ -167,7 +164,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "rejects duplicate scalar rvt-* params", async (c) => { const { endpoint } = await setupDriverTest( @@ -183,7 +180,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "strips rvt-* params before forwarding to actor", async (c) => { const { client, endpoint } = await setupDriverTest( @@ -231,7 +228,7 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( + test( "supports multi-component keys via comma-separated rvt-key", async (c) => { const { client, endpoint } = await setupDriverTest( diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts index e61bff8299..6189e1aa70 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts @@ -40,9 +40,9 @@ async function waitForMatchingJsonMessages( matcher: (message: Record) => boolean, timeoutMs: number, ): Promise>> { - return await new Promise>>( - (resolve, reject) => { - const messages: Array> = []; + return await new Promise>>( + (resolve, reject) => { + const messages: Array> = []; const timeout = setTimeout(() => { cleanup(); reject( @@ -51,40 +51,40 @@ async function waitForMatchingJsonMessages( ), ); }, timeoutMs); - const onMessage = (event: { data: string }) => { - let parsed: Record | undefined; - try { - parsed = JSON.parse(event.data as string); - } catch { - return; - } - if (!parsed) { - return; - } - if (!matcher(parsed)) { - return; - } + const onMessage = (event: { data: string }) => { + let parsed: Record | undefined; + try { + parsed = JSON.parse(event.data as string); + } catch { + return; + } + if (!parsed) { + return; + } + if (!matcher(parsed)) { + return; + } messages.push(parsed); if (messages.length >= count) { cleanup(); resolve(messages); } }; - const onClose = (event: unknown) => { - cleanup(); - reject(event); - }; - const cleanup = () => { - clearTimeout(timeout); - ws.removeEventListener("message", onMessage as (event: any) => void); - ws.removeEventListener("close", onClose as (event: any) => void); - }; - ws.addEventListener("message", onMessage as (event: any) => void); - ws.addEventListener("close", onClose as (event: any) => void, { - once: true, - }); - }, - ); + const onClose = (event: unknown) => { + cleanup(); + reject(event); + }; + const cleanup = () => { + clearTimeout(timeout); + ws.removeEventListener("message", onMessage as (event: any) => void); + ws.removeEventListener("close", onClose as (event: any) => void); + }; + ws.addEventListener("message", onMessage as (event: any) => void); + ws.addEventListener("close", onClose as (event: any) => void, { + once: true, + }); + }, + ); } async function readHibernatableAckState(websocket: WebSocket): Promise<{ @@ -139,114 +139,120 @@ export function runHibernatableWebSocketProtocolTests( test( "replays only unacked indexed websocket messages after sleep and wake", async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } - - const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.rawWebSocketActor.getOrCreate([ - "hibernatable-replay", - ]); - const ws = await actor.webSocket(); + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.rawWebSocketActor.getOrCreate([ + "hibernatable-replay", + ]); + const ws = await actor.webSocket(); - try { - expect(await waitForJsonMessage(ws, 4_000)).toMatchObject({ - type: "welcome", - }); + try { + expect(await waitForJsonMessage(ws, 4_000)).toMatchObject({ + type: "welcome", + }); - const firstProbePromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "indexedAckProbe", - 1_000, - ); - ws.send( - JSON.stringify({ + const firstProbePromise = waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "indexedAckProbe", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "indexedAckProbe", + payload: "durable-before-sleep", + }), + ); + expect((await firstProbePromise)[0]).toMatchObject({ type: "indexedAckProbe", - payload: "durable-before-sleep", - }), - ); - expect((await firstProbePromise)[0]).toMatchObject({ - type: "indexedAckProbe", - rivetMessageIndex: 1, - }); + rivetMessageIndex: 1, + }); - await vi.waitFor( - async () => { - expect(await readHibernatableAckState(ws)).toEqual({ - lastSentIndex: 1, - lastAckedIndex: 1, - pendingIndexes: [], - }); - }, - { timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, interval: 50 }, - ); + await vi.waitFor( + async () => { + expect(await readHibernatableAckState(ws)).toEqual({ + lastSentIndex: 1, + lastAckedIndex: 1, + pendingIndexes: [], + }); + }, + { + timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, + interval: 50, + }, + ); - const sleepScheduledPromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "sleepScheduled", - 1_000, - ); - ws.send( - JSON.stringify({ - type: "scheduleSleep", - }), - ); - await sleepScheduledPromise; - await waitFor(driverTestConfig, 250); + const sleepScheduledPromise = waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "sleepScheduled", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "scheduleSleep", + }), + ); + await sleepScheduledPromise; + await waitFor(driverTestConfig, 250); - const replayedMessagesPromise = waitForMatchingJsonMessages( - ws, - 2, - (message) => message.type === "indexedEcho", - 6_000, - ); - ws.send( - JSON.stringify({ - type: "indexedEcho", - payload: "after-sleep-1", - }), - ); - ws.send( - JSON.stringify({ - type: "indexedEcho", - payload: "after-sleep-2", - }), - ); + const replayedMessagesPromise = waitForMatchingJsonMessages( + ws, + 2, + (message) => message.type === "indexedEcho", + 6_000, + ); + ws.send( + JSON.stringify({ + type: "indexedEcho", + payload: "after-sleep-1", + }), + ); + ws.send( + JSON.stringify({ + type: "indexedEcho", + payload: "after-sleep-2", + }), + ); - const replayedIndexes = (await replayedMessagesPromise).map( - (message) => message.rivetMessageIndex as number, - ); + const replayedIndexes = (await replayedMessagesPromise).map( + (message) => message.rivetMessageIndex as number, + ); - expect(replayedIndexes).toEqual([3, 4]); + expect(replayedIndexes).toEqual([3, 4]); - await vi.waitFor( - async () => { - expect(await readHibernatableAckState(ws)).toEqual({ - lastSentIndex: 4, - lastAckedIndex: 4, - pendingIndexes: [], - }); - }, - { timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, interval: 50 }, - ); + await vi.waitFor( + async () => { + expect(await readHibernatableAckState(ws)).toEqual({ + lastSentIndex: 4, + lastAckedIndex: 4, + pendingIndexes: [], + }); + }, + { + timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, + interval: 50, + }, + ); - const actorObservedOrderPromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "indexedMessageOrder", - 1_000, - ); - ws.send( - JSON.stringify({ - type: "getIndexedMessageOrder", - }), - ); - expect((await actorObservedOrderPromise)[0].order).toEqual([1, 3, 4]); - } finally { - ws.close(); - } + const actorObservedOrderPromise = waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "indexedMessageOrder", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "getIndexedMessageOrder", + }), + ); + expect((await actorObservedOrderPromise)[0].order).toEqual([ + 1, + 3, + 4, + ]); + } finally { + ws.close(); + } }, 20_000, ); @@ -254,33 +260,29 @@ export function runHibernatableWebSocketProtocolTests( test( "cleans up stale hibernatable websocket connections on restore", async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } - - const { client } = await setupDriverTest(c, driverTestConfig); - const conn = client.fileSystemHibernationCleanupActor - .getOrCreate() - .connect(); - let wakeConn: typeof conn | undefined; - let connDisposed = false; + const { client } = await setupDriverTest(c, driverTestConfig); + const conn = client.fileSystemHibernationCleanupActor + .getOrCreate() + .connect(); + let wakeConn: typeof conn | undefined; + let connDisposed = false; - try { - expect(await conn.ping()).toBe("pong"); - await conn.triggerSleep(); - await waitFor(driverTestConfig, 700); + try { + expect(await conn.ping()).toBe("pong"); + await conn.triggerSleep(); + await waitFor(driverTestConfig, 700); - // Disconnect the original client while the actor is asleep so the - // persisted websocket metadata is stale on the next wake. - await conn.dispose(); - connDisposed = true; - await waitFor(driverTestConfig, 100); + // Disconnect the original client while the actor is asleep so the + // persisted websocket metadata is stale on the next wake. + await conn.dispose(); + connDisposed = true; + await waitFor(driverTestConfig, 100); - // Wake the actor through a new connection so restore must clean up - // the stale persisted websocket from the sleeping generation. - wakeConn = client.fileSystemHibernationCleanupActor - .getOrCreate() - .connect(); + // Wake the actor through a new connection so restore must clean up + // the stale persisted websocket from the sleeping generation. + wakeConn = client.fileSystemHibernationCleanupActor + .getOrCreate() + .connect(); await vi.waitFor( async () => { @@ -288,8 +290,8 @@ export function runHibernatableWebSocketProtocolTests( expect(counts.sleepCount).toBeGreaterThanOrEqual(1); expect(counts.wakeCount).toBeGreaterThanOrEqual(2); }, - { timeout: 5_000, interval: 100 }, - ); + { timeout: 5_000, interval: 100 }, + ); await vi.waitFor( async () => { @@ -297,14 +299,14 @@ export function runHibernatableWebSocketProtocolTests( await wakeConn!.getDisconnectWakeCounts(); expect(disconnectWakeCounts).toEqual([2]); }, - { timeout: 5_000, interval: 100 }, - ); - } finally { - await wakeConn?.dispose().catch(() => undefined); - if (!connDisposed) { - await conn.dispose().catch(() => undefined); + { timeout: 5_000, interval: 100 }, + ); + } finally { + await wakeConn?.dispose().catch(() => undefined); + if (!connDisposed) { + await conn.dispose().catch(() => undefined); + } } - } }, 15_000, ); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-http-direct-registry.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-http-direct-registry.ts index 36b213427b..35cf6349d0 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-http-direct-registry.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-http-direct-registry.ts @@ -16,10 +16,7 @@ export function runRawHttpDirectRegistryTests( driverTestConfig: DriverTestConfig, ) { describe("raw http - gateway query urls", () => { - const httpOnlyTest = - driverTestConfig.clientType === "http" ? test : test.skip; - - httpOnlyTest("handles GET requests via gateway query urls", async (c) => { + test("handles GET requests via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const handle = client.rawHttpActor.getOrCreate(["gateway-get"]); @@ -34,7 +31,7 @@ export function runRawHttpDirectRegistryTests( }); }); - httpOnlyTest("handles POST requests via gateway query urls", async (c) => { + test("handles POST requests via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const handle = client.rawHttpActor.getOrCreate(["gateway-post"]); const payload = { test: "gateway", number: 456 }; @@ -55,7 +52,7 @@ export function runRawHttpDirectRegistryTests( await expect(response.json()).resolves.toEqual(payload); }); - httpOnlyTest( + test( "passes custom headers through via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -85,7 +82,7 @@ export function runRawHttpDirectRegistryTests( }, ); - httpOnlyTest( + test( "returns 404 for actors without onRequest handler via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -105,7 +102,7 @@ export function runRawHttpDirectRegistryTests( }, ); - httpOnlyTest( + test( "handles different HTTP methods via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -149,7 +146,7 @@ export function runRawHttpDirectRegistryTests( }, ); - httpOnlyTest( + test( "handles binary data via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket-direct-registry.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket-direct-registry.ts index 6abcf5f33e..1f466e4438 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket-direct-registry.ts @@ -63,10 +63,7 @@ export function runRawWebSocketDirectRegistryTests( driverTestConfig: DriverTestConfig, ) { describe("raw websocket - gateway query urls", () => { - const httpOnlyTest = - driverTestConfig.clientType === "http" ? test : test.skip; - - httpOnlyTest("establishes a gateway websocket connection", async (c) => { + test("establishes a gateway websocket connection", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const WebSocket = await importWebSocket(); const handle = client.rawWebSocketActor.getOrCreate([ @@ -87,7 +84,7 @@ export function runRawWebSocketDirectRegistryTests( ws.close(); }); - httpOnlyTest("echoes messages over gateway websocket urls", async (c) => { + test("echoes messages over gateway websocket urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const WebSocket = await importWebSocket(); const handle = client.rawWebSocketActor.getOrCreate([ @@ -109,7 +106,7 @@ export function runRawWebSocketDirectRegistryTests( ws.close(); }); - httpOnlyTest( + test( "accepts connection params over gateway websocket urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -142,7 +139,7 @@ export function runRawWebSocketDirectRegistryTests( }, ); - httpOnlyTest( + test( "allows custom user protocols alongside rivet protocols", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); @@ -171,7 +168,7 @@ export function runRawWebSocketDirectRegistryTests( }, ); - httpOnlyTest( + test( "supports custom websocket subpaths via gateway query urls", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts index 87df9f5353..82fb017eb7 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts @@ -607,10 +607,6 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { }); test("should preserve indexed websocket message ordering", async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } - const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawWebSocketActor.getOrCreate([ "indexed-ordering", @@ -618,11 +614,9 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { const ws = await actor.webSocket(); try { - const welcome = await waitForJsonMessage(ws, 2000); - if (!welcome || welcome.type !== "welcome") { - // Some dynamic inline transports do not currently surface this path reliably. - return; - } + expect(await waitForJsonMessage(ws, 2000)).toMatchObject({ + type: "welcome", + }); const orderedResponsesPromise = new Promise( (resolve, reject) => { @@ -668,9 +662,7 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { setTimeout(() => resolve(undefined), 1500), ), ]); - if (!observedOrder) { - return; - } + expect(observedOrder).toBeDefined(); expect(observedOrder).toHaveLength(3); const actorObservedOrderPromise = waitForMatchingJsonMessages( ws, @@ -702,10 +694,6 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { !driverTestConfig.features?.hibernatableWebSocketProtocol, )("hibernatable websocket ack", () => { test("acks indexed raw websocket messages without extra actor writes", async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } - const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawWebSocketActor.getOrCreate([ "hibernatable-ack", @@ -749,10 +737,6 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { }); test("acks buffered indexed raw websocket messages immediately at the threshold", async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } - const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawWebSocketActor.getOrCreate([ "hibernatable-threshold", diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/request-access.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/request-access.ts index 170cabe850..f09734318f 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/request-access.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/request-access.ts @@ -19,30 +19,19 @@ export function runRequestAccessTests(driverTestConfig: DriverTestConfig) { // Get request info that was captured in onBeforeConnect const requestInfo = await connection.getRequestInfo(); - // Verify request was accessible in HTTP mode, but not in inline mode - if (driverTestConfig.clientType === "http") { - // Check onBeforeConnect - expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); - expect(requestInfo.onBeforeConnect.requestUrl).toBeDefined(); - expect(requestInfo.onBeforeConnect.requestMethod).toBeDefined(); - expect( - requestInfo.onBeforeConnect.requestHeaders, - ).toBeDefined(); + expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); + expect(requestInfo.onBeforeConnect.requestUrl).toBeDefined(); + expect(requestInfo.onBeforeConnect.requestMethod).toBeDefined(); + expect( + requestInfo.onBeforeConnect.requestHeaders, + ).toBeDefined(); - // Check createConnState - expect(requestInfo.createConnState.hasRequest).toBe(true); - expect(requestInfo.createConnState.requestUrl).toBeDefined(); - expect(requestInfo.createConnState.requestMethod).toBeDefined(); - expect( - requestInfo.createConnState.requestHeaders, - ).toBeDefined(); - } else { - // Inline client may or may not have request object depending on the driver - // - // e.g. - // - File system does not have a request for inline requests - // - Rivet Engine proxies the request so it has access to the request object - } + expect(requestInfo.createConnState.hasRequest).toBe(true); + expect(requestInfo.createConnState.requestUrl).toBeDefined(); + expect(requestInfo.createConnState.requestMethod).toBeDefined(); + expect( + requestInfo.createConnState.requestHeaders, + ).toBeDefined(); // Clean up await connection.dispose(); @@ -97,28 +86,21 @@ export function runRequestAccessTests(driverTestConfig: DriverTestConfig) { // Get request info const requestInfo = await connection.getRequestInfo(); - if (driverTestConfig.clientType === "http") { - // Verify request details were captured in both hooks - expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); - expect(requestInfo.onBeforeConnect.requestMethod).toBeTruthy(); - expect(requestInfo.onBeforeConnect.requestUrl).toBeTruthy(); - expect(requestInfo.onBeforeConnect.requestHeaders).toBeTruthy(); - expect(typeof requestInfo.onBeforeConnect.requestHeaders).toBe( - "object", - ); + expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); + expect(requestInfo.onBeforeConnect.requestMethod).toBeTruthy(); + expect(requestInfo.onBeforeConnect.requestUrl).toBeTruthy(); + expect(requestInfo.onBeforeConnect.requestHeaders).toBeTruthy(); + expect(typeof requestInfo.onBeforeConnect.requestHeaders).toBe( + "object", + ); - expect(requestInfo.createConnState.hasRequest).toBe(true); - expect(requestInfo.createConnState.requestMethod).toBeTruthy(); - expect(requestInfo.createConnState.requestUrl).toBeTruthy(); - expect(requestInfo.createConnState.requestHeaders).toBeTruthy(); - expect(typeof requestInfo.createConnState.requestHeaders).toBe( - "object", - ); - } else { - // Inline client may or may not have request object depending on the driver - // - // See "should have access to request object in onBeforeConnect and createConnState" - } + expect(requestInfo.createConnState.hasRequest).toBe(true); + expect(requestInfo.createConnState.requestMethod).toBeTruthy(); + expect(requestInfo.createConnState.requestUrl).toBeTruthy(); + expect(requestInfo.createConnState.requestHeaders).toBeTruthy(); + expect(typeof requestInfo.createConnState.requestHeaders).toBe( + "object", + ); // Clean up await connection.dispose(); @@ -141,14 +123,10 @@ export function runRequestAccessTests(driverTestConfig: DriverTestConfig) { const requestInfo = await viewHandle.getRequestInfo(); - if (driverTestConfig.clientType === "http") { - expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); - expect(requestInfo.onBeforeConnect.requestMethod).toBeTruthy(); - expect(requestInfo.onBeforeConnect.requestUrl).toBeTruthy(); - expect(requestInfo.onBeforeConnect.requestHeaders).toBeTruthy(); - } else { - // Inline client may or may not have request object depending on the driver. - } + expect(requestInfo.onBeforeConnect.hasRequest).toBe(true); + expect(requestInfo.onBeforeConnect.requestMethod).toBeTruthy(); + expect(requestInfo.onBeforeConnect.requestUrl).toBeTruthy(); + expect(requestInfo.onBeforeConnect.requestHeaders).toBeTruthy(); }); // TODO: re-expose this once we can have actor queries on the gateway diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts index ad7d2476e2..46f8f64ea4 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts @@ -1,12 +1,8 @@ import { type TestContext, vi } from "vitest"; -import { assertUnreachable } from "@/actor/utils"; import { type Client, createClient } from "@/client/mod"; -import { createClientWithDriver } from "@/mod"; import type { registry } from "../../fixtures/driver-test-suite/registry"; import { logger } from "./log"; import type { DriverTestConfig } from "./mod"; -import { createTestInlineClientDriver } from "./test-inline-client-driver"; -import { ClientConfigSchema } from "@/client/config"; export const FAKE_TIME = new Date("2024-01-01T00:00:00.000Z"); const CLIENT_WARMUP_ATTEMPTS = 6; @@ -62,33 +58,16 @@ export async function setupDriverTest( } = await driverTestConfig.start(); - let client: Client; - if (driverTestConfig.clientType === "http") { - // Create client - client = createClient({ - endpoint, - namespace, - poolName: runnerName, - encoding: driverTestConfig.encoding, - // Disable metadata lookup to prevent redirect to the wrong port. - // Each test starts a new server on a dynamic port, but the - // registry's publicEndpoint defaults to port 6420. - disableMetadataLookup: true, - }); - } else if (driverTestConfig.clientType === "inline") { - // Use inline client from driver - const encoding = driverTestConfig.encoding ?? "bare"; - const managerDriver = createTestInlineClientDriver( - endpoint, - encoding, - ); - const runConfig = ClientConfigSchema.parse({ - encoding: encoding, - }); - client = createClientWithDriver(managerDriver, runConfig); - } else { - assertUnreachable(driverTestConfig.clientType); - } + const client = createClient({ + endpoint, + namespace, + poolName: runnerName, + encoding: driverTestConfig.encoding, + // Disable metadata lookup to prevent redirect to the wrong port. + // Each test starts a new server on a dynamic port, but the + // registry's publicEndpoint defaults to port 6420. + disableMetadataLookup: true, + }); await waitForClientWarmup(client, driverTestConfig); diff --git a/rivetkit-typescript/packages/rivetkit/src/sandbox/actor.test.ts b/rivetkit-typescript/packages/rivetkit/src/sandbox/actor.test.ts deleted file mode 100644 index 5074f593b4..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/sandbox/actor.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, expect, test, vi } from "vitest"; -import { setup } from "@/mod"; -import { setupTest } from "@/test/mod"; -import { sandboxActor } from "./index"; -import type { SandboxProvider } from "sandbox-agent"; - -describe("sandbox actor direct URL access", () => { - test("getSandboxUrl provisions the sandbox without connecting the SDK", async (c) => { - const provider: SandboxProvider = { - name: "test", - create: vi.fn(async () => "sandbox-1"), - destroy: vi.fn(async () => {}), - getUrl: vi.fn( - async (sandboxId) => `https://sandbox.example/${sandboxId}`, - ), - }; - - const registry = setup({ - use: { - sandbox: sandboxActor({ - provider, - }), - }, - }); - const { client } = await setupTest(c, registry); - const sandbox = client.sandbox.getOrCreate(["task-1"]); - - const result = await sandbox.getSandboxUrl(); - expect(result.url).toMatch(/^https:\/\/sandbox\.example\//); - expect(provider.create).toHaveBeenCalledTimes(1); - expect(provider.getUrl).toHaveBeenCalled(); - - await sandbox.destroy(); - await expect(sandbox.getSandboxUrl()).rejects.toThrow( - "Internal error. Read the server logs for more details.", - ); - }); -}); diff --git a/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts b/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts deleted file mode 100644 index aa968b0782..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { LLMock } from "@copilotkit/llmock"; -import { afterAll, beforeAll, describe, expect, test } from "vitest"; -import { agentOs } from "@/agent-os/index"; -import { setup } from "@/mod"; -import { setupTest } from "@/test/mod"; -import common from "@rivet-dev/agent-os-common"; -import pi from "@rivet-dev/agent-os-pi"; - -describe("agentOS session lifecycle", () => { - let mock: LLMock; - let mockUrl: string; - let mockPort: number; - - beforeAll(async () => { - mock = new LLMock({ port: 0, logLevel: "silent" }); - mock.addFixtures([ - { match: { predicate: () => true }, response: { content: "Hello from mock LLM" } }, - ]); - mockUrl = await mock.start(); - mockPort = Number(new URL(mockUrl).port); - }); - - afterAll(async () => { - await mock.stop(); - }); - - function createRegistry() { - const vm = agentOs({ - options: { - software: [common, pi], - loopbackExemptPorts: [mockPort], - }, - }); - return setup({ use: { vm } }); - } - - test("writeFile, readFile, exec", async (c) => { - const { client } = await setupTest(c, createRegistry()); - const actor = (client as any).vm.getOrCreate([`basic-${crypto.randomUUID()}`]); - - await actor.writeFile("/tmp/test.txt", "hello"); - const data = await actor.readFile("/tmp/test.txt"); - expect(new TextDecoder().decode(data)).toBe("hello"); - - const result = await actor.exec("echo works"); - expect(result.exitCode).toBe(0); - expect(result.stdout.trim()).toBe("works"); - }, 60_000); - - test("create session, send prompt, close session", async (c) => { - const { client } = await setupTest(c, createRegistry()); - const actor = (client as any).vm.getOrCreate([`session-${crypto.randomUUID()}`]); - - const session = await actor.createSession("pi", { - env: { - ANTHROPIC_API_KEY: "mock-key", - ANTHROPIC_BASE_URL: mockUrl, - }, - }); - expect(session.sessionId).toBeTruthy(); - - const response = await actor.sendPrompt(session.sessionId, "Say hello"); - expect(response).toBeTruthy(); - expect(response.result).toBeTruthy(); - - await actor.closeSession(session.sessionId); - }, 120_000); -}); diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts deleted file mode 100644 index b64c7bc842..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts +++ /dev/null @@ -1,451 +0,0 @@ -import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; -import { existsSync } from "node:fs"; -import { join } from "node:path"; -import { pathToFileURL } from "node:url"; -import { createClient } from "@/client/mod"; -import { createTestRuntime } from "@/driver-test-suite/mod"; -import { RemoteEngineControlClient } from "@/engine-client/mod"; -import { convertRegistryConfigToClientConfig } from "@/client/config"; -import { afterEach, describe, expect, test } from "vitest"; -import { DYNAMIC_SOURCE } from "../fixtures/driver-test-suite/dynamic-registry"; -import type { registry } from "../fixtures/driver-test-suite/dynamic-registry"; - -const SECURE_EXEC_DIST_PATH = join( - process.env.HOME ?? "", - "secure-exec-rivet/packages/secure-exec/dist/index.js", -); -const hasSecureExecDist = existsSync(SECURE_EXEC_DIST_PATH); -const hasEngineEndpointEnv = !!( - process.env.RIVET_ENDPOINT || - process.env.RIVET_NAMESPACE_ENDPOINT || - process.env.RIVET_API_ENDPOINT -); -const initialDynamicSourceUrlEnv = - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; -const initialSecureExecSpecifierEnv = - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER; - -type DynamicHandle = { - increment: (amount?: number) => Promise; - getSourceCodeLength: () => Promise; - getState: () => Promise<{ - count: number; - wakeCount: number; - sleepCount: number; - alarmCount: number; - }>; - putText: (key: string, value: string) => Promise; - getText: (key: string) => Promise; - listText: (prefix: string) => Promise>; - triggerSleep: () => Promise; - scheduleAlarm: (duration: number) => Promise; - webSocket: (path?: string) => Promise; -}; - -type DynamicAuthHandle = DynamicHandle & { - fetch: (input: string | URL | Request, init?: RequestInit) => Promise; -}; - -describe.skipIf(!hasSecureExecDist || !hasEngineEndpointEnv)( - "engine dynamic actor runtime", - () => { - let sourceServer: - | { - url: string; - close: () => Promise; - } - | undefined; - - afterEach(async () => { - if (sourceServer) { - await sourceServer.close(); - sourceServer = undefined; - } - if (initialDynamicSourceUrlEnv === undefined) { - delete process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; - } else { - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = - initialDynamicSourceUrlEnv; - } - if (initialSecureExecSpecifierEnv === undefined) { - delete process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER; - } else { - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = - initialSecureExecSpecifierEnv; - } - }); - - test("loads dynamic actor source from URL", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, - }); - const bareClient = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "bare", - disableMetadataLookup: true, - }); - - try { - const actor = client.dynamicFromUrl.getOrCreate([ - "url-loader", - ]) as unknown as DynamicHandle; - expect(await actor.increment(2)).toBe(2); - expect(await actor.increment(3)).toBe(5); - expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); - - const bareActor = bareClient.dynamicFromUrl.getOrCreate([ - "url-loader", - ]) as unknown as DynamicHandle; - expect(await bareActor.increment(1)).toBe(6); - - const state = await actor.getState(); - expect(state.count).toBe(6); - expect(state.wakeCount).toBeGreaterThanOrEqual(1); - } finally { - await client.dispose(); - await bareClient.dispose(); - await runtime.cleanup(); - } - }, 180_000); - - test("supports actions, kv, websockets, alarms, and sleep/wake from actor-loaded source", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, - }); - - let ws: WebSocket | undefined; - - try { - const actor = client.dynamicFromActor.getOrCreate([ - "actor-loader", - ]) as unknown as DynamicHandle; - - expect(await actor.increment(1)).toBe(1); - expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); - - await actor.putText("prefix-a", "alpha"); - await actor.putText("prefix-b", "beta"); - expect(await actor.getText("prefix-a")).toBe("alpha"); - expect( - (await actor.listText("prefix-")).sort((a, b) => - a.key.localeCompare(b.key), - ), - ).toEqual([ - { key: "prefix-a", value: "alpha" }, - { key: "prefix-b", value: "beta" }, - ]); - - ws = await actor.webSocket(); - const welcome = await readWebSocketJson(ws); - expect(welcome).toMatchObject({ type: "welcome" }); - ws.send(JSON.stringify({ type: "ping" })); - expect(await readWebSocketJson(ws)).toEqual({ type: "pong" }); - ws.close(); - ws = undefined; - - const beforeSleep = await actor.getState(); - await actor.triggerSleep(); - await wait(350); - - const afterSleep = await actor.getState(); - expect(afterSleep.sleepCount).toBeGreaterThanOrEqual( - beforeSleep.sleepCount + 1, - ); - expect(afterSleep.wakeCount).toBeGreaterThanOrEqual( - beforeSleep.wakeCount + 1, - ); - - const beforeAlarm = await actor.getState(); - await actor.scheduleAlarm(500); - await wait(900); - - const afterAlarm = await actor.getState(); - expect(afterAlarm.alarmCount).toBeGreaterThanOrEqual( - beforeAlarm.alarmCount + 1, - ); - expect(afterAlarm.sleepCount).toBeGreaterThanOrEqual( - beforeAlarm.sleepCount + 1, - ); - expect(afterAlarm.wakeCount).toBeGreaterThanOrEqual( - beforeAlarm.wakeCount + 1, - ); - } finally { - ws?.close(); - await client.dispose(); - await runtime.cleanup(); - } - }, 180_000); - - test("authenticates dynamic actor actions, raw requests, and websockets", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, - }); - - let ws: WebSocket | undefined; - - try { - const unauthorized = client.dynamicWithAuth.getOrCreate([ - "auth-unauthorized", - ]) as unknown as DynamicAuthHandle; - await expect(unauthorized.increment(1)).rejects.toMatchObject({ - group: "user", - code: "unauthorized", - }); - - const unauthorizedResponse = await unauthorized.fetch("/auth"); - expect(unauthorizedResponse.status).toBe(400); - expect(await unauthorizedResponse.json()).toMatchObject({ - group: "user", - code: "unauthorized", - }); - - const headerAuthorized = client.dynamicWithAuth.getOrCreate([ - "auth-header", - ]) as unknown as DynamicAuthHandle; - const headerResponse = await headerAuthorized.fetch("/auth", { - headers: { - "x-dynamic-auth": "allow", - }, - }); - expect(headerResponse.status).toBe(200); - expect(await headerResponse.json()).toEqual({ - method: "GET", - token: "allow", - }); - - const paramsAuthorized = client.dynamicWithAuth.getOrCreate( - ["auth-params"], - { - params: { - token: "allow", - }, - }, - ) as unknown as DynamicAuthHandle; - expect(await paramsAuthorized.increment(1)).toBe(1); - - ws = await paramsAuthorized.webSocket(); - expect(await readWebSocketJson(ws)).toMatchObject({ - type: "welcome", - }); - } finally { - ws?.close(); - await client.dispose(); - await runtime.cleanup(); - } - }, 180_000); - }, -); - -async function createDynamicEngineRuntime() { - return await createTestRuntime( - join(__dirname, "../fixtures/driver-test-suite/dynamic-registry.ts"), - async (registry) => { - const endpoint = process.env.RIVET_ENDPOINT || "http://127.0.0.1:6420"; - const namespaceEndpoint = - process.env.RIVET_NAMESPACE_ENDPOINT || - process.env.RIVET_API_ENDPOINT || - endpoint; - const namespace = `test-${crypto.randomUUID().slice(0, 8)}`; - const runnerName = "test-runner"; - const token = "dev"; - - const response = await fetch(`${namespaceEndpoint}/namespaces`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer dev", - }, - body: JSON.stringify({ - name: namespace, - display_name: namespace, - }), - }); - if (!response.ok) { - const errorBody = await response.text().catch(() => ""); - throw new Error( - `Create namespace failed at ${namespaceEndpoint}: ${response.status} ${response.statusText} ${errorBody}`, - ); - } - - registry.config.endpoint = endpoint; - registry.config.namespace = namespace; - registry.config.token = token; - registry.config.envoy = { - ...registry.config.envoy, - poolName: runnerName, - }; - - const parsedConfig = registry.parseConfig(); - const engineClient = new RemoteEngineControlClient( - convertRegistryConfigToClientConfig(parsedConfig), - ); - - const runnersUrl = new URL(`${endpoint.replace(/\/$/, "")}/runners`); - runnersUrl.searchParams.set("namespace", namespace); - runnersUrl.searchParams.set("name", runnerName); - let probeError: unknown; - for (let attempt = 0; attempt < 120; attempt++) { - try { - const runnerResponse = await fetch(runnersUrl, { - method: "GET", - headers: { Authorization: `Bearer ${token}` }, - }); - if (!runnerResponse.ok) { - const errorBody = await runnerResponse.text().catch(() => ""); - probeError = new Error( - `List runners failed: ${runnerResponse.status} ${runnerResponse.statusText} ${errorBody}`, - ); - } else { - const responseJson = (await runnerResponse.json()) as { - runners?: Array<{ name?: string }>; - }; - const hasRunner = !!responseJson.runners?.some( - (runner) => runner.name === runnerName, - ); - if (hasRunner) { - probeError = undefined; - break; - } - probeError = new Error( - `Runner ${runnerName} not registered yet`, - ); - } - } catch (err) { - probeError = err; - } - if (attempt < 119) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - if (probeError) { - throw probeError; - } - - return { - rivetEngine: { - endpoint, - namespace, - runnerName, - token, - }, - engineClient, - cleanup: async () => { - ((engineClient as any).shutdown?.()); - }, - }; - }, - ); -} - -async function startSourceServer(source: string): Promise<{ - url: string; - close: () => Promise; -}> { - const server = createServer((req: IncomingMessage, res: ServerResponse) => { - if (req.url !== "/source.ts") { - res.writeHead(404); - res.end("not found"); - return; - } - - res.writeHead(200, { - "content-type": "text/plain; charset=utf-8", - }); - res.end(source); - }); - - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - const address = server.address(); - if (!address || typeof address === "string") { - throw new Error("failed to get dynamic source server address"); - } - - return { - url: `http://127.0.0.1:${address.port}/source.ts`, - close: async () => { - await new Promise((resolve, reject) => { - server.close((error) => { - if (error) { - reject(error); - return; - } - resolve(); - }); - }); - }, - }; -} - -async function readWebSocketJson(websocket: WebSocket): Promise { - const message = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("timed out waiting for websocket message")); - }, 5_000); - - websocket.addEventListener( - "message", - (event) => { - clearTimeout(timeout); - resolve(String(event.data)); - }, - { once: true }, - ); - websocket.addEventListener( - "error", - (event: Event) => { - clearTimeout(timeout); - reject(event); - }, - { once: true }, - ); - websocket.addEventListener( - "close", - () => { - clearTimeout(timeout); - reject(new Error("websocket closed")); - }, - { once: true }, - ); - }); - - return JSON.parse(message); -} - -async function wait(duration: number): Promise { - await new Promise((resolve) => setTimeout(resolve, duration)); -} diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts deleted file mode 100644 index e4f303ca5f..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -/** - * Smoke test that provisions its own serverless runner config, then verifies - * the native envoy client can route raw HTTP and raw WebSocket traffic through - * the current gateway URL flow. - */ -import { serve as honoServe } from "@hono/node-server"; -import { Hono } from "hono"; -import invariant from "invariant"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { WS_PROTOCOL_ENCODING, WS_PROTOCOL_STANDARD } from "@/driver-helpers/mod"; -import { EngineActorDriver } from "@/drivers/engine/mod"; -import { updateRunnerConfig } from "@/engine-client/api-endpoints"; -import { RemoteEngineControlClient } from "@/engine-client/mod"; -import { createClientWithDriver } from "@/client/client"; -import { convertRegistryConfigToClientConfig } from "@/client/config"; -import { createClient } from "@/client/mod"; -import { actor, setup } from "@/mod"; -import { handleHealthRequest, handleMetadataRequest } from "@/common/router"; -import { importWebSocket } from "@/common/websocket"; - -const RIVET_ENDPOINT = process.env.RIVET_ENDPOINT ?? "http://127.0.0.1:6420"; -const RIVET_TOKEN = process.env.RIVET_TOKEN ?? "dev"; - -const thingy = actor({ - onRequest(_c, request) { - const pathname = new URL(request.url).pathname; - if (pathname.endsWith("/ping")) { - return Response.json({ status: "ok" }); - } - - return new Response("Not Found", { status: 404 }); - }, - onWebSocket(_c, websocket) { - websocket.addEventListener("message", (event) => { - websocket.send(`Echo: ${String(event.data)}`); - }); - }, -}); - -const registry = setup({ - use: { - thingy, - }, -}); - -function buildGatewayRequestUrl(gatewayUrl: string, path: string): string { - const url = new URL(gatewayUrl); - const normalizedPath = path.replace(/^\//, ""); - url.pathname = `${url.pathname.replace(/\/$/, "")}/request/${normalizedPath}`; - return url.toString(); -} - -function buildGatewayWebSocketUrl(gatewayUrl: string, path = ""): string { - const url = new URL(gatewayUrl); - url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; - const normalizedPath = path.replace(/^\//, ""); - url.pathname = `${url.pathname.replace(/\/$/, "")}/websocket/${normalizedPath}`; - return url.toString(); -} - -async function waitForOpen(ws: WebSocket): Promise { - if (ws.readyState === WebSocket.OPEN) { - return; - } - - await new Promise((resolve, reject) => { - const onOpen = () => { - cleanup(); - resolve(); - }; - const onError = () => { - cleanup(); - reject(new Error("websocket error before open")); - }; - const onClose = (event: Event) => { - const closeEvent = event as CloseEvent; - cleanup(); - reject( - new Error( - `websocket closed before open (${closeEvent.code} ${closeEvent.reason})`, - ), - ); - }; - const cleanup = () => { - ws.removeEventListener("open", onOpen); - ws.removeEventListener("error", onError); - ws.removeEventListener("close", onClose); - }; - - ws.addEventListener("open", onOpen, { once: true }); - ws.addEventListener("error", onError, { once: true }); - ws.addEventListener("close", onClose, { once: true }); - }); -} - -async function closeNodeServer( - server: ReturnType, -): Promise { - await new Promise((resolve, reject) => { - server.close((error) => { - if (error) { - reject(error); - return; - } - resolve(); - }); - - server.closeIdleConnections?.(); - server.closeAllConnections?.(); - }); -} - -async function refreshRunnerMetadata( - endpoint: string, - namespace: string, - token: string, - poolName: string, -): Promise { - let lastError: unknown; - - for (let attempt = 0; attempt < 20; attempt += 1) { - try { - const response = await fetch( - `${endpoint}/runner-configs/${encodeURIComponent(poolName)}/refresh-metadata?namespace=${encodeURIComponent(namespace)}`, - { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - signal: AbortSignal.timeout(2_000), - }, - ); - if (response.ok) { - return; - } - lastError = new Error( - `refresh runner metadata failed: ${response.status} ${await response.text()}`, - ); - } catch (error) { - lastError = error; - } - - if (attempt < 19) { - await new Promise((resolve) => setTimeout(resolve, 250)); - } - } - - throw lastError; -} - -type SmokeClient = ReturnType>; - -let client: SmokeClient | undefined; -let actorDriver: EngineActorDriver | undefined; -let server: ReturnType | undefined; - -describe("engine driver smoke test", () => { - beforeAll(async () => { - const namespace = `test-smoke-${crypto.randomUUID().slice(0, 8)}`; - const poolName = `test-smoke-${crypto.randomUUID().slice(0, 8)}`; - - const nsResp = await fetch(`${RIVET_ENDPOINT}/namespaces`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${RIVET_TOKEN}`, - }, - body: JSON.stringify({ - name: namespace, - display_name: namespace, - }), - }); - if (!nsResp.ok) { - throw new Error( - `create namespace failed: ${nsResp.status} ${await nsResp.text()}`, - ); - } - - registry.config.endpoint = RIVET_ENDPOINT; - registry.config.namespace = namespace; - registry.config.token = RIVET_TOKEN; - registry.config.envoy = { - ...registry.config.envoy, - poolName, - }; - - const parsedConfig = registry.parseConfig(); - const clientConfig = convertRegistryConfigToClientConfig(parsedConfig); - const engineClient = new RemoteEngineControlClient(clientConfig); - const inlineClient = createClientWithDriver(engineClient, clientConfig); - - actorDriver = new EngineActorDriver( - parsedConfig, - engineClient, - inlineClient, - ); - - const app = new Hono(); - app.get("/health", (c) => handleHealthRequest(c)); - app.get("/metadata", (c) => - handleMetadataRequest( - c, - parsedConfig, - { serverless: {} }, - parsedConfig.publicEndpoint, - parsedConfig.publicNamespace, - parsedConfig.publicToken, - ), - ); - app.post("/start", async (c) => { - invariant(actorDriver, "missing actor driver"); - return await actorDriver.serverlessHandleStart!(c); - }); - - server = honoServe({ - fetch: app.fetch, - hostname: "127.0.0.1", - port: 0, - }); - if (!server.listening) { - await new Promise((resolve) => { - server!.once("listening", () => resolve()); - }); - } - const address = server.address(); - invariant(address && typeof address !== "string", "missing server address"); - const serverlessUrl = `http://127.0.0.1:${address.port}`; - - await updateRunnerConfig(clientConfig, poolName, { - datacenters: { - default: { - serverless: { - url: serverlessUrl, - headers: {}, - request_lifespan: 300, - slots_per_runner: 1, - min_runners: 0, - max_runners: 10000, - runners_margin: 0, - }, - }, - }, - }); - - await actorDriver.waitForReady(); - await refreshRunnerMetadata( - RIVET_ENDPOINT, - namespace, - RIVET_TOKEN, - poolName, - ); - - client = createClient({ - endpoint: RIVET_ENDPOINT, - namespace, - poolName, - disableMetadataLookup: true, - encoding: "bare", - }); - }, 30_000); - - afterAll(async () => { - await client?.dispose(); - await actorDriver?.shutdown(true); - if (server) { - await closeNodeServer(server); - } - }); - - it( - "HTTP ping returns JSON response", - async () => { - invariant(client, "missing smoke test client"); - const handle = client.thingy.getOrCreate([crypto.randomUUID()]); - const response = await fetch( - buildGatewayRequestUrl(await handle.getGatewayUrl(), "ping"), - ); - - expect(response.ok).toBe(true); - await expect(response.json()).resolves.toEqual({ status: "ok" }); - }, - 30_000, - ); - - it( - "WebSocket echo works", - async () => { - invariant(client, "missing smoke test client"); - const WebSocket = await importWebSocket(); - const handle = client.thingy.getOrCreate([crypto.randomUUID()]); - const ws = new WebSocket( - buildGatewayWebSocketUrl(await handle.getGatewayUrl()), - [ - WS_PROTOCOL_STANDARD, - `${WS_PROTOCOL_ENCODING}bare`, - ], - ) as WebSocket; - - await waitForOpen(ws); - - const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("websocket timeout")); - }, 10_000); - - ws.addEventListener( - "message", - (event: MessageEvent) => { - clearTimeout(timeout); - ws.close(); - resolve(String(event.data)); - }, - { once: true }, - ); - ws.addEventListener( - "error", - () => { - clearTimeout(timeout); - reject(new Error("websocket error")); - }, - { once: true }, - ); - - ws.send("ping"); - }); - - expect(result).toBe("Echo: ping"); - }, - 30_000, - ); -}); diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts index 5687e54822..67b4f2e1a3 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts @@ -87,7 +87,6 @@ for (const registryVariant of getDriverRegistryVariants(__dirname)) { }, // TODO: Re-enable cbor and json once metadata init delay is eliminated encodings: ["bare"], - clientTypes: ["http"], async start() { return await createTestRuntime( registryVariant.registryPath, diff --git a/rivetkit-typescript/packages/rivetkit/tests/standalone-driver-test.mts b/rivetkit-typescript/packages/rivetkit/tests/standalone-driver-test.mts deleted file mode 100644 index 0e5a80a61f..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/standalone-driver-test.mts +++ /dev/null @@ -1,126 +0,0 @@ -// Standalone test - run with: npx tsx tests/standalone-driver-test.mts -// Tests EngineActorDriver OUTSIDE vitest to isolate the issue - -import { EngineActorDriver } from "../src/drivers/engine/mod"; -import { RemoteEngineControlClient } from "../src/engine-client/mod"; -import { convertRegistryConfigToClientConfig } from "../src/client/config"; -import { createClientWithDriver } from "../src/client/client"; -import { updateRunnerConfig } from "../src/engine-client/api-endpoints"; -import { setup, actor } from "../src/mod"; -import { serve as honoServe } from "@hono/node-server"; -import { Hono } from "hono"; - -const endpoint = "http://127.0.0.1:6420"; -const namespace = process.env.TEST_NS || `test-${crypto.randomUUID().slice(0, 8)}`; -const poolName = "test-driver"; -const token = "dev"; - -// Create namespace if needed -if (!process.env.TEST_NS) { - const nsResp = await fetch(`${endpoint}/namespaces`, { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, - body: JSON.stringify({ name: namespace, display_name: namespace }), - }); - console.log("Namespace created:", nsResp.status, namespace); -} else { - console.log("Using existing namespace:", namespace); -} - -// Minimal registry with counter actor -const counterActor = actor({ - state: { count: 0 }, - actions: { - increment: (c: any, x: number) => { - c.state.count += x; - return c.state.count; - }, - }, -}); - -const registry = setup({ use: { counter: counterActor } }); -registry.config.endpoint = endpoint; -registry.config.namespace = namespace; -registry.config.token = token; -registry.config.envoy = { ...registry.config.envoy, poolName }; -registry.config.test = { enabled: true }; - -const parsedConfig = registry.parseConfig(); -const clientConfig = convertRegistryConfigToClientConfig(parsedConfig); -const engineClient = new RemoteEngineControlClient(clientConfig); -const inlineClient = createClientWithDriver(engineClient, clientConfig); - -// Create EngineActorDriver -console.log("Creating EngineActorDriver..."); -const actorDriver = new EngineActorDriver(parsedConfig, engineClient, inlineClient); - -// Start serverless HTTP server -const app = new Hono(); -app.get("/health", (c: any) => c.text("ok")); -app.get("/metadata", (c: any) => c.json({ runtime: "rivetkit", version: "1", envoyProtocolVersion: 1 })); -app.post("/start", async (c: any) => actorDriver.serverlessHandleStart(c)); - -const server = honoServe({ fetch: app.fetch, hostname: "127.0.0.1", port: 0 }); -await new Promise((resolve) => { - if (server.listening) resolve(); - else server.once("listening", resolve); -}); -const address = server.address() as any; -const port = address.port; -console.log("Serverless server on port:", port); - -// Register runner config -await updateRunnerConfig(clientConfig, poolName, { - datacenters: { - default: { - serverless: { - url: `http://127.0.0.1:${port}`, - request_lifespan: 300, - max_concurrent_actors: 10000, - slots_per_runner: 1, - min_runners: 0, - max_runners: 10000, - }, - }, - }, -}); -console.log("Runner config updated"); - -// Wait for envoy -await actorDriver.waitForReady(); -console.log("Envoy ready"); - -// Refresh metadata so engine knows our protocol version (enables v2 POST path) -const refreshResp = await fetch( - `${endpoint}/runner-configs/${poolName}/refresh-metadata?namespace=${namespace}`, - { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, - body: JSON.stringify({}), - }, -); -console.log("Metadata refreshed:", refreshResp.status); - -// Wait for engine to process the metadata and start the runner pool -await new Promise(r => setTimeout(r, 5000)); - -// Create actor via gateway (exactly what the client does) -console.log("Creating actor via gateway..."); -const start = Date.now(); -const gwResp = await fetch( - `${endpoint}/gateway/counter/action/increment?rvt-namespace=${namespace}&rvt-method=getOrCreate&rvt-runner=${poolName}&rvt-crash-policy=sleep`, - { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, - body: JSON.stringify(5), - signal: AbortSignal.timeout(15000), - }, -); -const elapsed = Date.now() - start; -console.log(`Gateway response: HTTP ${gwResp.status} in ${elapsed}ms`); -console.log("Body:", (await gwResp.text()).slice(0, 100)); - -// Cleanup -await actorDriver.shutdown(true); -server.close(); -process.exit(gwResp.ok ? 0 : 1); diff --git a/rivetkit-typescript/packages/rivetkit/tests/standalone-native-test.mts b/rivetkit-typescript/packages/rivetkit/tests/standalone-native-test.mts deleted file mode 100644 index 15d7b397d9..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/standalone-native-test.mts +++ /dev/null @@ -1,416 +0,0 @@ -// Standalone end-to-end test for the native envoy path. -// Verifies action calls, actor connections, raw WebSockets, and SQLite -// persistence through @rivetkit/rivetkit-native. -// -// Run: npx tsx tests/standalone-native-test.mts - -import { serve as honoServe } from "@hono/node-server"; -import { Hono } from "hono"; -import { createClientWithDriver } from "../src/client/client"; -import { convertRegistryConfigToClientConfig } from "../src/client/config"; -import { createClient } from "../src/client/mod"; -import { db } from "../src/db/mod"; -import { EngineActorDriver } from "../src/drivers/engine/mod"; -import { updateRunnerConfig } from "../src/engine-client/api-endpoints"; -import { RemoteEngineControlClient } from "../src/engine-client/mod"; -import { actor, setup } from "../src/mod"; - -const endpoint = "http://127.0.0.1:6420"; -const namespace = "default"; -const poolName = "test-envoy"; -const token = "dev"; - -const nativeActor = actor({ - state: { - count: 0, - lastWebSocketMessage: null as string | null, - }, - db: db({ - onMigrate: async (database) => { - await database.execute(` - CREATE TABLE IF NOT EXISTS message_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - source TEXT NOT NULL, - message TEXT NOT NULL, - created_at INTEGER NOT NULL - ); - `); - }, - }), - actions: { - increment: async (c: any, value: number) => { - c.state.count += value; - await c.db.execute( - "INSERT INTO message_log (source, message, created_at) VALUES (?, ?, ?)", - "action", - `increment:${c.state.count}`, - Date.now(), - ); - return c.state.count; - }, - record: async (c: any, source: string, message: string) => { - await c.db.execute( - "INSERT INTO message_log (source, message, created_at) VALUES (?, ?, ?)", - source, - message, - Date.now(), - ); - }, - getSummary: async (c: any) => { - const countRows = await c.db.execute<{ count: number }>( - "SELECT COUNT(*) AS count FROM message_log", - ); - const latestRows = await c.db.execute<{ - source: string; - message: string; - }>( - "SELECT source, message FROM message_log ORDER BY id DESC LIMIT 1", - ); - - return { - entryCount: Number(countRows[0]?.count ?? 0), - latest: latestRows[0] ?? null, - stateCount: c.state.count, - lastWebSocketMessage: c.state.lastWebSocketMessage, - }; - }, - getMessages: async (c: any) => { - return await c.db.execute<{ - id: number; - source: string; - message: string; - }>( - "SELECT id, source, message FROM message_log ORDER BY id ASC", - ); - }, - }, - onWebSocket(c: any, ws: WebSocket) { - ws.addEventListener("message", async (event: MessageEvent) => { - const message = String(event.data); - c.state.lastWebSocketMessage = message; - await c.db.execute( - "INSERT INTO message_log (source, message, created_at) VALUES (?, ?, ?)", - "websocket", - message, - Date.now(), - ); - ws.send( - JSON.stringify({ - ok: true, - echo: message, - stateCount: c.state.count, - }), - ); - }); - }, -}); - -const registry = setup({ use: { nativeActor } }); -registry.config.endpoint = endpoint; -registry.config.namespace = namespace; -registry.config.token = token; -registry.config.envoy = { ...registry.config.envoy, poolName }; -registry.config.test = { enabled: true }; - -const parsedConfig = registry.parseConfig(); -const clientConfig = convertRegistryConfigToClientConfig(parsedConfig); -const engineClient = new RemoteEngineControlClient(clientConfig); -const inlineClient = createClientWithDriver(engineClient, clientConfig); - -const app = new Hono(); - -app.get("/metadata", (c: any) => - c.json({ runtime: "rivetkit", version: "1", envoyProtocolVersion: 1 }), -); -app.post("/start", async (c: any) => actorDriver.serverlessHandleStart(c)); - -const actorDriver = new EngineActorDriver(parsedConfig, engineClient, inlineClient); -const server = honoServe({ fetch: app.fetch, hostname: "127.0.0.1", port: 0 }); - -const unexpectedFailures: string[] = []; -const onUnhandledRejection = (error: unknown) => { - unexpectedFailures.push( - `unhandled rejection: ${error instanceof Error ? error.stack ?? error.message : String(error)}`, - ); -}; -const onUncaughtException = (error: Error) => { - unexpectedFailures.push( - `uncaught exception: ${error.stack ?? error.message}`, - ); -}; - -process.on("unhandledRejection", onUnhandledRejection); -process.on("uncaughtException", onUncaughtException); - -let passed = 0; -let failed = 0; - -function ok(name: string) { - console.log(` ✓ ${name}`); - passed++; -} - -function fail(name: string, error: unknown) { - const message = - error instanceof Error ? error.stack ?? error.message : String(error); - console.log(` ✗ ${name}: ${message}`); - failed++; -} - -async function waitFor( - check: () => boolean, - label: string, - timeoutMs = 10_000, -): Promise { - const start = Date.now(); - while (!check()) { - if (Date.now() - start > timeoutMs) { - throw new Error(`timed out waiting for ${label}`); - } - await new Promise((resolve) => setTimeout(resolve, 25)); - } -} - -async function waitForOpen(ws: WebSocket): Promise { - if (ws.readyState === WebSocket.OPEN) { - return; - } - - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - cleanup(); - reject(new Error("timed out waiting for raw websocket open")); - }, 10_000); - const cleanup = () => { - clearTimeout(timeout); - ws.removeEventListener("open", onOpen); - ws.removeEventListener("error", onError); - ws.removeEventListener("close", onClose); - }; - const onOpen = () => { - cleanup(); - resolve(); - }; - const onError = () => { - cleanup(); - reject(new Error("raw websocket error before open")); - }; - const onClose = (event: Event) => { - const closeEvent = event as CloseEvent; - cleanup(); - reject( - new Error( - `raw websocket closed before open (${closeEvent.code} ${closeEvent.reason})`, - ), - ); - }; - - ws.addEventListener("open", onOpen, { once: true }); - ws.addEventListener("error", onError, { once: true }); - ws.addEventListener("close", onClose, { once: true }); - }); -} - -async function closeWebSocket(ws: WebSocket): Promise { - if ( - ws.readyState === WebSocket.CLOSING || - ws.readyState === WebSocket.CLOSED - ) { - return; - } - - await new Promise((resolve) => { - ws.addEventListener("close", () => resolve(), { once: true }); - ws.close(1000, "done"); - }); -} - -let client: - | ReturnType> - | undefined; -let conn: any; -let rawWs: WebSocket | undefined; - -try { - console.log("Starting EngineActorDriver..."); - await new Promise((resolve) => - server.listening ? resolve() : server.once("listening", resolve), - ); - const port = (server.address() as any).port; - - await updateRunnerConfig(clientConfig, poolName, { - datacenters: { - default: { - serverless: { - url: `http://127.0.0.1:${port}`, - request_lifespan: 300, - max_concurrent_actors: 10000, - slots_per_runner: 1, - min_runners: 0, - max_runners: 10000, - }, - }, - }, - }); - - await actorDriver.waitForReady(); - console.log(`Ready (serverless on :${port})`); - - client = createClient({ - endpoint, - namespace, - poolName, - encoding: "json", - disableMetadataLookup: true, - }); - - const key = `native-e2e-${Date.now()}`; - const handle = client.nativeActor.getOrCreate([key]); - - console.log("\n=== Action + SQLite Tests ==="); - try { - const value = await handle.increment(5); - if (value === 5) ok("increment persists count"); - else fail("increment persists count", `got ${value}`); - - await handle.record("action", "manual-record"); - const summary = await handle.getSummary(); - if (summary.entryCount === 2) ok("sqlite records action writes"); - else fail("sqlite records action writes", `got ${summary.entryCount}`); - - if (summary.stateCount === 5) ok("state survives sqlite usage"); - else fail("state survives sqlite usage", `got ${summary.stateCount}`); - } catch (error) { - fail("action + sqlite flow", error); - } - - console.log("\n=== Actor Connection Test ==="); - try { - conn = handle.connect(); - await waitFor(() => conn.isConnected, "actor connection"); - - const value = await conn.increment(7); - if (value === 12) ok("actor connection action works over websocket"); - else fail("actor connection action works over websocket", `got ${value}`); - - await conn.dispose(); - conn = undefined; - ok("actor connection disposes cleanly"); - } catch (error) { - fail("actor connection flow", error); - } - - console.log("\n=== Raw WebSocket + SQLite Tests ==="); - try { - rawWs = await handle.webSocket(); - await waitForOpen(rawWs); - - const responsePromise = new Promise<{ - ok: boolean; - echo: string; - stateCount: number; - }>((resolve, reject) => { - const timeout = setTimeout(() => { - cleanup(); - reject(new Error("timed out waiting for raw websocket message")); - }, 10_000); - const cleanup = () => { - clearTimeout(timeout); - rawWs?.removeEventListener("message", onMessage); - rawWs?.removeEventListener("error", onError); - rawWs?.removeEventListener("close", onClose); - }; - const onMessage = (event: MessageEvent) => { - cleanup(); - resolve(JSON.parse(String(event.data))); - }; - const onError = () => { - cleanup(); - reject(new Error("raw websocket error")); - }; - const onClose = (event: Event) => { - const closeEvent = event as CloseEvent; - cleanup(); - reject( - new Error( - `raw websocket closed early (${closeEvent.code} ${closeEvent.reason})`, - ), - ); - }; - - rawWs?.addEventListener("message", onMessage); - rawWs?.addEventListener("error", onError, { once: true }); - rawWs?.addEventListener("close", onClose, { once: true }); - }); - - rawWs.send("hello-native"); - const response = await responsePromise; - - if (response.ok && response.echo === "hello-native") { - ok("raw websocket echoes message"); - } else { - fail("raw websocket echoes message", JSON.stringify(response)); - } - - if (response.stateCount === 12) ok("raw websocket sees latest actor state"); - else fail("raw websocket sees latest actor state", `got ${response.stateCount}`); - - await closeWebSocket(rawWs); - rawWs = undefined; - - const summary = await handle.getSummary(); - if (summary.entryCount === 4) ok("raw websocket writes to sqlite"); - else fail("raw websocket writes to sqlite", `got ${summary.entryCount}`); - - if (summary.lastWebSocketMessage === "hello-native") { - ok("websocket message updates actor state"); - } else { - fail( - "websocket message updates actor state", - `got ${summary.lastWebSocketMessage}`, - ); - } - - if ( - summary.latest?.source === "websocket" && - summary.latest?.message === "hello-native" - ) { - ok("latest sqlite row comes from raw websocket"); - } else { - fail("latest sqlite row comes from raw websocket", JSON.stringify(summary.latest)); - } - - const messages = await handle.getMessages(); - const sources = messages.map((entry) => entry.source).join(","); - if (sources === "action,action,action,websocket") ok("sqlite preserves full write history"); - else fail("sqlite preserves full write history", `got ${sources}`); - } catch (error) { - fail("raw websocket + sqlite flow", error); - } -} finally { - try { - if (rawWs) { - await closeWebSocket(rawWs).catch(() => undefined); - } - if (conn) { - await conn.dispose().catch(() => undefined); - } - if (client) { - await client.dispose().catch(() => undefined); - } - await actorDriver.shutdown(false).catch(() => undefined); - await new Promise((resolve) => server.close(() => resolve())); - await new Promise((resolve) => setTimeout(resolve, 250)); - } finally { - process.off("unhandledRejection", onUnhandledRejection); - process.off("uncaughtException", onUncaughtException); - } -} -if (unexpectedFailures.length > 0) { - for (const failure of unexpectedFailures) { - fail("unexpected runtime failure", failure); - } -} - -console.log(`\n${passed} passed, ${failed} failed`); -process.exit(failed > 0 ? 1 : 0); diff --git a/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-sqlite.mts b/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-sqlite.mts deleted file mode 100644 index 5525d93876..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-sqlite.mts +++ /dev/null @@ -1,93 +0,0 @@ -// Standalone test for WebSocket and SQLite through rivetkit-native -// Run: npx tsx tests/standalone-ws-sqlite.mts -// -// Requires: engine running on localhost:6420, test-envoy on port 5051 - -const endpoint = "http://127.0.0.1:6420"; -const token = "dev"; -const namespace = "default"; -const poolName = "test-envoy"; - -async function createActor(name: string, key: string) { - const resp = await fetch(`${endpoint}/actors?namespace=${namespace}`, { - method: "POST", - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - body: JSON.stringify({ name, key, runner_name_selector: poolName, crash_policy: "sleep" }), - }); - const body = await resp.json(); - if (!resp.ok) throw new Error(`Create actor failed: ${resp.status} ${JSON.stringify(body)}`); - return body.actor.actor_id as string; -} - -// --- Test 1: WebSocket echo --- -async function testWebSocket() { - console.log("\n=== WebSocket Test ==="); - const actorId = await createActor("test", `ws-${Date.now()}`); - console.log("Actor:", actorId.slice(0, 12)); - - const wsEndpoint = endpoint.replace("http://", "ws://"); - const ws = new WebSocket(`${wsEndpoint}/ws`, [ - "rivet", - "rivet_target.actor", - `rivet_actor.${actorId}`, - `rivet_token.${token}`, - ]); - - const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("WebSocket timeout")), 10_000); - ws.addEventListener("open", () => ws.send("hello")); - ws.addEventListener("message", (ev) => { - clearTimeout(timeout); - ws.close(); - resolve(ev.data as string); - }); - ws.addEventListener("error", (e) => { - clearTimeout(timeout); - reject(new Error(`WebSocket error: ${(e as any)?.message ?? "unknown"}`)); - }); - }); - - console.log("Response:", result); - console.log(result === "Echo: hello" ? "✓ PASS" : `✗ FAIL (expected "Echo: hello")`); - return result === "Echo: hello"; -} - -// --- Test 2: HTTP action (baseline) --- -async function testAction() { - console.log("\n=== Action Test ==="); - const actorId = await createActor("test", `act-${Date.now()}`); - console.log("Actor:", actorId.slice(0, 12)); - - const resp = await fetch(`${endpoint}/ping`, { - headers: { - "X-Rivet-Token": token, - "X-Rivet-Target": "actor", - "X-Rivet-Actor": actorId, - }, - }); - const body = await resp.text(); - console.log(`HTTP ${resp.status}: ${body.slice(0, 60)}`); - console.log(resp.ok ? "✓ PASS" : "✗ FAIL"); - return resp.ok; -} - -// --- Run --- -let passed = 0; -let failed = 0; - -try { - (await testAction()) ? passed++ : failed++; -} catch (e) { - console.log("✗ FAIL:", (e as Error).message); - failed++; -} - -try { - (await testWebSocket()) ? passed++ : failed++; -} catch (e) { - console.log("✗ FAIL:", (e as Error).message); - failed++; -} - -console.log(`\n${passed} passed, ${failed} failed`); -process.exit(failed > 0 ? 1 : 0); diff --git a/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-test.mts b/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-test.mts deleted file mode 100644 index 354764d5c3..0000000000 --- a/rivetkit-typescript/packages/rivetkit/tests/standalone-ws-test.mts +++ /dev/null @@ -1,142 +0,0 @@ -// Test WebSocket through EngineActorDriver (native envoy path) -// Run: npx tsx tests/standalone-ws-test.mts - -import { EngineActorDriver } from "../src/drivers/engine/mod"; -import { RemoteEngineControlClient } from "../src/engine-client/mod"; -import { convertRegistryConfigToClientConfig } from "../src/client/config"; -import { createClientWithDriver } from "../src/client/client"; -import { updateRunnerConfig } from "../src/engine-client/api-endpoints"; -import { setup, actor } from "../src/mod"; -import { serve as honoServe } from "@hono/node-server"; -import { Hono } from "hono"; - -const endpoint = "http://127.0.0.1:6420"; -const namespace = "default"; -const poolName = "test-envoy"; // reuse existing pool that already has metadata -const token = "dev"; - -// Actor with WebSocket echo -const wsActor = actor({ - state: { msgCount: 0 }, - actions: { - getCount: (c: any) => c.state.msgCount, - }, - onWebSocket(ctx: any, ws: any) { - ws.addEventListener("message", (ev: any) => { - ctx.state.msgCount++; - ws.send(`Echo: ${ev.data}`); - }); - }, -}); - -const registry = setup({ use: { wsActor } }); -registry.config.endpoint = endpoint; -registry.config.namespace = namespace; -registry.config.token = token; -registry.config.envoy = { ...registry.config.envoy, poolName }; -registry.config.test = { enabled: true }; - -const parsedConfig = registry.parseConfig(); -const clientConfig = convertRegistryConfigToClientConfig(parsedConfig); -const engineClient = new RemoteEngineControlClient(clientConfig); -const inlineClient = createClientWithDriver(engineClient, clientConfig); - -console.log("Creating EngineActorDriver..."); -const actorDriver = new EngineActorDriver(parsedConfig, engineClient, inlineClient); - -// Serverless HTTP server -const app = new Hono(); -app.get("/metadata", (c: any) => c.json({ runtime: "rivetkit", version: "1", envoyProtocolVersion: 1 })); -app.post("/start", async (c: any) => actorDriver.serverlessHandleStart(c)); -const server = honoServe({ fetch: app.fetch, hostname: "127.0.0.1", port: 0 }); -await new Promise(r => server.listening ? r() : server.once("listening", r)); -const port = (server.address() as any).port; - -// Update runner config to point at our server -await updateRunnerConfig(clientConfig, poolName, { - datacenters: { default: { serverless: { - url: `http://127.0.0.1:${port}`, - request_lifespan: 300, - max_concurrent_actors: 10000, - slots_per_runner: 1, - min_runners: 0, - max_runners: 10000, - }}}, -}); - -await actorDriver.waitForReady(); -console.log("Envoy ready"); - -// No delay needed - "default" namespace already has metadata from test-envoy - -// Test 1: Create actor via API -console.log("\n--- Test: Action ---"); -const createResp = await fetch(`${endpoint}/actors?namespace=${namespace}`, { - method: "POST", - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - body: JSON.stringify({ name: "wsActor", key: `ws-${Date.now()}`, runner_name_selector: poolName, crash_policy: "sleep" }), -}); -const actorData = await createResp.json(); -const actorId = actorData.actor?.actor_id; -console.log("Created:", createResp.status, actorId?.slice(0, 12)); - -if (!actorId) { - console.log("✗ FAIL: no actor ID"); - process.exit(1); -} - -// Wait for actor to be ready -await new Promise(r => setTimeout(r, 2000)); - -// Test action first -const actionResp = await fetch( - `${endpoint}/gateway/wsActor/action/getCount?rvt-namespace=${namespace}&rvt-method=get&rvt-key=ws-${Date.now().toString().slice(-6)}`, - { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, - body: JSON.stringify(null), - signal: AbortSignal.timeout(10000), - }, -).catch(e => ({ ok: false, status: 0, text: () => Promise.resolve(e.message) } as any)); -console.log("Action:", actionResp.status, actionResp.ok ? "✓" : "✗"); - -// Test 2: WebSocket -console.log("\n--- Test: WebSocket ---"); -const wsEndpoint = endpoint.replace("http://", "ws://"); -const ws = new WebSocket(`${wsEndpoint}/ws`, [ - "rivet", - "rivet_target.actor", - `rivet_actor.${actorId}`, - `rivet_token.${token}`, -]); - -try { - const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("timeout")), 10000); - ws.addEventListener("open", () => { - console.log("WS connected, sending message..."); - ws.send("hello from native"); - }); - ws.addEventListener("message", (ev) => { - clearTimeout(timeout); - ws.close(); - resolve(ev.data as string); - }); - ws.addEventListener("error", (e) => { - clearTimeout(timeout); - reject(new Error(`WS error: ${(e as any)?.message}`)); - }); - ws.addEventListener("close", (e) => { - clearTimeout(timeout); - reject(new Error(`WS closed: ${(e as any)?.code} ${(e as any)?.reason}`)); - }); - }); - console.log("WS response:", result); - console.log(result.includes("Echo:") ? "✓ PASS" : "✗ FAIL"); -} catch (e) { - console.log("✗ FAIL:", (e as Error).message); -} - -await actorDriver.shutdown(true); -server.close(); -process.exit(0);