Files
diegosouzapw--omniroute/tests/unit/ui/conversation-tab-separators.test.tsx
2026-07-13 13:39:12 +08:00

152 lines
5.6 KiB
TypeScript

/**
* Tests for ConversationTab separators — CONTEXT HISTORY / MODEL RESPONSE
* Validates the rendering logic: both sections appear when both request/response have turns;
* only CONTEXT HISTORY appears when response is empty.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { normalizeConversation } from "../../../src/mitm/inspector/conversationNormalizer.ts";
import type { InterceptedRequest } from "../../../src/mitm/inspector/types.ts";
function makeRequest(overrides: Partial<InterceptedRequest> = {}): InterceptedRequest {
return {
id: "test-id",
source: "agent-bridge",
timestamp: new Date().toISOString(),
method: "POST",
host: "api.openai.com",
path: "/v1/chat/completions",
requestHeaders: { "content-type": "application/json" },
requestBody: null,
requestSize: 0,
responseHeaders: {},
responseBody: null,
responseSize: 0,
status: 200,
detectedKind: "llm",
...overrides,
};
}
describe("ConversationTab separators rendering logic", () => {
it("shows CONTEXT HISTORY section when request has turns", () => {
const reqBody = JSON.stringify({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are helpful." },
{ role: "user", content: "Hello!" },
],
});
const req = makeRequest({ requestBody: reqBody, responseBody: null });
const result = normalizeConversation(req);
if (result !== null) {
assert.ok(result.request.length > 0, "request section should have turns");
// Context History separator should be rendered (guarded by request.length > 0)
const shouldRenderContextHistory = result.request.length > 0;
assert.equal(shouldRenderContextHistory, true);
}
});
it("shows MODEL RESPONSE section when response has turns", () => {
const reqBody = JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Hi" }],
});
const resBody = JSON.stringify({
choices: [
{
message: { role: "assistant", content: "Hello! How can I help?" },
finish_reason: "stop",
},
],
usage: { prompt_tokens: 5, completion_tokens: 8 },
});
const req = makeRequest({ requestBody: reqBody, responseBody: resBody });
const result = normalizeConversation(req);
if (result !== null) {
// Model Response separator should be rendered (guarded by response.length > 0)
const shouldRenderModelResponse = result.response.length > 0;
assert.equal(shouldRenderModelResponse, true);
}
});
it("does NOT render MODEL RESPONSE when response is empty", () => {
const reqBody = JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Hi" }],
});
// No response body
const req = makeRequest({ requestBody: reqBody, responseBody: null });
const result = normalizeConversation(req);
if (result !== null) {
// Response section should be empty, so MODEL RESPONSE separator should NOT render
const shouldRenderModelResponse = result.response.length > 0;
assert.equal(shouldRenderModelResponse, false);
// But context history should still render
const shouldRenderContextHistory = result.request.length > 0;
assert.equal(shouldRenderContextHistory, true);
}
});
it("renders both separators when both request and response have turns", () => {
const reqBody = JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Hi" }],
});
const resBody = JSON.stringify({
choices: [
{
message: { role: "assistant", content: "Hello!" },
finish_reason: "stop",
},
],
});
const req = makeRequest({ requestBody: reqBody, responseBody: resBody });
const result = normalizeConversation(req);
if (result !== null) {
const contextHistoryVisible = result.request.length > 0;
const modelResponseVisible = result.response.length > 0;
assert.equal(contextHistoryVisible, true, "CONTEXT HISTORY should be visible");
assert.equal(modelResponseVisible, true, "MODEL RESPONSE should be visible");
}
});
it("request and response turns are keyed separately (req-N vs res-N)", () => {
// Keys used: `req-${i}` for request turns, `res-${i}` for response turns
const reqKeys = ["req-0", "req-1", "req-2"];
const resKeys = ["res-0", "res-1"];
// Verify no overlap
const allKeys = [...reqKeys, ...resKeys];
const uniqueKeys = new Set(allKeys);
assert.equal(uniqueKeys.size, allKeys.length, "all keys should be unique");
});
it("allTurns still accounts for correct total across both sections", () => {
const request = [
{ role: "user" as const, content: "Hi", contentType: "text" as const },
];
const response = [
{ role: "assistant" as const, content: "Hello!", contentType: "text" as const },
];
// Before: allTurns = [...request, ...response]
// After: both rendered in separate sections
const totalTurns = request.length + response.length;
assert.equal(totalTurns, 2);
});
it("conversationNotAvailable key resolves when body is null (normalizeConversation returns null)", () => {
// When requestBody is null and responseBody is null, normalizeConversation returns null.
// The ConversationTab renders t("conversationNotAvailable") in that case.
const req = makeRequest({ requestBody: null, responseBody: null });
const result = normalizeConversation(req);
// Must return null so the component falls through to the conversationNotAvailable branch.
assert.equal(result, null, "normalizeConversation must return null for non-LLM / null body");
});
});