feat(FN-1445): add project-scoping and fallback handling for AI routes and missions
- Add projectId parameter to refineText API and TaskForm for proper scoping - Update mission routes to require projectId and add regression tests - Add mission interview fallback to mission-level context when target not found - Add AI refine route scoping tests for project isolation - Update component tests to expect projectId argument - Update prompt-keys test counts for new system prompts
This commit is contained in:
@@ -1892,6 +1892,21 @@ describe("refineText", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("passes projectId as query param for scoped settings resolution", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(
|
||||
mockFetchResponse(true, { refined: "Refined with scoped settings" })
|
||||
);
|
||||
|
||||
const result = await refineText("Original text", "clarify", "proj-123");
|
||||
|
||||
expect(result).toBe("Refined with scoped settings");
|
||||
expect(globalThis.fetch).toHaveBeenCalledWith("/api/ai/refine-text?projectId=proj-123", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({ text: "Original text", type: "clarify" }),
|
||||
});
|
||||
});
|
||||
|
||||
it("works with all four refinement types", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(
|
||||
mockFetchResponse(true, { refined: "Refined" })
|
||||
|
||||
@@ -1829,11 +1829,12 @@ export interface RefineTextResponse {
|
||||
* Refine task description text using AI.
|
||||
* @param text - The text to refine (1-2000 characters)
|
||||
* @param type - The refinement type: clarify, add-details, expand, or simplify
|
||||
* @param projectId - Optional project ID for scoped settings resolution
|
||||
* @returns The refined text
|
||||
* @throws Error with message for rate limit (429), invalid type (422), validation (400), or server errors
|
||||
*/
|
||||
export async function refineText(text: string, type: RefinementType): Promise<string> {
|
||||
const response = await api<RefineTextResponse>("/ai/refine-text", {
|
||||
export async function refineText(text: string, type: RefinementType, projectId?: string): Promise<string> {
|
||||
const response = await api<RefineTextResponse>(withProjectId("/ai/refine-text", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ text, type }),
|
||||
});
|
||||
|
||||
@@ -919,7 +919,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
setIsRefineMenuOpen(false);
|
||||
setIsRefining(true);
|
||||
try {
|
||||
const refined = await refineText(trimmed, type);
|
||||
const refined = await refineText(trimmed, type, projectId);
|
||||
setDescription(refined);
|
||||
addToast("Description refined with AI", "success");
|
||||
// Auto-resize textarea after content update
|
||||
@@ -933,7 +933,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
} finally {
|
||||
setIsRefining(false);
|
||||
}
|
||||
}, [description, isRefining, addToast]);
|
||||
}, [description, isRefining, addToast, projectId]);
|
||||
|
||||
const truncate = (s: string, len: number) =>
|
||||
s.length > len ? s.slice(0, len) + "…" : s;
|
||||
|
||||
@@ -418,7 +418,7 @@ export function TaskForm({
|
||||
|
||||
setIsRefining(true);
|
||||
try {
|
||||
const refined = await refineText(trimmed, type);
|
||||
const refined = await refineText(trimmed, type, projectId);
|
||||
onDescriptionChange(refined);
|
||||
setIsRefineMenuOpen(false);
|
||||
addToast("Description refined with AI", "success");
|
||||
@@ -432,7 +432,7 @@ export function TaskForm({
|
||||
} finally {
|
||||
setIsRefining(false);
|
||||
}
|
||||
}, [description, isRefining, addToast, onDescriptionChange]);
|
||||
}, [description, isRefining, addToast, onDescriptionChange, projectId]);
|
||||
|
||||
const handleToggleFavorite = useCallback(async (provider: string) => {
|
||||
const currentFavorites = favoriteProviders;
|
||||
|
||||
@@ -1665,7 +1665,7 @@ describe("QuickEntryBox", () => {
|
||||
fireEvent.click(screen.getByTestId("refine-clarify"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(refineText).toHaveBeenCalledWith("Original text", "clarify");
|
||||
expect(refineText).toHaveBeenCalledWith("Original text", "clarify", TEST_PROJECT_ID);
|
||||
});
|
||||
|
||||
// Textarea should be updated
|
||||
|
||||
@@ -501,7 +501,8 @@ describe("TaskForm", () => {
|
||||
fireEvent.click(screen.getByTestId("refine-clarify"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(refineText).toHaveBeenCalledWith("Some text to refine", "clarify");
|
||||
// projectId is undefined in this test context
|
||||
expect(refineText).toHaveBeenCalledWith("Some text to refine", "clarify", undefined);
|
||||
expect(onDescriptionChange).toHaveBeenCalledWith("Refined text");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user