test: add api integration tests for contact form
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Successful in 2m28s
Build & Deploy / 🏗️ Build (push) Successful in 4m45s
Build & Deploy / 🚀 Deploy (push) Failing after 13s
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Successful in 2m28s
Build & Deploy / 🏗️ Build (push) Successful in 4m45s
Build & Deploy / 🚀 Deploy (push) Failing after 13s
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
This commit is contained in:
@@ -196,7 +196,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/arm64
|
platforms: linux/amd64
|
||||||
build-args: |
|
build-args: |
|
||||||
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
|
||||||
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
|
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
|
||||||
|
|||||||
1
tests/__mocks__/payload-config.ts
Normal file
1
tests/__mocks__/payload-config.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {};
|
||||||
168
tests/api-contact.test.ts
Normal file
168
tests/api-contact.test.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
// Mock Payload CMS
|
||||||
|
const { mockCreate, mockSendEmail } = vi.hoisted(() => ({
|
||||||
|
mockCreate: vi.fn(),
|
||||||
|
mockSendEmail: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("payload", () => ({
|
||||||
|
getPayload: vi.fn().mockResolvedValue({
|
||||||
|
create: mockCreate,
|
||||||
|
sendEmail: mockSendEmail,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock Email Template renders
|
||||||
|
vi.mock("@mintel/mail", () => ({
|
||||||
|
render: vi.fn().mockResolvedValue("<html>Mocked Email HTML</html>"),
|
||||||
|
ContactFormNotification: () => "ContactFormNotification",
|
||||||
|
ConfirmationMessage: () => "ConfirmationMessage",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock Notifications and Analytics
|
||||||
|
const { mockNotify, mockTrack, mockCaptureException } = vi.hoisted(() => ({
|
||||||
|
mockNotify: vi.fn(),
|
||||||
|
mockTrack: vi.fn(),
|
||||||
|
mockCaptureException: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/services/create-services.server", () => ({
|
||||||
|
getServerAppServices: () => ({
|
||||||
|
logger: {
|
||||||
|
child: () => ({
|
||||||
|
info: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
analytics: {
|
||||||
|
setServerContext: vi.fn(),
|
||||||
|
track: mockTrack,
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
notify: mockNotify,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
captureException: mockCaptureException,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import the route handler we want to test
|
||||||
|
import { POST } from "../app/api/contact/route";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import type { Mock } from "vitest";
|
||||||
|
|
||||||
|
describe("Contact API Integration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
(NextResponse.json as Mock).mockImplementation((body: any, init?: any) => ({
|
||||||
|
status: init?.status || 200,
|
||||||
|
json: async () => body,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should validate and decline empty or short messages", async () => {
|
||||||
|
const req = new Request("http://localhost/api/contact", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: "Test User",
|
||||||
|
email: "test@example.com",
|
||||||
|
message: "too short",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await POST(req);
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
expect(data.error).toBe("message_too_short");
|
||||||
|
|
||||||
|
// Ensure payload and email were NOT called
|
||||||
|
expect(mockCreate).not.toHaveBeenCalled();
|
||||||
|
expect(mockSendEmail).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should catch honeypot submissions", async () => {
|
||||||
|
const req = new Request("http://localhost/api/contact", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: "Spam Bot",
|
||||||
|
email: "spam@example.com",
|
||||||
|
message: "This is a very long spam message that passes length checks.",
|
||||||
|
website: "http://spam.com", // Honeypot filled
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await POST(req);
|
||||||
|
// Honeypot returns 200 OK so the bot thinks it succeeded
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
// But it actually does NOTHING internally
|
||||||
|
expect(mockCreate).not.toHaveBeenCalled();
|
||||||
|
expect(mockSendEmail).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully save to Payload and send emails", async () => {
|
||||||
|
const req = new Request("http://localhost/api/contact", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"user-agent": "vitest",
|
||||||
|
"x-forwarded-for": "127.0.0.1",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: "Jane Doe",
|
||||||
|
email: "jane@example.com",
|
||||||
|
company: "Jane Tech",
|
||||||
|
message:
|
||||||
|
"Hello, I am interested in exploring your high-voltage grid solutions.",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await POST(req);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
expect(data.message).toBe("Ok");
|
||||||
|
|
||||||
|
// 1. Verify Payload creation
|
||||||
|
expect(mockCreate).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockCreate).toHaveBeenCalledWith({
|
||||||
|
collection: "form-submissions",
|
||||||
|
data: {
|
||||||
|
name: "Jane Doe",
|
||||||
|
email: "jane@example.com",
|
||||||
|
company: "Jane Tech",
|
||||||
|
message:
|
||||||
|
"Hello, I am interested in exploring your high-voltage grid solutions.",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Verify Email Sending
|
||||||
|
// Note: sendEmail is called twice (Notification + User Confirmation)
|
||||||
|
expect(mockSendEmail).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
expect(mockSendEmail).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
expect.objectContaining({
|
||||||
|
subject: "Kontaktanfrage von Jane Doe",
|
||||||
|
replyTo: "jane@example.com",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockSendEmail).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
expect.objectContaining({
|
||||||
|
to: "jane@example.com",
|
||||||
|
subject: "Ihre Kontaktanfrage bei MB Grid Solutions",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Verify notification and analytics
|
||||||
|
expect(mockNotify).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockTrack).toHaveBeenCalledWith("contact-form-success", {
|
||||||
|
has_company: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,6 +9,8 @@ export default defineConfig({
|
|||||||
setupFiles: ['./tests/setup.tsx'],
|
setupFiles: ['./tests/setup.tsx'],
|
||||||
alias: {
|
alias: {
|
||||||
'next/server': 'next/server.js',
|
'next/server': 'next/server.js',
|
||||||
|
'@payload-config': new URL('./tests/__mocks__/payload-config.ts', import.meta.url).pathname,
|
||||||
|
'@': new URL('./', import.meta.url).pathname,
|
||||||
},
|
},
|
||||||
exclude: ['**/node_modules/**', '**/.next/**'],
|
exclude: ['**/node_modules/**', '**/.next/**'],
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user