mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 20:26:40 +02:00
first auth impl
This commit is contained in:
parent
8f7c63950c
commit
2a002bf6be
79 changed files with 13355 additions and 6076 deletions
695
services/mana-core-auth/src/auth/auth.controller.spec.ts
Normal file
695
services/mana-core-auth/src/auth/auth.controller.spec.ts
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
/**
|
||||
* AuthController Unit Tests
|
||||
*
|
||||
* Tests all authentication controller endpoints using BetterAuthService:
|
||||
*
|
||||
* B2C Endpoints:
|
||||
* - POST /auth/register - User registration
|
||||
* - POST /auth/login - User login
|
||||
* - POST /auth/logout - User logout
|
||||
* - POST /auth/refresh - Token refresh
|
||||
* - GET /auth/session - Get current session
|
||||
* - POST /auth/validate - Token validation
|
||||
*
|
||||
* B2B Endpoints:
|
||||
* - POST /auth/register/b2b - Organization registration
|
||||
* - GET /auth/organizations - List organizations
|
||||
* - GET /auth/organizations/:id - Get organization
|
||||
* - GET /auth/organizations/:id/members - Get organization members
|
||||
* - POST /auth/organizations/:id/invite - Invite employee
|
||||
* - POST /auth/organizations/accept-invitation - Accept invitation
|
||||
* - DELETE /auth/organizations/:id/members/:memberId - Remove member
|
||||
* - POST /auth/organizations/set-active - Set active organization
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import {
|
||||
UnauthorizedException,
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { BetterAuthService } from './services/better-auth.service';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { mockDtoFactory } from '../__tests__/utils/mock-factories';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
let betterAuthService: jest.Mocked<BetterAuthService>;
|
||||
|
||||
// Common test data
|
||||
const mockAuthHeader = 'Bearer valid-jwt-token';
|
||||
const mockToken = 'valid-jwt-token';
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create mock BetterAuthService with all methods
|
||||
const mockBetterAuthService = {
|
||||
registerB2C: jest.fn(),
|
||||
registerB2B: jest.fn(),
|
||||
signIn: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
getSession: jest.fn(),
|
||||
listOrganizations: jest.fn(),
|
||||
getOrganization: jest.fn(),
|
||||
getOrganizationMembers: jest.fn(),
|
||||
inviteEmployee: jest.fn(),
|
||||
acceptInvitation: jest.fn(),
|
||||
removeMember: jest.fn(),
|
||||
setActiveOrganization: jest.fn(),
|
||||
refreshToken: jest.fn(),
|
||||
validateToken: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
{
|
||||
provide: BetterAuthService,
|
||||
useValue: mockBetterAuthService,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(JwtAuthGuard)
|
||||
.useValue({ canActivate: jest.fn(() => true) })
|
||||
.compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
betterAuthService = module.get(BetterAuthService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/register (B2C)
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/register', () => {
|
||||
it('should successfully register a new B2C user', async () => {
|
||||
const registerDto = mockDtoFactory.register({
|
||||
email: 'newuser@example.com',
|
||||
password: 'SecurePassword123!',
|
||||
name: 'New User',
|
||||
});
|
||||
|
||||
const expectedResult = {
|
||||
user: {
|
||||
id: 'user-123',
|
||||
email: registerDto.email,
|
||||
name: registerDto.name,
|
||||
},
|
||||
token: 'jwt-token',
|
||||
};
|
||||
|
||||
betterAuthService.registerB2C.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.register(registerDto);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.registerB2C).toHaveBeenCalledWith({
|
||||
email: registerDto.email,
|
||||
password: registerDto.password,
|
||||
name: registerDto.name,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle registration without name', async () => {
|
||||
const registerDto = {
|
||||
email: 'noname@example.com',
|
||||
password: 'SecurePassword123!',
|
||||
};
|
||||
|
||||
const expectedResult = {
|
||||
user: { id: 'user-456', email: registerDto.email, name: '' },
|
||||
token: 'jwt-token',
|
||||
};
|
||||
|
||||
betterAuthService.registerB2C.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.register(registerDto as any);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.registerB2C).toHaveBeenCalledWith({
|
||||
email: registerDto.email,
|
||||
password: registerDto.password,
|
||||
name: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should propagate ConflictException when user exists', async () => {
|
||||
const registerDto = mockDtoFactory.register({ email: 'existing@example.com' });
|
||||
|
||||
betterAuthService.registerB2C.mockRejectedValue(
|
||||
new ConflictException('User with this email already exists')
|
||||
);
|
||||
|
||||
await expect(controller.register(registerDto)).rejects.toThrow(ConflictException);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/login
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/login', () => {
|
||||
it('should successfully login a user', async () => {
|
||||
const loginDto = mockDtoFactory.login({
|
||||
email: 'user@example.com',
|
||||
password: 'SecurePassword123!',
|
||||
});
|
||||
|
||||
const expectedResult = {
|
||||
user: {
|
||||
id: 'user-123',
|
||||
email: loginDto.email,
|
||||
name: 'Test User',
|
||||
role: 'user',
|
||||
},
|
||||
token: 'jwt-access-token',
|
||||
};
|
||||
|
||||
betterAuthService.signIn.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.login(loginDto);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.signIn).toHaveBeenCalledWith({
|
||||
email: loginDto.email,
|
||||
password: loginDto.password,
|
||||
deviceId: undefined,
|
||||
deviceName: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass device info when provided', async () => {
|
||||
const loginDto = {
|
||||
email: 'user@example.com',
|
||||
password: 'SecurePassword123!',
|
||||
deviceId: 'device-abc-123',
|
||||
deviceName: 'iPhone 15 Pro',
|
||||
};
|
||||
|
||||
betterAuthService.signIn.mockResolvedValue({
|
||||
user: { id: '123', email: 'user@example.com', name: 'Test', role: 'user' },
|
||||
token: 'token',
|
||||
});
|
||||
|
||||
await controller.login(loginDto);
|
||||
|
||||
expect(betterAuthService.signIn).toHaveBeenCalledWith({
|
||||
email: loginDto.email,
|
||||
password: loginDto.password,
|
||||
deviceId: 'device-abc-123',
|
||||
deviceName: 'iPhone 15 Pro',
|
||||
});
|
||||
});
|
||||
|
||||
it('should propagate UnauthorizedException for invalid credentials', async () => {
|
||||
const loginDto = mockDtoFactory.login({ password: 'WrongPassword' });
|
||||
|
||||
betterAuthService.signIn.mockRejectedValue(
|
||||
new UnauthorizedException('Invalid email or password')
|
||||
);
|
||||
|
||||
await expect(controller.login(loginDto)).rejects.toThrow(UnauthorizedException);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/logout
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/logout', () => {
|
||||
it('should successfully logout a user', async () => {
|
||||
const expectedResult = { success: true, message: 'Signed out successfully' };
|
||||
|
||||
betterAuthService.signOut.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.logout(mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.signOut).toHaveBeenCalledWith(mockToken);
|
||||
});
|
||||
|
||||
it('should extract token from Bearer header', async () => {
|
||||
betterAuthService.signOut.mockResolvedValue({ success: true, message: 'Signed out' });
|
||||
|
||||
await controller.logout('Bearer my-secret-token');
|
||||
|
||||
expect(betterAuthService.signOut).toHaveBeenCalledWith('my-secret-token');
|
||||
});
|
||||
|
||||
it('should handle raw token without Bearer prefix', async () => {
|
||||
betterAuthService.signOut.mockResolvedValue({ success: true, message: 'Signed out' });
|
||||
|
||||
await controller.logout('raw-token');
|
||||
|
||||
expect(betterAuthService.signOut).toHaveBeenCalledWith('raw-token');
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/refresh
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/refresh', () => {
|
||||
it('should successfully refresh tokens', async () => {
|
||||
const refreshTokenDto = { refreshToken: 'valid-refresh-token' };
|
||||
|
||||
const expectedResult = {
|
||||
accessToken: 'new-access-token',
|
||||
refreshToken: 'new-refresh-token',
|
||||
expiresIn: 900,
|
||||
tokenType: 'Bearer',
|
||||
user: { id: 'user-123', email: 'user@example.com', name: 'Test', role: 'user' as const },
|
||||
};
|
||||
|
||||
betterAuthService.refreshToken.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.refresh(refreshTokenDto);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.refreshToken).toHaveBeenCalledWith('valid-refresh-token');
|
||||
});
|
||||
|
||||
it('should propagate UnauthorizedException for invalid refresh token', async () => {
|
||||
const refreshTokenDto = { refreshToken: 'invalid-token' };
|
||||
|
||||
betterAuthService.refreshToken.mockRejectedValue(
|
||||
new UnauthorizedException('Invalid refresh token')
|
||||
);
|
||||
|
||||
await expect(controller.refresh(refreshTokenDto)).rejects.toThrow(UnauthorizedException);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// GET /auth/session
|
||||
// ============================================================================
|
||||
|
||||
describe('GET /auth/session', () => {
|
||||
it('should return current session', async () => {
|
||||
const expectedResult = {
|
||||
user: { id: 'user-123', email: 'user@example.com', name: 'Test' },
|
||||
session: { id: 'session-123', activeOrganizationId: null },
|
||||
};
|
||||
|
||||
betterAuthService.getSession.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.getSession(mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.getSession).toHaveBeenCalledWith(mockToken);
|
||||
});
|
||||
|
||||
it('should propagate UnauthorizedException for invalid session', async () => {
|
||||
betterAuthService.getSession.mockRejectedValue(
|
||||
new UnauthorizedException('Invalid or expired session')
|
||||
);
|
||||
|
||||
await expect(controller.getSession(mockAuthHeader)).rejects.toThrow(UnauthorizedException);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/validate
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/validate', () => {
|
||||
it('should return valid for a valid token', async () => {
|
||||
const body = { token: 'valid-jwt-token' };
|
||||
|
||||
const expectedResult = {
|
||||
valid: true,
|
||||
payload: { sub: 'user-123', email: 'user@example.com', role: 'user' },
|
||||
};
|
||||
|
||||
betterAuthService.validateToken.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.validate(body);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.validateToken).toHaveBeenCalledWith(body.token);
|
||||
});
|
||||
|
||||
it('should return invalid for expired token', async () => {
|
||||
const body = { token: 'expired-token' };
|
||||
|
||||
betterAuthService.validateToken.mockResolvedValue({ valid: false, error: 'Token expired' } as any);
|
||||
|
||||
const result = await controller.validate(body);
|
||||
|
||||
expect((result as any).valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/register/b2b
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/register/b2b', () => {
|
||||
it('should successfully register a B2B organization', async () => {
|
||||
const registerDto = {
|
||||
ownerEmail: 'owner@acme.com',
|
||||
password: 'SecurePassword123!',
|
||||
ownerName: 'John Owner',
|
||||
organizationName: 'Acme Corporation',
|
||||
};
|
||||
|
||||
const expectedResult = {
|
||||
user: { id: 'user-123', email: registerDto.ownerEmail, name: registerDto.ownerName },
|
||||
organization: { id: 'org-456', name: 'Acme Corporation', slug: 'acme-corporation' },
|
||||
token: 'jwt-token',
|
||||
};
|
||||
|
||||
betterAuthService.registerB2B.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.registerB2B(registerDto);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.registerB2B).toHaveBeenCalledWith(registerDto);
|
||||
});
|
||||
|
||||
it('should propagate ConflictException when owner email exists', async () => {
|
||||
const registerDto = {
|
||||
ownerEmail: 'existing@acme.com',
|
||||
password: 'SecurePassword123!',
|
||||
ownerName: 'John',
|
||||
organizationName: 'Acme',
|
||||
};
|
||||
|
||||
betterAuthService.registerB2B.mockRejectedValue(
|
||||
new ConflictException('Owner email already exists')
|
||||
);
|
||||
|
||||
await expect(controller.registerB2B(registerDto)).rejects.toThrow(ConflictException);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// GET /auth/organizations
|
||||
// ============================================================================
|
||||
|
||||
describe('GET /auth/organizations', () => {
|
||||
it('should list user organizations', async () => {
|
||||
const expectedResult = {
|
||||
organizations: [
|
||||
{ id: 'org-1', name: 'Org One', slug: 'org-one' },
|
||||
{ id: 'org-2', name: 'Org Two', slug: 'org-two' },
|
||||
],
|
||||
};
|
||||
|
||||
betterAuthService.listOrganizations.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.listOrganizations(mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.listOrganizations).toHaveBeenCalledWith(mockToken);
|
||||
});
|
||||
|
||||
it('should return empty array when user has no organizations', async () => {
|
||||
betterAuthService.listOrganizations.mockResolvedValue({ organizations: [] });
|
||||
|
||||
const result = await controller.listOrganizations(mockAuthHeader);
|
||||
|
||||
expect(result.organizations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// GET /auth/organizations/:id
|
||||
// ============================================================================
|
||||
|
||||
describe('GET /auth/organizations/:id', () => {
|
||||
it('should get organization details', async () => {
|
||||
const orgId = 'org-123';
|
||||
const expectedResult = {
|
||||
id: orgId,
|
||||
name: 'Acme Corp',
|
||||
slug: 'acme-corp',
|
||||
members: [{ id: 'member-1', userId: 'user-1', role: 'owner' }],
|
||||
};
|
||||
|
||||
betterAuthService.getOrganization.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.getOrganization(orgId, mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.getOrganization).toHaveBeenCalledWith(orgId, mockToken);
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when organization not found', async () => {
|
||||
betterAuthService.getOrganization.mockRejectedValue(
|
||||
new NotFoundException('Organization not found')
|
||||
);
|
||||
|
||||
await expect(controller.getOrganization('invalid-id', mockAuthHeader)).rejects.toThrow(
|
||||
NotFoundException
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// GET /auth/organizations/:id/members
|
||||
// ============================================================================
|
||||
|
||||
describe('GET /auth/organizations/:id/members', () => {
|
||||
it('should get organization members', async () => {
|
||||
const orgId = 'org-123';
|
||||
const expectedMembers = [
|
||||
{ id: 'member-1', userId: 'user-1', organizationId: orgId, role: 'owner' },
|
||||
{ id: 'member-2', userId: 'user-2', organizationId: orgId, role: 'member' },
|
||||
];
|
||||
|
||||
betterAuthService.getOrganizationMembers.mockResolvedValue(expectedMembers as any);
|
||||
|
||||
const result = await controller.getOrganizationMembers(orgId);
|
||||
|
||||
expect(result).toEqual(expectedMembers);
|
||||
expect(betterAuthService.getOrganizationMembers).toHaveBeenCalledWith(orgId);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/organizations/:id/invite
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/organizations/:id/invite', () => {
|
||||
it('should invite an employee to organization', async () => {
|
||||
const orgId = 'org-123';
|
||||
const inviteDto = { organizationId: orgId, employeeEmail: 'employee@acme.com', role: 'member' as const };
|
||||
|
||||
const expectedResult = {
|
||||
id: 'invitation-123',
|
||||
email: 'employee@acme.com',
|
||||
organizationId: orgId,
|
||||
role: 'member',
|
||||
status: 'pending',
|
||||
};
|
||||
|
||||
betterAuthService.inviteEmployee.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.inviteEmployee(orgId, inviteDto, mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.inviteEmployee).toHaveBeenCalledWith({
|
||||
organizationId: orgId,
|
||||
employeeEmail: 'employee@acme.com',
|
||||
role: 'member',
|
||||
inviterToken: mockToken,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException when inviter lacks permission', async () => {
|
||||
const orgId = 'org-123';
|
||||
const inviteDto = { organizationId: orgId, employeeEmail: 'employee@acme.com', role: 'member' as const };
|
||||
|
||||
betterAuthService.inviteEmployee.mockRejectedValue(
|
||||
new ForbiddenException('You do not have permission to invite members')
|
||||
);
|
||||
|
||||
await expect(controller.inviteEmployee(orgId, inviteDto, mockAuthHeader)).rejects.toThrow(
|
||||
ForbiddenException
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/organizations/accept-invitation
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/organizations/accept-invitation', () => {
|
||||
it('should accept an invitation', async () => {
|
||||
const acceptDto = { invitationId: 'invitation-123' };
|
||||
|
||||
const expectedResult = {
|
||||
member: { id: 'member-123', userId: 'user-456', organizationId: 'org-123', role: 'member' },
|
||||
organization: { id: 'org-123', name: 'Acme Corp' },
|
||||
};
|
||||
|
||||
betterAuthService.acceptInvitation.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.acceptInvitation(acceptDto, mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.acceptInvitation).toHaveBeenCalledWith({
|
||||
invitationId: 'invitation-123',
|
||||
userToken: mockToken,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when invitation not found', async () => {
|
||||
const acceptDto = { invitationId: 'invalid-invitation' };
|
||||
|
||||
betterAuthService.acceptInvitation.mockRejectedValue(
|
||||
new NotFoundException('Invitation not found or expired')
|
||||
);
|
||||
|
||||
await expect(controller.acceptInvitation(acceptDto, mockAuthHeader)).rejects.toThrow(
|
||||
NotFoundException
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// DELETE /auth/organizations/:id/members/:memberId
|
||||
// ============================================================================
|
||||
|
||||
describe('DELETE /auth/organizations/:id/members/:memberId', () => {
|
||||
it('should remove a member from organization', async () => {
|
||||
const orgId = 'org-123';
|
||||
const memberId = 'member-456';
|
||||
|
||||
const expectedResult = { success: true, message: 'Member removed successfully' };
|
||||
|
||||
betterAuthService.removeMember.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await controller.removeMember(orgId, memberId, mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.removeMember).toHaveBeenCalledWith({
|
||||
organizationId: orgId,
|
||||
memberId,
|
||||
removerToken: mockToken,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw ForbiddenException when remover lacks permission', async () => {
|
||||
betterAuthService.removeMember.mockRejectedValue(
|
||||
new ForbiddenException('You do not have permission to remove members')
|
||||
);
|
||||
|
||||
await expect(controller.removeMember('org-123', 'member-456', mockAuthHeader)).rejects.toThrow(
|
||||
ForbiddenException
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// POST /auth/organizations/set-active
|
||||
// ============================================================================
|
||||
|
||||
describe('POST /auth/organizations/set-active', () => {
|
||||
it('should set active organization', async () => {
|
||||
const setActiveDto = { organizationId: 'org-123' };
|
||||
|
||||
const expectedResult = {
|
||||
userId: 'user-123',
|
||||
activeOrganizationId: 'org-123',
|
||||
};
|
||||
|
||||
betterAuthService.setActiveOrganization.mockResolvedValue(expectedResult as any);
|
||||
|
||||
const result = await controller.setActiveOrganization(setActiveDto, mockAuthHeader);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(betterAuthService.setActiveOrganization).toHaveBeenCalledWith({
|
||||
organizationId: 'org-123',
|
||||
userToken: mockToken,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when not a member', async () => {
|
||||
const setActiveDto = { organizationId: 'org-999' };
|
||||
|
||||
betterAuthService.setActiveOrganization.mockRejectedValue(
|
||||
new NotFoundException('Organization not found or you are not a member')
|
||||
);
|
||||
|
||||
await expect(controller.setActiveOrganization(setActiveDto, mockAuthHeader)).rejects.toThrow(
|
||||
NotFoundException
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Guard Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Guards', () => {
|
||||
it('should have JwtAuthGuard on protected endpoints', () => {
|
||||
const protectedEndpoints: (keyof AuthController)[] = [
|
||||
'logout',
|
||||
'getSession',
|
||||
'listOrganizations',
|
||||
'getOrganization',
|
||||
'getOrganizationMembers',
|
||||
'inviteEmployee',
|
||||
'acceptInvitation',
|
||||
'removeMember',
|
||||
'setActiveOrganization',
|
||||
];
|
||||
|
||||
protectedEndpoints.forEach((endpoint) => {
|
||||
const guards = Reflect.getMetadata(
|
||||
'__guards__',
|
||||
AuthController.prototype[endpoint as keyof AuthController]
|
||||
);
|
||||
expect(guards).toBeDefined();
|
||||
expect(guards).toContain(JwtAuthGuard);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT have JwtAuthGuard on public endpoints', () => {
|
||||
const publicEndpoints: (keyof AuthController)[] = [
|
||||
'register',
|
||||
'login',
|
||||
'refresh',
|
||||
'validate',
|
||||
'registerB2B',
|
||||
];
|
||||
|
||||
publicEndpoints.forEach((endpoint) => {
|
||||
const guards = Reflect.getMetadata(
|
||||
'__guards__',
|
||||
AuthController.prototype[endpoint as keyof AuthController]
|
||||
);
|
||||
expect(guards).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Token Extraction Helper
|
||||
// ============================================================================
|
||||
|
||||
describe('Token Extraction', () => {
|
||||
it('should extract token from Bearer authorization header', async () => {
|
||||
betterAuthService.signOut.mockResolvedValue({ success: true, message: 'OK' });
|
||||
|
||||
await controller.logout('Bearer my-token-123');
|
||||
|
||||
expect(betterAuthService.signOut).toHaveBeenCalledWith('my-token-123');
|
||||
});
|
||||
|
||||
it('should handle missing authorization header', async () => {
|
||||
betterAuthService.signOut.mockResolvedValue({ success: true, message: 'OK' });
|
||||
|
||||
await controller.logout('');
|
||||
|
||||
expect(betterAuthService.signOut).toHaveBeenCalledWith('');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue