Skip to content

Commit

Permalink
Merge pull request #27 from w3bdesign/backend
Browse files Browse the repository at this point in the history
Backend tests and coverage
  • Loading branch information
w3bdesign authored Nov 19, 2024
2 parents 95913b0 + 6949f7e commit e37aaac
Show file tree
Hide file tree
Showing 17 changed files with 1,636 additions and 150 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![codecov](https://codecov.io/gh/w3bdesign/frisorsalong-booking/graph/badge.svg?token=YDY1N2NMWA)](https://codecov.io/gh/w3bdesign/frisorsalong-booking)

# Hair Salon Booking System

## This is still Work In Progress (WIP)!
Expand Down
17 changes: 16 additions & 1 deletion backend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@ module.exports = {
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: ['**/*.(t|j)s'],
collectCoverageFrom: [
'**/*.(t|j)s',
'!**/*.spec.ts',
'!**/*.test.ts',
'!**/node_modules/**',
'!**/dist/**',
'!**/coverage/**',
],
coverageDirectory: '../coverage',
testEnvironment: 'node',
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/$1',
},
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
108 changes: 108 additions & 0 deletions backend/src/auth/guards/roles.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Reflector } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common';
import { RolesGuard } from './roles.guard';
import { UserRole } from '../../users/entities/user.entity';

describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector;

const mockReflector = {
getAllAndOverride: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
RolesGuard,
{
provide: Reflector,
useValue: mockReflector,
},
],
}).compile();

guard = module.get<RolesGuard>(RolesGuard);
reflector = module.get<Reflector>(Reflector);
});

it('should be defined', () => {
expect(guard).toBeDefined();
});

describe('canActivate', () => {
let mockExecutionContext: ExecutionContext;

beforeEach(() => {
mockExecutionContext = {
getHandler: jest.fn(),
getClass: jest.fn(),
switchToHttp: jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({
user: {
role: UserRole.CUSTOMER,
},
}),
}),
} as unknown as ExecutionContext;
});

it('should allow access when no roles are required', () => {
mockReflector.getAllAndOverride.mockReturnValue(null);

const result = guard.canActivate(mockExecutionContext);

expect(result).toBe(true);
expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
mockExecutionContext.getHandler(),
mockExecutionContext.getClass(),
]);
});

it('should allow access when user has required role', () => {
mockReflector.getAllAndOverride.mockReturnValue([UserRole.CUSTOMER]);

const result = guard.canActivate(mockExecutionContext);

expect(result).toBe(true);
});

it('should deny access when user does not have required role', () => {
mockReflector.getAllAndOverride.mockReturnValue([UserRole.ADMIN]);

const result = guard.canActivate(mockExecutionContext);

expect(result).toBe(false);
});

it('should allow access when user has one of multiple required roles', () => {
mockReflector.getAllAndOverride.mockReturnValue([
UserRole.ADMIN,
UserRole.CUSTOMER,
]);

const result = guard.canActivate(mockExecutionContext);

expect(result).toBe(true);
});

it('should handle roles defined at both handler and class level', () => {
mockReflector.getAllAndOverride.mockReturnValue([UserRole.CUSTOMER]);

const mockContext = {
...mockExecutionContext,
getHandler: jest.fn(),
getClass: jest.fn(),
};

const result = guard.canActivate(mockContext as ExecutionContext);

expect(result).toBe(true);
expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
mockContext.getHandler(),
mockContext.getClass(),
]);
});
});
});
154 changes: 154 additions & 0 deletions backend/src/database/migrations/1731981975581-InitialMigration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { QueryRunner } from 'typeorm';
import { InitialMigration1731981975581 } from './1731981975581-InitialMigration';

describe('InitialMigration1731981975581', () => {
let migration: InitialMigration1731981975581;
let queryRunner: QueryRunner;

beforeEach(() => {
migration = new InitialMigration1731981975581();
queryRunner = {
query: jest.fn(),
} as unknown as QueryRunner;
});

it('should have correct name', () => {
expect(migration.name).toBe('InitialMigration1731981975581');
});

describe('up', () => {
it('should create user role enum', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TYPE "public"."users_role_enum"'),
);
});

it('should create users table', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TABLE "users"'),
);
});

it('should create employees table', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TABLE "employees"'),
);
});

it('should create services table', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TABLE "services"'),
);
});

it('should create booking status enum', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TYPE "public"."bookings_status_enum"'),
);
});

it('should create bookings table', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TABLE "bookings"'),
);
});

it('should create employee_services table', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE TABLE "employee_services"'),
);
});

it('should create indexes', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('CREATE INDEX'),
);
});

it('should create foreign key constraints', async () => {
await migration.up(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('ALTER TABLE'),
);
expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('FOREIGN KEY'),
);
});
});

describe('down', () => {
it('should drop foreign key constraints', async () => {
await migration.down(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('DROP CONSTRAINT'),
);
});

it('should drop indexes', async () => {
await migration.down(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('DROP INDEX'),
);
});

it('should drop tables in correct order', async () => {
await migration.down(queryRunner);

const calls = (queryRunner.query as jest.Mock).mock.calls.map(
call => call[0],
);

// Verify drop order: employee_services -> bookings -> services -> employees -> users
const employeeServicesIndex = calls.findIndex(call =>
call.includes('DROP TABLE "employee_services"'),
);
const bookingsIndex = calls.findIndex(call =>
call.includes('DROP TABLE "bookings"'),
);
const servicesIndex = calls.findIndex(call =>
call.includes('DROP TABLE "services"'),
);
const employeesIndex = calls.findIndex(call =>
call.includes('DROP TABLE "employees"'),
);
const usersIndex = calls.findIndex(call =>
call.includes('DROP TABLE "users"'),
);

expect(employeeServicesIndex).toBeLessThan(bookingsIndex);
expect(bookingsIndex).toBeLessThan(servicesIndex);
expect(servicesIndex).toBeLessThan(employeesIndex);
expect(employeesIndex).toBeLessThan(usersIndex);
});

it('should drop enums', async () => {
await migration.down(queryRunner);

expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('DROP TYPE "public"."users_role_enum"'),
);
expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('DROP TYPE "public"."bookings_status_enum"'),
);
});
});
});
Loading

0 comments on commit e37aaac

Please sign in to comment.