mirror of
https://github.com/cloudflare/workers-rs.git
synced 2025-12-08 18:01:59 +00:00
275 lines
9.5 KiB
TypeScript
275 lines
9.5 KiB
TypeScript
import { describe, test, expect } from "vitest";
|
|
import { mf, mfUrl } from "./mf";
|
|
|
|
const productNames = ["Laptop", "Mouse", "Keyboard", "Monitor", "Headphones"];
|
|
const expectedTypedStrings = [
|
|
"Product 1: Laptop - $999.99 (in stock: true)",
|
|
"Product 2: Mouse - $29.99 (in stock: true)",
|
|
"Product 3: Keyboard - $79.99 (in stock: false)",
|
|
"Product 4: Monitor - $299.99 (in stock: true)",
|
|
"Product 5: Headphones - $149.99 (in stock: false)",
|
|
];
|
|
|
|
describe("sql iterator durable object", () => {
|
|
test("next() iterator returns typed results", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/test/next`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("next() iterator results:");
|
|
|
|
// Check that we get all 5 products
|
|
for (const s of expectedTypedStrings) {
|
|
expect(text).toContain(s);
|
|
}
|
|
});
|
|
|
|
test("raw() iterator returns column names and raw values", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/test/raw`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("raw() iterator results:");
|
|
|
|
// Check column names are included
|
|
expect(text).toContain("Columns: id, name, price, in_stock");
|
|
|
|
// Check that we get raw data rows - should contain all products
|
|
for (const name of productNames) {
|
|
expect(text).toContain(`String("${name}")`);
|
|
}
|
|
|
|
// Check for different data types in raw format
|
|
expect(text).toContain("Integer("); // For IDs
|
|
expect(text).toContain("Float(999.99)");
|
|
expect(text).toContain("Integer(1)"); // in_stock: true
|
|
expect(text).toContain("Integer(0)"); // in_stock: false
|
|
});
|
|
|
|
test("different object instances have independent data", async () => {
|
|
// Test first instance
|
|
const resp1 = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/instance1/next`,
|
|
);
|
|
expect(resp1.status).toBe(200);
|
|
const text1 = await resp1.text();
|
|
expect(text1).toContain("Product 1: Laptop");
|
|
|
|
// Test second instance - should have same seeded data
|
|
const resp2 = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/instance2/next`,
|
|
);
|
|
expect(resp2.status).toBe(200);
|
|
const text2 = await resp2.text();
|
|
expect(text2).toContain("Product 1: Laptop");
|
|
|
|
// Both should be identical since they use the same seed data
|
|
expect(text1).toBe(text2);
|
|
});
|
|
|
|
test("iterator handles empty results gracefully", async () => {
|
|
// Create a new instance to test empty query behavior
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/empty-test/raw`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
// Should still show column names even with data
|
|
expect(text).toContain("Columns: id, name, price, in_stock");
|
|
});
|
|
|
|
test("next() iterator handles deserialization errors", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/test/next-invalid`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("next-invalid() iterator results:");
|
|
|
|
// Should have 5 error messages (one for each row that failed deserialization)
|
|
const deserializationErrors = text.match(/Error deserializing row:/g);
|
|
expect(deserializationErrors).toBeTruthy();
|
|
expect(deserializationErrors!.length).toBe(5);
|
|
|
|
// Check that the error messages contain information about the type mismatch
|
|
expect(text).toContain("invalid type");
|
|
});
|
|
|
|
test.each([
|
|
["root", ""],
|
|
["invalid", "/invalid"],
|
|
])("%s endpoint returns help message", async (_, path) => {
|
|
const resp = await mf.dispatchFetch(
|
|
`http://fake.host/sql-iterator/test${path}`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toBe("SQL Iterator Test - try /next, /raw, /next-invalid, /blob-next, /blob-raw, or /blob-roundtrip endpoints");
|
|
});
|
|
|
|
test("data consistency between next() and raw() methods", async () => {
|
|
// Get data from next() method
|
|
const nextResp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/consistency-test/next`,
|
|
);
|
|
expect(nextResp.status).toBe(200);
|
|
const nextText = await nextResp.text();
|
|
|
|
// Get data from raw() method
|
|
const rawResp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/consistency-test/raw`,
|
|
);
|
|
expect(rawResp.status).toBe(200);
|
|
const rawText = await rawResp.text();
|
|
|
|
// Both should contain the same 5 products
|
|
for (const name of productNames) {
|
|
expect(nextText).toContain(name);
|
|
expect(rawText).toContain(name);
|
|
}
|
|
|
|
// Raw should show 5 data rows (plus column header)
|
|
const rowMatches = rawText.match(/Row: \[/g);
|
|
expect(rowMatches).toBeTruthy();
|
|
expect(rowMatches!.length).toBe(5);
|
|
});
|
|
|
|
describe("BLOB handling", () => {
|
|
test("blob-next() iterator returns BLOB data correctly", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/blob-test/blob-next`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("blob-next() iterator results:");
|
|
|
|
// Check for various BLOB test cases
|
|
expect(text).toContain("binary_data"); // Binary data test case
|
|
expect(text).toContain("empty_blob"); // Empty blob test case
|
|
expect(text).toContain("text_as_blob"); // Text converted to blob
|
|
expect(text).toContain("large_blob"); // Large blob test case
|
|
|
|
// Check that binary data is displayed correctly
|
|
expect(text).toContain("[0, 1, 2, 3, 255, 254]");
|
|
|
|
// Check empty blob handling
|
|
expect(text).toContain("data: []");
|
|
|
|
// Check large blob truncation
|
|
expect(text).toContain("[1000 bytes total]");
|
|
});
|
|
|
|
test("blob-raw() iterator returns raw BLOB values", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/blob-test/blob-raw`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("blob-raw() iterator results:");
|
|
|
|
// Check column names are included
|
|
expect(text).toContain("Columns: id, name, data");
|
|
|
|
// Check BLOB data in raw format
|
|
expect(text).toContain("Blob([0, 1, 2, 3, 255, 254])");
|
|
expect(text).toContain("Blob([])"); // Empty blob
|
|
expect(text).toContain("Blob([72, 101, 108, 108, 111"); // "Hello" bytes
|
|
|
|
// Check large blob truncation in raw format
|
|
expect(text).toMatch(/Blob\(\[0, 1, 2, 3, 4, 5, 6, 7, 8, 9\]\.\.\..*1000 bytes/);
|
|
});
|
|
|
|
test("blob roundtrip test verifies data integrity", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/roundtrip-test/blob-roundtrip`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("blob-roundtrip test results:");
|
|
|
|
// Check that original data is shown
|
|
expect(text).toContain("Original data: [222, 173, 190, 239, 0, 255]");
|
|
|
|
// Check that both next() and raw() methods return matching data
|
|
expect(text).toContain("next() result: [222, 173, 190, 239, 0, 255], matches_original: true");
|
|
expect(text).toContain("raw() result: [222, 173, 190, 239, 0, 255], matches_original: true");
|
|
});
|
|
|
|
test("BLOB data consistency between next() and raw() methods", async () => {
|
|
// Get data from blob-next() method
|
|
const nextResp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/consistency-blob-test/blob-next`,
|
|
);
|
|
expect(nextResp.status).toBe(200);
|
|
const nextText = await nextResp.text();
|
|
|
|
// Get data from blob-raw() method
|
|
const rawResp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/consistency-blob-test/blob-raw`,
|
|
);
|
|
expect(rawResp.status).toBe(200);
|
|
const rawText = await rawResp.text();
|
|
|
|
// Both should contain the same BLOB test data
|
|
const blobNames = ["binary_data", "empty_blob", "text_as_blob", "large_blob"];
|
|
for (const name of blobNames) {
|
|
expect(nextText).toContain(name);
|
|
expect(rawText).toContain(name);
|
|
}
|
|
|
|
// Both should show 4 BLOB data rows
|
|
const nextRows = nextText.match(/BlobData \d+:/g);
|
|
const rawRows = rawText.match(/Row: \[/g);
|
|
expect(nextRows).toBeTruthy();
|
|
expect(rawRows).toBeTruthy();
|
|
expect(nextRows!.length).toBe(4);
|
|
expect(rawRows!.length).toBe(4);
|
|
});
|
|
|
|
test("empty BLOB handling", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/empty-blob-test/blob-next`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("empty_blob");
|
|
expect(text).toContain("data: []");
|
|
});
|
|
|
|
test("large BLOB handling", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/large-blob-test/blob-raw`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("large_blob");
|
|
// Large blob should be truncated for display
|
|
expect(text).toMatch(/Blob\(\[.*\]\.\.\..*1000 bytes/);
|
|
});
|
|
|
|
test("binary data with null bytes handling", async () => {
|
|
const resp = await mf.dispatchFetch(
|
|
`${mfUrl}sql-iterator/binary-test/blob-next`,
|
|
);
|
|
expect(resp.status).toBe(200);
|
|
|
|
const text = await resp.text();
|
|
expect(text).toContain("binary_data");
|
|
// Should handle null bytes (0x00) and high bytes (0xFF) correctly
|
|
expect(text).toContain("[0, 1, 2, 3, 255, 254]");
|
|
});
|
|
});
|
|
});
|