- Introduced new columns `status`, `created_by`, and `updated_by` in the `users` table to track user status and ownership. - Updated the `employees` table to include a foreign key reference to the `users` table via `user_id`. - Created migration script `0012_users_primary.sql` to apply these changes to the database schema. - Enhanced the `EmployeesService` and `EmployeesRepository` to support user assignments and related data retrieval. - Updated DTOs and service methods to reflect the new user and employee relationships. - Added unit tests to validate the new functionality and ensure data integrity. - Modified existing controllers to accommodate the new fields and relationships in user and employee management.
122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import { UnauthorizedException } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { DateTime } from '../../../common/value-objects/date-time/date-time';
|
|
import { Status } from '../../../common/value-objects/status/status';
|
|
import type { User } from '../../users/user';
|
|
import { UsersService } from '../../users/users.service';
|
|
import { RevokedAccessTokensRepository } from '../revoked-access-tokens.repository';
|
|
import { JwtStrategy } from './jwt.strategy';
|
|
|
|
describe('JwtStrategy', () => {
|
|
let strategy: JwtStrategy;
|
|
let usersService: jest.Mocked<
|
|
Pick<UsersService, 'findById' | 'assertCanAuthenticate'>
|
|
>;
|
|
let revoked: jest.Mocked<Pick<RevokedAccessTokensRepository, 'exists'>>;
|
|
|
|
const now = DateTime.fromUnixMs(1_700_000_000_000);
|
|
const user: User = {
|
|
id: 'user-1',
|
|
username: 'alice',
|
|
passwordHash: 'hash',
|
|
privilegeId: null,
|
|
isSuperadmin: false,
|
|
status: Status.create('active'),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
createdBy: 'user-1',
|
|
updatedBy: 'user-1',
|
|
privilege: null,
|
|
employee: null,
|
|
createdByUser: { id: 'user-1', username: 'alice' },
|
|
updatedByUser: { id: 'user-1', username: 'alice' },
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
usersService = {
|
|
findById: jest.fn(),
|
|
assertCanAuthenticate: jest.fn(),
|
|
};
|
|
revoked = { exists: jest.fn() };
|
|
|
|
const moduleRef: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
JwtStrategy,
|
|
{
|
|
provide: ConfigService,
|
|
useValue: {
|
|
getOrThrow: () => 'test-secret-at-least-32-characters-long!!',
|
|
},
|
|
},
|
|
{ provide: UsersService, useValue: usersService },
|
|
{ provide: RevokedAccessTokensRepository, useValue: revoked },
|
|
],
|
|
}).compile();
|
|
|
|
strategy = moduleRef.get(JwtStrategy);
|
|
});
|
|
|
|
it('returns AuthUser for a valid access payload', async () => {
|
|
revoked.exists.mockResolvedValue(false);
|
|
usersService.findById.mockResolvedValue(user);
|
|
|
|
await expect(
|
|
strategy.validate({
|
|
sub: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
typ: 'access',
|
|
}),
|
|
).resolves.toEqual({
|
|
id: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
isSuperadmin: false,
|
|
});
|
|
});
|
|
|
|
it('maps isSuperadmin from the persisted user', async () => {
|
|
revoked.exists.mockResolvedValue(false);
|
|
usersService.findById.mockResolvedValue({ ...user, isSuperadmin: true });
|
|
|
|
await expect(
|
|
strategy.validate({
|
|
sub: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
typ: 'access',
|
|
}),
|
|
).resolves.toEqual({
|
|
id: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
isSuperadmin: true,
|
|
});
|
|
});
|
|
|
|
it('rejects revoked access tokens', async () => {
|
|
revoked.exists.mockResolvedValue(true);
|
|
|
|
await expect(
|
|
strategy.validate({
|
|
sub: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
typ: 'access',
|
|
}),
|
|
).rejects.toBeInstanceOf(UnauthorizedException);
|
|
});
|
|
|
|
it('rejects non-access token types', async () => {
|
|
await expect(
|
|
strategy.validate({
|
|
sub: 'user-1',
|
|
username: 'alice',
|
|
jti: 'jti-1',
|
|
typ: 'refresh' as 'access',
|
|
}),
|
|
).rejects.toBeInstanceOf(UnauthorizedException);
|
|
});
|
|
});
|