-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tasks: Initial implementation (#224)
* save * bump * start and stop tasks * save * fix: fitness data not awaited * enh: Added more tasks * add single task endpoint * added some tests Signed-off-by: Henry Brink <[email protected]> * added further tests Signed-off-by: Henry Brink <[email protected]> * fix: TaskLogs would already be considered started * make api compliant to docs * fix: enums are not correct in api doc * linter & ghas errors --------- Signed-off-by: Henry Brink <[email protected]>
- Loading branch information
1 parent
3ef0e6d
commit ca4d7b0
Showing
21 changed files
with
670 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
-- CreateTable | ||
CREATE TABLE "TaskLog" ( | ||
"userId" TEXT NOT NULL, | ||
"task" TEXT NOT NULL, | ||
"start" DATETIME NOT NULL, | ||
"end" DATETIME, | ||
"status" TEXT NOT NULL, | ||
"metadata" TEXT, | ||
|
||
PRIMARY KEY ("userId", "task") | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { | ||
Body, | ||
Controller, | ||
Get, | ||
Param, | ||
Put, | ||
Req, | ||
Res, | ||
UseGuards, | ||
} from '@nestjs/common'; | ||
import { | ||
ConcurrentTaskError, | ||
FitnessDataNotAvailable, | ||
TaskAlreadyCompleted, | ||
TaskNotAvailableError, | ||
TaskService, | ||
} from './task.service'; | ||
import { AutoGuard } from '../../auth/auto.guard'; | ||
import { NestRequest } from '../../types/request.type'; | ||
import { Response } from 'express'; | ||
|
||
@Controller('task') | ||
export class TaskController { | ||
constructor(private taskService: TaskService) {} | ||
|
||
@Get() | ||
@UseGuards(AutoGuard) | ||
public async getTasks(@Req() req: NestRequest) { | ||
return (await this.taskService.getTasks(req.user.id)).map((t) => | ||
t.getInfo(), | ||
); | ||
} | ||
|
||
@Put('/:id') | ||
@UseGuards(AutoGuard) | ||
public async putTask( | ||
@Req() req: NestRequest, | ||
@Param('id') id: string, | ||
@Body('action') action: string, | ||
@Res() response: Response, | ||
) { | ||
if (action == 'start') { | ||
return this.startTask(req, id, response); | ||
} else if (action == 'stop') { | ||
return this.stopTask(req, id, response); | ||
} else { | ||
response | ||
.status(500) | ||
.json({ error: 'action must be either start or stop' }); | ||
} | ||
} | ||
|
||
private async startTask(req: NestRequest, id: string, response: Response) { | ||
try { | ||
await this.taskService.startTask(req.user.id, id); | ||
const task = await this.taskService.getTask(req.user.id, id); | ||
|
||
return response.json(task?.getInfo()); | ||
} catch (e) { | ||
if (e instanceof TaskNotAvailableError) { | ||
return response.status(400).json({ error: 'Invalid task' }); | ||
} else if (e instanceof FitnessDataNotAvailable) { | ||
return response | ||
.status(400) | ||
.json({ error: 'No fitness provider connected.' }); | ||
} | ||
|
||
if (e instanceof ConcurrentTaskError) { | ||
return response | ||
.status(400) | ||
.json({ error: 'You already have a running task' }); | ||
} else if (e instanceof TaskAlreadyCompleted) { | ||
return response | ||
.status(400) | ||
.json({ error: 'You already completed this task' }); | ||
} | ||
|
||
response.status(500).json({ error: 'Unknown error' }); | ||
} | ||
} | ||
|
||
private async stopTask(req: NestRequest, id: string, response: Response) { | ||
await this.taskService.stopTask(req.user.id, id); | ||
|
||
const task = await this.taskService.getTask(req.user.id, id); | ||
|
||
response.status(200).json(task?.getInfo()); | ||
} | ||
|
||
@Get('/:id') | ||
@UseGuards(AutoGuard) | ||
public async getTask( | ||
@Req() req: NestRequest, | ||
@Param('id') id: string, | ||
@Res() response: Response, | ||
) { | ||
const task = await this.taskService.getTask(req.user.id, id); | ||
|
||
if (!task) { | ||
return response.status(404).json({ error: 'Task not found' }); | ||
} | ||
|
||
return response.status(200).json(task.getInfo()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { PrismaModule } from '../../db/prisma.module'; | ||
import { TaskService } from './task.service'; | ||
import { TaskController } from './task.controller'; | ||
import FitnessModule from '../../integration/fitness/fitness.module'; | ||
|
||
@Module({ | ||
imports: [PrismaModule, FitnessModule], | ||
providers: [TaskService], | ||
controllers: [TaskController], | ||
}) | ||
export class TaskModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { Test } from '@nestjs/testing'; | ||
import { TaskService } from './task.service'; | ||
import { PrismaModule } from '../../db/prisma.module'; | ||
import { DeepMockProxy, mockDeep } from 'jest-mock-extended'; | ||
import { TaskRepository } from '../../db/repositories/task.repository'; | ||
import { FitnessService } from '../../integration/fitness/fitness.service'; | ||
import FitnessModule from '../../integration/fitness/fitness.module'; | ||
import { TestConstants } from '../../../test/lib/constants'; | ||
import { MockProvider } from '../../integration/fitness/providers/mock.provider'; | ||
|
||
describe('task service tests', () => { | ||
let taskService: TaskService; | ||
let taskRepository: DeepMockProxy<TaskRepository>; | ||
let fitnessService: DeepMockProxy<FitnessService>; | ||
|
||
beforeAll(async () => { | ||
const testModule = await Test.createTestingModule({ | ||
imports: [PrismaModule, FitnessModule], | ||
providers: [TaskService], | ||
}) | ||
.overrideProvider(TaskRepository) | ||
.useValue(mockDeep<TaskRepository>()) | ||
.overrideProvider(FitnessService) | ||
.useValue(mockDeep<FitnessService>()) | ||
.compile(); | ||
|
||
taskService = testModule.get(TaskService); | ||
taskRepository = testModule.get(TaskRepository); | ||
fitnessService = testModule.get(FitnessService); | ||
}); | ||
|
||
it('it should return all available tasks including user info', async () => { | ||
const logs = [ | ||
TestConstants.database.taskLogs.task1, | ||
TestConstants.database.taskLogs.task2, | ||
]; | ||
|
||
taskRepository.getTaskLogsForUser.mockResolvedValue(logs); | ||
|
||
const tasks = await taskService.getTasks( | ||
TestConstants.database.users.exampleUser.id, | ||
); | ||
|
||
expect(tasks.length).toBeGreaterThan(0); | ||
|
||
const task1Info = tasks[0].getInfo(); | ||
expect(task1Info.id).toBe('1'); | ||
expect(task1Info.status).toBe('pending'); | ||
|
||
const task2Info = tasks[1].getInfo(); | ||
expect(task2Info.id).toBe('2'); | ||
expect(task2Info.status).toBe('failed'); | ||
|
||
const task3Info = tasks[2].getInfo(); | ||
expect(task3Info.id).toBe('3'); | ||
expect(task3Info.status).toBe('not started'); | ||
}); | ||
|
||
it('it should return all a specific including user info', async () => { | ||
taskRepository.getTaskLog.mockResolvedValue( | ||
TestConstants.database.taskLogs.task1, | ||
); | ||
|
||
const task = await taskService.getTask( | ||
TestConstants.database.users.exampleUser.id, | ||
TestConstants.database.taskLogs.task1.task, | ||
); | ||
|
||
expect(task).toBeDefined(); | ||
|
||
const task1Info = task!.getInfo(); | ||
expect(task1Info.id).toBe('1'); | ||
expect(task1Info.status).toBe('pending'); | ||
}); | ||
|
||
it('should be able to start a task', async () => { | ||
taskRepository.getStartedTasksForUser.mockResolvedValue([]); | ||
|
||
fitnessService.getDatasourcesForUser.mockResolvedValue([ | ||
new MockProvider(), | ||
]); | ||
|
||
taskRepository.saveTaskLog.mockResolvedValue( | ||
TestConstants.database.taskLogs.task3, | ||
); | ||
|
||
taskRepository.getTaskLog.mockResolvedValue(null); | ||
|
||
const log = await taskService.startTask( | ||
TestConstants.database.users.exampleUser.id, | ||
TestConstants.database.taskLogs.task3.task, | ||
); | ||
|
||
expect(log).toBeDefined(); | ||
expect(log.task).toBe('3'); | ||
expect(log.status).toBe('in progress'); | ||
}); | ||
|
||
it('should be able to stop a running task', async () => { | ||
taskRepository.getTaskLog.mockResolvedValue( | ||
TestConstants.database.taskLogs.task3, | ||
); | ||
|
||
taskRepository.updateTaskLog.mockResolvedValue( | ||
TestConstants.database.taskLogs.task3, | ||
); | ||
|
||
const log = await taskService.stopTask( | ||
TestConstants.database.users.exampleUser.id, | ||
TestConstants.database.taskLogs.task3.task, | ||
); | ||
|
||
expect(log).toBeDefined(); | ||
expect(log.task).toBe('3'); | ||
}); | ||
}); |
Oops, something went wrong.