-
Notifications
You must be signed in to change notification settings - Fork 0
test(api): agents(wikiCompose / research ノード・core tools)の未テストノードにテストを追加する (#1033) #1042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| /** | ||
| * `resolveWebSearchModelId` unit tests (#1033). | ||
| * Priority: fixed Wiki Compose model → env override → cheapest OpenAI/Google. | ||
| */ | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { WIKI_COMPOSE_MODEL_ID } from "../../../../agents/core/llm/wikiComposeModelId.js"; | ||
| import { resolveWebSearchModelId } from "../../../../agents/core/tools/resolveWebSearchModel.js"; | ||
| import { createMockDb } from "../../../createMockDb.js"; | ||
|
|
||
| const ENV_KEY = "WIKI_COMPOSE_WEB_SEARCH_MODEL_ID"; | ||
|
|
||
| beforeEach(() => { | ||
| vi.unstubAllEnvs(); | ||
| }); | ||
| afterEach(() => { | ||
| vi.unstubAllEnvs(); | ||
| }); | ||
|
|
||
| describe("resolveWebSearchModelId", () => { | ||
| it("returns the fixed Wiki Compose model when active and tier-accessible", async () => { | ||
| const { db } = createMockDb([[{ id: WIKI_COMPOSE_MODEL_ID }]]); | ||
| const id = await resolveWebSearchModelId(db as never, "free"); | ||
| expect(id).toBe(WIKI_COMPOSE_MODEL_ID); | ||
| }); | ||
|
|
||
| it("validates env override against active + tier before returning it", async () => { | ||
| vi.stubEnv(ENV_KEY, "openai:gpt-4o-mini"); | ||
| const { db } = createMockDb([[], [{ id: "openai:gpt-4o-mini" }]]); | ||
| const id = await resolveWebSearchModelId(db as never, "free"); | ||
| expect(id).toBe("openai:gpt-4o-mini"); | ||
| }); | ||
|
|
||
| it("falls through when env override is inactive and picks cheapest OpenAI among ties", async () => { | ||
| vi.stubEnv(ENV_KEY, "openai:inactive-model"); | ||
| const { db } = createMockDb([ | ||
| [], | ||
| [], | ||
| [ | ||
| { | ||
| id: "google:cheap", | ||
| provider: "google", | ||
| inputCostUnits: 1, | ||
| outputCostUnits: 1, | ||
| }, | ||
| { | ||
| id: "openai:cheap", | ||
| provider: "openai", | ||
| inputCostUnits: 1, | ||
| outputCostUnits: 1, | ||
| }, | ||
| ], | ||
| ]); | ||
| const id = await resolveWebSearchModelId(db as never, "pro"); | ||
| expect(id).toBe("openai:cheap"); | ||
| }); | ||
|
|
||
| it("returns null when no active OpenAI/Google models exist", async () => { | ||
| const { db } = createMockDb([[], []]); | ||
| const id = await resolveWebSearchModelId(db as never, "free"); | ||
| expect(id).toBeNull(); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,180 @@ | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * `briefDialogue` unit tests (#1033). | ||||||||||||||||||||||||||||
| * - Loads page snapshot once and projects briefQuestions into state. | ||||||||||||||||||||||||||||
| * - LLM failure degrades to empty questions with `briefDegraded=true`. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Japanese text to the test header comment to satisfy repo documentation policy. Lines 1-5 are English-only; this violates the bilingual comment/documentation requirement for Suggested patch /**
- * `briefDialogue` unit tests (`#1033`).
- * - Loads page snapshot once and projects briefQuestions into state.
- * - LLM failure degrades to empty questions with `briefDegraded=true`.
+ * `briefDialogue` unit tests (`#1033`).
+ * `briefDialogue` のユニットテスト (`#1033`)。
+ * - Loads page snapshot once and projects briefQuestions into state.
+ * - pageSnapshot を 1 回だけ読み込み、briefQuestions を state に反映する。
+ * - LLM failure degrades to empty questions with `briefDegraded=true`.
+ * - LLM 失敗時は空の質問へフォールバックし、`briefDegraded=true` を設定する。
*/As per coding guidelines: 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const { createZediChatModel, loadPageSnapshot } = vi.hoisted(() => ({ | ||||||||||||||||||||||||||||
| createZediChatModel: vi.fn(), | ||||||||||||||||||||||||||||
| loadPageSnapshot: vi.fn(), | ||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| vi.mock("../../../../../agents/core/llm/wikiComposeModelId.js", () => ({ | ||||||||||||||||||||||||||||
| resolveWikiComposeModelId: vi.fn(async () => "google:gemini-3.5-flash"), | ||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| vi.mock("../../../../../agents/core/llm/modelFactory.js", async () => { | ||||||||||||||||||||||||||||
| const actual = await vi.importActual< | ||||||||||||||||||||||||||||
| typeof import("../../../../../agents/core/llm/modelFactory.js") | ||||||||||||||||||||||||||||
| >("../../../../../agents/core/llm/modelFactory.js"); | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| ...actual, | ||||||||||||||||||||||||||||
| createZediChatModel: (...args: unknown[]) => | ||||||||||||||||||||||||||||
| createZediChatModel(...(args as Parameters<typeof actual.createZediChatModel>)), | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| vi.mock("../../../../../agents/graphs/wikiCompose/nodes/shared/loadPageSnapshot.js", () => ({ | ||||||||||||||||||||||||||||
| loadPageSnapshot: (...args: unknown[]) => | ||||||||||||||||||||||||||||
| loadPageSnapshot( | ||||||||||||||||||||||||||||
| ...(args as Parameters< | ||||||||||||||||||||||||||||
| typeof import("../../../../../agents/graphs/wikiCompose/nodes/shared/loadPageSnapshot.js").loadPageSnapshot | ||||||||||||||||||||||||||||
| >), | ||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| vi.mock("../../../../../agents/graphs/wikiCompose/nodes/shared/dispatch.js", () => ({ | ||||||||||||||||||||||||||||
| dispatchComposePhase: vi.fn(async () => undefined), | ||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { briefDialogue } from "../../../../../agents/graphs/wikiCompose/nodes/briefDialogue.js"; | ||||||||||||||||||||||||||||
| import { GRAPH_CONTEXT_CONFIG_KEY } from "../../../../../agents/core/types/graphContext.js"; | ||||||||||||||||||||||||||||
| import type { GraphContext } from "../../../../../agents/core/types/graphContext.js"; | ||||||||||||||||||||||||||||
| import type { Database } from "../../../../../types/index.js"; | ||||||||||||||||||||||||||||
| import type { WikiComposeStateType } from "../../../../../agents/graphs/wikiCompose/state.js"; | ||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||
| BriefQuestion, | ||||||||||||||||||||||||||||
| PageSnapshot, | ||||||||||||||||||||||||||||
| } from "../../../../../agents/graphs/wikiCompose/types.js"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function fakeContext(): GraphContext { | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| threadId: "t", | ||||||||||||||||||||||||||||
| sessionId: "t", | ||||||||||||||||||||||||||||
| userId: "u-1", | ||||||||||||||||||||||||||||
| pageId: "p-1", | ||||||||||||||||||||||||||||
| graphId: "wiki-compose", | ||||||||||||||||||||||||||||
| backend: "zedi_managed", | ||||||||||||||||||||||||||||
| tier: "free", | ||||||||||||||||||||||||||||
| db: {} as Database, | ||||||||||||||||||||||||||||
| feature: "wiki_compose:test", | ||||||||||||||||||||||||||||
| userEmail: null, | ||||||||||||||||||||||||||||
| contentLocale: "ja", | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function state(overrides: Partial<WikiComposeStateType>): WikiComposeStateType { | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| messages: [], | ||||||||||||||||||||||||||||
| phase: "brief", | ||||||||||||||||||||||||||||
| pageId: "p-1", | ||||||||||||||||||||||||||||
| userId: "u-1", | ||||||||||||||||||||||||||||
| iteration: 0, | ||||||||||||||||||||||||||||
| maxIterations: 3, | ||||||||||||||||||||||||||||
| queries: [], | ||||||||||||||||||||||||||||
| pendingSources: [], | ||||||||||||||||||||||||||||
| lastEvaluation: null, | ||||||||||||||||||||||||||||
| exitReason: null, | ||||||||||||||||||||||||||||
| batches: [], | ||||||||||||||||||||||||||||
| approvedResearch: [], | ||||||||||||||||||||||||||||
| rejectedResearch: [], | ||||||||||||||||||||||||||||
| additionalRequest: null, | ||||||||||||||||||||||||||||
| researchConflicts: [], | ||||||||||||||||||||||||||||
| briefQuestions: [], | ||||||||||||||||||||||||||||
| briefDegraded: false, | ||||||||||||||||||||||||||||
| brief: null, | ||||||||||||||||||||||||||||
| outlineProposal: [], | ||||||||||||||||||||||||||||
| approvedOutline: null, | ||||||||||||||||||||||||||||
| draftedSections: [], | ||||||||||||||||||||||||||||
| completion: null, | ||||||||||||||||||||||||||||
| chatSeed: null, | ||||||||||||||||||||||||||||
| pageSnapshot: null, | ||||||||||||||||||||||||||||
| ...overrides, | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function fakeStructuredModel(returnValue: unknown) { | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| withStructuredOutput: vi.fn(() => ({ | ||||||||||||||||||||||||||||
| invoke: vi.fn(async () => returnValue), | ||||||||||||||||||||||||||||
| })), | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||
| createZediChatModel.mockReset(); | ||||||||||||||||||||||||||||
| loadPageSnapshot.mockReset(); | ||||||||||||||||||||||||||||
| loadPageSnapshot.mockResolvedValue({ | ||||||||||||||||||||||||||||
| pageId: "p-1", | ||||||||||||||||||||||||||||
| title: "Loaded Title", | ||||||||||||||||||||||||||||
| body: "Existing body", | ||||||||||||||||||||||||||||
| hasContent: true, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| afterEach(() => { | ||||||||||||||||||||||||||||
| createZediChatModel.mockReset(); | ||||||||||||||||||||||||||||
| loadPageSnapshot.mockReset(); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| describe("briefDialogue", () => { | ||||||||||||||||||||||||||||
| const config = { configurable: { [GRAPH_CONTEXT_CONFIG_KEY]: fakeContext() } }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| it("loads page snapshot when absent and projects structured brief questions", async () => { | ||||||||||||||||||||||||||||
| createZediChatModel.mockResolvedValue( | ||||||||||||||||||||||||||||
| fakeStructuredModel({ | ||||||||||||||||||||||||||||
| questions: [ | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| question: "What audience?", | ||||||||||||||||||||||||||||
| rationale: "Scope matters", | ||||||||||||||||||||||||||||
| options: [{ label: "Developers", hint: "Technical readers" }], | ||||||||||||||||||||||||||||
| required: true, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const update = await briefDialogue(state({ pageSnapshot: null }), config as never); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expect(loadPageSnapshot).toHaveBeenCalledTimes(1); | ||||||||||||||||||||||||||||
| expect((update.pageSnapshot as PageSnapshot).title).toBe("Loaded Title"); | ||||||||||||||||||||||||||||
| const questions = (update.briefQuestions ?? []) as BriefQuestion[]; | ||||||||||||||||||||||||||||
| expect(questions).toHaveLength(1); | ||||||||||||||||||||||||||||
| expect(questions[0]?.question).toBe("What audience?"); | ||||||||||||||||||||||||||||
| expect(questions[0]?.options).toHaveLength(1); | ||||||||||||||||||||||||||||
| expect(questions[0]?.required).toBe(true); | ||||||||||||||||||||||||||||
| expect(update.briefDegraded).toBe(false); | ||||||||||||||||||||||||||||
| expect(update.phase).toBe("brief:await_user"); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| it("reuses pageSnapshot from state without hitting the DB", async () => { | ||||||||||||||||||||||||||||
| createZediChatModel.mockResolvedValue(fakeStructuredModel({ questions: [] })); | ||||||||||||||||||||||||||||
| const snapshot = { | ||||||||||||||||||||||||||||
| pageId: "p-1", | ||||||||||||||||||||||||||||
| title: "Cached", | ||||||||||||||||||||||||||||
| body: "", | ||||||||||||||||||||||||||||
| hasContent: false, | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const update = await briefDialogue(state({ pageSnapshot: snapshot }), config as never); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expect(loadPageSnapshot).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||
| expect(update.pageSnapshot).toEqual(snapshot); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| it("sets briefDegraded when the LLM call fails", async () => { | ||||||||||||||||||||||||||||
| createZediChatModel.mockResolvedValue({ | ||||||||||||||||||||||||||||
| withStructuredOutput: vi.fn(() => ({ | ||||||||||||||||||||||||||||
| invoke: vi.fn(async () => { | ||||||||||||||||||||||||||||
| throw new Error("provider timeout"); | ||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||
| })), | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const update = await briefDialogue(state({ pageSnapshot: null }), config as never); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expect(update.briefQuestions).toEqual([]); | ||||||||||||||||||||||||||||
| expect(update.briefDegraded).toBe(true); | ||||||||||||||||||||||||||||
| expect(update.phase).toBe("brief:await_user"); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consolidated: all three new test files have English-only header docs.
Root cause is a single policy mismatch: file-level comments were added in English only, while repo rules require bilingual Japanese+English comments/documentation for
.tsfiles.As per coding guidelines, "
**/*.{ts,tsx,js,jsx,md}: Comments and documentation must include both Japanese and English text".🤖 Prompt for AI Agents
Source: Coding guidelines