mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
refactor(frontend): simplify enterprise lead submission without service layer
- Remove form-submission-service directory (api + types + tests) - Inline API call directly in useSubmitEnterpriseLead mutation hook - Fix API endpoint to /api/forms/submit (was incorrectly /api/v1/forms/submit) - Update tests to mock openHands.post directly Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { formSubmissionService } from "#/api/form-submission-service/form-submission-service.api";
|
||||
|
||||
const { mockPost } = vi.hoisted(() => ({ mockPost: vi.fn() }));
|
||||
vi.mock("#/api/open-hands-axios", () => ({
|
||||
openHands: { post: mockPost },
|
||||
}));
|
||||
|
||||
describe("formSubmissionService", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("submitEnterpriseLead", () => {
|
||||
const mockFormData = {
|
||||
requestType: "saas" as const,
|
||||
name: "John Doe",
|
||||
company: "Acme Corp",
|
||||
email: "john@acme.com",
|
||||
message: "Interested in enterprise plan.",
|
||||
};
|
||||
|
||||
const mockResponse = {
|
||||
id: "test-submission-id",
|
||||
status: "pending",
|
||||
created_at: "2025-03-19T00:00:00Z",
|
||||
};
|
||||
|
||||
it("should call the correct endpoint with correct payload", async () => {
|
||||
mockPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
await formSubmissionService.submitEnterpriseLead(mockFormData);
|
||||
|
||||
expect(mockPost).toHaveBeenCalledTimes(1);
|
||||
expect(mockPost).toHaveBeenCalledWith(
|
||||
"/api/v1/forms/submit",
|
||||
{
|
||||
form_type: "enterprise_lead",
|
||||
answers: {
|
||||
request_type: "saas",
|
||||
name: "John Doe",
|
||||
company: "Acme Corp",
|
||||
email: "john@acme.com",
|
||||
message: "Interested in enterprise plan.",
|
||||
},
|
||||
},
|
||||
{ withCredentials: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("should return the response data", async () => {
|
||||
mockPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result =
|
||||
await formSubmissionService.submitEnterpriseLead(mockFormData);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it("should handle self-hosted request type", async () => {
|
||||
mockPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const selfHostedFormData = {
|
||||
...mockFormData,
|
||||
requestType: "self-hosted" as const,
|
||||
};
|
||||
|
||||
await formSubmissionService.submitEnterpriseLead(selfHostedFormData);
|
||||
|
||||
expect(mockPost).toHaveBeenCalledWith(
|
||||
"/api/v1/forms/submit",
|
||||
expect.objectContaining({
|
||||
answers: expect.objectContaining({
|
||||
request_type: "self-hosted",
|
||||
}),
|
||||
}),
|
||||
{ withCredentials: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("should propagate errors from the API", async () => {
|
||||
const mockError = new Error("Network error");
|
||||
mockPost.mockRejectedValue(mockError);
|
||||
|
||||
await expect(
|
||||
formSubmissionService.submitEnterpriseLead(mockFormData),
|
||||
).rejects.toThrow("Network error");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { formSubmissionService } from "#/api/form-submission-service/form-submission-service.api";
|
||||
import { openHands } from "#/api/open-hands-axios";
|
||||
import { useSubmitEnterpriseLead } from "#/hooks/mutation/use-submit-enterprise-lead";
|
||||
|
||||
vi.mock("#/api/form-submission-service/form-submission-service.api");
|
||||
vi.mock("#/api/open-hands-axios");
|
||||
|
||||
describe("useSubmitEnterpriseLead", () => {
|
||||
const mockFormData = {
|
||||
@@ -25,10 +25,8 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should call submitEnterpriseLead with correct data", async () => {
|
||||
vi.mocked(formSubmissionService.submitEnterpriseLead).mockResolvedValue(
|
||||
mockResponse,
|
||||
);
|
||||
it("should call API with correct payload", async () => {
|
||||
vi.mocked(openHands.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const { result } = renderHook(() => useSubmitEnterpriseLead(), {
|
||||
wrapper: ({ children }) => (
|
||||
@@ -41,16 +39,25 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
result.current.mutate(mockFormData);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
formSubmissionService.submitEnterpriseLead,
|
||||
).toHaveBeenCalledWith(mockFormData);
|
||||
expect(openHands.post).toHaveBeenCalledWith(
|
||||
"/api/forms/submit",
|
||||
{
|
||||
form_type: "enterprise_lead",
|
||||
answers: {
|
||||
request_type: "saas",
|
||||
name: "John Doe",
|
||||
company: "Acme Corp",
|
||||
email: "john@acme.com",
|
||||
message: "Interested in enterprise plan.",
|
||||
},
|
||||
},
|
||||
{ withCredentials: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return success state after successful submission", async () => {
|
||||
vi.mocked(formSubmissionService.submitEnterpriseLead).mockResolvedValue(
|
||||
mockResponse,
|
||||
);
|
||||
vi.mocked(openHands.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const { result } = renderHook(() => useSubmitEnterpriseLead(), {
|
||||
wrapper: ({ children }) => (
|
||||
@@ -70,9 +77,7 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
|
||||
it("should return error state after failed submission", async () => {
|
||||
const mockError = new Error("Network error");
|
||||
vi.mocked(formSubmissionService.submitEnterpriseLead).mockRejectedValue(
|
||||
mockError,
|
||||
);
|
||||
vi.mocked(openHands.post).mockRejectedValue(mockError);
|
||||
|
||||
const { result } = renderHook(() => useSubmitEnterpriseLead(), {
|
||||
wrapper: ({ children }) => (
|
||||
@@ -100,10 +105,8 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should call submitEnterpriseLead with self-hosted request type", async () => {
|
||||
vi.mocked(formSubmissionService.submitEnterpriseLead).mockResolvedValue(
|
||||
mockResponse,
|
||||
);
|
||||
it("should handle self-hosted request type", async () => {
|
||||
vi.mocked(openHands.post).mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const selfHostedFormData = {
|
||||
...mockFormData,
|
||||
@@ -121,23 +124,28 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
result.current.mutate(selfHostedFormData);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
formSubmissionService.submitEnterpriseLead,
|
||||
).toHaveBeenCalledWith(selfHostedFormData);
|
||||
expect(openHands.post).toHaveBeenCalledWith(
|
||||
"/api/forms/submit",
|
||||
expect.objectContaining({
|
||||
answers: expect.objectContaining({
|
||||
request_type: "self-hosted",
|
||||
}),
|
||||
}),
|
||||
{ withCredentials: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be in pending state while submitting", async () => {
|
||||
// Create a promise that we can control
|
||||
let resolvePromise: (value: typeof mockResponse) => void;
|
||||
const controlledPromise = new Promise<typeof mockResponse>((resolve) => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
|
||||
vi.mocked(formSubmissionService.submitEnterpriseLead).mockReturnValue(
|
||||
controlledPromise,
|
||||
let resolvePromise: (value: { data: typeof mockResponse }) => void;
|
||||
const controlledPromise = new Promise<{ data: typeof mockResponse }>(
|
||||
(resolve) => {
|
||||
resolvePromise = resolve;
|
||||
},
|
||||
);
|
||||
|
||||
vi.mocked(openHands.post).mockReturnValue(controlledPromise);
|
||||
|
||||
const { result } = renderHook(() => useSubmitEnterpriseLead(), {
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
@@ -152,8 +160,7 @@ describe("useSubmitEnterpriseLead", () => {
|
||||
expect(result.current.isPending).toBe(true);
|
||||
});
|
||||
|
||||
// Resolve the promise
|
||||
resolvePromise!(mockResponse);
|
||||
resolvePromise!({ data: mockResponse });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isPending).toBe(false);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { openHands } from "../open-hands-axios";
|
||||
import {
|
||||
EnterpriseLeadFormData,
|
||||
FormSubmissionResponse,
|
||||
} from "./form-submission-service.types";
|
||||
|
||||
/**
|
||||
* Form Submission Service API - Handles form submission endpoints
|
||||
*/
|
||||
export const formSubmissionService = {
|
||||
/**
|
||||
* Submit an enterprise lead capture form
|
||||
* @param formData - The form data containing requestType, name, company, email, and message
|
||||
* @returns The submission response with id, status, and created_at
|
||||
*/
|
||||
submitEnterpriseLead: async (
|
||||
formData: EnterpriseLeadFormData,
|
||||
): Promise<FormSubmissionResponse> => {
|
||||
const { data } = await openHands.post<FormSubmissionResponse>(
|
||||
"/api/v1/forms/submit",
|
||||
{
|
||||
form_type: "enterprise_lead",
|
||||
answers: {
|
||||
request_type: formData.requestType,
|
||||
name: formData.name,
|
||||
company: formData.company,
|
||||
email: formData.email,
|
||||
message: formData.message,
|
||||
},
|
||||
},
|
||||
{ withCredentials: true },
|
||||
);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
export type RequestType = "saas" | "self-hosted";
|
||||
|
||||
export interface EnterpriseLeadFormData {
|
||||
requestType: RequestType;
|
||||
name: string;
|
||||
company: string;
|
||||
email: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface FormSubmissionRequest {
|
||||
form_type: string;
|
||||
answers: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface FormSubmissionResponse {
|
||||
id: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { formSubmissionService } from "#/api/form-submission-service/form-submission-service.api";
|
||||
import { EnterpriseLeadFormData } from "#/api/form-submission-service/form-submission-service.types";
|
||||
import { openHands } from "#/api/open-hands-axios";
|
||||
|
||||
export interface EnterpriseLeadFormData {
|
||||
requestType: "saas" | "self-hosted";
|
||||
name: string;
|
||||
company: string;
|
||||
email: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for submitting enterprise lead capture forms.
|
||||
@@ -8,6 +15,25 @@ import { EnterpriseLeadFormData } from "#/api/form-submission-service/form-submi
|
||||
*/
|
||||
export const useSubmitEnterpriseLead = () =>
|
||||
useMutation({
|
||||
mutationFn: (formData: EnterpriseLeadFormData) =>
|
||||
formSubmissionService.submitEnterpriseLead(formData),
|
||||
mutationFn: async (formData: EnterpriseLeadFormData) => {
|
||||
const { data } = await openHands.post<{
|
||||
id: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
}>(
|
||||
"/api/forms/submit",
|
||||
{
|
||||
form_type: "enterprise_lead",
|
||||
answers: {
|
||||
request_type: formData.requestType,
|
||||
name: formData.name,
|
||||
company: formData.company,
|
||||
email: formData.email,
|
||||
message: formData.message,
|
||||
},
|
||||
},
|
||||
{ withCredentials: true },
|
||||
);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user