Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2202175
refactor(architecture): align with CSR pattern [MODULE-001]
RedaChannaCiscode Feb 2, 2026
0df1e5b
test(auth-service): implement testing infrastructure and AuthService …
RedaChannaCiscode Feb 2, 2026
81fd471
test(auth-controller): add integration tests (WIP - 13/25 passing)
RedaChannaCiscode Feb 2, 2026
eb46daf
test(services): add LoggerService & MailService tests
RedaChannaCiscode Feb 2, 2026
54265c7
test(services): add AdminRoleService & SeedService tests
RedaChannaCiscode Feb 2, 2026
23b8b75
test(services): add UsersService tests - 22 tests, 100% coverage
RedaChannaCiscode Feb 2, 2026
7b6aa69
test(services): add RolesService tests - 18 tests, 100% coverage
RedaChannaCiscode Feb 2, 2026
3f728b2
test(services): add PermissionsService tests - 14 tests, 100% coverage
RedaChannaCiscode Feb 2, 2026
c4bde5e
refactor(oauth): restructure OAuthService into modular architecture
RedaChannaCiscode Feb 2, 2026
af5bfeb
test(oauth): add comprehensive tests for refactored OAuth architecture
RedaChannaCiscode Feb 2, 2026
651952b
test(controllers): add unit tests for 4 controllers (Health, Permissi…
RedaChannaCiscode Feb 2, 2026
79c7a15
test(guards): add unit tests for 3 guards + fix configuration error h…
RedaChannaCiscode Feb 2, 2026
204c9a0
refactor(auth-kit): complete code quality refactoring and test organi…
RedaChannaCiscode Feb 2, 2026
d8d72fd
refactor(module): align architecture to CSR pattern [MODULE-001]
RedaChannaCiscode Feb 4, 2026
e450460
docs: complete API documentation with Swagger and structured error co…
RedaChannaCiscode Feb 4, 2026
671efe4
docs(copilot): update Copilot instructions to align with current arch…
RedaChannaCiscode Feb 4, 2026
9d0e9c7
feat(rbac): implement manual permission query and fix role/permission…
RedaChannaCiscode Feb 5, 2026
5750ade
test(oauth): stabilize FacebookOAuthProvider spec and fix mongoose ch…
RedaChannaCiscode Feb 5, 2026
f1e368b
fix: Rename workflow file - remove space from ci .yml to ci.yml
Zaiidmo Mar 5, 2026
bb34cdf
Merge branch 'develop' of github.com:CISCODE-MA/AuthKit into refactor…
Zaiidmo Mar 5, 2026
5ffa8b6
fix: resolve merge conflicts and dependency issues
Zaiidmo Mar 5, 2026
3783351
chore: updated npm threshhold for branches;
Zaiidmo Mar 5, 2026
1d15dfe
fix: align prettier config and scripts with develop branch
Zaiidmo Mar 5, 2026
f18f35a
chore: cleanup script files and gitignore
Zaiidmo Mar 5, 2026
a73bfb5
fix: add explicit cache-dependency-path to CI workflow
Zaiidmo Mar 5, 2026
cb1c539
ops: added write permission to the prettier step
Zaiidmo Mar 5, 2026
00bb4c8
fix(security): replace hardcoded passwords with constant in RBAC tests
Zaiidmo Mar 5, 2026
64e8f3b
refactor(tests): extract common test utilities to reduce duplication
Zaiidmo Mar 5, 2026
8b6d1b2
fix(security): resolve remaining hardcoded password violations
Zaiidmo Mar 5, 2026
e31bc7a
refactor(tests): consolidate duplicate placeholder tests
Zaiidmo Mar 5, 2026
77102c2
fix(security): eliminate hardcoded passwords using dynamic generation
Zaiidmo Mar 5, 2026
59a3eae
fix(security): eliminate ALL password literals using dynamic constants
Zaiidmo Mar 5, 2026
2b02992
fix(security): replace weak password literal in test
Zaiidmo Mar 5, 2026
d343990
Merge branch 'develop' into refactor/MODULE-001-align-architecture-csr
Zaiidmo Mar 5, 2026
1e8d71e
1.6.0
Zaiidmo Mar 5, 2026
44ce85d
Merge branch 'refactor/MODULE-001-align-architecture-csr' of github.c…
Zaiidmo Mar 5, 2026
09c3d67
docs: add comprehensive v1.6.0 changeset
Zaiidmo Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test(controllers): add unit tests for 4 controllers (Health, Permissi…
…ons, Roles, Users)

- Add 23 new controller tests (all passing)
- Coverage increased from 59.67% to 68.64% (+9%)
- Override guards (AdminGuard, AuthenticateGuard) to avoid complex DI in tests

Test Coverage:
- HealthController: 6 tests - checkSmtp (connected/disconnected/error/config masking), checkAll
- PermissionsController: 4 tests - CRUD operations (create, list, update, delete)
- RolesController: 5 tests - CRUD + setPermissions
- UsersController: 8 tests - create, list (with filters), ban/unban, delete, updateRoles

Total tests: 222/234 passing (94.9% pass rate)
Remaining 12 failures: AuthController integration tests (known WIP)

[MODULE-001]
  • Loading branch information
RedaChannaCiscode committed Feb 2, 2026
commit 651952b61052fa18c07547a09df2d6cc92ba7d16
123 changes: 123 additions & 0 deletions src/controllers/health.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HealthController } from './health.controller';
import { MailService } from '@services/mail.service';
import { LoggerService } from '@services/logger.service';

describe('HealthController', () => {
let controller: HealthController;
let mockMailService: jest.Mocked<MailService>;
let mockLoggerService: jest.Mocked<LoggerService>;

beforeEach(async () => {
mockMailService = {
verifyConnection: jest.fn(),
} as any;

mockLoggerService = {
error: jest.fn(),
log: jest.fn(),
} as any;

const module: TestingModule = await Test.createTestingModule({
controllers: [HealthController],
providers: [
{ provide: MailService, useValue: mockMailService },
{ provide: LoggerService, useValue: mockLoggerService },
],
}).compile();

controller = module.get<HealthController>(HealthController);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('checkSmtp', () => {
it('should return connected status when SMTP is working', async () => {
mockMailService.verifyConnection.mockResolvedValue({
connected: true,
});

const result = await controller.checkSmtp();

expect(result).toMatchObject({
service: 'smtp',
status: 'connected',
});
expect((result as any).config).toBeDefined();
expect(mockMailService.verifyConnection).toHaveBeenCalled();
});

it('should return disconnected status when SMTP fails', async () => {
mockMailService.verifyConnection.mockResolvedValue({
connected: false,
error: 'Connection timeout',
});

const result = await controller.checkSmtp();

expect(result).toMatchObject({
service: 'smtp',
status: 'disconnected',
error: 'Connection timeout',
});
expect(mockMailService.verifyConnection).toHaveBeenCalled();
});

it('should handle exceptions and log errors', async () => {
const error = new Error('SMTP crashed');
mockMailService.verifyConnection.mockRejectedValue(error);

const result = await controller.checkSmtp();

expect(result).toMatchObject({
service: 'smtp',
status: 'error',
});
expect(mockLoggerService.error).toHaveBeenCalledWith(
expect.stringContaining('SMTP health check failed'),
error.stack,
'HealthController',
);
});

it('should mask sensitive config values', async () => {
process.env.SMTP_USER = 'testuser@example.com';
mockMailService.verifyConnection.mockResolvedValue({ connected: true });

const result = await controller.checkSmtp();

expect((result as any).config.user).toMatch(/^\*\*\*/);
expect((result as any).config.user).not.toContain('testuser');
});
});

describe('checkAll', () => {
it('should return overall health status', async () => {
mockMailService.verifyConnection.mockResolvedValue({ connected: true });

const result = await controller.checkAll();

expect(result).toMatchObject({
status: 'healthy',
checks: {
smtp: expect.objectContaining({ service: 'smtp' }),
},
environment: expect.any(Object),
});
});

it('should return degraded status when SMTP fails', async () => {
mockMailService.verifyConnection.mockResolvedValue({
connected: false,
error: 'Connection failed',
});

const result = await controller.checkAll();

expect(result.status).toBe('degraded');
expect(result.checks.smtp.status).toBe('disconnected');
});
});
});
114 changes: 114 additions & 0 deletions src/controllers/permissions.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Response } from 'express';
import { PermissionsController } from './permissions.controller';
import { PermissionsService } from '@services/permissions.service';
import { CreatePermissionDto } from '@dto/permission/create-permission.dto';
import { UpdatePermissionDto } from '@dto/permission/update-permission.dto';
import { AdminGuard } from '@guards/admin.guard';
import { AuthenticateGuard } from '@guards/authenticate.guard';

describe('PermissionsController', () => {
let controller: PermissionsController;
let mockService: jest.Mocked<PermissionsService>;
let mockResponse: Partial<Response>;

beforeEach(async () => {
mockService = {
create: jest.fn(),
list: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
} as any;

mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [PermissionsController],
providers: [{ provide: PermissionsService, useValue: mockService }],
})
.overrideGuard(AdminGuard)
.useValue({ canActivate: () => true })
.overrideGuard(AuthenticateGuard)
.useValue({ canActivate: () => true })
.compile();

controller = module.get<PermissionsController>(PermissionsController);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('create', () => {
it('should create a permission and return 201', async () => {
const dto: CreatePermissionDto = {
name: 'read:users',
description: 'Read users',
};
const created = { _id: 'perm-id', ...dto };

mockService.create.mockResolvedValue(created as any);

await controller.create(dto, mockResponse as Response);

expect(mockService.create).toHaveBeenCalledWith(dto);
expect(mockResponse.status).toHaveBeenCalledWith(201);
expect(mockResponse.json).toHaveBeenCalledWith(created);
});
});

describe('list', () => {
it('should return all permissions with 200', async () => {
const permissions = [
{ _id: 'p1', name: 'read:users', description: 'Read' },
{ _id: 'p2', name: 'write:users', description: 'Write' },
];

mockService.list.mockResolvedValue(permissions as any);

await controller.list(mockResponse as Response);

expect(mockService.list).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(permissions);
});
});

describe('update', () => {
it('should update a permission and return 200', async () => {
const dto: UpdatePermissionDto = {
description: 'Updated description',
};
const updated = {
_id: 'perm-id',
name: 'read:users',
description: 'Updated description',
};

mockService.update.mockResolvedValue(updated as any);

await controller.update('perm-id', dto, mockResponse as Response);

expect(mockService.update).toHaveBeenCalledWith('perm-id', dto);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(updated);
});
});

describe('delete', () => {
it('should delete a permission and return 200', async () => {
const deleted = { ok: true };

mockService.delete.mockResolvedValue(deleted as any);

await controller.delete('perm-id', mockResponse as Response);

expect(mockService.delete).toHaveBeenCalledWith('perm-id');
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(deleted);
});
});
});
135 changes: 135 additions & 0 deletions src/controllers/roles.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Response } from 'express';
import { RolesController } from './roles.controller';
import { RolesService } from '@services/roles.service';
import { CreateRoleDto } from '@dto/role/create-role.dto';
import { UpdateRoleDto, UpdateRolePermissionsDto } from '@dto/role/update-role.dto';
import { AdminGuard } from '@guards/admin.guard';
import { AuthenticateGuard } from '@guards/authenticate.guard';

describe('RolesController', () => {
let controller: RolesController;
let mockService: jest.Mocked<RolesService>;
let mockResponse: Partial<Response>;

beforeEach(async () => {
mockService = {
create: jest.fn(),
list: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
setPermissions: jest.fn(),
} as any;

mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [RolesController],
providers: [{ provide: RolesService, useValue: mockService }],
})
.overrideGuard(AdminGuard)
.useValue({ canActivate: () => true })
.overrideGuard(AuthenticateGuard)
.useValue({ canActivate: () => true })
.compile();

controller = module.get<RolesController>(RolesController);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('create', () => {
it('should create a role and return 201', async () => {
const dto: CreateRoleDto = {
name: 'editor',
};
const created = { _id: 'role-id', ...dto, permissions: [] };

mockService.create.mockResolvedValue(created as any);

await controller.create(dto, mockResponse as Response);

expect(mockService.create).toHaveBeenCalledWith(dto);
expect(mockResponse.status).toHaveBeenCalledWith(201);
expect(mockResponse.json).toHaveBeenCalledWith(created);
});
});

describe('list', () => {
it('should return all roles with 200', async () => {
const roles = [
{ _id: 'r1', name: 'admin', permissions: [] },
{ _id: 'r2', name: 'user', permissions: [] },
];

mockService.list.mockResolvedValue(roles as any);

await controller.list(mockResponse as Response);

expect(mockService.list).toHaveBeenCalled();
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(roles);
});
});

describe('update', () => {
it('should update a role and return 200', async () => {
const dto: UpdateRoleDto = {
name: 'editor-updated',
};
const updated = {
_id: 'role-id',
name: 'editor-updated',
permissions: [],
};

mockService.update.mockResolvedValue(updated as any);

await controller.update('role-id', dto, mockResponse as Response);

expect(mockService.update).toHaveBeenCalledWith('role-id', dto);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(updated);
});
});

describe('delete', () => {
it('should delete a role and return 200', async () => {
const deleted = { ok: true };

mockService.delete.mockResolvedValue(deleted as any);

await controller.delete('role-id', mockResponse as Response);

expect(mockService.delete).toHaveBeenCalledWith('role-id');
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(deleted);
});
});

describe('setPermissions', () => {
it('should update role permissions and return 200', async () => {
const dto: UpdateRolePermissionsDto = {
permissions: ['perm-1', 'perm-2'],
};
const updated = {
_id: 'role-id',
name: 'editor',
permissions: ['perm-1', 'perm-2'],
};

mockService.setPermissions.mockResolvedValue(updated as any);

await controller.setPermissions('role-id', dto, mockResponse as Response);

expect(mockService.setPermissions).toHaveBeenCalledWith('role-id', dto.permissions);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith(updated);
});
});
});
Loading