import request from "supertest"; import { afterEach, describe, expect, it, vi } from "vitest"; const FLAG_KEYS = [ "FEATURE_ASSISTANT_INVESTIGATION_STATE_V1", "FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1", "FEATURE_ASSISTANT_CONTRACTS_V11" ] as const; const ORIGINAL_FLAGS: Record = Object.fromEntries( FLAG_KEYS.map((key) => [key, process.env[key]]) ); function restoreFlags(): void { for (const key of FLAG_KEYS) { const original = ORIGINAL_FLAGS[key]; if (original === undefined) { delete process.env[key]; } else { process.env[key] = original; } } } async function createAppWithFlags(flags: { state: "0" | "1"; binding: "0" | "1"; contracts?: "0" | "1"; }) { process.env.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = flags.state; process.env.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = flags.binding; process.env.FEATURE_ASSISTANT_CONTRACTS_V11 = flags.contracts ?? "1"; vi.resetModules(); const { createApp } = await import("../src/server"); return createApp(); } describe.sequential("assistant follow-up state binding", () => { afterEach(() => { restoreFlags(); vi.resetModules(); }); it("applies investigation_state binding in follow-up flow when flags are ON", async () => { const app = await createAppWithFlags({ state: "1", binding: "1" }); const sessionId = `asst-wave2-on-${Date.now()}`; const first = await request(app).post("/api/assistant/message").send({ session_id: sessionId, useMock: true, promptVersion: "normalizer_v2_0_2", user_message: "Разложи цепочку документов по контрагентам." }); expect(first.status).toBe(200); expect(first.body.debug?.followup_state_usage).toBeUndefined(); expect(first.body.debug?.investigation_state_snapshot?.turn_index).toBe(1); const second = await request(app).post("/api/assistant/message").send({ session_id: sessionId, useMock: true, promptVersion: "normalizer_v2_0_2", user_message: "И по периоду 2020-06." }); expect(second.status).toBe(200); expect(second.body.debug?.followup_state_usage?.applied).toBe(true); expect(second.body.debug?.followup_state_usage?.context_patch?.business_context_from_state).toBe(true); expect(second.body.debug?.followup_state_usage?.state_turn_index).toBe(1); expect( (second.body.debug?.routes ?? []).some((item: { route?: string }) => item.route && item.route !== "no_route") ).toBe(true); expect(second.body.debug?.investigation_state_snapshot?.turn_index).toBe(2); }); it("does not apply follow-up binding when binding flag is OFF", async () => { const app = await createAppWithFlags({ state: "1", binding: "0" }); const sessionId = `asst-wave2-off-${Date.now()}`; const first = await request(app).post("/api/assistant/message").send({ session_id: sessionId, useMock: true, promptVersion: "normalizer_v2_0_2", user_message: "Разложи цепочку документов по контрагентам." }); expect(first.status).toBe(200); const second = await request(app).post("/api/assistant/message").send({ session_id: sessionId, useMock: true, promptVersion: "normalizer_v2_0_2", user_message: "И по периоду 2020-06." }); expect(second.status).toBe(200); expect(second.body.debug?.followup_state_usage).toBeUndefined(); expect((second.body.debug?.routes ?? []).every((item: { route?: string }) => item.route === "no_route")).toBe(true); }); it("keeps legacy-like behavior when investigation state flag is OFF", async () => { const app = await createAppWithFlags({ state: "0", binding: "1" }); const response = await request(app).post("/api/assistant/message").send({ useMock: true, promptVersion: "normalizer_v2_0_2", user_message: "И по периоду 2020-06." }); expect(response.status).toBe(200); expect(response.body.debug?.investigation_state_snapshot).toBeNull(); expect(response.body.debug?.followup_state_usage).toBeUndefined(); }); });