From 976d9d1ab916e5f50607af534e986e86e477a907 Mon Sep 17 00:00:00 2001 From: Bharath A V <157507162+AVBharath10@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:51:08 +0530 Subject: [PATCH] Refactor(mocks): modularize MSW handlers into domain-specific files (#11974) Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com> --- frontend/src/mocks/analytics-handlers.ts | 7 + frontend/src/mocks/auth-handlers.ts | 23 ++ frontend/src/mocks/conversation-handlers.ts | 118 +++++++ frontend/src/mocks/feedback-handlers.ts | 15 + frontend/src/mocks/handlers.ts | 341 +------------------- frontend/src/mocks/settings-handlers.ts | 155 +++++++++ 6 files changed, 334 insertions(+), 325 deletions(-) create mode 100644 frontend/src/mocks/analytics-handlers.ts create mode 100644 frontend/src/mocks/auth-handlers.ts create mode 100644 frontend/src/mocks/conversation-handlers.ts create mode 100644 frontend/src/mocks/feedback-handlers.ts create mode 100644 frontend/src/mocks/settings-handlers.ts diff --git a/frontend/src/mocks/analytics-handlers.ts b/frontend/src/mocks/analytics-handlers.ts new file mode 100644 index 0000000000..09b3ac0c60 --- /dev/null +++ b/frontend/src/mocks/analytics-handlers.ts @@ -0,0 +1,7 @@ +import { http, HttpResponse } from "msw"; + +export const ANALYTICS_HANDLERS = [ + http.post("https://us.i.posthog.com/e", async () => + HttpResponse.json(null, { status: 200 }), + ), +]; diff --git a/frontend/src/mocks/auth-handlers.ts b/frontend/src/mocks/auth-handlers.ts new file mode 100644 index 0000000000..bb4baf2397 --- /dev/null +++ b/frontend/src/mocks/auth-handlers.ts @@ -0,0 +1,23 @@ +import { http, HttpResponse } from "msw"; +import { GitUser } from "#/types/git"; + +export const AUTH_HANDLERS = [ + http.get("/api/user/info", () => { + const user: GitUser = { + id: "1", + login: "octocat", + avatar_url: "https://avatars.githubusercontent.com/u/583231?v=4", + company: "GitHub", + email: "placeholder@placeholder.placeholder", + name: "monalisa octocat", + }; + + return HttpResponse.json(user); + }), + + http.post("/api/authenticate", async () => + HttpResponse.json({ message: "Authenticated" }), + ), + + http.post("/api/logout", () => HttpResponse.json(null, { status: 200 })), +]; diff --git a/frontend/src/mocks/conversation-handlers.ts b/frontend/src/mocks/conversation-handlers.ts new file mode 100644 index 0000000000..1ec536fd92 --- /dev/null +++ b/frontend/src/mocks/conversation-handlers.ts @@ -0,0 +1,118 @@ +import { http, delay, HttpResponse } from "msw"; +import { Conversation, ResultSet } from "#/api/open-hands.types"; + +const conversations: Conversation[] = [ + { + conversation_id: "1", + title: "My New Project", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: new Date().toISOString(), + created_at: new Date().toISOString(), + status: "RUNNING", + runtime_status: "STATUS$READY", + url: null, + session_api_key: null, + }, + { + conversation_id: "2", + title: "Repo Testing", + selected_repository: "octocat/hello-world", + git_provider: "github", + selected_branch: null, + last_updated_at: new Date( + Date.now() - 2 * 24 * 60 * 60 * 1000, + ).toISOString(), + created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + status: "STOPPED", + runtime_status: null, + url: null, + session_api_key: null, + }, + { + conversation_id: "3", + title: "Another Project", + selected_repository: "octocat/earth", + git_provider: null, + selected_branch: "main", + last_updated_at: new Date( + Date.now() - 5 * 24 * 60 * 60 * 1000, + ).toISOString(), + created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), + status: "STOPPED", + runtime_status: null, + url: null, + session_api_key: null, + }, +]; + +const CONVERSATIONS = new Map( + conversations.map((c) => [c.conversation_id, c]), +); + +export const CONVERSATION_HANDLERS = [ + http.get("/api/conversations", async () => { + const values = Array.from(CONVERSATIONS.values()); + const results: ResultSet = { + results: values, + next_page_id: null, + }; + return HttpResponse.json(results); + }), + + http.get("/api/conversations/:conversationId", async ({ params }) => { + const conversationId = params.conversationId as string; + const project = CONVERSATIONS.get(conversationId); + if (project) return HttpResponse.json(project); + return HttpResponse.json(null, { status: 404 }); + }), + + http.post("/api/conversations", async () => { + await delay(); + const conversation: Conversation = { + conversation_id: (Math.random() * 100).toString(), + title: "New Conversation", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: new Date().toISOString(), + created_at: new Date().toISOString(), + status: "RUNNING", + runtime_status: "STATUS$READY", + url: null, + session_api_key: null, + }; + CONVERSATIONS.set(conversation.conversation_id, conversation); + return HttpResponse.json(conversation, { status: 201 }); + }), + + http.patch( + "/api/conversations/:conversationId", + async ({ params, request }) => { + const conversationId = params.conversationId as string; + const conversation = CONVERSATIONS.get(conversationId); + + if (conversation) { + const body = await request.json(); + if (typeof body === "object" && body?.title) { + CONVERSATIONS.set(conversationId, { + ...conversation, + title: body.title, + }); + return HttpResponse.json(null, { status: 200 }); + } + } + return HttpResponse.json(null, { status: 404 }); + }, + ), + + http.delete("/api/conversations/:conversationId", async ({ params }) => { + const conversationId = params.conversationId as string; + if (CONVERSATIONS.has(conversationId)) { + CONVERSATIONS.delete(conversationId); + return HttpResponse.json(null, { status: 200 }); + } + return HttpResponse.json(null, { status: 404 }); + }), +]; diff --git a/frontend/src/mocks/feedback-handlers.ts b/frontend/src/mocks/feedback-handlers.ts new file mode 100644 index 0000000000..8e4e602b33 --- /dev/null +++ b/frontend/src/mocks/feedback-handlers.ts @@ -0,0 +1,15 @@ +import { http, delay, HttpResponse } from "msw"; + +export const FEEDBACK_HANDLERS = [ + http.post("/api/submit-feedback", async () => { + await delay(1200); + return HttpResponse.json({ + statusCode: 200, + body: { message: "Success", link: "fake-url.com", password: "abc123" }, + }); + }), + + http.post("/api/submit-feedback", async () => + HttpResponse.json({ statusCode: 200 }, { status: 200 }), + ), +]; diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts index 09053bfc31..b1f2966f3a 100644 --- a/frontend/src/mocks/handlers.ts +++ b/frontend/src/mocks/handlers.ts @@ -1,146 +1,18 @@ -import { delay, http, HttpResponse } from "msw"; -import { GetConfigResponse } from "#/api/option-service/option.types"; -import { Conversation, ResultSet } from "#/api/open-hands.types"; -import { DEFAULT_SETTINGS } from "#/services/settings"; import { STRIPE_BILLING_HANDLERS } from "./billing-handlers"; -import { Provider } from "#/types/settings"; -import { - ApiSettings, - PostApiSettings, -} from "#/api/settings-service/settings.types"; import { FILE_SERVICE_HANDLERS } from "./file-service-handlers"; -import { GitUser } from "#/types/git"; import { TASK_SUGGESTIONS_HANDLERS } from "./task-suggestions-handlers"; import { SECRETS_HANDLERS } from "./secrets-handlers"; import { GIT_REPOSITORY_HANDLERS } from "./git-repository-handlers"; -export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = { - llm_model: DEFAULT_SETTINGS.LLM_MODEL, - llm_base_url: DEFAULT_SETTINGS.LLM_BASE_URL, - llm_api_key: null, - llm_api_key_set: DEFAULT_SETTINGS.LLM_API_KEY_SET, - search_api_key_set: DEFAULT_SETTINGS.SEARCH_API_KEY_SET, - agent: DEFAULT_SETTINGS.AGENT, - language: DEFAULT_SETTINGS.LANGUAGE, - confirmation_mode: DEFAULT_SETTINGS.CONFIRMATION_MODE, - security_analyzer: DEFAULT_SETTINGS.SECURITY_ANALYZER, - remote_runtime_resource_factor: - DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR, - provider_tokens_set: {}, - enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER, - condenser_max_size: DEFAULT_SETTINGS.CONDENSER_MAX_SIZE, - enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS, - enable_proactive_conversation_starters: - DEFAULT_SETTINGS.ENABLE_PROACTIVE_CONVERSATION_STARTERS, - enable_solvability_analysis: DEFAULT_SETTINGS.ENABLE_SOLVABILITY_ANALYSIS, - user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS, - max_budget_per_task: DEFAULT_SETTINGS.MAX_BUDGET_PER_TASK, -}; - -const MOCK_USER_PREFERENCES: { - settings: ApiSettings | PostApiSettings | null; -} = { - settings: null, -}; - -/** - * Set the user settings to the default settings - * - * Useful for resetting the settings in tests - */ -export const resetTestHandlersMockSettings = () => { - MOCK_USER_PREFERENCES.settings = MOCK_DEFAULT_USER_SETTINGS; -}; - -const conversations: Conversation[] = [ - { - conversation_id: "1", - title: "My New Project", - selected_repository: null, - git_provider: null, - selected_branch: null, - last_updated_at: new Date().toISOString(), - created_at: new Date().toISOString(), - status: "RUNNING", - runtime_status: "STATUS$READY", - url: null, - session_api_key: null, - }, - { - conversation_id: "2", - title: "Repo Testing", - selected_repository: "octocat/hello-world", - git_provider: "github", - selected_branch: null, - // 2 days ago - last_updated_at: new Date( - Date.now() - 2 * 24 * 60 * 60 * 1000, - ).toISOString(), - created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), - status: "STOPPED", - runtime_status: null, - url: null, - session_api_key: null, - }, - { - conversation_id: "3", - title: "Another Project", - selected_repository: "octocat/earth", - git_provider: null, - selected_branch: "main", - // 5 days ago - last_updated_at: new Date( - Date.now() - 5 * 24 * 60 * 60 * 1000, - ).toISOString(), - created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), - status: "STOPPED", - runtime_status: null, - url: null, - session_api_key: null, - }, -]; - -const CONVERSATIONS = new Map( - conversations.map((conversation) => [ - conversation.conversation_id, - conversation, - ]), -); - -const openHandsHandlers = [ - http.get("/api/options/models", async () => - HttpResponse.json([ - "gpt-3.5-turbo", - "gpt-4o", - "gpt-4o-mini", - "anthropic/claude-3.5", - "anthropic/claude-sonnet-4-20250514", - "anthropic/claude-sonnet-4-5-20250929", - "anthropic/claude-haiku-4-5-20251001", - "openhands/claude-sonnet-4-20250514", - "openhands/claude-sonnet-4-5-20250929", - "openhands/claude-haiku-4-5-20251001", - "sambanova/Meta-Llama-3.1-8B-Instruct", - ]), - ), - - http.get("/api/options/agents", async () => - HttpResponse.json(["CodeActAgent", "CoActAgent"]), - ), - - http.get("/api/options/security-analyzers", async () => - HttpResponse.json(["llm", "none"]), - ), - - http.post("http://localhost:3001/api/submit-feedback", async () => { - await delay(1200); - - return HttpResponse.json({ - statusCode: 200, - body: { message: "Success", link: "fake-url.com", password: "abc123" }, - }); - }), -]; +import { + SETTINGS_HANDLERS, + MOCK_DEFAULT_USER_SETTINGS, + resetTestHandlersMockSettings, +} from "./settings-handlers"; +import { CONVERSATION_HANDLERS } from "./conversation-handlers"; +import { AUTH_HANDLERS } from "./auth-handlers"; +import { FEEDBACK_HANDLERS } from "./feedback-handlers"; +import { ANALYTICS_HANDLERS } from "./analytics-handlers"; export const handlers = [ ...STRIPE_BILLING_HANDLERS, @@ -148,192 +20,11 @@ export const handlers = [ ...TASK_SUGGESTIONS_HANDLERS, ...SECRETS_HANDLERS, ...GIT_REPOSITORY_HANDLERS, - ...openHandsHandlers, - http.get("/api/user/info", () => { - const user: GitUser = { - id: "1", - login: "octocat", - avatar_url: "https://avatars.githubusercontent.com/u/583231?v=4", - company: "GitHub", - email: "placeholder@placeholder.placeholder", - name: "monalisa octocat", - }; - - return HttpResponse.json(user); - }), - http.post("http://localhost:3001/api/submit-feedback", async () => - HttpResponse.json({ statusCode: 200 }, { status: 200 }), - ), - http.post("https://us.i.posthog.com/e", async () => - HttpResponse.json(null, { status: 200 }), - ), - http.get("/api/options/config", () => { - const mockSaas = import.meta.env.VITE_MOCK_SAAS === "true"; - - const config: GetConfigResponse = { - APP_MODE: mockSaas ? "saas" : "oss", - GITHUB_CLIENT_ID: "fake-github-client-id", - POSTHOG_CLIENT_KEY: "fake-posthog-client-key", - FEATURE_FLAGS: { - ENABLE_BILLING: false, - HIDE_LLM_SETTINGS: mockSaas, - ENABLE_JIRA: false, - ENABLE_JIRA_DC: false, - ENABLE_LINEAR: false, - }, - // Uncomment the following to test the maintenance banner - // MAINTENANCE: { - // startTime: "2024-01-15T10:00:00-05:00", // EST timestamp - // }, - }; - - return HttpResponse.json(config); - }), - http.get("/api/settings", async () => { - await delay(); - - const { settings } = MOCK_USER_PREFERENCES; - - if (!settings) return HttpResponse.json(null, { status: 404 }); - - return HttpResponse.json(settings); - }), - http.post("/api/settings", async ({ request }) => { - await delay(); - const body = await request.json(); - - if (body) { - const current = MOCK_USER_PREFERENCES.settings || { - ...MOCK_DEFAULT_USER_SETTINGS, - }; - // Persist new values over current/mock defaults - MOCK_USER_PREFERENCES.settings = { - ...current, - ...(body as Partial), - }; - return HttpResponse.json(null, { status: 200 }); - } - - return HttpResponse.json(null, { status: 400 }); - }), - - http.post("/api/authenticate", async () => - HttpResponse.json({ message: "Authenticated" }), - ), - - http.get("/api/conversations", async () => { - const values = Array.from(CONVERSATIONS.values()); - const results: ResultSet = { - results: values, - next_page_id: null, - }; - - return HttpResponse.json(results, { status: 200 }); - }), - - http.delete("/api/conversations/:conversationId", async ({ params }) => { - const { conversationId } = params; - - if (typeof conversationId === "string") { - CONVERSATIONS.delete(conversationId); - return HttpResponse.json(null, { status: 200 }); - } - - return HttpResponse.json(null, { status: 404 }); - }), - - http.patch( - "/api/conversations/:conversationId", - async ({ params, request }) => { - const { conversationId } = params; - - if (typeof conversationId === "string") { - const conversation = CONVERSATIONS.get(conversationId); - - if (conversation) { - const body = await request.json(); - if (typeof body === "object" && body?.title) { - CONVERSATIONS.set(conversationId, { - ...conversation, - title: body.title, - }); - return HttpResponse.json(null, { status: 200 }); - } - } - } - - return HttpResponse.json(null, { status: 404 }); - }, - ), - - http.post("/api/conversations", async () => { - await delay(); - - const conversation: Conversation = { - conversation_id: (Math.random() * 100).toString(), - title: "New Conversation", - selected_repository: null, - git_provider: null, - selected_branch: null, - last_updated_at: new Date().toISOString(), - created_at: new Date().toISOString(), - status: "RUNNING", - runtime_status: "STATUS$READY", - url: null, - session_api_key: null, - }; - - CONVERSATIONS.set(conversation.conversation_id, conversation); - return HttpResponse.json(conversation, { status: 201 }); - }), - - http.get("/api/conversations/:conversationId", async ({ params }) => { - const { conversationId } = params; - - if (typeof conversationId === "string") { - const project = CONVERSATIONS.get(conversationId); - - if (project) { - return HttpResponse.json(project, { status: 200 }); - } - } - - return HttpResponse.json(null, { status: 404 }); - }), - - http.post("/api/logout", () => HttpResponse.json(null, { status: 200 })), - - http.post("/api/reset-settings", async () => { - await delay(); - MOCK_USER_PREFERENCES.settings = { ...MOCK_DEFAULT_USER_SETTINGS }; - return HttpResponse.json(null, { status: 200 }); - }), - - http.post("/api/add-git-providers", async ({ request }) => { - const body = await request.json(); - - if (typeof body === "object" && body?.provider_tokens) { - const rawTokens = body.provider_tokens as Record< - string, - { token?: string } - >; - - const providerTokensSet: Partial> = - Object.fromEntries( - Object.entries(rawTokens) - .filter(([, val]) => val && val.token) - .map(([provider]) => [provider as Provider, ""]), - ); - - const newSettings = { - ...(MOCK_USER_PREFERENCES.settings ?? MOCK_DEFAULT_USER_SETTINGS), - provider_tokens_set: providerTokensSet, - }; - MOCK_USER_PREFERENCES.settings = newSettings; - - return HttpResponse.json(true, { status: 200 }); - } - - return HttpResponse.json(null, { status: 400 }); - }), + ...SETTINGS_HANDLERS, + ...CONVERSATION_HANDLERS, + ...AUTH_HANDLERS, + ...FEEDBACK_HANDLERS, + ...ANALYTICS_HANDLERS, ]; + +export { MOCK_DEFAULT_USER_SETTINGS, resetTestHandlersMockSettings }; diff --git a/frontend/src/mocks/settings-handlers.ts b/frontend/src/mocks/settings-handlers.ts new file mode 100644 index 0000000000..df7fa032f8 --- /dev/null +++ b/frontend/src/mocks/settings-handlers.ts @@ -0,0 +1,155 @@ +import { http, delay, HttpResponse } from "msw"; +import { + ApiSettings, + PostApiSettings, +} from "#/api/settings-service/settings.types"; +import { GetConfigResponse } from "#/api/option-service/option.types"; +import { DEFAULT_SETTINGS } from "#/services/settings"; +import { Provider } from "#/types/settings"; + +export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = { + llm_model: DEFAULT_SETTINGS.LLM_MODEL, + llm_base_url: DEFAULT_SETTINGS.LLM_BASE_URL, + llm_api_key: null, + llm_api_key_set: DEFAULT_SETTINGS.LLM_API_KEY_SET, + search_api_key_set: DEFAULT_SETTINGS.SEARCH_API_KEY_SET, + agent: DEFAULT_SETTINGS.AGENT, + language: DEFAULT_SETTINGS.LANGUAGE, + confirmation_mode: DEFAULT_SETTINGS.CONFIRMATION_MODE, + security_analyzer: DEFAULT_SETTINGS.SECURITY_ANALYZER, + remote_runtime_resource_factor: + DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR, + provider_tokens_set: {}, + enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER, + condenser_max_size: DEFAULT_SETTINGS.CONDENSER_MAX_SIZE, + enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS, + enable_proactive_conversation_starters: + DEFAULT_SETTINGS.ENABLE_PROACTIVE_CONVERSATION_STARTERS, + enable_solvability_analysis: DEFAULT_SETTINGS.ENABLE_SOLVABILITY_ANALYSIS, + user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS, + max_budget_per_task: DEFAULT_SETTINGS.MAX_BUDGET_PER_TASK, +}; + +const MOCK_USER_PREFERENCES: { + settings: ApiSettings | PostApiSettings | null; +} = { + settings: null, +}; + +// Reset mock +export const resetTestHandlersMockSettings = () => { + MOCK_USER_PREFERENCES.settings = MOCK_DEFAULT_USER_SETTINGS; +}; + +// --- Handlers for options/config/settings --- + +export const SETTINGS_HANDLERS = [ + http.get("/api/options/models", async () => + HttpResponse.json([ + "gpt-3.5-turbo", + "gpt-4o", + "gpt-4o-mini", + "anthropic/claude-3.5", + "anthropic/claude-sonnet-4-20250514", + "anthropic/claude-sonnet-4-5-20250929", + "anthropic/claude-haiku-4-5-20251001", + "openhands/claude-sonnet-4-20250514", + "openhands/claude-sonnet-4-5-20250929", + "openhands/claude-haiku-4-5-20251001", + "sambanova/Meta-Llama-3.1-8B-Instruct", + ]), + ), + + http.get("/api/options/agents", async () => + HttpResponse.json(["CodeActAgent", "CoActAgent"]), + ), + + http.get("/api/options/security-analyzers", async () => + HttpResponse.json(["llm", "none"]), + ), + + http.get("/api/options/config", () => { + const mockSaas = import.meta.env.VITE_MOCK_SAAS === "true"; + + const config: GetConfigResponse = { + APP_MODE: mockSaas ? "saas" : "oss", + GITHUB_CLIENT_ID: "fake-github-client-id", + POSTHOG_CLIENT_KEY: "fake-posthog-client-key", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: mockSaas, + ENABLE_JIRA: false, + ENABLE_JIRA_DC: false, + ENABLE_LINEAR: false, + }, + // Uncomment the following to test the maintenance banner + // MAINTENANCE: { + // startTime: "2024-01-15T10:00:00-05:00", // EST timestamp + // }, + }; + + return HttpResponse.json(config); + }), + + http.get("/api/settings", async () => { + await delay(); + const { settings } = MOCK_USER_PREFERENCES; + + if (!settings) return HttpResponse.json(null, { status: 404 }); + + return HttpResponse.json(settings); + }), + + http.post("/api/settings", async ({ request }) => { + await delay(); + const body = await request.json(); + + if (body) { + const current = MOCK_USER_PREFERENCES.settings || { + ...MOCK_DEFAULT_USER_SETTINGS, + }; + + MOCK_USER_PREFERENCES.settings = { + ...current, + ...(body as Partial), + }; + + return HttpResponse.json(null, { status: 200 }); + } + + return HttpResponse.json(null, { status: 400 }); + }), + + http.post("/api/reset-settings", async () => { + await delay(); + MOCK_USER_PREFERENCES.settings = { ...MOCK_DEFAULT_USER_SETTINGS }; + return HttpResponse.json(null, { status: 200 }); + }), + + http.post("/api/add-git-providers", async ({ request }) => { + const body = await request.json(); + + if (typeof body === "object" && body?.provider_tokens) { + const rawTokens = body.provider_tokens as Record< + string, + { token?: string } + >; + + const providerTokensSet: Partial> = + Object.fromEntries( + Object.entries(rawTokens) + .filter(([, val]) => val && val.token) + .map(([provider]) => [provider as Provider, ""]), + ); + + MOCK_USER_PREFERENCES.settings = { + ...(MOCK_USER_PREFERENCES.settings || MOCK_DEFAULT_USER_SETTINGS), + provider_tokens_set: providerTokensSet, + }; + + return HttpResponse.json(true, { status: 200 }); + } + + return HttpResponse.json(null, { status: 400 }); + }), +];