wip
This commit is contained in:
40
tests/unit/infrastructure/DemoImageServiceAdapter.test.ts
Normal file
40
tests/unit/infrastructure/DemoImageServiceAdapter.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { DemoImageServiceAdapter } from '../../../packages/demo-infrastructure/media/DemoImageServiceAdapter';
|
||||
|
||||
describe('DemoImageServiceAdapter - driver avatars', () => {
|
||||
it('returns male default avatar for a demo driver treated as male (odd id suffix)', () => {
|
||||
// Given a demo driver id that maps to a male profile
|
||||
const adapter = new DemoImageServiceAdapter();
|
||||
|
||||
// When resolving the driver avatar
|
||||
const src = adapter.getDriverAvatar('driver-1');
|
||||
|
||||
// Then it should use the male default avatar asset
|
||||
expect(src).toBe('/images/avatars/male-default-avatar.jpg');
|
||||
});
|
||||
|
||||
it('returns female default avatar for a demo driver treated as female (even id suffix)', () => {
|
||||
// Given a demo driver id that maps to a female profile
|
||||
const adapter = new DemoImageServiceAdapter();
|
||||
|
||||
// When resolving the driver avatar
|
||||
const src = adapter.getDriverAvatar('driver-2');
|
||||
|
||||
// Then it should use the female default avatar asset
|
||||
expect(src).toBe('/images/avatars/female-default-avatar.jpeg');
|
||||
});
|
||||
|
||||
it('falls back to a sensible default avatar when driver id has no numeric suffix', () => {
|
||||
// Given a demo driver id without a numeric suffix
|
||||
const adapter = new DemoImageServiceAdapter();
|
||||
|
||||
// When resolving the driver avatar
|
||||
const src = adapter.getDriverAvatar('demo-driver');
|
||||
|
||||
// Then it should still resolve to one of the default avatar assets
|
||||
expect(['/images/avatars/male-default-avatar.jpg', '/images/avatars/female-default-avatar.jpeg']).toContain(
|
||||
src,
|
||||
);
|
||||
});
|
||||
});
|
||||
63
tests/unit/website/getAppMode.test.ts
Normal file
63
tests/unit/website/getAppMode.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { getAppMode, AppMode } from '../../../apps/website/lib/mode';
|
||||
|
||||
const ORIGINAL_NODE_ENV = process.env.NODE_ENV;
|
||||
|
||||
describe('getAppMode', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
process.env.NODE_ENV = 'production';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
process.env.NODE_ENV = ORIGINAL_NODE_ENV;
|
||||
});
|
||||
|
||||
it('returns "pre-launch" when NEXT_PUBLIC_GRIDPILOT_MODE is undefined', () => {
|
||||
delete process.env.NEXT_PUBLIC_GRIDPILOT_MODE;
|
||||
|
||||
const mode = getAppMode();
|
||||
|
||||
expect(mode).toBe<AppMode>('pre-launch');
|
||||
});
|
||||
|
||||
it('returns "pre-launch" when NEXT_PUBLIC_GRIDPILOT_MODE is explicitly set to "pre-launch"', () => {
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'pre-launch';
|
||||
|
||||
const mode = getAppMode();
|
||||
|
||||
expect(mode).toBe<AppMode>('pre-launch');
|
||||
});
|
||||
|
||||
it('returns "alpha" when NEXT_PUBLIC_GRIDPILOT_MODE is set to "alpha"', () => {
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'alpha';
|
||||
|
||||
const mode = getAppMode();
|
||||
|
||||
expect(mode).toBe<AppMode>('alpha');
|
||||
});
|
||||
|
||||
it('falls back to "pre-launch" and logs when NEXT_PUBLIC_GRIDPILOT_MODE is invalid in production', () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode';
|
||||
|
||||
const mode = getAppMode();
|
||||
|
||||
expect(mode).toBe<AppMode>('pre-launch');
|
||||
expect(consoleError).toHaveBeenCalled();
|
||||
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
|
||||
it('throws in development when NEXT_PUBLIC_GRIDPILOT_MODE is invalid', () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode';
|
||||
|
||||
expect(() => getAppMode()).toThrowError(/Invalid NEXT_PUBLIC_GRIDPILOT_MODE/);
|
||||
});
|
||||
});
|
||||
135
tests/unit/website/signupRoute.test.ts
Normal file
135
tests/unit/website/signupRoute.test.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
type RateLimitResult = {
|
||||
allowed: boolean;
|
||||
remaining: number;
|
||||
resetAt: number;
|
||||
};
|
||||
|
||||
const mockCheckRateLimit = vi.fn<[], Promise<RateLimitResult>>();
|
||||
const mockGetClientIp = vi.fn<[], string>();
|
||||
|
||||
vi.mock('../../../apps/website/lib/rate-limit', () => ({
|
||||
checkRateLimit: (...args: any[]) => mockCheckRateLimit(...(args as [])),
|
||||
getClientIp: (..._args: any[]) => mockGetClientIp(),
|
||||
}));
|
||||
|
||||
async function getPostHandler() {
|
||||
const routeModule: any = await import('../../../apps/website/app/api/signup/route');
|
||||
return routeModule.POST as (request: Request) => Promise<Response>;
|
||||
}
|
||||
|
||||
function createJsonRequest(body: unknown): Request {
|
||||
return new Request('http://localhost/api/signup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
describe('/api/signup POST', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockCheckRateLimit.mockReset();
|
||||
mockGetClientIp.mockReset();
|
||||
|
||||
mockGetClientIp.mockReturnValue('127.0.0.1');
|
||||
mockCheckRateLimit.mockResolvedValue({
|
||||
allowed: true,
|
||||
remaining: 4,
|
||||
resetAt: Date.now() + 60 * 60 * 1000,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts a valid email within rate limits and returns success payload', async () => {
|
||||
const POST = await getPostHandler();
|
||||
|
||||
const response = await POST(
|
||||
createJsonRequest({
|
||||
email: 'user@example.com',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBeGreaterThanOrEqual(200);
|
||||
expect(response.status).toBeLessThan(300);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
|
||||
expect(data).toHaveProperty('message');
|
||||
expect(typeof data.message).toBe('string');
|
||||
expect(data).toHaveProperty('ok', true);
|
||||
});
|
||||
|
||||
it('rejects an invalid email with 400 and error message', async () => {
|
||||
const POST = await getPostHandler();
|
||||
|
||||
const response = await POST(
|
||||
createJsonRequest({
|
||||
email: 'not-an-email',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data.error.toLowerCase()).toContain('email');
|
||||
});
|
||||
|
||||
it('rejects disposable email domains with 400 and error message', async () => {
|
||||
const POST = await getPostHandler();
|
||||
|
||||
const response = await POST(
|
||||
createJsonRequest({
|
||||
email: 'foo@mailinator.com',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
expect(typeof data.error).toBe('string');
|
||||
});
|
||||
|
||||
it('returns 409 and friendly message when email is already subscribed', async () => {
|
||||
const POST = await getPostHandler();
|
||||
|
||||
const email = 'duplicate@example.com';
|
||||
|
||||
const first = await POST(createJsonRequest({ email }));
|
||||
expect(first.status).toBeGreaterThanOrEqual(200);
|
||||
expect(first.status).toBeLessThan(300);
|
||||
|
||||
const second = await POST(createJsonRequest({ email }));
|
||||
|
||||
expect(second.status).toBe(409);
|
||||
|
||||
const data = (await second.json()) as any;
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data.error.toLowerCase()).toContain('already');
|
||||
});
|
||||
|
||||
it('returns 429 with retryAfter when rate limit is exceeded', async () => {
|
||||
mockCheckRateLimit.mockResolvedValueOnce({
|
||||
allowed: false,
|
||||
remaining: 0,
|
||||
resetAt: Date.now() + 30_000,
|
||||
});
|
||||
|
||||
const POST = await getPostHandler();
|
||||
|
||||
const response = await POST(
|
||||
createJsonRequest({
|
||||
email: 'limited@example.com',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(429);
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
expect(typeof data.error).toBe('string');
|
||||
expect(data).toHaveProperty('retryAfter');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user