Compare commits
4 Commits
api-unit-t
...
111-add-pa
| Author | SHA1 | Date | |
|---|---|---|---|
| db7a5444b3 | |||
| 6964531e12 | |||
| 08baaa6d83 | |||
| 487afb08d6 |
@@ -10,7 +10,7 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-and-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: ${{ inputs.environment_name }}
|
environment: ${{ inputs.environment_name }}
|
||||||
steps:
|
steps:
|
||||||
@@ -38,6 +38,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm run build -w api
|
npm run build -w api
|
||||||
|
|
||||||
|
- name: Test API
|
||||||
|
run: |
|
||||||
|
npm run test -w api
|
||||||
|
|
||||||
- name: Build Client
|
- name: Build Client
|
||||||
env:
|
env:
|
||||||
VITE_API_URL: ${{ vars.VITE_API_URL }}
|
VITE_API_URL: ${{ vars.VITE_API_URL }}
|
||||||
2
.github/workflows/main.yaml
vendored
2
.github/workflows/main.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
name: build
|
name: build
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- changes
|
||||||
uses: ./.github/workflows/build.yaml
|
uses: ./.github/workflows/build_and_test.yaml
|
||||||
with:
|
with:
|
||||||
environment_name: test
|
environment_name: test
|
||||||
version_number: ${{ github.run_id }}
|
version_number: ${{ github.run_id }}
|
||||||
|
|||||||
2
.github/workflows/pull_request.yaml
vendored
2
.github/workflows/pull_request.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
name: build
|
name: build
|
||||||
needs:
|
needs:
|
||||||
- changes
|
- changes
|
||||||
uses: ./.github/workflows/build.yaml
|
uses: ./.github/workflows/build_and_test.yaml
|
||||||
with:
|
with:
|
||||||
environment_name: pull_request
|
environment_name: pull_request
|
||||||
version_number: ${{ github.run_id }}
|
version_number: ${{ github.run_id }}
|
||||||
|
|||||||
2
.github/workflows/tag.yaml
vendored
2
.github/workflows/tag.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: build
|
name: build
|
||||||
uses: ./.github/workflows/build.yaml
|
uses: ./.github/workflows/build_and_test.yaml
|
||||||
with:
|
with:
|
||||||
environment_name: prod
|
environment_name: prod
|
||||||
version_number: ${{ github.ref_name }}
|
version_number: ${{ github.ref_name }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "api",
|
"name": "api",
|
||||||
"version": "2.0.0-beta-4",
|
"version": "2.0.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PilotEntity } from 'src/pilot/pilot.entity';
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'endorsements' })
|
@Entity({ name: 'endorsements' })
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
return fileUrl;
|
return fileUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadFile(containerName: string, rowKey: string, fileName: string): Promise<string> {
|
async downloadFile(containerName: string, logId: string, fileName: string): Promise<string> {
|
||||||
this.containerName = containerName;
|
this.containerName = containerName;
|
||||||
|
|
||||||
const blockBlobClient = await this.getBlobClient(`${rowKey}/${fileName}`);
|
const blockBlobClient = await this.getBlobClient(`${logId}/${fileName}`);
|
||||||
const downloadBlockBlobResponse = await blockBlobClient.download();
|
const downloadBlockBlobResponse = await blockBlobClient.download();
|
||||||
const downloaded: string = (await this.streamToBuffer(downloadBlockBlobResponse.readableStreamBody)).toString()
|
const downloaded: string = (await this.streamToBuffer(downloadBlockBlobResponse.readableStreamBody)).toString()
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,25 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { HealthController } from './health.controller';
|
import { HealthController } from "./health.controller"
|
||||||
import { HealthService } from './health.service';
|
import { HttpStatus } from "@nestjs/common";
|
||||||
|
|
||||||
describe('HealthController', () => {
|
describe('HealthController', () => {
|
||||||
let controller; HealthController;
|
let controller: HealthController;
|
||||||
|
|
||||||
const mockHealthService = {
|
|
||||||
isDatabaseConnected: jest.fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [HealthController],
|
controllers: [HealthController]
|
||||||
providers: [HealthService]
|
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<HealthController>(HealthController);
|
controller = module.get<HealthController>(HealthController);
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isHealthy => should return true', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
|
||||||
|
|
||||||
it('should return database connected', async () => {
|
|
||||||
jest.spyOn(mockHealthService, 'isDatabaseConnected').mockReturnValue(true);
|
|
||||||
|
|
||||||
const result = await controller.isHealthy();
|
|
||||||
|
|
||||||
expect(mockHealthService.isDatabaseConnected).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(true);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isHealthy => should return error', async () => {
|
it('isHealth => should return status ok', async () => {
|
||||||
jest.spyOn(mockHealthService, 'isDatabaseConnected').mockReturnValue(false);
|
|
||||||
|
|
||||||
const result = await controller.isHealthy();
|
const result = await controller.isHealthy();
|
||||||
|
|
||||||
expect(mockHealthService.isDatabaseConnected).toHaveBeenCalled();
|
expect(result).toEqual(HttpStatus.OK);
|
||||||
expect(result).toEqual(false);
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,27 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
HttpException,
|
HttpStatus,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { HealthService } from './health.service';
|
|
||||||
import { CustomError } from 'src/error/customError';
|
|
||||||
|
|
||||||
|
|
||||||
@Controller('health')
|
@Controller('health')
|
||||||
export class HealthController {
|
export class HealthController {
|
||||||
constructor(
|
|
||||||
private readonly healthService: HealthService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async isHealthy(): Promise<boolean> {
|
async isHealthy(): Promise<HttpStatus> {
|
||||||
try {
|
return HttpStatus.OK
|
||||||
return await this.healthService.isDatabaseConnected();
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { HealthController } from './health.controller';
|
import { HealthController } from './health.controller';
|
||||||
import { HealthService } from './health.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [HealthController],
|
controllers: [HealthController],
|
||||||
providers: [
|
|
||||||
HealthService
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class HealthModule {}
|
export class HealthModule {}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { HealthService } from './health.service';
|
|
||||||
|
|
||||||
describe('HealthService', () => {
|
|
||||||
let service: HealthService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [HealthService]
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<HealthService>(HealthService);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { CustomError } from '../error/customError';
|
|
||||||
import { DataSource } from 'typeorm';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HealthService {
|
|
||||||
constructor(private dataSource: DataSource) {}
|
|
||||||
|
|
||||||
async isDatabaseConnected(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const isDatabaseConnected: boolean = this.dataSource.isInitialized;
|
|
||||||
|
|
||||||
if (isDatabaseConnected) {
|
|
||||||
return isDatabaseConnected;
|
|
||||||
} else {
|
|
||||||
throw new CustomError('Database not connected', 'Database not connected', 400)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
200
api/src/log/log.controller.spec.ts
Normal file
200
api/src/log/log.controller.spec.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { LogController } from './log.controller';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
import { LogDto } from './log.dto';
|
||||||
|
import { LogEntity } from './log.entity';
|
||||||
|
import { FileService } from '../file/file.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
describe('LogController', () => {
|
||||||
|
let controller: LogController;
|
||||||
|
|
||||||
|
const mockLogService = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findLogsWithCount: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [LogController],
|
||||||
|
providers: [
|
||||||
|
ConfigService,
|
||||||
|
FileService,
|
||||||
|
{
|
||||||
|
provide: LogService,
|
||||||
|
useValue: mockLogService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile()
|
||||||
|
|
||||||
|
controller = module.get<LogController>(LogController);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should find a log by id', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const log = {
|
||||||
|
|
||||||
|
} as LogEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockReturnValue(log);
|
||||||
|
|
||||||
|
const result = await controller.find(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(log);
|
||||||
|
expect(mockLogService.find).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.find).toHaveBeenCalledWith(id);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should fail to find a log by id', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockRejectedValue(new Error('Log not found'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.find(id);
|
||||||
|
|
||||||
|
fail('find did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockLogService.find).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.find).toHaveBeenCalledWith(id);
|
||||||
|
expect(mockLogService.find).rejects.toThrow('Log not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findLogsWithCount => should find all logs', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const log = {
|
||||||
|
|
||||||
|
} as LogEntity;
|
||||||
|
const logs = [log]
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'findLogsWithCount').mockReturnValue(logs);
|
||||||
|
|
||||||
|
const result = await controller.findLogsWithCount();
|
||||||
|
|
||||||
|
expect(result).toEqual(logs);
|
||||||
|
expect(mockLogService.findLogsWithCount).toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findLogsWithCount => should fail to find logs with count', async () => {
|
||||||
|
jest.spyOn(mockLogService, 'findLogsWithCount').mockRejectedValue(new Error('Logs not found'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.findLogsWithCount();
|
||||||
|
|
||||||
|
fail('findLogsWithCount did not throw error');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockLogService.findLogsWithCount).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.findLogsWithCount).rejects.toThrow('Logs not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should create a new log', async () => {
|
||||||
|
const logDto = {
|
||||||
|
|
||||||
|
} as LogDto;
|
||||||
|
|
||||||
|
const log = {
|
||||||
|
|
||||||
|
} as LogEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'create').mockReturnValue(log);
|
||||||
|
|
||||||
|
const result = await controller.create(logDto);
|
||||||
|
|
||||||
|
expect(mockLogService.create).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.create).toHaveBeenCalledWith(logDto);
|
||||||
|
expect(result).toEqual(log);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should failt to create a new log', async () => {
|
||||||
|
const logDto = {
|
||||||
|
|
||||||
|
} as LogDto;
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'create').mockRejectedValue(new Error('Log failed to create'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.create(logDto);
|
||||||
|
|
||||||
|
fail('create did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockLogService.create).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.create).toHaveBeenCalledWith(logDto);
|
||||||
|
expect(mockLogService.create).rejects.toThrow('Log failed to create')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should update an existing log', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const logDto = {
|
||||||
|
|
||||||
|
} as LogDto
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'update').mockReturnValue(logDto);
|
||||||
|
|
||||||
|
const result = await controller.update(id, logDto);
|
||||||
|
|
||||||
|
expect(result).toEqual(logDto);
|
||||||
|
expect(mockLogService.update).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.update).toHaveBeenCalledWith(id, logDto);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should fail to update an exising log', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const logDto = {
|
||||||
|
|
||||||
|
} as LogDto;
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'update').mockRejectedValue(new Error('Log failed to update'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.update(id, logDto);
|
||||||
|
|
||||||
|
fail('update did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockLogService.update).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.update).toHaveBeenCalledWith(id, logDto);
|
||||||
|
expect(mockLogService.update).rejects.toThrow('Log failed to update')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete and existing log', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'delete');
|
||||||
|
|
||||||
|
const result = await controller.delete(id);
|
||||||
|
|
||||||
|
expect(mockLogService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.delete).toHaveBeenCalledWith(id);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should fail to delete and exising log', async () => {
|
||||||
|
const id: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'delete').mockRejectedValue(new Error('Log failed to delete'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.delete(id);
|
||||||
|
|
||||||
|
fail('delete did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockLogService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.delete).toHaveBeenCalledWith(id);
|
||||||
|
expect(mockLogService.delete).rejects.toThrow('Log failed to delete')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
UseInterceptors
|
UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
@@ -47,25 +48,25 @@ export class LogController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Public()
|
@Public()
|
||||||
async findAll(): Promise<LogEntity[]> {
|
async findLogsWithCount(@Query('skip') skip?, @Query('take') take?: number,): Promise<{ entities: LogEntity[], total: number, hasNextPage: boolean }> {
|
||||||
try {
|
try {
|
||||||
return await this.logService.findAll();
|
return await this.logService.findLogsWithCount(skip, take)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode);
|
throw new HttpException(customError.message, customError.statusCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async create(@Body() logDto: LogDto): Promise<InsertResult> {
|
async create(@Body() logDto: LogDto) {
|
||||||
try {
|
try {
|
||||||
return await this.logService.create(logDto);
|
return await this.logService.create(logDto);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
console.log(error)
|
|
||||||
throw new HttpException(customError.message, customError.statusCode);
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,13 +76,12 @@ export class LogController {
|
|||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() logDto: LogDto
|
@Body() logDto: LogDto
|
||||||
): Promise<UpdateResult> {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log(logDto);
|
|
||||||
return await this.logService.update(id, logDto);
|
return await this.logService.update(id, logDto);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
console.log(customError)
|
|
||||||
throw new HttpException(customError.message, customError.statusCode);
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { PilotEntity } from "src/pilot/pilot.entity";
|
|
||||||
|
|
||||||
export class LogDto {
|
export class LogDto {
|
||||||
pilotId: string;
|
pilotId: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PilotEntity } from 'src/pilot/pilot.entity';
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
import { TrackEntity } from 'src/track/track.entity';
|
import { TrackEntity } from '../track/track.entity';
|
||||||
import { ColumnNumericTransformer } from 'src/transformers/columnNumeric.transformer';
|
import { ColumnNumericTransformer } from '../transformers/columnNumeric.transformer';
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'logs' })
|
@Entity({ name: 'logs' })
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export class LogInterceptor implements NestInterceptor {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return handler.handle().pipe(
|
return handler.handle().pipe(
|
||||||
map((data: LogEntity[]) => {
|
map((data: { entities: LogEntity[], total: number, hasNextPage: boolean }) => {
|
||||||
const req = context.switchToHttp().getRequest();
|
const req = context.switchToHttp().getRequest();
|
||||||
const limitData = (data) => {
|
const limitData = (entities: LogEntity[]) => {
|
||||||
return data.map((log: LogEntity) => {
|
return entities.map((log: LogEntity) => {
|
||||||
return {
|
return {
|
||||||
id: log.id,
|
id: log.id,
|
||||||
pilot: {
|
pilot: {
|
||||||
@@ -41,17 +41,25 @@ export class LogInterceptor implements NestInterceptor {
|
|||||||
const rolesKeyName = Object.keys(jwtPayload).find((key) => key.includes('roles'));
|
const rolesKeyName = Object.keys(jwtPayload).find((key) => key.includes('roles'));
|
||||||
|
|
||||||
if (jwtPayload[rolesKeyName].includes('Flying.Read')) {
|
if (jwtPayload[rolesKeyName].includes('Flying.Read')) {
|
||||||
const logs = limitData(data);
|
const logs = limitData(data.entities);
|
||||||
|
|
||||||
return logs;
|
return {
|
||||||
|
entities: logs,
|
||||||
|
total: data.total,
|
||||||
|
hasNextPage: data.hasNextPage
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
} else if (!req.headers.authorization && isPublic) {
|
} else if (!req.headers.authorization && isPublic) {
|
||||||
const publicData = limitData(data)
|
const publicData = limitData(data.entities)
|
||||||
const logs = publicData.slice(0, 5)
|
const logs = publicData.slice(0, 5)
|
||||||
|
|
||||||
return logs;
|
return {
|
||||||
|
entities: logs,
|
||||||
|
total: data.total,
|
||||||
|
hasNextPage: data.hasNextPage
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { LogEntity } from './log.entity';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { FileService } from '../file/file.service';
|
import { FileService } from '../file/file.service';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { PilotModule } from 'src/pilot/pilot.module';
|
import { PilotModule } from '../pilot/pilot.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
292
api/src/log/log.service.spec.ts
Normal file
292
api/src/log/log.service.spec.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { LogEntity } from './log.entity';
|
||||||
|
import { LogDto } from './log.dto';
|
||||||
|
import { FileService } from '../file/file.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { PilotService } from '../pilot/pilot.service';
|
||||||
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
|
|
||||||
|
describe('LogService', () => {
|
||||||
|
let service: LogService;
|
||||||
|
|
||||||
|
const mockFileService = {
|
||||||
|
deleteFolder: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockLogRepository = {
|
||||||
|
delete: jest.fn(),
|
||||||
|
findAndCount: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
findOneBy: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockPilotRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
ConfigService,
|
||||||
|
LogService,
|
||||||
|
PilotService,
|
||||||
|
{
|
||||||
|
provide: FileService,
|
||||||
|
useValue: mockFileService
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(LogEntity),
|
||||||
|
useValue: mockLogRepository
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(PilotEntity),
|
||||||
|
useValue: mockPilotRepository
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<LogService>(LogService);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => shoule create a log entry', async () => {
|
||||||
|
const logDto = {
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test'
|
||||||
|
} as LogDto
|
||||||
|
|
||||||
|
jest.spyOn(mockLogRepository, 'save').mockReturnValue(logDto);
|
||||||
|
|
||||||
|
const result = await service.create(logDto);
|
||||||
|
|
||||||
|
expect(mockLogRepository.save).toHaveBeenCalled();
|
||||||
|
expect(mockLogRepository.save).toHaveBeenCalledWith(logDto);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete a log entry', async () => {
|
||||||
|
const id: string = '';
|
||||||
|
const log = {
|
||||||
|
id: 'd685f1ca-28e0-40b9-8713-74467db12965',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockFileService, 'deleteFolder').mockReturnValue(undefined);
|
||||||
|
jest.spyOn(mockLogRepository, 'delete').mockReturnValue(log);
|
||||||
|
|
||||||
|
const result = await service.delete(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(log);
|
||||||
|
expect(mockLogRepository.delete).toHaveBeenCalled();
|
||||||
|
expect(mockLogRepository.delete).toHaveBeenCalledWith({ id: id })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should find a log entry by id', async () => {
|
||||||
|
const id: string = 'd685f1ca-28e0-40b9-8713-74467db12965';
|
||||||
|
const log = {
|
||||||
|
id: 'd685f1ca-28e0-40b9-8713-74467db12965',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockLogRepository, 'findOne').mockReturnValue(log);
|
||||||
|
|
||||||
|
const result = await service.find(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(log);
|
||||||
|
expect(mockLogRepository.findOne).toHaveBeenCalled();
|
||||||
|
expect(mockLogRepository.findOne).toHaveBeenCalledWith({
|
||||||
|
relations: [
|
||||||
|
'pilot',
|
||||||
|
'tracks'
|
||||||
|
],
|
||||||
|
where: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findLogsWithCount => should find log entries with count', async () => {
|
||||||
|
const log = {
|
||||||
|
id: 'd685f1ca-28e0-40b9-8713-74467db12965',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
const logs = [log];
|
||||||
|
const count = 1
|
||||||
|
const morePages = false
|
||||||
|
|
||||||
|
jest.spyOn(mockLogRepository, 'findAndCount').mockReturnValue([logs, count, morePages]);
|
||||||
|
|
||||||
|
const {entities, total, hasNextPage} = await service.findLogsWithCount();
|
||||||
|
|
||||||
|
expect(entities).toEqual(logs);
|
||||||
|
expect(count).toEqual(total);
|
||||||
|
expect(hasNextPage).toEqual(morePages);
|
||||||
|
expect(mockLogRepository.findAndCount).toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should update a log entry', async () => {
|
||||||
|
const id: string = 'd685f1ca-28e0-40b9-8713-74467db12965';
|
||||||
|
const logDto = {
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test'
|
||||||
|
} as LogDto
|
||||||
|
const log = {
|
||||||
|
id: 'd685f1ca-28e0-40b9-8713-74467db12965',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockLogRepository, 'findOneBy').mockReturnValue(log);
|
||||||
|
jest.spyOn(mockLogRepository, 'save').mockReturnValue(logDto);
|
||||||
|
|
||||||
|
const result = await service.update(id, logDto);
|
||||||
|
|
||||||
|
expect(result).toEqual(logDto);
|
||||||
|
expect(mockLogRepository.findOneBy).toHaveBeenCalled();
|
||||||
|
expect(mockLogRepository.findOneBy).toHaveBeenCalledWith({ id: id });
|
||||||
|
expect(mockLogRepository.save).toHaveBeenCalled();
|
||||||
|
expect(mockLogRepository.save).toHaveBeenCalledWith(logDto);
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,10 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { LogEntity } from './log.entity';
|
import { LogEntity } from './log.entity';
|
||||||
import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm';
|
import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm';
|
||||||
import { LogDto } from './log.dto';
|
import { LogDto } from './log.dto';
|
||||||
import { PilotService } from 'src/pilot/pilot.service';
|
import { PilotService } from '../pilot/pilot.service';
|
||||||
import { PilotEntity } from 'src/pilot/pilot.entity';
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
import { CustomError } from 'src/error/customError';
|
import { CustomError } from '../error/customError';
|
||||||
import { FileService } from 'src/file/file.service';
|
import { FileService } from '../file/file.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LogService {
|
export class LogService {
|
||||||
@@ -25,38 +25,31 @@ export class LogService {
|
|||||||
return logEntity;
|
return logEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(): Promise<LogEntity[]> {
|
async findLogsWithCount(skip?: number, take?: number): Promise<{entities: LogEntity[], total: number, hasNextPage: boolean}> {
|
||||||
return await this.logRepository.find({
|
const [entities, total] = await this.logRepository.findAndCount({
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
relations: ['pilot', 'tracks']
|
relations: ['pilot', 'tracks']
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
async create(logDto: LogDto): Promise<InsertResult> {
|
return {
|
||||||
try{
|
entities,
|
||||||
const pilotEntity: PilotEntity = await this.pilotService.find(logDto.pilotId);
|
total,
|
||||||
|
hasNextPage: skip + take < total
|
||||||
if (pilotEntity) {
|
|
||||||
const { pilotId, ...newLogDto } = logDto;
|
|
||||||
const log = this.logRepository.create({
|
|
||||||
...newLogDto,
|
|
||||||
pilot: pilotEntity
|
|
||||||
})
|
|
||||||
|
|
||||||
return this.logRepository.insert(log);
|
|
||||||
} else {
|
|
||||||
throw new CustomError('Pilot not found', 'Not found', 404);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, logDto: LogDto): Promise<UpdateResult> {
|
async create(logDto: LogDto): Promise<LogDto> {
|
||||||
const log = logDto
|
return this.logRepository.save(logDto);
|
||||||
|
}
|
||||||
|
|
||||||
delete log.tracks;
|
async update(id: string, logDto: LogDto): Promise<LogDto> {
|
||||||
|
const logEntity: LogEntity = await this.logRepository.findOneBy({ id });
|
||||||
|
const logEntityUpdated = Object.assign(logEntity, logDto)
|
||||||
|
|
||||||
return await this.logRepository.update(id, log);
|
delete logEntityUpdated.tracks;
|
||||||
|
|
||||||
|
return await this.logRepository.save(logEntityUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string): Promise<DeleteResult> {
|
async delete(id: string): Promise<DeleteResult> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PilotEntity } from 'src/pilot/pilot.entity';
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'medical' })
|
@Entity({ name: 'medical' })
|
||||||
|
|||||||
260
api/src/pilot/pilot.controller.spec.ts
Normal file
260
api/src/pilot/pilot.controller.spec.ts
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { PilotController } from "./pilot.controller";
|
||||||
|
import { PilotService } from './pilot.service';
|
||||||
|
import { PilotDto } from './pilot.dto';
|
||||||
|
import { PilotEntity } from './pilot.entity';
|
||||||
|
import { CustomError } from '../error/customError';
|
||||||
|
import { HttpException } from '@nestjs/common';
|
||||||
|
|
||||||
|
describe('PilotController', () => {
|
||||||
|
let controller: PilotController;
|
||||||
|
|
||||||
|
const mockPilotService = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [PilotController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PilotService,
|
||||||
|
useValue: mockPilotService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<PilotController>(PilotController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('find => should find a pilot by id', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'find').mockReturnValue(pilot);
|
||||||
|
|
||||||
|
const result = await controller.find(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(pilot);
|
||||||
|
expect(mockPilotService.find).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.find).toHaveBeenCalledWith(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('find => should fail to find a pilot by id', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'find').mockRejectedValue(new Error('Pilot not found'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.find(id);
|
||||||
|
|
||||||
|
fail('find did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect(mockPilotService.find).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.find).toHaveBeenCalledWith(id);
|
||||||
|
expect(mockPilotService.find).rejects.toThrow('Pilot not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should find all pilots', async () => {
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity;
|
||||||
|
const pilots = [pilot];
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'findAll').mockReturnValue(pilots);
|
||||||
|
|
||||||
|
const result = await controller.findAll();
|
||||||
|
|
||||||
|
expect(result).toEqual(pilots);
|
||||||
|
expect(mockPilotService.findAll).toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should fail to find all pilots', async () => {
|
||||||
|
jest.spyOn(mockPilotService, 'findAll').mockRejectedValue(new Error('Pilots not found'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.findAll();
|
||||||
|
|
||||||
|
fail('findAll did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect(mockPilotService.findAll).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.findAll).rejects.toThrow('Pilots not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should create a new pilot', async () => {
|
||||||
|
const pilotDto = {
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'create').mockReturnValue(pilot);
|
||||||
|
|
||||||
|
const result = await controller.create(pilotDto);
|
||||||
|
|
||||||
|
expect(mockPilotService.create).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.create).toHaveBeenCalledWith(pilotDto)
|
||||||
|
expect(result).toEqual(pilot);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should fail to create a new pilot', async () => {
|
||||||
|
const pilotDto = {
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'create').mockRejectedValue(new Error('Pilot failed to create'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.create(pilotDto);
|
||||||
|
|
||||||
|
fail('create did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect(mockPilotService.create).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.create).toHaveBeenCalledWith(pilotDto);
|
||||||
|
expect(mockPilotService.create).rejects.toThrow('Pilot failed to create')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should update an existing pilot', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilotDto = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'update').mockReturnValue(pilotDto);
|
||||||
|
|
||||||
|
const result = await controller.update(id, pilotDto);
|
||||||
|
|
||||||
|
expect(result).toEqual(pilotDto);
|
||||||
|
expect(mockPilotService.update).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.update).toHaveBeenCalledWith(id, pilotDto)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should fail to update an existing pilot', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilotDto = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'update').mockRejectedValue(new Error('Pilot failed to update'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.update(id, pilotDto);
|
||||||
|
|
||||||
|
fail('update function did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect(mockPilotService.update).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.update).toHaveBeenCalledWith(id, pilotDto);
|
||||||
|
expect(mockPilotService.update).rejects.toThrow('Pilot failed to update')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete an existing pilot', async () => {
|
||||||
|
const id = '39465ae3-7947-4cc2-b565-ca00a982fdd8';
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'delete');
|
||||||
|
|
||||||
|
const result = await controller.delete(id);
|
||||||
|
|
||||||
|
expect(mockPilotService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.delete).toHaveBeenCalledWith(id);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should fail to delete an existing pilot', async () => {
|
||||||
|
const id = '39465ae3-7947-4cc2-b565-ca00a982fdd8';
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotService, 'delete').mockRejectedValue(new Error('Pilot failed to delete'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.delete(id);
|
||||||
|
|
||||||
|
fail('delete function did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error)
|
||||||
|
expect(mockPilotService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockPilotService.delete).toHaveBeenCalledWith(id);
|
||||||
|
expect(mockPilotService.delete).rejects.toThrow('Pilot failed to delete')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -50,9 +50,8 @@ export class PilotController {
|
|||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async create(@Body() pilotDto: PilotDto) {
|
async create(@Body() pilotDto: PilotDto): Promise<PilotDto> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
return await this.pilotService.create(pilotDto);
|
return await this.pilotService.create(pilotDto);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { LogEntity } from "src/log/log.entity";
|
|
||||||
|
|
||||||
export class PilotDto {
|
export class PilotDto {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { LogEntity } from 'src/log/log.entity';
|
import { LogEntity } from '../log/log.entity';
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
|
||||||
import { CertificateEntity } from '../certificate/certificate.entity';
|
import { CertificateEntity } from '../certificate/certificate.entity';
|
||||||
import { EndorsementEntity } from 'src/endorsement/endorsement.entity';
|
import { EndorsementEntity } from '../endorsement/endorsement.entity';
|
||||||
import { MedicalEntity } from 'src/medical/medical.entity';
|
import { MedicalEntity } from '../medical/medical.entity';
|
||||||
|
|
||||||
@Entity({ name: 'pilots' })
|
@Entity({ name: 'pilots' })
|
||||||
export class PilotEntity {
|
export class PilotEntity {
|
||||||
|
|||||||
206
api/src/pilot/pilot.service.spec.ts
Normal file
206
api/src/pilot/pilot.service.spec.ts
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { PilotService } from './pilot.service';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { PilotEntity } from './pilot.entity';
|
||||||
|
import { PilotDto } from './pilot.dto';
|
||||||
|
import { CustomError } from '../error/customError';
|
||||||
|
|
||||||
|
describe('PilotService', () => {
|
||||||
|
let service: PilotService;
|
||||||
|
|
||||||
|
const mockPilotRepository = {
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findOneBy: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
PilotService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(PilotEntity),
|
||||||
|
useValue: mockPilotRepository
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<PilotService>(PilotService);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should find one pilot by id', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity;
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'findOneBy').mockReturnValue(pilot);
|
||||||
|
|
||||||
|
const result = await service.find(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(pilot);
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalledWith({ id: id });
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should fail to find one pilot by id', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8';
|
||||||
|
const mockCustomError = new CustomError('Pilot not found', 'Not found', 404)
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'findOneBy').mockReturnValue(undefined);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.find(id);
|
||||||
|
|
||||||
|
fail('find did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(CustomError);
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalledWith({ id: id });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should find all pilots', async () => {
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity;
|
||||||
|
const pilots = [pilot];
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'find').mockReturnValue(pilots);
|
||||||
|
|
||||||
|
const result = await service.findAll();
|
||||||
|
|
||||||
|
expect(result).toEqual(pilots);
|
||||||
|
expect(mockPilotRepository.find).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should create a new pilot', async () => {
|
||||||
|
const pilotDto = {
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'save').mockReturnValue(pilot);
|
||||||
|
|
||||||
|
const result = await service.create(pilotDto);
|
||||||
|
|
||||||
|
expect(mockPilotRepository.save).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.save).toHaveBeenCalledWith(pilotDto);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should update a pilot', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilotDto = {
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234'
|
||||||
|
} as PilotDto
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'findOneBy').mockReturnValue(pilot)
|
||||||
|
jest.spyOn(mockPilotRepository, 'save').mockReturnValue(pilotDto);
|
||||||
|
|
||||||
|
const result = await service.update(id, pilotDto)
|
||||||
|
|
||||||
|
expect(result).toEqual(pilotDto);
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.findOneBy).toHaveBeenCalledWith({ id: id });
|
||||||
|
expect(mockPilotRepository.save).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.save).toHaveBeenCalledWith(pilotDto)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete a pilot', async () => {
|
||||||
|
const id: string = '39465ae3-7947-4cc2-b565-ca00a982fdd8'
|
||||||
|
const pilot = {
|
||||||
|
id: '39465ae3-7947-4cc2-b565-ca00a982fdd8',
|
||||||
|
name: 'Test pilot',
|
||||||
|
address: '123 Any Street',
|
||||||
|
city: 'Any Town',
|
||||||
|
state: 'Minnesota',
|
||||||
|
postalCode: '55123',
|
||||||
|
email: 'user@example.com',
|
||||||
|
phone: '555-555-1234',
|
||||||
|
userId: 'user@example.com',
|
||||||
|
logs: [],
|
||||||
|
certificates: [],
|
||||||
|
endorsements: [],
|
||||||
|
medical: []
|
||||||
|
} as PilotEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockPilotRepository, 'delete').mockReturnValue(pilot);
|
||||||
|
|
||||||
|
const result = await service.delete(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(pilot);
|
||||||
|
expect(mockPilotRepository.delete).toHaveBeenCalled();
|
||||||
|
expect(mockPilotRepository.delete).toHaveBeenCalledWith({ id: id });
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,7 +3,7 @@ import { PilotEntity } from './pilot.entity';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm';
|
import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm';
|
||||||
import { PilotDto } from './pilot.dto';
|
import { PilotDto } from './pilot.dto';
|
||||||
import { CustomError } from 'src/error/customError';
|
import { CustomError } from '../error/customError';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PilotService {
|
export class PilotService {
|
||||||
@@ -13,7 +13,7 @@ export class PilotService {
|
|||||||
|
|
||||||
async find(id: string): Promise<PilotEntity> {
|
async find(id: string): Promise<PilotEntity> {
|
||||||
try {
|
try {
|
||||||
const pilotEntity = await this.pilotRepository.findOneBy({ id });
|
const pilotEntity: PilotEntity = await this.pilotRepository.findOneBy({ id });
|
||||||
|
|
||||||
if (pilotEntity) {
|
if (pilotEntity) {
|
||||||
return pilotEntity
|
return pilotEntity
|
||||||
@@ -29,15 +29,18 @@ export class PilotService {
|
|||||||
return await this.pilotRepository.find();
|
return await this.pilotRepository.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(pilot: PilotDto): Promise<InsertResult> {
|
async create(pilot: PilotDto): Promise<PilotDto> {
|
||||||
return await this.pilotRepository.insert(pilot);
|
return await this.pilotRepository.save(pilot);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
id: string,
|
id: string,
|
||||||
pilot: PilotDto
|
pilotDto: PilotDto
|
||||||
): Promise<UpdateResult> {
|
): Promise<PilotDto> {
|
||||||
return await this.pilotRepository.update(id, pilot);
|
const pilotEntity: PilotEntity = await this.pilotRepository.findOneBy({ id })
|
||||||
|
const pilotEntityUpdated = Object.assign(pilotEntity, pilotDto)
|
||||||
|
|
||||||
|
return await this.pilotRepository.save(pilotEntityUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string): Promise<DeleteResult> {
|
async delete(id: string): Promise<DeleteResult> {
|
||||||
|
|||||||
218
api/src/track/track.controller.spec.ts
Normal file
218
api/src/track/track.controller.spec.ts
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { TrackController } from './track.controller';
|
||||||
|
import { TrackService } from './track.service';
|
||||||
|
import { TrackDto } from './track.dto';
|
||||||
|
import { TrackEntity } from './track.entity';
|
||||||
|
import { FileService } from '../file/file.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { HttpException } from '@nestjs/common';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
import { DeleteResult, InsertResult } from 'typeorm';
|
||||||
|
|
||||||
|
describe('TrackController', () => {
|
||||||
|
let controller: TrackController;
|
||||||
|
|
||||||
|
const mockTrackService = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
downloadTrackFile: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [TrackController],
|
||||||
|
providers: [
|
||||||
|
ConfigService,
|
||||||
|
FileService,
|
||||||
|
{
|
||||||
|
provide: TrackService,
|
||||||
|
useValue: mockTrackService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<TrackController>(TrackController);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should find all tracks by log id', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const track = {
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
url: 'http://track.example.com',
|
||||||
|
order: 1
|
||||||
|
} as TrackEntity
|
||||||
|
const tracks = [track]
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'findAll').mockReturnValue(tracks);
|
||||||
|
|
||||||
|
const result = await controller.findAll(logId);
|
||||||
|
|
||||||
|
expect(result).toEqual(tracks);
|
||||||
|
expect(mockTrackService.findAll).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.findAll).toHaveBeenLastCalledWith(logId);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should fail to find all tracks by log id', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'findAll').mockRejectedValue(new Error('Tracks not found'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.findAll(logId);
|
||||||
|
|
||||||
|
fail('findAll did not throw error');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(mockTrackService.findAll).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.findAll).rejects.toThrow('Tracks not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => create a track by a log id', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const order = 1;
|
||||||
|
const file: Express.Multer.File = {
|
||||||
|
fieldname: 'file',
|
||||||
|
originalname: 'test_track.kml',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimetype: 'application/vnd.google-earth.kml+xml',
|
||||||
|
size: 12345,
|
||||||
|
destination: '/tmp/uploads',
|
||||||
|
filename: 'unique-filename-123.kml',
|
||||||
|
path: '/tmp/uploads/unique-filename-123.kml',
|
||||||
|
buffer: Buffer.from('<kml xmlns="http://www.opengis.net"></kml>'),
|
||||||
|
stream: new Readable()
|
||||||
|
}
|
||||||
|
const mockInsertResult: InsertResult = {
|
||||||
|
identifiers: [
|
||||||
|
{
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
generatedMaps: [
|
||||||
|
{
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
createdAd: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
raw: {
|
||||||
|
affectedRows: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'create').mockReturnValue(mockInsertResult);
|
||||||
|
|
||||||
|
const result = await controller.create(logId, order, file);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockInsertResult);
|
||||||
|
expect(mockTrackService.create).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.create).toHaveBeenCalledWith(logId, order, file)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should fail to create a track by log id', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const order = 1;
|
||||||
|
const file: Express.Multer.File = {
|
||||||
|
fieldname: 'file',
|
||||||
|
originalname: 'test_track.kml',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimetype: 'application/vnd.google-earth.kml+xml',
|
||||||
|
size: 12345,
|
||||||
|
destination: '/tmp/uploads',
|
||||||
|
filename: 'unique-filename-123.kml',
|
||||||
|
path: '/tmp/uploads/unique-filename-123.kml',
|
||||||
|
buffer: Buffer.from('<kml xmlns="http://www.opengis.net"></kml>'),
|
||||||
|
stream: new Readable()
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'create').mockRejectedValue(new Error('Track failed to create'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.create(logId, order, file)
|
||||||
|
|
||||||
|
fail('create did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(mockTrackService.create).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.create).toHaveBeenCalledWith(logId, order, file);
|
||||||
|
expect(mockTrackService.create).rejects.toThrow('Track failed to create')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete a track by log id', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde'
|
||||||
|
const logId: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const fileName = 'test_track.kml';
|
||||||
|
const mockDeleteResult: DeleteResult ={
|
||||||
|
raw: [],
|
||||||
|
affected: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'delete').mockReturnValue(mockDeleteResult);
|
||||||
|
|
||||||
|
const result = await controller.delete(id, fileName, logId);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockDeleteResult);
|
||||||
|
expect(mockTrackService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.delete).toHaveBeenCalledWith(id, logId, fileName);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should fail to delete a track by log id', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde'
|
||||||
|
const logId: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const fileName = 'test_track.kml';
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'delete').mockRejectedValue(new Error('Track failed to delete'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.delete(id, fileName, logId);
|
||||||
|
|
||||||
|
fail('delete did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(mockTrackService.delete).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.delete).toHaveBeenCalledWith(id, logId, fileName);
|
||||||
|
expect(mockTrackService.delete).rejects.toThrow('Track failed to delete')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('downloadTrack => should download a track by log id', async () => {
|
||||||
|
const logId: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const fileName = 'test_track.kml';
|
||||||
|
const mockStreamToBufferString = 'fake-stream-to-buffer-string'
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'downloadTrackFile').mockReturnValue(mockStreamToBufferString);
|
||||||
|
|
||||||
|
const result = await controller.downloadTrack(logId, fileName);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockStreamToBufferString);
|
||||||
|
expect(mockTrackService.downloadTrackFile).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.downloadTrackFile).toHaveBeenCalledWith(logId, fileName)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('downloadTrack => should fail to download a track by log id', async () => {
|
||||||
|
const logId: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const fileName = 'test_track.kml';
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackService, 'downloadTrackFile').mockRejectedValue(new Error('Track failed to download'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.downloadTrack(logId, fileName);
|
||||||
|
|
||||||
|
fail('downloadTrack did not throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(mockTrackService.downloadTrackFile).toHaveBeenCalled();
|
||||||
|
expect(mockTrackService.downloadTrackFile).toHaveBeenCalledWith(logId, fileName);
|
||||||
|
expect(mockTrackService.downloadTrackFile).rejects.toThrow('Track failed to download')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LogEntity } from 'src/log/log.entity';
|
import { LogEntity } from '../log/log.entity';
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'tracks' })
|
@Entity({ name: 'tracks' })
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { TrackEntity } from "./track.entity";
|
|||||||
import { TrackController } from "./track.controller";
|
import { TrackController } from "./track.controller";
|
||||||
import { FileService } from "../file/file.service";
|
import { FileService } from "../file/file.service";
|
||||||
import { TrackService } from './track.service';
|
import { TrackService } from './track.service';
|
||||||
import { LogModule } from 'src/log/log.module';
|
import { LogModule } from '../log/log.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
346
api/src/track/track.service.spec.ts
Normal file
346
api/src/track/track.service.spec.ts
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { TrackService } from './track.service';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { TrackEntity } from './track.entity'
|
||||||
|
import { TrackDto } from './track.dto';
|
||||||
|
import { FileService } from '../file/file.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { LogService } from '../log/log.service';
|
||||||
|
import { LogEntity } from '../log/log.entity';
|
||||||
|
import { PilotService } from '../pilot/pilot.service';
|
||||||
|
import { PilotEntity } from '../pilot/pilot.entity';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
import { DeleteResult, InsertResult, UpdateResult } from 'typeorm';
|
||||||
|
import { CustomError } from '../error/customError';
|
||||||
|
|
||||||
|
describe('TrackService', () => {
|
||||||
|
let service: TrackService;
|
||||||
|
|
||||||
|
const mockFileService = {
|
||||||
|
deleteFile: jest.fn(),
|
||||||
|
downloadFile: jest.fn(),
|
||||||
|
uploadFile: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockLogService = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockPilotRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockTrackRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
downloadTrackFile: jest.fn(),
|
||||||
|
findOneBy: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
insert: jest.fn(),
|
||||||
|
update: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
ConfigService,
|
||||||
|
LogService,
|
||||||
|
PilotService,
|
||||||
|
TrackService,
|
||||||
|
{
|
||||||
|
provide: FileService,
|
||||||
|
useValue: mockFileService
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: LogService,
|
||||||
|
useValue: mockLogService
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(PilotEntity),
|
||||||
|
useValue: mockPilotRepository
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(TrackEntity),
|
||||||
|
useValue: mockTrackRepository
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).compile()
|
||||||
|
|
||||||
|
service = module.get<TrackService>(TrackService);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should upload a track file', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const log = {
|
||||||
|
id: '094ec69c-72c4-4995-8821-79d5b79bedda',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
const order = 1;
|
||||||
|
const file: Express.Multer.File = {
|
||||||
|
fieldname: 'file',
|
||||||
|
originalname: 'test_track.kml',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimetype: 'application/vnd.google-earth.kml+xml',
|
||||||
|
size: 12345,
|
||||||
|
destination: '/tmp/uploads',
|
||||||
|
filename: 'unique-filename-123.kml',
|
||||||
|
path: '/tmp/uploads/unique-filename-123.kml',
|
||||||
|
buffer: Buffer.from('<kml xmlns="http://www.opengis.net"></kml>'),
|
||||||
|
stream: new Readable()
|
||||||
|
}
|
||||||
|
const mockInsertResult: InsertResult = {
|
||||||
|
identifiers: [
|
||||||
|
{
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
generatedMaps: [
|
||||||
|
{
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
createdAd: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
raw: {
|
||||||
|
affectedRows: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const track = {
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
url: 'http://track.example.com',
|
||||||
|
order: 1
|
||||||
|
} as TrackEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockReturnValue(log)
|
||||||
|
jest.spyOn(mockTrackRepository, 'create').mockReturnValue(track)
|
||||||
|
jest.spyOn(mockTrackRepository, 'insert').mockReturnValue(mockInsertResult);
|
||||||
|
|
||||||
|
const result = await service.create(logId, order, file);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockInsertResult)
|
||||||
|
expect(mockTrackRepository.insert).toHaveBeenCalled();
|
||||||
|
expect(mockTrackRepository.insert).toHaveBeenCalledWith({
|
||||||
|
id: track.id,
|
||||||
|
order: track.order,
|
||||||
|
url: track.url
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('create => should fail to find log', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const order = 1;
|
||||||
|
const file: Express.Multer.File = {
|
||||||
|
fieldname: 'file',
|
||||||
|
originalname: 'test_track.kml',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
mimetype: 'application/vnd.google-earth.kml+xml',
|
||||||
|
size: 12345,
|
||||||
|
destination: '/tmp/uploads',
|
||||||
|
filename: 'unique-filename-123.kml',
|
||||||
|
path: '/tmp/uploads/unique-filename-123.kml',
|
||||||
|
buffer: Buffer.from('<kml xmlns="http://www.opengis.net"></kml>'),
|
||||||
|
stream: new Readable()
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockReturnValue(undefined);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.create(logId, order, file);
|
||||||
|
|
||||||
|
fail('find failed to throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(CustomError);
|
||||||
|
expect(mockLogService.find).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.find).toHaveBeenCalledWith(logId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should delete a track file', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde';
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const fileName = 'test_track.kml'
|
||||||
|
const mockDeleteResult: DeleteResult ={
|
||||||
|
raw: [],
|
||||||
|
affected: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackRepository, 'delete').mockReturnValue(mockDeleteResult);
|
||||||
|
|
||||||
|
const result = await service.delete(id, logId, fileName)
|
||||||
|
|
||||||
|
expect(result).toEqual(mockDeleteResult);
|
||||||
|
expect(mockTrackRepository.delete).toHaveBeenCalled();
|
||||||
|
expect(mockTrackRepository.delete).toHaveBeenCalledWith({ id: id })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('delete => should fail to delete file in file service', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde';
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const fileName = 'test_track.kml'
|
||||||
|
|
||||||
|
jest.spyOn(mockFileService, 'deleteFile').mockRejectedValue(new Error('Failed to delete file'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.delete(id, logId, fileName)
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(mockFileService.deleteFile).toHaveBeenCalled();
|
||||||
|
expect(mockFileService.deleteFile).toHaveBeenCalledWith('tracks', logId, fileName)
|
||||||
|
expect(mockFileService.deleteFile).rejects.toThrow('Failed to delete file')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('downloadTrackFile => should download a track file by log id and filename', async () => {
|
||||||
|
const logId: string = '95834f84-0a02-44d3-884e-a20237adeca0';
|
||||||
|
const fileName = 'test_track.kml';
|
||||||
|
const mockStreamToBufferString = 'fake-stream-to-buffer-string'
|
||||||
|
|
||||||
|
jest.spyOn(mockFileService, 'downloadFile').mockReturnValue(mockStreamToBufferString);
|
||||||
|
|
||||||
|
const result = await service.downloadTrackFile(logId, fileName);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockStreamToBufferString);
|
||||||
|
expect(mockFileService.downloadFile).toHaveBeenCalled();
|
||||||
|
expect(mockFileService.downloadFile).toHaveBeenCalledWith('tracks', logId, fileName);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('find => should find a track file by id', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde';
|
||||||
|
const track = {
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
url: 'http://track.example.com',
|
||||||
|
order: 1
|
||||||
|
} as TrackEntity
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackRepository, 'findOneBy').mockReturnValue(track)
|
||||||
|
|
||||||
|
const result = await service.find(id);
|
||||||
|
|
||||||
|
expect(result).toEqual(track);
|
||||||
|
expect(mockTrackRepository.findOneBy).toHaveBeenCalled();
|
||||||
|
expect(mockTrackRepository.findOneBy).toHaveBeenCalledWith({ id: id })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should find all track files by log id', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
const log = {
|
||||||
|
id: '094ec69c-72c4-4995-8821-79d5b79bedda',
|
||||||
|
pilotId: '7c4bae6b-9ec4-469e-8e16-fcbf0b940936',
|
||||||
|
date: new Date('2026-02-21'),
|
||||||
|
aircraftMakeModel: 'Cessna 172M',
|
||||||
|
aircraftIdentity: 'N12345',
|
||||||
|
routeFrom: 'KMSP',
|
||||||
|
routeTo: 'KMSP',
|
||||||
|
durationOfFlight: 1,
|
||||||
|
singleEngineLand: 1,
|
||||||
|
simulatorAtd: null,
|
||||||
|
landingsDay: 1,
|
||||||
|
landingsNight: null,
|
||||||
|
groundTrainingReceived: null,
|
||||||
|
flightTrainingReceived: null,
|
||||||
|
crossCountry: 1,
|
||||||
|
night: null,
|
||||||
|
solo: 1,
|
||||||
|
pilotInCommand: 1,
|
||||||
|
instrumentActual: null,
|
||||||
|
instrumentSimulated: null,
|
||||||
|
instrumentApproaches: null,
|
||||||
|
instrumentHolds: null,
|
||||||
|
instrumentNavTrack: null,
|
||||||
|
notes: 'This is a test',
|
||||||
|
tracks: []
|
||||||
|
} as LogEntity;
|
||||||
|
const track = {
|
||||||
|
id: 'a6973d91-629f-4e10-aa28-016076a21fde',
|
||||||
|
url: 'http://track.example.com',
|
||||||
|
order: 1
|
||||||
|
} as TrackEntity;
|
||||||
|
const tracks = [track]
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockReturnValue(log)
|
||||||
|
jest.spyOn(mockTrackRepository, 'find').mockReturnValue(tracks);
|
||||||
|
|
||||||
|
const result = await service.findAll(logId);
|
||||||
|
|
||||||
|
expect(result).toEqual(tracks);
|
||||||
|
expect(mockTrackRepository.find).toHaveBeenCalled();
|
||||||
|
expect(mockTrackRepository.find).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
log: {
|
||||||
|
id: logId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('findAll => should fail to find log', async () => {
|
||||||
|
const logId = '094ec69c-72c4-4995-8821-79d5b79bedda';
|
||||||
|
|
||||||
|
jest.spyOn(mockLogService, 'find').mockReturnValue(undefined);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.findAll(logId);
|
||||||
|
|
||||||
|
fail('find failed to throw error')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(CustomError);
|
||||||
|
expect(mockLogService.find).toHaveBeenCalled();
|
||||||
|
expect(mockLogService.find).toHaveBeenCalledWith(logId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('update => should update a track', async () => {
|
||||||
|
const id = 'a6973d91-629f-4e10-aa28-016076a21fde';
|
||||||
|
const track = {
|
||||||
|
|
||||||
|
} as TrackDto;
|
||||||
|
const mockUpdateResult: UpdateResult = {
|
||||||
|
affected: 1,
|
||||||
|
raw: [],
|
||||||
|
generatedMaps: []
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.spyOn(mockTrackRepository, 'update').mockReturnValue(mockUpdateResult)
|
||||||
|
|
||||||
|
const result = await service.update(id, track);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockUpdateResult);
|
||||||
|
expect(mockTrackRepository.update).toHaveBeenCalled();
|
||||||
|
expect(mockTrackRepository.update).toHaveBeenCalledWith(id, track)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,10 +3,10 @@ import { TrackDto } from "./track.dto";
|
|||||||
import { TrackEntity } from "./track.entity";
|
import { TrackEntity } from "./track.entity";
|
||||||
import { InjectRepository } from "@nestjs/typeorm";
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { LogService } from "src/log/log.service";
|
import { LogService } from "../log/log.service";
|
||||||
import { LogEntity } from "src/log/log.entity";
|
import { LogEntity } from "../log/log.entity";
|
||||||
import { CustomError } from "src/error/customError";
|
import { CustomError } from "../error/customError";
|
||||||
import { FileService } from "src/file/file.service";
|
import { FileService } from "../file/file.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TrackService {
|
export class TrackService {
|
||||||
@@ -19,11 +19,7 @@ export class TrackService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async find(id: string): Promise<TrackEntity> {
|
async find(id: string): Promise<TrackEntity> {
|
||||||
try {
|
return await this.trackRepository.findOneBy({ id });
|
||||||
return await this.trackRepository.findOneBy({ id });
|
|
||||||
} catch (error) {
|
|
||||||
throw new CustomError('Track not found', 'Not found', 404)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(logId: string): Promise<TrackEntity[]> {
|
async findAll(logId: string): Promise<TrackEntity[]> {
|
||||||
@@ -40,10 +36,11 @@ export class TrackService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return tracks;
|
return tracks;
|
||||||
|
} else {
|
||||||
|
throw new CustomError('Tracks not found', 'Not found', 404);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
throw error
|
||||||
throw new CustomError('Tracks not found', 'Not found', 404);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,21 +66,11 @@ export class TrackService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, track: TrackDto): Promise<UpdateResult> {
|
async update(id: string, track: TrackDto): Promise<UpdateResult> {
|
||||||
try {
|
return await this.trackRepository.update(id, track);
|
||||||
return await this.trackRepository.update(id, track);
|
|
||||||
} catch(error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadTrackFile(logId: string, fileName: string): Promise<string> {
|
async downloadTrackFile(logId: string, fileName: string): Promise<string> {
|
||||||
try {
|
return await this.fileService.downloadFile(this.containerName, logId, fileName);
|
||||||
const downloadedFile: string = await this.fileService.downloadFile(this.containerName, logId, fileName);
|
|
||||||
|
|
||||||
return downloadedFile;
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string, logId: string, fileName: string): Promise<DeleteResult> {
|
async delete(id: string, logId: string, fileName: string): Promise<DeleteResult> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.0-beta-4",
|
"version": "2.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { useEffect, useReducer } from "react";
|
|||||||
import { useLogs } from "../../hooks/logs/UseLogs";
|
import { useLogs } from "../../hooks/logs/UseLogs";
|
||||||
import { LogbookEntry } from "../logbook/LogbookEntry.interface";
|
import { LogbookEntry } from "../logbook/LogbookEntry.interface";
|
||||||
import { initialState, reducer } from "./reducer";
|
import { initialState, reducer } from "./reducer";
|
||||||
|
import { useOidc } from "../../auth/oidcConfig";
|
||||||
import Alert from "../alert/Alert";
|
import Alert from "../alert/Alert";
|
||||||
|
|
||||||
const Flights = () => {
|
const Flights = () => {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
const { logs, logsLoading } = useLogs();
|
const { logs, logsLoading } = useLogs();
|
||||||
|
const { isUserLoggedIn } = useOidc();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
@@ -19,7 +21,12 @@ const Flights = () => {
|
|||||||
|
|
||||||
if (flights && flights.length > 0) {
|
if (flights && flights.length > 0) {
|
||||||
dispatch({ type: 'SET_FLIGHTS', payload: flights})
|
dispatch({ type: 'SET_FLIGHTS', payload: flights})
|
||||||
dispatch({ type: 'SET_ALERT', payload: undefined })
|
|
||||||
|
if (!isUserLoggedIn && flights.length >= 5) {
|
||||||
|
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'A limited number of flights displayed. Sign in to view all flights.'}})
|
||||||
|
} else {
|
||||||
|
dispatch({ type: 'SET_ALERT', payload: undefined })
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No flights found' }})
|
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No flights found' }})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const LogForm = () => {
|
|||||||
`api/logs/${logbookContext.state.selectedLogId}`
|
`api/logs/${logbookContext.state.selectedLogId}`
|
||||||
);
|
);
|
||||||
const log = response.data;
|
const log = response.data;
|
||||||
console.log(log)
|
|
||||||
reset(log);
|
reset(log);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
@@ -53,11 +53,16 @@ const LogForm = () => {
|
|||||||
if (pilots && FormMode.ADD) {
|
if (pilots && FormMode.ADD) {
|
||||||
const newPilotsOptions = pilots.map((pilot) => {
|
const newPilotsOptions = pilots.map((pilot) => {
|
||||||
return {
|
return {
|
||||||
key: pilot.id,
|
|
||||||
label: pilot.name,
|
label: pilot.name,
|
||||||
|
value: pilot.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newPilotsOptions.unshift({
|
||||||
|
label: '',
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
|
||||||
dispatch({ type: 'SET_PILOT_OPTIONS', payload: newPilotsOptions });
|
dispatch({ type: 'SET_PILOT_OPTIONS', payload: newPilotsOptions });
|
||||||
}
|
}
|
||||||
}, [pilots]);
|
}, [pilots]);
|
||||||
@@ -80,9 +85,9 @@ const LogForm = () => {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={[value]}
|
value={[value]}
|
||||||
>
|
>
|
||||||
{state.pilotOptions?.map((pilotOption: { key: string; label: string, }) => {
|
{state.pilotOptions?.map((pilotOption: { label: string, value: string; }) => {
|
||||||
return (
|
return (
|
||||||
<option key={pilotOption.key}>{pilotOption.label}</option>
|
<option value={pilotOption.value}>{pilotOption.label}</option>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ export interface LogFormState {
|
|||||||
alert: Alert | undefined;
|
alert: Alert | undefined;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
pilotOptions: { key: string; label: string; }[];
|
pilotOptions: { label: string; value: string; }[];
|
||||||
selectedPilotName: string;
|
selectedPilotName: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type Action =
|
|||||||
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
||||||
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
| { type: 'SET_IS_DISABLED'; payload: boolean }
|
||||||
| { type: 'SET_IS_LOADING'; payload: boolean }
|
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||||
| { type: 'SET_PILOT_OPTIONS'; payload: { key: string, label: string; }[] }
|
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||||
| { type: 'SET_SELECTED_PILOT_NAME'; payload: string };
|
| { type: 'SET_SELECTED_PILOT_NAME'; payload: string };
|
||||||
|
|
||||||
export const initialState: LogFormState = {
|
export const initialState: LogFormState = {
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ import { UserRole } from '../../enums/userRole';
|
|||||||
import { useBreakpoints } from '../../hooks/useBreakpoints/UseBreakpoints';
|
import { useBreakpoints } from '../../hooks/useBreakpoints/UseBreakpoints';
|
||||||
import { ScreenSize } from '../../enums/screenSize';
|
import { ScreenSize } from '../../enums/screenSize';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faAdd, faEllipsisVertical, faPen, faEye, faTrash, faMapLocationDot } from '@fortawesome/free-solid-svg-icons'
|
import { faAdd, faEllipsisVertical, faPen, faEye, faTrash, faAngleLeft, faAngleRight, faAnglesLeft, faAnglesRight } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, HeaderContext, useReactTable } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, flexRender, getCoreRowModel, HeaderContext, PaginationState, useReactTable } from '@tanstack/react-table';
|
||||||
import { useLogbookContext } from '../../hooks/logbookContext/UseLogbookContext';
|
import { useLogbookContext } from '../../hooks/logbookContext/UseLogbookContext';
|
||||||
import LogbookDrawer from '../logbookDrawer/LogbookDrawer';
|
import LogbookDrawer from '../logbookDrawer/LogbookDrawer';
|
||||||
import Alert from '../alert/Alert';
|
import Alert from '../alert/Alert';
|
||||||
|
|
||||||
const Logbook: React.FC<unknown> = () => {
|
const Logbook: React.FC<unknown> = () => {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
const [columnVisibility, setColumnVisibility] = useState({})
|
const [columnVisibility, setColumnVisibility] = useState({});
|
||||||
const logbookContext = useLogbookContext()
|
const logbookContext = useLogbookContext();
|
||||||
const { isUserLoggedIn } = useOidc();
|
const { isUserLoggedIn } = useOidc();
|
||||||
const { userRole } = useUserRole();
|
const { userRole } = useUserRole();
|
||||||
const { screenSize } = useBreakpoints();
|
const { screenSize } = useBreakpoints();
|
||||||
@@ -338,25 +338,51 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
actions
|
actions
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const onPaginationChange = (updater: any) => {
|
||||||
|
const newPaginationState: PaginationState = typeof updater === 'function' ? updater(state.pagination) : updater;
|
||||||
|
|
||||||
|
dispatch({ type: 'SET_PAGINATION', payload: newPaginationState })
|
||||||
|
}
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: state.entries,
|
data: state.entries,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
manualPagination: true,
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onPaginationChange: onPaginationChange,
|
||||||
|
rowCount: state.totalEntries,
|
||||||
state: {
|
state: {
|
||||||
columnVisibility: columnVisibility
|
columnVisibility: columnVisibility,
|
||||||
|
pagination: state.pagination
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLogbookEntries = async () => {
|
const {
|
||||||
|
firstPage,
|
||||||
|
getCanNextPage,
|
||||||
|
getCanPreviousPage,
|
||||||
|
getPageCount,
|
||||||
|
getState,
|
||||||
|
lastPage,
|
||||||
|
nextPage,
|
||||||
|
previousPage,
|
||||||
|
setPageIndex
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const getLogbookEntries = async (pageIndex?: number, pageSize?: number) => {
|
||||||
try {
|
try {
|
||||||
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
dispatch({ type: 'SET_IS_LOADING', payload: true });
|
||||||
|
|
||||||
const response: AxiosResponse = await httpClient.get(`api/logs`);
|
const response: AxiosResponse = await httpClient.get(`api/logs`, {
|
||||||
|
params: {
|
||||||
|
skip: pageIndex,
|
||||||
|
take: pageSize
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (response.data.length > 0) {
|
if (response.data.entities.length > 0) {
|
||||||
const entries: LogbookEntry[] = response.data;
|
const entries: LogbookEntry[] = response.data.entities;
|
||||||
const entryColumns: string[] = Object.keys(entries[0]);
|
const entryColumns: string[] = Object.keys(entries[0]);
|
||||||
const columnVisibility: {[key: string]: boolean} = {}
|
const columnVisibility: {[key: string]: boolean} = {}
|
||||||
|
|
||||||
@@ -375,9 +401,11 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
|
|
||||||
entries.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
entries.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
setColumnVisibility(columnVisibility)
|
setColumnVisibility(columnVisibility)
|
||||||
dispatch({ type: 'SET_ENTRIES', payload: entries });
|
dispatch({ type: 'SET_ENTRIES', payload: { entries: entries, totalEntries: response.data.total }});
|
||||||
|
|
||||||
if (state.alert) {
|
if (!isUserLoggedIn && response.data.length >= 5) {
|
||||||
|
dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'A limited number of log entries displayed. Sign in to view all log entries.'}})
|
||||||
|
} else {
|
||||||
dispatch({ type: 'SET_ALERT', payload: undefined})
|
dispatch({ type: 'SET_ALERT', payload: undefined})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -440,7 +468,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
payload: false
|
payload: false
|
||||||
});
|
});
|
||||||
logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: '' })
|
logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: '' })
|
||||||
await getLogbookEntries();
|
await getLogbookEntries(state.pagination?.pageIndex, state.pagination?.pageSize);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
|
|
||||||
@@ -460,11 +488,35 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onRowsPerPageChanged = (event: any) => {
|
||||||
|
console.log(event.target.value)
|
||||||
|
const newPaginationState: PaginationState = {
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: event.target.value !== 'All' ? Number(event.target.value) : state.totalEntries
|
||||||
|
};
|
||||||
|
console.log(newPaginationState)
|
||||||
|
dispatch({ type: 'SET_PAGINATION', payload: newPaginationState })
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!logbookContext.state.isDrawerOpen) {
|
if (!logbookContext.state.isDrawerOpen) {
|
||||||
getLogbookEntries();
|
getLogbookEntries(state.pagination.pageIndex, state.pagination.pageSize);
|
||||||
}
|
}
|
||||||
}, [logbookContext.state.isDrawerOpen]);
|
}, [logbookContext.state.isDrawerOpen, state.pagination.pageIndex, state.pagination.pageSize]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const pages: number[] = [];
|
||||||
|
|
||||||
|
if (state.pagination?.pageSize) {
|
||||||
|
const totalPages = getPageCount();
|
||||||
|
|
||||||
|
for(let i = 0; i < totalPages; i++) {
|
||||||
|
pages.push(i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: 'SET_PAGES', payload: pages })
|
||||||
|
}, [state.totalEntries, state.pagination?.pageSize])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -496,8 +548,24 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!state.isLoading && state.entries.length > 0 && screenSize !== ScreenSize.SM && (
|
{!state.isLoading && state.entries.length > 0 && screenSize !== ScreenSize.SM && (
|
||||||
<div className='col-span-12 p-5 bg-base-100 border border-base-100 rounded-lg'>
|
<div className='col-span-12 pr-5 pb-5 pl-5 bg-base-100 border border-base-100 rounded-lg '>
|
||||||
<div className='overflow-x-auto'>
|
<div className='overflow-x-auto mb-5'>
|
||||||
|
<div className='col-span-12 justify-self-end self-center mt-1 mr-1 mb-2'>
|
||||||
|
<label className='select select-sm select-ghost'>
|
||||||
|
<span className='label'>Rows per page</span>
|
||||||
|
<select
|
||||||
|
defaultValue={1}
|
||||||
|
onChange={onRowsPerPageChanged}
|
||||||
|
value={state.pagination?.pageSize}
|
||||||
|
>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={25}>25</option>
|
||||||
|
<option value={3}>50</option>
|
||||||
|
<option value={state.totalEntries}>All</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
<table className='table min-w-full h-auto table-auto w-full'>
|
<table className='table min-w-full h-auto table-auto w-full'>
|
||||||
<thead className='bg-base-200'>
|
<thead className='bg-base-200'>
|
||||||
{table.getHeaderGroups().map((headerGroup, headerGroupIndex) => (
|
{table.getHeaderGroups().map((headerGroup, headerGroupIndex) => (
|
||||||
@@ -583,6 +651,48 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{state.pagination.pageSize !== state.totalEntries &&
|
||||||
|
<div className='col-span-12 justify-self-center self-center'>
|
||||||
|
<div className='join'>
|
||||||
|
<button
|
||||||
|
className='join-item btn btn-sm'
|
||||||
|
onClick={firstPage}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faAnglesLeft} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='join-item btn btn-sm'
|
||||||
|
onClick={previousPage}
|
||||||
|
disabled={!getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faAngleLeft} />
|
||||||
|
</button>
|
||||||
|
{state.pages.map((page, index) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`join-item btn btn-sm ${getState().pagination.pageIndex === index ? 'btn-active' : ''}`}
|
||||||
|
onClick={() => setPageIndex(index)}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<button
|
||||||
|
className='join-item btn btn-sm'
|
||||||
|
onClick={() => nextPage()}
|
||||||
|
disabled={!getCanNextPage()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faAngleRight} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='join-item btn btn-sm'
|
||||||
|
onClick={lastPage}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faAnglesRight} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{state.entries.length > 0 && screenSize === ScreenSize.SM &&
|
{state.entries.length > 0 && screenSize === ScreenSize.SM &&
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef, PaginationState } from '@tanstack/react-table';
|
||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
import { Alert } from '../../interfaces/Alert.interface';
|
import { Alert } from '../../interfaces/Alert.interface';
|
||||||
import { LogbookEntry } from './LogbookEntry.interface';
|
import { LogbookEntry } from './LogbookEntry.interface';
|
||||||
@@ -10,4 +10,7 @@ export interface LogbookState {
|
|||||||
isConfirmDialogLoading: boolean;
|
isConfirmDialogLoading: boolean;
|
||||||
isConfirmDialogOpen: boolean;
|
isConfirmDialogOpen: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
pages: number[];
|
||||||
|
pagination: PaginationState;
|
||||||
|
totalEntries: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef, PaginationState } from '@tanstack/react-table';
|
||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
import { Alert } from '../../interfaces/Alert.interface';
|
import { Alert } from '../../interfaces/Alert.interface';
|
||||||
import { LogbookEntry } from './LogbookEntry.interface';
|
import { LogbookEntry } from './LogbookEntry.interface';
|
||||||
@@ -7,11 +7,12 @@ import { LogbookState } from './LogbookState.interface';
|
|||||||
type Action =
|
type Action =
|
||||||
| { type: 'SET_COLUMNS'; payload: ColumnDef<LogbookEntry>[] }
|
| { type: 'SET_COLUMNS'; payload: ColumnDef<LogbookEntry>[] }
|
||||||
| { type: 'SET_IS_CONFIRMATION_DIALOG_OPEN'; payload: boolean }
|
| { type: 'SET_IS_CONFIRMATION_DIALOG_OPEN'; payload: boolean }
|
||||||
| { type: 'SET_ENTRIES'; payload: LogbookEntry[] }
|
| { type: 'SET_ENTRIES'; payload: { entries: LogbookEntry[], totalEntries: number } }
|
||||||
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
| { type: 'SET_ALERT'; payload: Alert | undefined }
|
||||||
| { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean }
|
| { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean }
|
||||||
| { type: 'SET_IS_LOADING'; payload: boolean };
|
| { type: 'SET_IS_LOADING'; payload: boolean }
|
||||||
|
| { type: 'SET_PAGES'; payload: number[] }
|
||||||
|
| { type: 'SET_PAGINATION'; payload: PaginationState };
|
||||||
|
|
||||||
export const initialState: LogbookState = {
|
export const initialState: LogbookState = {
|
||||||
alert: undefined,
|
alert: undefined,
|
||||||
@@ -19,7 +20,13 @@ export const initialState: LogbookState = {
|
|||||||
entries: [],
|
entries: [],
|
||||||
isConfirmDialogLoading: false,
|
isConfirmDialogLoading: false,
|
||||||
isConfirmDialogOpen: false,
|
isConfirmDialogOpen: false,
|
||||||
isLoading: false
|
isLoading: false,
|
||||||
|
pages: [],
|
||||||
|
pagination: {
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10
|
||||||
|
},
|
||||||
|
totalEntries: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reducer = (
|
export const reducer = (
|
||||||
@@ -42,7 +49,8 @@ export const reducer = (
|
|||||||
case 'SET_ENTRIES': {
|
case 'SET_ENTRIES': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
entries: action.payload
|
entries: action.payload.entries,
|
||||||
|
totalEntries: action.payload.totalEntries
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'SET_ALERT': {
|
case 'SET_ALERT': {
|
||||||
@@ -63,6 +71,18 @@ export const reducer = (
|
|||||||
isLoading: action.payload
|
isLoading: action.payload
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'SET_PAGES': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pages: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_PAGINATION': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pagination: action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -60,7 +60,7 @@ resource "azurerm_container_app" "container_app" {
|
|||||||
|
|
||||||
container {
|
container {
|
||||||
cpu = 0.25
|
cpu = 0.25
|
||||||
image = "noahspan/flying:20588458616"
|
image = "noahspan/flying:20589367895"
|
||||||
memory = "0.5Gi"
|
memory = "0.5Gi"
|
||||||
name = "flying"
|
name = "flying"
|
||||||
|
|
||||||
@@ -99,6 +99,11 @@ resource "azurerm_container_app" "container_app" {
|
|||||||
value = var.JWKS_URI
|
value = var.JWKS_URI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "NODE_ENV"
|
||||||
|
value = "test"
|
||||||
|
}
|
||||||
|
|
||||||
env {
|
env {
|
||||||
name = "SESSION_SECRET"
|
name = "SESSION_SECRET"
|
||||||
secret_name = "session-secret"
|
secret_name = "session-secret"
|
||||||
@@ -116,13 +121,13 @@ resource "azurerm_container_app" "container_app" {
|
|||||||
|
|
||||||
env {
|
env {
|
||||||
name = "DB_SYNC"
|
name = "DB_SYNC"
|
||||||
value = "false"
|
value = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
startup_probe {
|
startup_probe {
|
||||||
failure_count_threshold = 10
|
failure_count_threshold = 3
|
||||||
initial_delay = 1
|
initial_delay = 15
|
||||||
interval_seconds = 2
|
interval_seconds = 30
|
||||||
path = "/api/health"
|
path = "/api/health"
|
||||||
port = 3000
|
port = 3000
|
||||||
transport = "HTTP"
|
transport = "HTTP"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noahspan/flying",
|
"name": "@noahspan/flying",
|
||||||
"version": "2.0.0-beta-4",
|
"version": "2.0.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node api/dist/main",
|
"start": "node api/dist/main",
|
||||||
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
||||||
|
|||||||
Reference in New Issue
Block a user