test(frontend/builder): restore seed-message tests + guard empty messages array

- Re-add describe block for seed message sending (removed in 8b8eb80480):
  - verifies sendMessage is called with buildSeedPrompt when isGraphLoaded=true
  - verifies sendMessage is NOT called when isGraphLoaded=false (default)
  - verifies the hasSentSeedMessageRef guard fires only once per session
- Add test for empty messages guard in prepareSendMessagesRequest
- Guard messages.at(-1) in prepareSendMessagesRequest with an early throw
  so a runtime TypeError cannot occur if the AI SDK contract is violated
This commit is contained in:
majdyz
2026-04-09 22:15:53 +07:00
parent 0b06e948b9
commit 969d9cfc41
2 changed files with 82 additions and 1 deletions

View File

@@ -252,6 +252,58 @@ describe("useBuilderChatPanel no auto-send on open", () => {
});
});
describe("useBuilderChatPanel seed message", () => {
it("sends seed message via sendMessage when session is available and isGraphLoaded=true", async () => {
mockPostV2CreateSession.mockResolvedValue({
status: 200,
data: { id: "sess-seed" },
});
mockNodes.push({ id: "n1", data: { title: "Search", description: "" } });
const { result } = renderHook(() =>
useBuilderChatPanel({ isGraphLoaded: true }),
);
await openAndFlush(() => result.current.handleToggle());
expect(mockSendMessage).toHaveBeenCalledOnce();
const callArg = mockSendMessage.mock.calls[0][0] as { text: string };
expect(typeof callArg.text).toBe("string");
expect(callArg.text).toContain("I'm building an agent");
});
it("does NOT send seed message when isGraphLoaded is false (default)", async () => {
mockPostV2CreateSession.mockResolvedValue({
status: 200,
data: { id: "sess-no-seed" },
});
const { result } = renderHook(() => useBuilderChatPanel());
await openAndFlush(() => result.current.handleToggle());
expect(mockSendMessage).not.toHaveBeenCalled();
});
it("sends seed message only once even when sessionId and isGraphLoaded deps re-run (hasSentSeedMessageRef guard)", async () => {
mockPostV2CreateSession.mockResolvedValue({
status: 200,
data: { id: "sess-once" },
});
const { result, rerender } = renderHook(() =>
useBuilderChatPanel({ isGraphLoaded: true }),
);
await openAndFlush(() => result.current.handleToggle());
expect(mockSendMessage).toHaveBeenCalledOnce();
rerender();
expect(mockSendMessage).toHaveBeenCalledOnce();
});
});
describe("useBuilderChatPanel flowID reset", () => {
it("resets appliedActionKeys when flowID changes", () => {
mockNodes.push({ id: "n1", data: { hardcodedValues: {} } });
@@ -1208,6 +1260,31 @@ describe("useBuilderChatPanel transport prepareSendMessagesRequest", () => {
ctorArg.prepareSendMessagesRequest({ messages }),
).rejects.toThrow("Authentication failed");
});
it("throws when messages array is empty (empty messages guard)", async () => {
const { DefaultChatTransport } = await import("ai");
const MockTransport = DefaultChatTransport as ReturnType<typeof vi.fn>;
mockPostV2CreateSession.mockResolvedValue({
status: 200,
data: { id: "sess-empty-msg" },
});
const { result } = renderHook(() => useBuilderChatPanel());
await openAndFlush(() => result.current.handleToggle());
const ctorArg = MockTransport.mock.calls[
MockTransport.mock.calls.length - 1
][0] as {
prepareSendMessagesRequest: (args: {
messages: unknown[];
}) => Promise<unknown>;
};
await expect(
ctorArg.prepareSendMessagesRequest({ messages: [] }),
).rejects.toThrow("No message to send");
});
});
describe("useBuilderChatPanel handleKeyDown empty input guard", () => {

View File

@@ -207,7 +207,11 @@ export function useBuilderChatPanel({
? new DefaultChatTransport({
api: `${environment.getAGPTServerBaseUrl()}/api/chat/sessions/${sessionId}/stream`,
prepareSendMessagesRequest: async ({ messages }) => {
const last = messages[messages.length - 1];
const last = messages.at(-1);
if (!last)
throw new Error(
"No message to send — messages array is empty.",
);
const { token, error } = await getWebSocketToken();
if (error || !token)
throw new Error(