contact.test.ts78 lines · main
1/**
2 * Validation-contract tests for the public POST /v1/contact route.
3 * Exercises the exported `contactSchema` (the pure, testable seam) so
4 * the accept/reject matrix is pinned without standing up postgres /
5 * redis. The handler itself (rate-limit + insert) depends on infra and
6 * is covered by integration runs.
7 */
8
9import { describe, expect, test } from 'bun:test';
10
11import { contactSchema } from './contact.js';
12
13const valid = {
14 name: 'Ada Lovelace',
15 email: 'ada@example.com',
16 topic: 'support',
17 message: 'I have a question about the dashboard.',
18};
19
20describe('contactSchema', () => {
21 test('accepts a well-formed submission', () => {
22 const r = contactSchema.safeParse(valid);
23 expect(r.success).toBe(true);
24 });
25
26 test('accepts every allowed topic', () => {
27 for (const topic of ['general', 'support', 'sales', 'security', 'privacy', 'other']) {
28 expect(contactSchema.safeParse({ ...valid, topic }).success).toBe(true);
29 }
30 });
31
32 test('rejects an unknown topic', () => {
33 expect(contactSchema.safeParse({ ...valid, topic: 'partnerships' }).success).toBe(false);
34 });
35
36 test('rejects a missing / empty name', () => {
37 expect(contactSchema.safeParse({ ...valid, name: '' }).success).toBe(false);
38 expect(contactSchema.safeParse({ ...valid, name: ' ' }).success).toBe(false);
39 });
40
41 test('rejects an invalid email', () => {
42 expect(contactSchema.safeParse({ ...valid, email: 'not-an-email' }).success).toBe(false);
43 });
44
45 test('rejects an empty message', () => {
46 expect(contactSchema.safeParse({ ...valid, message: '' }).success).toBe(false);
47 });
48
49 test('rejects an over-long message (>8000)', () => {
50 expect(contactSchema.safeParse({ ...valid, message: 'x'.repeat(8001) }).success).toBe(false);
51 });
52
53 test('accepts an optional subject + country', () => {
54 const r = contactSchema.safeParse({
55 ...valid,
56 subject: 'dashboard question',
57 country: 'United States',
58 });
59 expect(r.success).toBe(true);
60 });
61
62 test('accepts a submission with neither subject nor country', () => {
63 expect(contactSchema.safeParse(valid).success).toBe(true);
64 });
65
66 test('rejects an over-long subject (>200)', () => {
67 expect(contactSchema.safeParse({ ...valid, subject: 'x'.repeat(201) }).success).toBe(false);
68 });
69
70 test('rejects an over-long country (>100)', () => {
71 expect(contactSchema.safeParse({ ...valid, country: 'x'.repeat(101) }).success).toBe(false);
72 });
73
74 test('rejects a non-object body', () => {
75 expect(contactSchema.safeParse(null).success).toBe(false);
76 expect(contactSchema.safeParse('nope').success).toBe(false);
77 });
78});