mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
test(coach): pin the answered-clarification line on the local path
The line that resolves an answered clarifying question was already appended to the system prompt on both paths; nothing proved it for the no-tools (local model) path. Three route-level cases now do: the local prompt and the tool-mode prompt both carry the server-resolved line, and a turn without an answer carries neither. Removing the line from the local prompt fails the first case. The golden harness gains a coachMessage.findMany mock for this; it defaults to no rows, so no existing transcript changes and the golden snapshots are untouched. The result-table caption test used the regex dotAll flag, which the TypeScript target rejects (TS1501); it matches across lines with [\s\S] instead.
This commit is contained in:
1 parent
b40de3a4d9
commit
db10ba714e
3 files changed
+108
-1
No files matched your search
@@ -114,6 +114,7 @@ export const m = {
|
||||
userFindUnique: vi.fn(),
|
||||
userUpdate: vi.fn(),
|
||||
conversationFindFirst: vi.fn(),
|
||||
coachMessageFindMany: vi.fn(),
|
||||
checkRateLimit: vi.fn(),
|
||||
resolveServerLocale: vi.fn(),
|
||||
runStreamingRawCompletionWithFallback: vi.fn(),
|
||||
@@ -192,6 +193,7 @@ export function resetGolden(): void {
|
||||
return {};
|
||||
});
|
||||
m.conversationFindFirst.mockResolvedValue({ id: "c-existing" });
|
||||
m.coachMessageFindMany.mockResolvedValue([]);
|
||||
m.checkRateLimit.mockResolvedValue({ allowed: true, resetAt: 0 });
|
||||
m.resolveServerLocale.mockResolvedValue("en");
|
||||
m.resolveProviderChain.mockResolvedValue([
|
||||
|
||||
@@ -62,6 +62,7 @@ vi.mock("@/lib/db", () => ({
|
||||
prisma: {
|
||||
user: { findUnique: h.m.userFindUnique, update: h.m.userUpdate },
|
||||
coachConversation: { findFirst: h.m.conversationFindFirst },
|
||||
coachMessage: { findMany: h.m.coachMessageFindMany },
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/rate-limit", () => ({ checkRateLimit: h.m.checkRateLimit }));
|
||||
@@ -574,6 +575,110 @@ describe("coach chat golden transcripts", () => {
|
||||
expect(types.filter((t) => t === "step")).toHaveLength(6);
|
||||
expect(JSON.stringify(transcript)).not.toContain("Ferritin");
|
||||
});
|
||||
// 23 ── an answered clarification ──────────────────────────────────────
|
||||
describe("an answered clarification reaches the prompt on both paths", () => {
|
||||
const QUESTION_ID = "msg-question";
|
||||
const CLARIFIED =
|
||||
"CLARIFIED: the person answered your clarifying question by choosing metric=pulse window=last30days.";
|
||||
|
||||
beforeEach(() => {
|
||||
m.fetchConversationWithMessages.mockResolvedValue({
|
||||
id: "c-existing",
|
||||
summary: null,
|
||||
attachmentCount: 0,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "How is my pulse?",
|
||||
providerType: null,
|
||||
metricSource: null,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Which pulse do you mean?",
|
||||
providerType: "anthropic",
|
||||
metricSource: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
m.coachMessageFindMany.mockResolvedValue([
|
||||
{
|
||||
id: QUESTION_ID,
|
||||
role: "assistant",
|
||||
providerType: "anthropic",
|
||||
metricSourceJson: JSON.stringify({
|
||||
windows: [],
|
||||
metrics: [],
|
||||
clarification: {
|
||||
kind: "metric",
|
||||
choices: [
|
||||
{
|
||||
id: "c1",
|
||||
labelKey: "coach.step.domain.snapshot",
|
||||
label: "Resting pulse",
|
||||
value: { metric: "pulse", window: "last30days" },
|
||||
},
|
||||
{
|
||||
id: "c2",
|
||||
labelKey: "coach.step.domain.snapshot",
|
||||
label: "Heart rate variability",
|
||||
value: { metric: "hrv" },
|
||||
},
|
||||
],
|
||||
freeText: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const body = {
|
||||
conversationId: "c-existing",
|
||||
message: "Resting pulse",
|
||||
clarification: { messageId: QUESTION_ID, choiceId: "c1" },
|
||||
};
|
||||
|
||||
it("the no-tools (local) prompt carries the resolved line", async () => {
|
||||
m.resolveProviderChain.mockResolvedValue(NO_TOOLS_CHAIN);
|
||||
m.runStreamingRawCompletionWithFallback.mockImplementation(
|
||||
streamingResult("Your resting pulse averaged 62 bpm."),
|
||||
);
|
||||
const transcript = await runTurn(post, body, {});
|
||||
expect(transcript.thrown).toBeNull();
|
||||
expect(m.runStreamingRawCompletionWithFallback).toHaveBeenCalledTimes(1);
|
||||
const { params } = m.runStreamingRawCompletionWithFallback.mock
|
||||
.calls[0][0] as { params: { system: string } };
|
||||
expect(params.system).toContain(CLARIFIED);
|
||||
// Server-authored: the request's choice id picks the stored value,
|
||||
// the label the model once wrote never rides along.
|
||||
expect(params.system).not.toContain("Resting pulse");
|
||||
});
|
||||
|
||||
it("the tool-mode prompt carries the same line", async () => {
|
||||
reply("Your resting pulse averaged 62 bpm.");
|
||||
const transcript = await runTurn(post, body, {});
|
||||
expect(transcript.thrown).toBeNull();
|
||||
const { system } = m.runCoachToolLoop.mock.calls[0][0] as {
|
||||
system: string;
|
||||
};
|
||||
expect(system).toContain(CLARIFIED);
|
||||
});
|
||||
|
||||
it("an unanswered turn sends neither prompt the line", async () => {
|
||||
m.resolveProviderChain.mockResolvedValue(NO_TOOLS_CHAIN);
|
||||
m.runStreamingRawCompletionWithFallback.mockImplementation(
|
||||
streamingResult("Your resting pulse averaged 62 bpm."),
|
||||
);
|
||||
await runTurn(
|
||||
post,
|
||||
{ conversationId: "c-existing", message: "Resting pulse" },
|
||||
{},
|
||||
);
|
||||
const { params } = m.runStreamingRawCompletionWithFallback.mock
|
||||
.calls[0][0] as { params: { system: string } };
|
||||
expect(params.system).not.toContain("CLARIFIED:");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("coach chat golden transcripts — refusals before the stream", () => {
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("CoachResultTable", () => {
|
||||
<CoachResultTable result={table(3)} method="30 readings, daily means" />,
|
||||
);
|
||||
expect(html).toMatch(
|
||||
/<caption[^>]*>.*Blood pressure by day.*30 readings, daily means.*<\/caption>/s,
|
||||
/<caption[^>]*>[\s\S]*Blood pressure by day[\s\S]*30 readings, daily means[\s\S]*<\/caption>/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user