From 6700c836925c160d1d0946de916b6124d571c922 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Sun, 1 Mar 2026 16:46:27 -0600 Subject: [PATCH 1/2] adding api unit tests --- .../{build.yaml => build_and_test.yaml} | 6 +- .github/workflows/main.yaml | 2 +- .github/workflows/pull_request.yaml | 2 +- .github/workflows/tag.yaml | 2 +- api/src/endorsement/endorsement.entity.ts | 2 +- api/src/file/file.service.ts | 4 +- api/src/health/health.controller.spec.ts | 33 +- api/src/health/health.controller.ts | 20 +- api/src/health/health.module.ts | 4 - api/src/health/health.service.spec.ts | 14 - api/src/health/health.service.ts | 22 -- api/src/log/log.controller.spec.ts | 200 ++++++++++ api/src/log/log.controller.ts | 9 +- api/src/log/log.dto.ts | 2 - api/src/log/log.entity.ts | 6 +- api/src/log/log.module.ts | 2 +- api/src/log/log.service.spec.ts | 288 +++++++++++++++ api/src/log/log.service.ts | 37 +- api/src/medical/medical.entity.ts | 2 +- api/src/pilot/pilot.controller.spec.ts | 260 +++++++++++++ api/src/pilot/pilot.controller.ts | 3 +- api/src/pilot/pilot.dto.ts | 2 - api/src/pilot/pilot.entity.ts | 6 +- api/src/pilot/pilot.service.spec.ts | 206 +++++++++++ api/src/pilot/pilot.service.ts | 17 +- api/src/track/track.controller.spec.ts | 218 +++++++++++ api/src/track/track.entity.ts | 2 +- api/src/track/track.module.ts | 2 +- api/src/track/track.service.spec.ts | 346 ++++++++++++++++++ api/src/track/track.service.ts | 33 +- client/src/components/logForm/LogForm.tsx | 13 +- .../logForm/LogFormState.interface.ts | 2 +- client/src/components/logForm/reducer.tsx | 2 +- database/flying.db | Bin 61440 -> 69632 bytes infrastructure/container_app.tf | 15 +- 35 files changed, 1607 insertions(+), 177 deletions(-) rename .github/workflows/{build.yaml => build_and_test.yaml} (95%) delete mode 100644 api/src/health/health.service.spec.ts delete mode 100644 api/src/health/health.service.ts create mode 100644 api/src/log/log.controller.spec.ts create mode 100644 api/src/log/log.service.spec.ts create mode 100644 api/src/pilot/pilot.controller.spec.ts create mode 100644 api/src/pilot/pilot.service.spec.ts create mode 100644 api/src/track/track.controller.spec.ts create mode 100644 api/src/track/track.service.spec.ts diff --git a/.github/workflows/build.yaml b/.github/workflows/build_and_test.yaml similarity index 95% rename from .github/workflows/build.yaml rename to .github/workflows/build_and_test.yaml index 2b5efeb..10b72b1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build_and_test.yaml @@ -10,7 +10,7 @@ on: type: string jobs: - build: + build-and-test: runs-on: ubuntu-latest environment: ${{ inputs.environment_name }} steps: @@ -38,6 +38,10 @@ jobs: run: | npm run build -w api + - name: Test API + run: | + npm run test -w api + - name: Build Client env: VITE_API_URL: ${{ vars.VITE_API_URL }} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index c679d15..78fbb5b 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -13,7 +13,7 @@ jobs: name: build needs: - changes - uses: ./.github/workflows/build.yaml + uses: ./.github/workflows/build_and_test.yaml with: environment_name: test version_number: ${{ github.run_id }} diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 6a219ed..a465173 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -12,7 +12,7 @@ jobs: name: build needs: - changes - uses: ./.github/workflows/build.yaml + uses: ./.github/workflows/build_and_test.yaml with: environment_name: pull_request version_number: ${{ github.run_id }} diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index a074947..84bb150 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -7,7 +7,7 @@ on: jobs: build: name: build - uses: ./.github/workflows/build.yaml + uses: ./.github/workflows/build_and_test.yaml with: environment_name: prod version_number: ${{ github.ref_name }} diff --git a/api/src/endorsement/endorsement.entity.ts b/api/src/endorsement/endorsement.entity.ts index 240e40b..2e1f53d 100644 --- a/api/src/endorsement/endorsement.entity.ts +++ b/api/src/endorsement/endorsement.entity.ts @@ -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'; @Entity({ name: 'endorsements' }) diff --git a/api/src/file/file.service.ts b/api/src/file/file.service.ts index 3c82e58..0753120 100644 --- a/api/src/file/file.service.ts +++ b/api/src/file/file.service.ts @@ -48,10 +48,10 @@ import { ConfigService } from '@nestjs/config'; return fileUrl; } - async downloadFile(containerName: string, rowKey: string, fileName: string): Promise { + async downloadFile(containerName: string, logId: string, fileName: string): Promise { this.containerName = containerName; - const blockBlobClient = await this.getBlobClient(`${rowKey}/${fileName}`); + const blockBlobClient = await this.getBlobClient(`${logId}/${fileName}`); const downloadBlockBlobResponse = await blockBlobClient.download(); const downloaded: string = (await this.streamToBuffer(downloadBlockBlobResponse.readableStreamBody)).toString() diff --git a/api/src/health/health.controller.spec.ts b/api/src/health/health.controller.spec.ts index c2bf1be..05b5224 100644 --- a/api/src/health/health.controller.spec.ts +++ b/api/src/health/health.controller.spec.ts @@ -1,42 +1,25 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { HealthController } from './health.controller'; -import { HealthService } from './health.service'; +import { Test, TestingModule } from "@nestjs/testing"; +import { HealthController } from "./health.controller" +import { HttpStatus } from "@nestjs/common"; describe('HealthController', () => { - let controller; HealthController; - - const mockHealthService = { - isDatabaseConnected: jest.fn() - } + let controller: HealthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [HealthController], - providers: [HealthService] + controllers: [HealthController] }).compile(); controller = module.get(HealthController); }) - it('isHealthy => should return true', () => { + it('should be defined', () => { 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 () => { - jest.spyOn(mockHealthService, 'isDatabaseConnected').mockReturnValue(false); - + it('isHealth => should return status ok', async () => { const result = await controller.isHealthy(); - expect(mockHealthService.isDatabaseConnected).toHaveBeenCalled(); - expect(result).toEqual(false); + expect(result).toEqual(HttpStatus.OK); }) }) \ No newline at end of file diff --git a/api/src/health/health.controller.ts b/api/src/health/health.controller.ts index 6a0d33e..2b36083 100644 --- a/api/src/health/health.controller.ts +++ b/api/src/health/health.controller.ts @@ -1,27 +1,13 @@ import { Controller, Get, - HttpException, + HttpStatus, } from '@nestjs/common'; -import { HealthService } from './health.service'; -import { CustomError } from 'src/error/customError'; - @Controller('health') export class HealthController { - constructor( - private readonly healthService: HealthService - ) {} - - @Get() - async isHealthy(): Promise { - try { - return await this.healthService.isDatabaseConnected(); - } catch (error) { - const customError = error as CustomError; - - throw new HttpException(customError.message, customError.statusCode); - } + async isHealthy(): Promise { + return HttpStatus.OK } } diff --git a/api/src/health/health.module.ts b/api/src/health/health.module.ts index 15a913d..7476abe 100644 --- a/api/src/health/health.module.ts +++ b/api/src/health/health.module.ts @@ -1,11 +1,7 @@ import { Module } from '@nestjs/common'; import { HealthController } from './health.controller'; -import { HealthService } from './health.service'; @Module({ controllers: [HealthController], - providers: [ - HealthService - ] }) export class HealthModule {} diff --git a/api/src/health/health.service.spec.ts b/api/src/health/health.service.spec.ts deleted file mode 100644 index a3d3f85..0000000 --- a/api/src/health/health.service.spec.ts +++ /dev/null @@ -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); - }); -}); \ No newline at end of file diff --git a/api/src/health/health.service.ts b/api/src/health/health.service.ts deleted file mode 100644 index 53ec80b..0000000 --- a/api/src/health/health.service.ts +++ /dev/null @@ -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 { - 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; - } - } -} diff --git a/api/src/log/log.controller.spec.ts b/api/src/log/log.controller.spec.ts new file mode 100644 index 0000000..4f28318 --- /dev/null +++ b/api/src/log/log.controller.spec.ts @@ -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(), + findAll: 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); + }) + + 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('findAll => should find all logs', async () => { + const id: string = '95834f84-0a02-44d3-884e-a20237adeca0'; + const log = { + + } as LogEntity; + const logs = [log] + + jest.spyOn(mockLogService, 'findAll').mockReturnValue(logs); + + const result = await controller.findAll(); + + expect(result).toEqual(logs); + expect(mockLogService.findAll).toHaveBeenCalled(); + }) + + it('findAll => should fail to find all logs', async () => { + jest.spyOn(mockLogService, 'findAll').mockRejectedValue(new Error('Logs not found')) + + try { + await controller.findAll(); + + fail('findAll did not throw error'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(mockLogService.findAll).toHaveBeenCalled(); + expect(mockLogService.findAll).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') + } + }) +}) \ No newline at end of file diff --git a/api/src/log/log.controller.ts b/api/src/log/log.controller.ts index c61c220..57e7fac 100644 --- a/api/src/log/log.controller.ts +++ b/api/src/log/log.controller.ts @@ -60,12 +60,12 @@ export class LogController { @Post() @UseGuards(AuthGuard) - async create(@Body() logDto: LogDto): Promise { + async create(@Body() logDto: LogDto) { try { return await this.logService.create(logDto); } catch (error) { const customError = error as CustomError; - console.log(error) + throw new HttpException(customError.message, customError.statusCode); } } @@ -75,13 +75,12 @@ export class LogController { async update( @Param('id') id: string, @Body() logDto: LogDto - ): Promise { + ) { try { - console.log(logDto); return await this.logService.update(id, logDto); } catch (error) { const customError = error as CustomError; - console.log(customError) + throw new HttpException(customError.message, customError.statusCode); } } diff --git a/api/src/log/log.dto.ts b/api/src/log/log.dto.ts index 561984f..216d55f 100644 --- a/api/src/log/log.dto.ts +++ b/api/src/log/log.dto.ts @@ -1,5 +1,3 @@ -import { PilotEntity } from "src/pilot/pilot.entity"; - export class LogDto { pilotId: string; date: Date; diff --git a/api/src/log/log.entity.ts b/api/src/log/log.entity.ts index 8d7a64f..22a472b 100644 --- a/api/src/log/log.entity.ts +++ b/api/src/log/log.entity.ts @@ -1,6 +1,6 @@ -import { PilotEntity } from 'src/pilot/pilot.entity'; -import { TrackEntity } from 'src/track/track.entity'; -import { ColumnNumericTransformer } from 'src/transformers/columnNumeric.transformer'; +import { PilotEntity } from '../pilot/pilot.entity'; +import { TrackEntity } from '../track/track.entity'; +import { ColumnNumericTransformer } from '../transformers/columnNumeric.transformer'; import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm'; @Entity({ name: 'logs' }) diff --git a/api/src/log/log.module.ts b/api/src/log/log.module.ts index b2a93af..a4bc081 100644 --- a/api/src/log/log.module.ts +++ b/api/src/log/log.module.ts @@ -5,7 +5,7 @@ import { LogEntity } from './log.entity'; import { ConfigService } from '@nestjs/config'; import { FileService } from '../file/file.service'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { PilotModule } from 'src/pilot/pilot.module'; +import { PilotModule } from '../pilot/pilot.module'; @Module({ imports: [ diff --git a/api/src/log/log.service.spec.ts b/api/src/log/log.service.spec.ts new file mode 100644 index 0000000..f0ee18c --- /dev/null +++ b/api/src/log/log.service.spec.ts @@ -0,0 +1,288 @@ +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(), + find: 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); + }) + + 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('findAll => should find all log entries', 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] + + jest.spyOn(mockLogRepository, 'find').mockReturnValue(logs); + + const result = await service.findAll(); + + expect(result).toEqual(logs); + expect(mockLogRepository.find).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); + }) +}) \ No newline at end of file diff --git a/api/src/log/log.service.ts b/api/src/log/log.service.ts index cd2556b..2b12158 100644 --- a/api/src/log/log.service.ts +++ b/api/src/log/log.service.ts @@ -3,10 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm'; import { LogEntity } from './log.entity'; import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; import { LogDto } from './log.dto'; -import { PilotService } from 'src/pilot/pilot.service'; -import { PilotEntity } from 'src/pilot/pilot.entity'; -import { CustomError } from 'src/error/customError'; -import { FileService } from 'src/file/file.service'; +import { PilotService } from '../pilot/pilot.service'; +import { PilotEntity } from '../pilot/pilot.entity'; +import { CustomError } from '../error/customError'; +import { FileService } from '../file/file.service'; @Injectable() export class LogService { @@ -31,32 +31,17 @@ export class LogService { }); } - async create(logDto: LogDto): Promise { - try{ - const pilotEntity: PilotEntity = await this.pilotService.find(logDto.pilotId); - - 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 create(logDto: LogDto): Promise { + return this.logRepository.save(logDto); } - async update(id: string, logDto: LogDto): Promise { - const log = logDto + async update(id: string, logDto: LogDto): Promise { + const logEntity: LogEntity = await this.logRepository.findOneBy({ id }); + const logEntityUpdated = Object.assign(logEntity, logDto) - delete log.tracks; + delete logEntityUpdated.tracks; - return await this.logRepository.update(id, log); + return await this.logRepository.save(logEntityUpdated); } async delete(id: string): Promise { diff --git a/api/src/medical/medical.entity.ts b/api/src/medical/medical.entity.ts index 02550fc..0dd123d 100644 --- a/api/src/medical/medical.entity.ts +++ b/api/src/medical/medical.entity.ts @@ -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'; @Entity({ name: 'medical' }) diff --git a/api/src/pilot/pilot.controller.spec.ts b/api/src/pilot/pilot.controller.spec.ts new file mode 100644 index 0000000..9717008 --- /dev/null +++ b/api/src/pilot/pilot.controller.spec.ts @@ -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); + }); + + 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') + } + }) +}) \ No newline at end of file diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index 797e0e5..a5a7fc1 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -50,9 +50,8 @@ export class PilotController { @Post() @UseGuards(AuthGuard) - async create(@Body() pilotDto: PilotDto) { + async create(@Body() pilotDto: PilotDto): Promise { try { - return await this.pilotService.create(pilotDto); } catch (error) { const customError = error as CustomError; diff --git a/api/src/pilot/pilot.dto.ts b/api/src/pilot/pilot.dto.ts index 1b6c0ac..c5b6ada 100644 --- a/api/src/pilot/pilot.dto.ts +++ b/api/src/pilot/pilot.dto.ts @@ -1,5 +1,3 @@ -import { LogEntity } from "src/log/log.entity"; - export class PilotDto { name: string; address: string; diff --git a/api/src/pilot/pilot.entity.ts b/api/src/pilot/pilot.entity.ts index d78a017..af660f3 100644 --- a/api/src/pilot/pilot.entity.ts +++ b/api/src/pilot/pilot.entity.ts @@ -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 { CertificateEntity } from '../certificate/certificate.entity'; -import { EndorsementEntity } from 'src/endorsement/endorsement.entity'; -import { MedicalEntity } from 'src/medical/medical.entity'; +import { EndorsementEntity } from '../endorsement/endorsement.entity'; +import { MedicalEntity } from '../medical/medical.entity'; @Entity({ name: 'pilots' }) export class PilotEntity { diff --git a/api/src/pilot/pilot.service.spec.ts b/api/src/pilot/pilot.service.spec.ts new file mode 100644 index 0000000..4b1a813 --- /dev/null +++ b/api/src/pilot/pilot.service.spec.ts @@ -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); + }) + + 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 }); + }) +}) \ No newline at end of file diff --git a/api/src/pilot/pilot.service.ts b/api/src/pilot/pilot.service.ts index 15a44b0..0359b2c 100644 --- a/api/src/pilot/pilot.service.ts +++ b/api/src/pilot/pilot.service.ts @@ -3,7 +3,7 @@ import { PilotEntity } from './pilot.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; import { PilotDto } from './pilot.dto'; -import { CustomError } from 'src/error/customError'; +import { CustomError } from '../error/customError'; @Injectable() export class PilotService { @@ -13,7 +13,7 @@ export class PilotService { async find(id: string): Promise { try { - const pilotEntity = await this.pilotRepository.findOneBy({ id }); + const pilotEntity: PilotEntity = await this.pilotRepository.findOneBy({ id }); if (pilotEntity) { return pilotEntity @@ -29,15 +29,18 @@ export class PilotService { return await this.pilotRepository.find(); } - async create(pilot: PilotDto): Promise { - return await this.pilotRepository.insert(pilot); + async create(pilot: PilotDto): Promise { + return await this.pilotRepository.save(pilot); } async update( id: string, - pilot: PilotDto - ): Promise { - return await this.pilotRepository.update(id, pilot); + pilotDto: PilotDto + ): Promise { + 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 { diff --git a/api/src/track/track.controller.spec.ts b/api/src/track/track.controller.spec.ts new file mode 100644 index 0000000..5f56715 --- /dev/null +++ b/api/src/track/track.controller.spec.ts @@ -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); + }) + + 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(''), + 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(''), + 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') + } + }) +}) \ No newline at end of file diff --git a/api/src/track/track.entity.ts b/api/src/track/track.entity.ts index af3e65a..76c7cc5 100644 --- a/api/src/track/track.entity.ts +++ b/api/src/track/track.entity.ts @@ -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'; @Entity({ name: 'tracks' }) diff --git a/api/src/track/track.module.ts b/api/src/track/track.module.ts index 50460ca..1828d4f 100644 --- a/api/src/track/track.module.ts +++ b/api/src/track/track.module.ts @@ -4,7 +4,7 @@ import { TrackEntity } from "./track.entity"; import { TrackController } from "./track.controller"; import { FileService } from "../file/file.service"; import { TrackService } from './track.service'; -import { LogModule } from 'src/log/log.module'; +import { LogModule } from '../log/log.module'; @Module({ imports: [ diff --git a/api/src/track/track.service.spec.ts b/api/src/track/track.service.spec.ts new file mode 100644 index 0000000..2c4f110 --- /dev/null +++ b/api/src/track/track.service.spec.ts @@ -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); + }) + + 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(''), + 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(''), + 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) + }) +}) \ No newline at end of file diff --git a/api/src/track/track.service.ts b/api/src/track/track.service.ts index 9414905..16c579b 100644 --- a/api/src/track/track.service.ts +++ b/api/src/track/track.service.ts @@ -3,10 +3,10 @@ import { TrackDto } from "./track.dto"; import { TrackEntity } from "./track.entity"; import { InjectRepository } from "@nestjs/typeorm"; import { Injectable } from "@nestjs/common"; -import { LogService } from "src/log/log.service"; -import { LogEntity } from "src/log/log.entity"; -import { CustomError } from "src/error/customError"; -import { FileService } from "src/file/file.service"; +import { LogService } from "../log/log.service"; +import { LogEntity } from "../log/log.entity"; +import { CustomError } from "../error/customError"; +import { FileService } from "../file/file.service"; @Injectable() export class TrackService { @@ -19,11 +19,7 @@ export class TrackService { ) {} async find(id: string): Promise { - try { - return await this.trackRepository.findOneBy({ id }); - } catch (error) { - throw new CustomError('Track not found', 'Not found', 404) - } + return await this.trackRepository.findOneBy({ id }); } async findAll(logId: string): Promise { @@ -40,10 +36,11 @@ export class TrackService { }) return tracks; + } else { + throw new CustomError('Tracks not found', 'Not found', 404); } } catch (error) { - console.log(error) - throw new CustomError('Tracks not found', 'Not found', 404); + throw error } } @@ -69,21 +66,11 @@ export class TrackService { } async update(id: string, track: TrackDto): Promise { - try { - return await this.trackRepository.update(id, track); - } catch(error) { - throw error - } + return await this.trackRepository.update(id, track); } async downloadTrackFile(logId: string, fileName: string): Promise { - try { - const downloadedFile: string = await this.fileService.downloadFile(this.containerName, logId, fileName); - - return downloadedFile; - } catch (error) { - throw error - } + return await this.fileService.downloadFile(this.containerName, logId, fileName); } async delete(id: string, logId: string, fileName: string): Promise { diff --git a/client/src/components/logForm/LogForm.tsx b/client/src/components/logForm/LogForm.tsx index 6b102e3..3834625 100644 --- a/client/src/components/logForm/LogForm.tsx +++ b/client/src/components/logForm/LogForm.tsx @@ -33,7 +33,7 @@ const LogForm = () => { `api/logs/${logbookContext.state.selectedLogId}` ); const log = response.data; - console.log(log) + reset(log); } catch (error) { const axiosError = error as AxiosError; @@ -53,11 +53,16 @@ const LogForm = () => { if (pilots && FormMode.ADD) { const newPilotsOptions = pilots.map((pilot) => { return { - key: pilot.id, label: pilot.name, + value: pilot.id, }; }); + newPilotsOptions.unshift({ + label: '', + value: '', + }) + dispatch({ type: 'SET_PILOT_OPTIONS', payload: newPilotsOptions }); } }, [pilots]); @@ -80,9 +85,9 @@ const LogForm = () => { onChange={onChange} value={[value]} > - {state.pilotOptions?.map((pilotOption: { key: string; label: string, }) => { + {state.pilotOptions?.map((pilotOption: { label: string, value: string; }) => { return ( - + ) })} diff --git a/client/src/components/logForm/LogFormState.interface.ts b/client/src/components/logForm/LogFormState.interface.ts index 449ccdc..d87a714 100644 --- a/client/src/components/logForm/LogFormState.interface.ts +++ b/client/src/components/logForm/LogFormState.interface.ts @@ -4,6 +4,6 @@ export interface LogFormState { alert: Alert | undefined; isDisabled: boolean; isLoading: boolean; - pilotOptions: { key: string; label: string; }[]; + pilotOptions: { label: string; value: string; }[]; selectedPilotName: string; } diff --git a/client/src/components/logForm/reducer.tsx b/client/src/components/logForm/reducer.tsx index 277d03f..ec0b089 100644 --- a/client/src/components/logForm/reducer.tsx +++ b/client/src/components/logForm/reducer.tsx @@ -5,7 +5,7 @@ type Action = | { type: 'SET_ALERT'; payload: Alert | undefined } | { type: 'SET_IS_DISABLED'; 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 }; export const initialState: LogFormState = { diff --git a/database/flying.db b/database/flying.db index 7efd5187e49d5000d95ed55539df047256856010..0b48e2638de5baf30eed7e3904012a56d7a08840 100644 GIT binary patch delta 1664 zcmb_b&u`mQ9JdqCA+{4c6Oo%ms=N%;M6HYCxcNa3CF>$lwM|BsF!iuZ&+$uY;n-d5 zAdn{7^*|*K)2JmRK#;-#O$@1H{sZ_25S$P@!i@_DB#<~D#B26SM5)7q5~mQ3PzpJ0a7vwh{h z{)@z8=CBV3dMBHgX7V|AAv)Rn7cwJcY`s4;fMX4+j+POERs)!tZuaae;O*0mb-lOY z1kCPN(R3jCchU4#(0^xO+4iC}G@ZdS0+`Qm?5AuQRv;WgH+92umdr(~-$zOR*RGqk zlOCXcm`=6h41p1#xHqYZK*sM-skLCnzMCgrUbPI(4md%1924r`&E^1f-Ux8q&%z&E zIJbvA-^k&ZZffu?`<9_Q@aEJ7lAlky59tYaL{nRds}m$YJ&muA@A{Bq!&^gWc3@x| zTU@JEn>C?Xy}VKr0>-Qf`fvuF%-zZG5&q&ue1{$T?CZS&aCFPGk5|l*r`N}>GRKyT zkj?0*vAXHN9vlc)*OpeRYwN;tZC$9gnvJD8I=Wh`H-&lwX=`N#b;Hs7&~`w7Gb5;a z4;^(gOeT`Uu<-4trP2i2Uh^iOYlPh|=~=H#@Hi=+C;4i6+>Amz;Wf^QI9cLIe)&Zo z^VIljxchUY;@u|EA`}z7AL(iQ5l3v37>8kO+e>gw)@_eIXV6dQ3yzdR=(nu*_#n&e zNi@!QiHEtghHeXJ08=Vz@qtH~i6?MO9zwi%a;p_JR delta 483 zcmZozz|!!5d4jYc9|HpeKM?Z*F%uArPSi2h@{6xN{jq;w>2&GV@YW*+kiZPGMj$=bOBd zTV}EiuQW?rfGGRs{jC1XQoQ;MBL12Xg%!nxIhiG?@#ynw5n&0d5}JXjZG ziYCIbB}IwJ*~RgO=pyX8LTuuW(je7>P*dU!p$yi|>$&q81%N*Ag?Pyks5Vp|q*fJS z4qO+85bGuZmIgLP{x}ByxXpqBA^el$<}aGU$il(Me}e&txPXFZ_&48JuOToofSr{Q YXb}%k=so|$0M<h($ diff --git a/infrastructure/container_app.tf b/infrastructure/container_app.tf index e8c3f30..ca804a1 100644 --- a/infrastructure/container_app.tf +++ b/infrastructure/container_app.tf @@ -60,7 +60,7 @@ resource "azurerm_container_app" "container_app" { container { cpu = 0.25 - image = "noahspan/flying:20588458616" + image = "noahspan/flying:20589367895" memory = "0.5Gi" name = "flying" @@ -99,6 +99,11 @@ resource "azurerm_container_app" "container_app" { value = var.JWKS_URI } + env { + name = "NODE_ENV" + value = "test" + } + env { name = "SESSION_SECRET" secret_name = "session-secret" @@ -116,13 +121,13 @@ resource "azurerm_container_app" "container_app" { env { name = "DB_SYNC" - value = "false" + value = "true" } startup_probe { - failure_count_threshold = 10 - initial_delay = 1 - interval_seconds = 2 + failure_count_threshold = 3 + initial_delay = 15 + interval_seconds = 30 path = "/api/health" port = 3000 transport = "HTTP" -- 2.49.1 From 881b351410814105946fb4ba57a0740fdb92506a Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Sun, 8 Mar 2026 14:51:07 -0500 Subject: [PATCH 2/2] adding not signed in alerts to flights and logs --- api/package.json | 2 +- client/package.json | 2 +- client/src/components/flights/Flights.tsx | 9 ++++++++- client/src/components/logbook/Logbook.tsx | 4 +++- package.json | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/api/package.json b/api/package.json index eea3ce4..596fe1b 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "2.0.0-beta-4", + "version": "2.0.0", "description": "", "author": "", "private": true, diff --git a/client/package.json b/client/package.json index bb88e90..147868c 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "2.0.0-beta-4", + "version": "2.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/client/src/components/flights/Flights.tsx b/client/src/components/flights/Flights.tsx index cf559e0..1727f25 100644 --- a/client/src/components/flights/Flights.tsx +++ b/client/src/components/flights/Flights.tsx @@ -3,11 +3,13 @@ import { useEffect, useReducer } from "react"; import { useLogs } from "../../hooks/logs/UseLogs"; import { LogbookEntry } from "../logbook/LogbookEntry.interface"; import { initialState, reducer } from "./reducer"; +import { useOidc } from "../../auth/oidcConfig"; import Alert from "../alert/Alert"; const Flights = () => { const [state, dispatch] = useReducer(reducer, initialState); const { logs, logsLoading } = useLogs(); + const { isUserLoggedIn } = useOidc(); useEffect(() => { @@ -19,7 +21,12 @@ const Flights = () => { if (flights && flights.length > 0) { 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 { dispatch({ type: 'SET_ALERT', payload: { severity: 'info', message: 'No flights found' }}) } diff --git a/client/src/components/logbook/Logbook.tsx b/client/src/components/logbook/Logbook.tsx index c2957e4..79a9c85 100644 --- a/client/src/components/logbook/Logbook.tsx +++ b/client/src/components/logbook/Logbook.tsx @@ -377,7 +377,9 @@ const Logbook: React.FC = () => { setColumnVisibility(columnVisibility) dispatch({ type: 'SET_ENTRIES', payload: entries }); - 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}) } } else { diff --git a/package.json b/package.json index d0a0f29..de688dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@noahspan/flying", - "version": "2.0.0-beta-4", + "version": "2.0.0", "scripts": { "start": "node api/dist/main", "format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"", -- 2.49.1