UNPKG

rivetkit

Version:

Lightweight libraries for building stateful actors on edge platforms

415 lines (349 loc) 13.8 kB
import { describe, expect, test } from "vitest"; import { z } from "zod"; import { registry } from "../../../fixtures/driver-test-suite/registry"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; export function runRawHttpRequestPropertiesTests( driverTestConfig: DriverTestConfig, ) { describe("raw http request properties", () => { test("should pass all Request properties correctly to onFetch", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test basic request properties const response = await actor.fetch("test/path?foo=bar&baz=qux", { method: "POST", headers: { "Content-Type": "application/json", "X-Custom-Header": "custom-value", Authorization: "Bearer test-token", }, body: JSON.stringify({ test: "data" }), }); expect(response.ok).toBe(true); const data = (await response.json()) as any; // Verify URL properties expect(data.url).toContain("/test/path?foo=bar&baz=qux"); expect(data.pathname).toBe("/test/path"); expect(data.search).toBe("?foo=bar&baz=qux"); expect(data.searchParams).toEqual({ foo: "bar", baz: "qux", }); // Verify method expect(data.method).toBe("POST"); // Verify headers expect(data.headers["content-type"]).toBe("application/json"); expect(data.headers["x-custom-header"]).toBe("custom-value"); expect(data.headers["authorization"]).toBe("Bearer test-token"); // Verify body expect(data.body).toEqual({ test: "data" }); }); test("should handle GET requests with no body", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const response = await actor.fetch("test/get", { method: "GET", }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.method).toBe("GET"); expect(data.body).toBeNull(); }); test("should handle different content types", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test form data const formData = new URLSearchParams(); formData.append("field1", "value1"); formData.append("field2", "value2"); const formResponse = await actor.fetch("test/form", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: formData.toString(), }); expect(formResponse.ok).toBe(true); const formResult = (await formResponse.json()) as any; expect(formResult.headers["content-type"]).toBe( "application/x-www-form-urlencoded", ); expect(formResult.bodyText).toBe("field1=value1&field2=value2"); // Test plain text const textResponse = await actor.fetch("test/text", { method: "POST", headers: { "Content-Type": "text/plain", }, body: "Hello, World!", }); expect(textResponse.ok).toBe(true); const textResult = (await textResponse.json()) as any; expect(textResult.headers["content-type"]).toBe("text/plain"); expect(textResult.bodyText).toBe("Hello, World!"); }); test("should preserve all header casing and values", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const response = await actor.fetch("test/headers", { headers: { Accept: "application/json", "Accept-Language": "en-US,en;q=0.9", "Cache-Control": "no-cache", "User-Agent": "RivetKit-Test/1.0", "X-Request-ID": "12345", }, }); expect(response.ok).toBe(true); const data = (await response.json()) as any; // Headers should be normalized to lowercase expect(data.headers["accept"]).toBe("application/json"); expect(data.headers["accept-language"]).toBe("en-US,en;q=0.9"); expect(data.headers["cache-control"]).toBe("no-cache"); // User-Agent might be overwritten by the HTTP client, so just check it exists expect(data.headers["user-agent"]).toBeTruthy(); expect(data.headers["x-request-id"]).toBe("12345"); }); test("should handle empty and special URL paths", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test root path const rootResponse = await actor.fetch(""); expect(rootResponse.ok).toBe(true); const rootData = (await rootResponse.json()) as any; expect(rootData.pathname).toBe("/"); // Test path with special characters const specialResponse = await actor.fetch( "test/path%20with%20spaces/and%2Fslashes", ); expect(specialResponse.ok).toBe(true); const specialData = (await specialResponse.json()) as any; // Note: The URL path may or may not be decoded depending on the HTTP client/server // Just verify it contains the expected segments expect(specialData.pathname).toMatch(/path.*with.*spaces.*and.*slashes/); // Test path with fragment (should be ignored in server-side) const fragmentResponse = await actor.fetch("test/path#fragment"); expect(fragmentResponse.ok).toBe(true); const fragmentData = (await fragmentResponse.json()) as any; expect(fragmentData.pathname).toBe("/test/path"); expect(fragmentData.hash).toBe(""); // Fragments are not sent to server }); test("should handle request properties for all HTTP methods", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const methods = [ "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", ]; for (const method of methods) { const response = await actor.fetch(`test/${method.toLowerCase()}`, { method, // Only include body for methods that support it body: ["POST", "PUT", "PATCH"].includes(method) ? JSON.stringify({ method }) : undefined, }); // HEAD responses have no body if (method === "HEAD") { expect(response.status).toBe(200); const text = await response.text(); expect(text).toBe(""); } else if (method === "OPTIONS") { expect(response.status).toBe(204); const text = await response.text(); expect(text).toBe(""); } else { expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.method).toBe(method); } } }); test("should handle complex query parameters", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test multiple values for same key const response = await actor.fetch( "test?key=value1&key=value2&array[]=1&array[]=2&nested[prop]=val", ); expect(response.ok).toBe(true); const data = (await response.json()) as any; // Note: URLSearchParams only keeps the last value for duplicate keys expect(data.searchParams.key).toBe("value2"); expect(data.searchParams["array[]"]).toBe("2"); expect(data.searchParams["nested[prop]"]).toBe("val"); }); test("should handle multipart form data", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Create multipart boundary const boundary = "----RivetKitBoundary"; const body = [ `------${boundary}`, 'Content-Disposition: form-data; name="field1"', "", "value1", `------${boundary}`, 'Content-Disposition: form-data; name="field2"', "", "value2", `------${boundary}--`, ].join("\r\n"); const response = await actor.fetch("test/multipart", { method: "POST", headers: { "Content-Type": `multipart/form-data; boundary=----${boundary}`, }, body: body, }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.headers["content-type"]).toContain("multipart/form-data"); expect(data.bodyText).toContain("field1"); expect(data.bodyText).toContain("value1"); }); test("should handle very long URLs", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Create a very long query string const longValue = "x".repeat(1000); const response = await actor.fetch(`test/long?param=${longValue}`); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.searchParams.param).toBe(longValue); expect(data.search.length).toBeGreaterThan(1000); }); test("should handle large request bodies", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Create a large JSON body (1MB+) const largeArray = new Array(10000).fill({ id: 1, name: "Test", description: "This is a test object with some data", }); const response = await actor.fetch("test/large", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(largeArray), }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.body).toHaveLength(10000); }); test("should handle missing content-type header", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const response = await actor.fetch("test/no-content-type", { method: "POST", body: "plain text without content-type", }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.bodyText).toBe("plain text without content-type"); }); test("should handle empty request body", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const response = await actor.fetch("test/empty", { method: "POST", headers: { "Content-Type": "application/json", }, body: "", }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.body).toBeNull(); expect(data.bodyText).toBe(""); }); test("should handle custom HTTP methods", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test a custom method (though most HTTP clients may not support this) try { const response = await actor.fetch("test/custom", { method: "CUSTOM", }); // If the request succeeds, verify the method if (response.ok) { const data = (await response.json()) as any; expect(data.method).toBe("CUSTOM"); } } catch (error) { // Some HTTP clients may reject custom methods // This is expected behavior } }); test("should handle cookies in headers", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); const response = await actor.fetch("test/cookies", { headers: { Cookie: "session=abc123; user=test; preferences=dark_mode", }, }); expect(response.ok).toBe(true); const data = (await response.json()) as any; expect(data.headers.cookie).toBe( "session=abc123; user=test; preferences=dark_mode", ); }); test("should handle URL encoding properly", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Test various encoded characters const response = await actor.fetch( "test/encoded?special=%20%21%40%23%24%25%5E%26&unicode=%E2%9C%93&email=test%40example.com", ); expect(response.ok).toBe(true); const data = (await response.json()) as any; // Verify URL decoding expect(data.searchParams.special).toBe(" !@#$%^&"); expect(data.searchParams.unicode).toBe("✓"); expect(data.searchParams.email).toBe("test@example.com"); }); test("should handle concurrent requests maintaining separate contexts", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.rawHttpRequestPropertiesActor.getOrCreate(["test"]); // Send multiple concurrent requests with different data const requests = [ actor.fetch("test/concurrent?id=1", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ request: 1 }), }), actor.fetch("test/concurrent?id=2", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ request: 2 }), }), actor.fetch("test/concurrent?id=3", { method: "DELETE", }), ]; const responses = await Promise.all(requests); const results = (await Promise.all( responses.map((r) => r.json()), )) as any[]; // Verify each request maintained its own context expect(results[0].searchParams.id).toBe("1"); expect(results[0].method).toBe("POST"); expect(results[0].body).toEqual({ request: 1 }); expect(results[1].searchParams.id).toBe("2"); expect(results[1].method).toBe("PUT"); expect(results[1].body).toEqual({ request: 2 }); expect(results[2].searchParams.id).toBe("3"); expect(results[2].method).toBe("DELETE"); expect(results[2].body).toBeNull(); }); }); }