UNPKG

rivetkit

Version:

Lightweight libraries for building stateful actors on edge platforms

348 lines (293 loc) 12.6 kB
import { describe, expect, test } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; export function runRawHttpTests(driverTestConfig: DriverTestConfig) { describe("raw http", () => { test("should handle raw HTTP GET requests", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["test"]); // Test the hello endpoint const helloResponse = await actor.fetch("api/hello"); expect(helloResponse.ok).toBe(true); const helloData = await helloResponse.json(); expect(helloData).toEqual({ message: "Hello from actor!" }); }); test("should handle raw HTTP POST requests with echo", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["test"]); const testData = { test: "data", number: 123 }; const echoResponse = await actor.fetch("api/echo", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(testData), }); expect(echoResponse.ok).toBe(true); const echoData = await echoResponse.json(); expect(echoData).toEqual(testData); }); test("should track state across raw HTTP requests", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["state-test"]); // Make a few requests await actor.fetch("api/hello"); await actor.fetch("api/hello"); await actor.fetch("api/state"); // Check the state endpoint const stateResponse = await actor.fetch("api/state"); expect(stateResponse.ok).toBe(true); const stateData = (await stateResponse.json()) as { requestCount: number; }; expect(stateData.requestCount).toBe(4); // 4 total requests }); test("should pass headers correctly", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["headers-test"]); const customHeaders = { "X-Custom-Header": "test-value", "X-Another-Header": "another-value", }; const response = await actor.fetch("api/headers", { headers: customHeaders, }); expect(response.ok).toBe(true); const headers = (await response.json()) as Record<string, string>; expect(headers["x-custom-header"]).toBe("test-value"); expect(headers["x-another-header"]).toBe("another-value"); }); test("should return 404 for unhandled paths", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["404-test"]); const response = await actor.fetch("api/nonexistent"); expect(response.ok).toBe(false); expect(response.status).toBe(404); }); test("should return 404 when no onFetch handler defined", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpNoHandlerActor.getOrCreate(["no-handler"]); const response = await actor.fetch("api/anything"); expect(response.ok).toBe(false); expect(response.status).toBe(404); // No actions available without onFetch handler }); test("should return 500 error when onFetch returns void", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpVoidReturnActor.getOrCreate(["void-return"]); const response = await actor.fetch("api/anything"); expect(response.ok).toBe(false); expect(response.status).toBe(500); // Check error message - response might be CBOR encoded try { const errorData = (await response.json()) as { message: string }; expect(errorData.message).toContain( "onFetch handler must return a Response", ); } catch { // If JSON parsing fails, just check that we got a 500 error // The error details are already validated by the status code } // No actions available when onFetch returns void }); test("should handle different HTTP methods", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["methods-test"]); // Test various HTTP methods const methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]; for (const method of methods) { const response = await actor.fetch("api/echo", { method, body: ["POST", "PUT", "PATCH"].includes(method) ? JSON.stringify({ method }) : undefined, }); // Echo endpoint only handles POST, others should fall through to 404 if (method === "POST") { expect(response.ok).toBe(true); const data = await response.json(); expect(data).toEqual({ method }); } else if (method === "GET") { // GET to echo should return 404 expect(response.status).toBe(404); } else { // Other methods with body should also return 404 expect(response.status).toBe(404); } } }); test("should handle binary data", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["binary-test"]); // Send binary data const binaryData = new Uint8Array([1, 2, 3, 4, 5]); const response = await actor.fetch("api/echo", { method: "POST", headers: { "Content-Type": "application/octet-stream", }, body: binaryData, }); expect(response.ok).toBe(true); const responseBuffer = await response.arrayBuffer(); const responseArray = new Uint8Array(responseBuffer); expect(Array.from(responseArray)).toEqual([1, 2, 3, 4, 5]); }); test("should work with Hono router using createVars", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpHonoActor.getOrCreate(["hono-test"]); // Test root endpoint const rootResponse = await actor.fetch("/"); expect(rootResponse.ok).toBe(true); const rootData = await rootResponse.json(); expect(rootData).toEqual({ message: "Welcome to Hono actor!" }); // Test GET all users const usersResponse = await actor.fetch("/users"); expect(usersResponse.ok).toBe(true); const users = await usersResponse.json(); expect(users).toEqual([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]); // Test GET single user const userResponse = await actor.fetch("/users/1"); expect(userResponse.ok).toBe(true); const user = await userResponse.json(); expect(user).toEqual({ id: 1, name: "Alice" }); // Test POST new user const newUser = { name: "Charlie" }; const createResponse = await actor.fetch("/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newUser), }); expect(createResponse.ok).toBe(true); expect(createResponse.status).toBe(201); const createdUser = await createResponse.json(); expect(createdUser).toEqual({ id: 3, name: "Charlie" }); // Test PUT update user const updateData = { name: "Alice Updated" }; const updateResponse = await actor.fetch("/users/1", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(updateData), }); expect(updateResponse.ok).toBe(true); const updatedUser = await updateResponse.json(); expect(updatedUser).toEqual({ id: 1, name: "Alice Updated" }); // Test DELETE user const deleteResponse = await actor.fetch("/users/2", { method: "DELETE", }); expect(deleteResponse.ok).toBe(true); const deleteResult = await deleteResponse.json(); expect(deleteResult).toEqual({ message: "User 2 deleted" }); // Test 404 for non-existent route const notFoundResponse = await actor.fetch("/api/unknown"); expect(notFoundResponse.ok).toBe(false); expect(notFoundResponse.status).toBe(404); // No actions available on Hono actor }); test("should handle paths with and without leading slashes", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["path-test"]); // Test path without leading slash const responseWithoutSlash = await actor.fetch("api/hello"); expect(responseWithoutSlash.ok).toBe(true); const dataWithoutSlash = await responseWithoutSlash.json(); expect(dataWithoutSlash).toEqual({ message: "Hello from actor!" }); // Test path with leading slash const responseWithSlash = await actor.fetch("/api/hello"); expect(responseWithSlash.ok).toBe(true); const dataWithSlash = await responseWithSlash.json(); expect(dataWithSlash).toEqual({ message: "Hello from actor!" }); // Both should work the same way }); test("should not create double slashes in request URLs", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); // Create a special actor that logs the request URL const actor = client.rawHttpHonoActor.getOrCreate(["url-test"]); // Test with leading slash - this was causing double slashes const response = await actor.fetch("/users"); expect(response.ok).toBe(true); // The Hono router should receive a clean path without double slashes // If there were double slashes, Hono would not match the route correctly const data = await response.json(); expect(data).toEqual([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]); }); test("should handle forwarded requests correctly without double slashes", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpHonoActor.getOrCreate(["forward-test"]); // Simulate what the example does - pass path as string and Request as init const truncatedPath = "/users"; const url = new URL(truncatedPath, "http://example.com"); const newRequest = new Request(url, { method: "GET", }); // This simulates calling actor.fetch(truncatedPath, newRequest) // which was causing double slashes in the example const response = await actor.fetch(truncatedPath, newRequest as any); expect(response.ok).toBe(true); const users = await response.json(); expect(users).toEqual([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]); }); test("example fix: should properly forward requests using just Request object", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpHonoActor.getOrCreate(["forward-fix"]); // The correct way - just pass the Request object const truncatedPath = "/users/1"; const url = new URL(truncatedPath, "http://example.com"); const newRequest = new Request(url, { method: "GET", }); // Correct usage - just pass the Request const response = await actor.fetch(newRequest); expect(response.ok).toBe(true); const user = await response.json(); expect(user).toEqual({ id: 1, name: "Alice" }); }); test("should support standard fetch API with URL and Request objects", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpActor.getOrCreate(["fetch-api-test"]); // Test with URL object const url = new URL("/api/echo", "http://example.com"); const urlResponse = await actor.fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ from: "URL object" }), }); expect(urlResponse.ok).toBe(true); const urlData = await urlResponse.json(); expect(urlData).toEqual({ from: "URL object" }); // Test with Request object const request = new Request("http://example.com/api/echo", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ from: "Request object" }), }); const requestResponse = await actor.fetch(request); expect(requestResponse.ok).toBe(true); const requestData = await requestResponse.json(); expect(requestData).toEqual({ from: "Request object" }); // Test with Request object and additional init params const request2 = new Request("http://example.com/api/headers", { method: "GET", headers: { "X-Original": "request-header" }, }); const overrideResponse = await actor.fetch(request2, { headers: { "X-Override": "init-header" }, }); expect(overrideResponse.ok).toBe(true); const headers = (await overrideResponse.json()) as Record<string, string>; expect(headers["x-override"]).toBe("init-header"); // Original headers should be present too expect(headers["x-original"]).toBe("request-header"); }); }); }