branch:
validate-messages.test.ts
7456 bytesRaw
import { env, exports } from "cloudflare:workers";
import { describe, it, expect } from "vitest";
import type { UIMessage as ChatMessage } from "ai";
import { getAgentByName } from "agents";
describe("Message Structural Validation", () => {
async function setupAgent(room: string) {
const res = await exports.default.fetch(
`http://example.com/agents/test-chat-agent/${room}`,
{ headers: { Upgrade: "websocket" } }
);
expect(res.status).toBe(101);
const ws = res.webSocket as WebSocket;
ws.accept();
const agentStub = await getAgentByName(env.TestChatAgent, room);
return { ws, agentStub };
}
/**
* Fetch messages via the /get-messages HTTP endpoint, which goes through
* _loadMessagesFromDb() and applies structural validation.
*/
async function getValidatedMessages(room: string): Promise<ChatMessage[]> {
const getMessagesRes = await exports.default.fetch(
`http://example.com/agents/test-chat-agent/${room}/get-messages`
);
expect(getMessagesRes.status).toBe(200);
return (await getMessagesRes.json()) as ChatMessage[];
}
it("loads valid messages without filtering", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
const messages: ChatMessage[] = [
{ id: "msg1", role: "user", parts: [{ type: "text", text: "Hello" }] },
{
id: "msg2",
role: "assistant",
parts: [{ type: "text", text: "Hi!" }]
}
];
await agentStub.persistMessages(messages);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(2);
expect(persisted[0].id).toBe("msg1");
expect(persisted[1].id).toBe("msg2");
ws.close(1000);
});
it("filters out messages with missing id", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
// Manually insert a malformed message (no id field)
agentStub.insertRawMessage(
"bad-no-id",
JSON.stringify({
role: "user",
parts: [{ type: "text", text: "bad" }]
})
);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("filters out messages with invalid role", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
agentStub.insertRawMessage(
"bad-role",
JSON.stringify({
id: "bad-role",
role: "invalid-role",
parts: [{ type: "text", text: "bad" }]
})
);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("filters out messages with non-array parts", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
agentStub.insertRawMessage(
"bad-parts",
JSON.stringify({
id: "bad-parts",
role: "user",
parts: "not an array"
})
);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("filters out messages with empty id", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
agentStub.insertRawMessage(
"bad-empty-id",
JSON.stringify({
id: "",
role: "user",
parts: [{ type: "text", text: "bad" }]
})
);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("keeps messages with empty parts array (lenient)", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "empty-parts", role: "assistant", parts: [] }
]);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("empty-parts");
expect(persisted[0].parts).toEqual([]);
ws.close(1000);
});
it("filters out non-object values stored as messages", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
// Raw string value (not a JSON object)
agentStub.insertRawMessage("bad-string", JSON.stringify("just a string"));
// null value
agentStub.insertRawMessage("bad-null", "null");
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("preserves messages with metadata", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
const messageWithMeta: ChatMessage = {
id: "meta1",
role: "assistant",
parts: [{ type: "text", text: "Response" }],
metadata: { model: "gpt-4o", totalTokens: 150 }
};
await agentStub.persistMessages([messageWithMeta]);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("meta1");
expect(persisted[0].metadata).toEqual({
model: "gpt-4o",
totalTokens: 150
});
ws.close(1000);
});
it("filters out completely unparseable JSON", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
await agentStub.persistMessages([
{ id: "valid1", role: "user", parts: [{ type: "text", text: "Hello" }] }
]);
// Insert raw broken JSON that will fail JSON.parse
agentStub.insertRawMessage("bad-json", "{broken json!!!");
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(1);
expect(persisted[0].id).toBe("valid1");
ws.close(1000);
});
it("accepts all valid roles: user, assistant, system", async () => {
const room = crypto.randomUUID();
const { ws, agentStub } = await setupAgent(room);
const messages: ChatMessage[] = [
{
id: "sys1",
role: "system",
parts: [{ type: "text", text: "You are helpful" }]
},
{ id: "usr1", role: "user", parts: [{ type: "text", text: "Hello" }] },
{
id: "ast1",
role: "assistant",
parts: [{ type: "text", text: "Hi!" }]
}
];
await agentStub.persistMessages(messages);
const persisted = await getValidatedMessages(room);
expect(persisted.length).toBe(3);
expect(persisted.map((m) => m.role)).toEqual([
"system",
"user",
"assistant"
]);
ws.close(1000);
});
});