Files
insforge--insforge/backend/tests/unit/auth-create-user-route.test.ts
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

220 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Router, Request, Response, NextFunction } from 'express';
interface AppError {
statusCode: number;
code: string;
message: string;
}
const mocks = vi.hoisted(() => ({
verifyUser: vi.fn(),
register: vi.fn(),
getAuthConfig: vi.fn(),
broadcastToRoom: vi.fn(),
generateRefreshToken: vi.fn(),
generateRefreshTokenWithCsrf: vi.fn(),
}));
vi.mock('@/api/middlewares/auth.js', () => ({
verifyUser: mocks.verifyUser,
verifyOptionalUser: mocks.verifyUser,
verifyAdmin: vi.fn((_req, _res, next: NextFunction) => next()),
verifyToken: vi.fn((_req, _res, next: NextFunction) => next()),
}));
vi.mock('@/services/auth/auth.service.js', () => ({
AuthService: { getInstance: () => ({ register: mocks.register }) },
}));
vi.mock('@/services/auth/auth-config.service.js', () => ({
AuthConfigService: {
getInstance: () => ({
getAuthConfig: mocks.getAuthConfig,
validateRedirectUrl: vi.fn().mockResolvedValue(true),
}),
},
}));
vi.mock('@/infra/socket/socket.manager.js', () => ({
SocketManager: { getInstance: () => ({ broadcastToRoom: mocks.broadcastToRoom }) },
}));
vi.mock('@/infra/security/token.manager.js', () => ({
TokenManager: {
getInstance: () => ({
verifyToken: vi.fn(),
generateAccessToken: vi.fn().mockReturnValue('test-access-token'),
generateRefreshToken: mocks.generateRefreshToken,
generateRefreshTokenWithCsrf: mocks.generateRefreshTokenWithCsrf,
}),
},
}));
vi.mock('@/utils/logger.js', () => ({
default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));
const NEW_USER = {
user: {
id: 'new-uuid',
email: 'new@test.com',
emailVerified: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
providers: ['email'],
profile: { name: 'Test' },
metadata: {},
},
accessToken: 'test-access-token',
};
function callRoute(
router: Router,
overrides: { headers?: Record<string, string>; body?: Record<string, unknown> }
): Promise<{ statusCode: number; body: unknown }> {
return new Promise((resolve) => {
let statusCode = 200;
const req: Partial<Request> = {
url: '/users',
method: 'POST',
headers: overrides.headers ?? {},
query: {},
body: overrides.body ?? {},
};
const res: Partial<Response> = {
status: vi.fn((c: number) => {
statusCode = c;
return res;
}),
json: vi.fn((d: unknown) => resolve({ statusCode, body: d })),
cookie: vi.fn(() => res),
};
router(
req as Request,
res as Response,
vi.fn((error?: unknown) => {
if (error && typeof error === 'object' && 'statusCode' in error) {
const appError = error as AppError;
resolve({
statusCode: appError.statusCode ?? 500,
body: {
error: appError.code,
message: appError.message,
statusCode: appError.statusCode ?? 500,
},
});
}
})
);
});
}
describe('POST /api/auth/users disableSignup gate', () => {
let router: Router;
beforeAll(async () => {
router = (await import('../../src/api/routes/auth/index.routes.js')).default;
});
beforeEach(() => {
vi.clearAllMocks();
});
const validBody = { email: 'test@test.com', password: 'Pass1234', name: 'Test' };
it('blocks anon/public signup when disableSignup=true', async () => {
mocks.verifyUser.mockImplementation((req, _res, next) => {
req.user = { id: 'anonymous', role: 'anon' };
next();
});
mocks.getAuthConfig.mockResolvedValue({ disableSignup: true });
const response = await callRoute(router, { body: validBody });
expect(response.statusCode).toBe(403);
expect((response.body as Record<string, unknown>).error).toBe('AUTH_SIGNUP_DISABLED');
expect(mocks.getAuthConfig).toHaveBeenCalledOnce();
expect(mocks.register).not.toHaveBeenCalled();
});
it('allows API-key admin user creation when disableSignup=true', async () => {
mocks.verifyUser.mockImplementation((req, _res, next) => {
req.hasApiKey = true;
next();
});
mocks.register.mockResolvedValue(NEW_USER);
const response = await callRoute(router, { body: validBody });
expect(response.statusCode).toBe(200);
expect((response.body as Record<string, unknown>).user).toMatchObject({
email: 'new@test.com',
});
expect(mocks.getAuthConfig).not.toHaveBeenCalled();
expect(mocks.register).toHaveBeenCalledOnce();
expect(mocks.register.mock.calls[0][4]).toMatchObject({ isAdminCreation: true });
});
it('allows admin JWT user creation when disableSignup=true', async () => {
mocks.verifyUser.mockImplementation((req, _res, next) => {
req.user = { id: 'admin-uuid', role: 'project_admin' };
next();
});
mocks.register.mockResolvedValue(NEW_USER);
const response = await callRoute(router, { body: validBody });
expect(response.statusCode).toBe(200);
expect(mocks.getAuthConfig).not.toHaveBeenCalled();
expect(mocks.register.mock.calls[0][4]).toMatchObject({ isAdminCreation: true });
});
it('allows public signup when disableSignup=false', async () => {
mocks.verifyUser.mockImplementation((req, _res, next) => {
req.user = { id: 'anonymous', role: 'anon' };
next();
});
mocks.getAuthConfig.mockResolvedValue({ disableSignup: false });
mocks.register.mockResolvedValue(NEW_USER);
mocks.generateRefreshTokenWithCsrf.mockReturnValue({ refreshToken: 'rt', csrfToken: 'csrf' });
const response = await callRoute(router, { body: validBody });
expect(response.statusCode).toBe(200);
expect(mocks.getAuthConfig).toHaveBeenCalledOnce();
expect(mocks.register).toHaveBeenCalledOnce();
expect(mocks.register.mock.calls[0][4]).toMatchObject({ isAdminCreation: false });
});
it('returns 401 when no credentials are provided', async () => {
mocks.verifyUser.mockImplementation((_req, res) => {
res.status(401).json({
error: 'AUTH_INVALID_CREDENTIALS',
message: 'No token provided',
statusCode: 401,
});
});
const response = await callRoute(router, { body: validBody });
expect(response.statusCode).toBe(401);
expect((response.body as Record<string, unknown>).error).toBe('AUTH_INVALID_CREDENTIALS');
expect(mocks.register).not.toHaveBeenCalled();
});
it('returns 400 for invalid request body', async () => {
mocks.verifyUser.mockImplementation((req, _res, next) => {
req.user = { id: 'anonymous', role: 'anon' };
next();
});
const response = await callRoute(router, { body: {} });
expect(response.statusCode).toBe(400);
});
});