Files
insforge--insforge/backend/tests/unit/auth-oauth-unsupported-provider.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

185 lines
6.7 KiB
TypeScript

/**
* Tests that the OAuth "unsupported provider" branches in AuthService throw a
* structured AppError(501, AUTH_UNSUPPORTED_PROVIDER) instead of a raw Error.
*
* Issue #1405 — Phase 2: OAuth Error Standardization.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ERROR_CODES } from '@insforge/shared-schemas';
// ---------------------------------------------------------------------------
// Minimal mocks for all AuthService dependencies
// ---------------------------------------------------------------------------
vi.mock('../../src/infra/database/database.manager.js', () => ({
DatabaseManager: {
getInstance: () => ({ getPool: () => ({ query: vi.fn(), connect: vi.fn() }) }),
},
}));
vi.mock('../../src/infra/security/token.manager.js', () => ({
TokenManager: { getInstance: () => ({ generateAccessToken: vi.fn(), verifyToken: vi.fn() }) },
}));
vi.mock('../../src/services/auth/oauth-config.service.js', () => ({
OAuthConfigService: { getInstance: () => ({}) },
}));
vi.mock('../../src/services/auth/custom-oauth-config.service.js', () => ({
CustomOAuthConfigService: { getInstance: () => ({}) },
}));
vi.mock('../../src/services/auth/auth-config.service.js', () => ({
AuthConfigService: { getInstance: () => ({}) },
}));
vi.mock('../../src/services/auth/auth-otp.service.js', () => ({
AuthOTPService: { getInstance: () => ({}) },
OTPPurpose: {},
OTPType: {},
}));
vi.mock('../../src/services/email/smtp-config.service.js', () => ({
SmtpConfigService: { getInstance: () => ({}) },
}));
vi.mock('../../src/services/email/email.service.js', () => ({
EmailService: { getInstance: () => ({}) },
}));
vi.mock('../../src/utils/logger.js', () => ({
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));
vi.mock('../../src/utils/environment.js', () => ({
getApiBaseUrl: () => 'http://localhost:3000',
}));
// Mock all OAuth providers — each has generateOAuthUrl and handleCallback
const makeOAuthProviderMock = () => ({
getInstance: () => ({
generateOAuthUrl: vi.fn().mockResolvedValue('https://oauth.example.com'),
handleCallback: vi.fn().mockResolvedValue({
provider: 'google',
providerId: '123',
email: 'test@test.com',
userName: 'Test',
avatarUrl: '',
identityData: {},
}),
handleSharedCallback: vi.fn().mockReturnValue({
provider: 'google',
providerId: '123',
email: 'test@test.com',
userName: 'Test',
avatarUrl: '',
identityData: {},
}),
}),
});
vi.mock('../../src/providers/oauth/google.provider.js', () => ({
GoogleOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/github.provider.js', () => ({
GitHubOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/discord.provider.js', () => ({
DiscordOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/linkedin.provider.js', () => ({
LinkedInOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/facebook.provider.js', () => ({
FacebookOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/microsoft.provider.js', () => ({
MicrosoftOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/x.provider.js', () => ({
XOAuthProvider: makeOAuthProviderMock(),
}));
vi.mock('../../src/providers/oauth/apple.provider.js', () => ({
AppleOAuthProvider: makeOAuthProviderMock(),
}));
// ---------------------------------------------------------------------------
async function getAuthService() {
// Must stub env vars before importing AuthService
vi.stubEnv('ADMIN_EMAIL', 'admin@test.com');
vi.stubEnv('ADMIN_PASSWORD', 'password123');
const { AuthService } = await import('../../src/services/auth/auth.service.js');
return AuthService.getInstance();
}
describe('AuthService — unsupported OAuth provider branches (Issue #1405 Phase 2)', () => {
beforeEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
});
describe('generateOAuthUrl() default branch', () => {
it('throws AppError(501, AUTH_UNSUPPORTED_PROVIDER) for unknown provider', async () => {
const authService = await getAuthService();
// Force the TypeScript exhaustive switch to hit its default branch
await expect(authService.generateOAuthUrl('unknown_provider' as never)).rejects.toMatchObject(
{
statusCode: 501,
code: ERROR_CODES.AUTH_UNSUPPORTED_PROVIDER,
name: 'AppError',
}
);
});
it('error message matches exactly', async () => {
const authService = await getAuthService();
await expect(authService.generateOAuthUrl('tiktok' as never)).rejects.toThrow(
"OAuth provider 'tiktok' is not implemented yet."
);
});
it('is an AppError (name === AppError), not a plain Error', async () => {
const authService = await getAuthService();
let thrown: unknown;
try {
await authService.generateOAuthUrl('nonexistent' as never);
} catch (e) {
thrown = e;
}
// Check name since class identity breaks across module boundaries in Vitest
expect((thrown as Error).name).toBe('AppError');
expect((thrown as { statusCode?: number }).statusCode).toBe(501);
});
});
describe('handleOAuthCallback() default branch', () => {
it('throws AppError(501, AUTH_UNSUPPORTED_PROVIDER) for unknown provider', async () => {
const authService = await getAuthService();
await expect(
authService.handleOAuthCallback('unknown_provider' as never, { code: 'abc' })
).rejects.toMatchObject({
statusCode: 501,
code: ERROR_CODES.AUTH_UNSUPPORTED_PROVIDER,
name: 'AppError',
});
});
it('error message matches exactly', async () => {
const authService = await getAuthService();
await expect(
authService.handleOAuthCallback('unknown_provider' as never, { code: 'abc' })
).rejects.toThrow("OAuth provider 'unknown_provider' is not implemented yet.");
});
});
describe('handleSharedCallback() default branch', () => {
it('throws AppError(501, AUTH_UNSUPPORTED_PROVIDER) for unknown provider', async () => {
const authService = await getAuthService();
await expect(authService.handleSharedCallback('unknown' as never, {})).rejects.toMatchObject({
statusCode: 501,
code: ERROR_CODES.AUTH_UNSUPPORTED_PROVIDER,
name: 'AppError',
});
});
it('error message matches exactly', async () => {
const authService = await getAuthService();
await expect(authService.handleSharedCallback('unknown' as never, {})).rejects.toThrow(
"OAuth provider 'unknown' is not supported for shared callback."
);
});
});
});