Skip to content

Commit 2f75c5f

Browse files
committed
multithread build creation supported
closes Visual-Regression-Tracker/Visual-Regression-Tracker#258
1 parent c86c8ea commit 2f75c5f

File tree

8 files changed

+212
-169
lines changed

8 files changed

+212
-169
lines changed

src/_data_/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Project } from '@prisma/client';
1+
import { Build, Project } from '@prisma/client';
22

33
export const TEST_PROJECT: Project = {
44
id: '1',
@@ -8,3 +8,16 @@ export const TEST_PROJECT: Project = {
88
createdAt: new Date(),
99
updatedAt: new Date(),
1010
};
11+
12+
export const TEST_BUILD: Build = {
13+
id: 'a9385fc1-884d-4f9f-915e-40da0e7773d5',
14+
ciBuildId: 'ciBuildId',
15+
number: 2345,
16+
branchName: 'develop',
17+
status: 'new',
18+
projectId: 'e0a37894-6f29-478d-b13e-6182fecc715e',
19+
updatedAt: new Date(),
20+
createdAt: new Date(),
21+
userId: '2341235',
22+
isRunning: true,
23+
};

src/builds/builds.controller.spec.ts

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,100 @@ import { BuildsService } from './builds.service';
44
import { PrismaService } from '../prisma/prisma.service';
55
import { ApiGuard } from '../auth/guards/api.guard';
66
import { JwtAuthGuard } from '../auth/guards/auth.guard';
7+
import { ProjectsService } from '../projects/projects.service';
8+
import { TEST_BUILD, TEST_PROJECT } from '../_data_';
9+
import { Build } from '.prisma/client';
10+
import { BuildDto } from './dto/build.dto';
11+
import { CreateBuildDto } from './dto/build-create.dto';
12+
import { EventsGateway } from '../shared/events/events.gateway';
713

14+
const initController = async ({
15+
projectFindOneMock = jest.fn(),
16+
buildFindOrCreateMock = jest.fn(),
17+
buildIncrementBuildNumberMock = jest.fn(),
18+
eventBuildCreatedMock = jest.fn(),
19+
}) => {
20+
const module: TestingModule = await Test.createTestingModule({
21+
controllers: [BuildsController],
22+
providers: [
23+
{
24+
provide: ProjectsService,
25+
useValue: {
26+
findOne: projectFindOneMock,
27+
},
28+
},
29+
{
30+
provide: BuildsService,
31+
useValue: {
32+
findOrCreate: buildFindOrCreateMock,
33+
incrementBuildNumber: buildIncrementBuildNumberMock,
34+
},
35+
},
36+
{ provide: EventsGateway, useValue: { buildCreated: eventBuildCreatedMock } },
37+
{ provide: PrismaService, useValue: {} },
38+
{ provide: ApiGuard, useValue: {} },
39+
{ provide: JwtAuthGuard, useValue: {} },
40+
],
41+
}).compile();
42+
43+
return module.get<BuildsController>(BuildsController);
44+
};
845
describe('Builds Controller', () => {
946
let controller: BuildsController;
1047

11-
beforeEach(async () => {
12-
const module: TestingModule = await Test.createTestingModule({
13-
controllers: [BuildsController],
14-
providers: [
15-
{ provide: BuildsService, useValue: {} },
16-
{ provide: PrismaService, useValue: {} },
17-
{ provide: ApiGuard, useValue: {} },
18-
{ provide: JwtAuthGuard, useValue: {} },
19-
],
20-
}).compile();
21-
22-
controller = module.get<BuildsController>(BuildsController);
23-
});
48+
const createBuildDto: CreateBuildDto = {
49+
ciBuildId: 'ciBuildId',
50+
branchName: 'branchName',
51+
project: 'name',
52+
};
53+
const project = TEST_PROJECT;
54+
const newBuild: Build = {
55+
...TEST_BUILD,
56+
number: null,
57+
};
58+
const buildWithNumber: Build = {
59+
...TEST_BUILD,
60+
number: 12,
61+
};
62+
63+
beforeEach(async () => {});
2464

25-
it('should be defined', () => {
65+
it('should be defined', async () => {
66+
controller = await initController({});
2667
expect(controller).toBeDefined();
2768
});
69+
70+
it('should create new build', async () => {
71+
const eventBuildCreatedMock = jest.fn();
72+
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
73+
const buildFindOrCreateMock = jest.fn().mockResolvedValueOnce(newBuild);
74+
const buildIncrementBuildNumberMock = jest.fn().mockResolvedValueOnce(buildWithNumber);
75+
controller = await initController({
76+
projectFindOneMock,
77+
buildFindOrCreateMock,
78+
buildIncrementBuildNumberMock,
79+
eventBuildCreatedMock,
80+
});
81+
82+
const result = await controller.create(createBuildDto);
83+
84+
expect(result).toStrictEqual(new BuildDto(buildWithNumber));
85+
expect(buildIncrementBuildNumberMock).toHaveBeenCalledWith(newBuild.id, project.id);
86+
expect(eventBuildCreatedMock).toHaveBeenCalledWith(new BuildDto(buildWithNumber));
87+
});
88+
89+
it('should reuse build', async () => {
90+
const eventBuildCreatedMock = jest.fn();
91+
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
92+
const buildFindOrCreateMock = jest.fn().mockResolvedValueOnce(buildWithNumber);
93+
controller = await initController({
94+
projectFindOneMock,
95+
buildFindOrCreateMock,
96+
eventBuildCreatedMock,
97+
});
98+
99+
const result = await controller.create(createBuildDto);
100+
101+
expect(result).toStrictEqual(new BuildDto(buildWithNumber));
102+
});
28103
});

src/builds/builds.controller.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
Patch,
1212
ParseIntPipe,
1313
ParseBoolPipe,
14+
forwardRef,
15+
Inject,
1416
} from '@nestjs/common';
1517
import { BuildsService } from './builds.service';
1618
import { JwtAuthGuard } from '../auth/guards/auth.guard';
@@ -22,11 +24,18 @@ import { BuildDto } from './dto/build.dto';
2224
import { MixedGuard } from '../auth/guards/mixed.guard';
2325
import { PaginatedBuildDto } from './dto/build-paginated.dto';
2426
import { ModifyBuildDto } from './dto/build-modify.dto';
27+
import { ProjectsService } from '../projects/projects.service';
28+
import { EventsGateway } from '../shared/events/events.gateway';
2529

2630
@Controller('builds')
2731
@ApiTags('builds')
2832
export class BuildsController {
29-
constructor(private buildsService: BuildsService) {}
33+
constructor(
34+
private buildsService: BuildsService,
35+
private eventsGateway: EventsGateway,
36+
@Inject(forwardRef(() => ProjectsService))
37+
private projectService: ProjectsService
38+
) {}
3039

3140
@Get()
3241
@ApiOkResponse({ type: PaginatedBuildDto })
@@ -59,8 +68,18 @@ export class BuildsController {
5968
@ApiOkResponse({ type: BuildDto })
6069
@ApiSecurity('api_key')
6170
@UseGuards(ApiGuard)
62-
create(@Body() createBuildDto: CreateBuildDto): Promise<BuildDto> {
63-
return this.buildsService.create(createBuildDto);
71+
async create(@Body() createBuildDto: CreateBuildDto): Promise<BuildDto> {
72+
const project = await this.projectService.findOne(createBuildDto.project);
73+
let build = await this.buildsService.findOrCreate({
74+
projectId: project.id,
75+
branchName: createBuildDto.branchName,
76+
ciBuildId: createBuildDto.ciBuildId,
77+
});
78+
if (!build.number) {
79+
build = await this.buildsService.incrementBuildNumber(build.id, project.id);
80+
this.eventsGateway.buildCreated(new BuildDto(build));
81+
}
82+
return new BuildDto(build);
6483
}
6584

6685
@Patch(':id')

src/builds/builds.service.spec.ts

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -174,88 +174,6 @@ describe('BuildsService', () => {
174174
});
175175
});
176176

177-
describe('create', () => {
178-
const createBuildDto: CreateBuildDto = {
179-
ciBuildId: 'ciBuildId',
180-
branchName: 'branchName',
181-
project: 'name',
182-
};
183-
184-
const project: Project = {
185-
id: 'project id',
186-
name: 'name',
187-
mainBranchName: 'master',
188-
buildsCounter: 1,
189-
updatedAt: new Date(),
190-
createdAt: new Date(),
191-
};
192-
193-
it('should create', async () => {
194-
const buildFindUniqueMock = jest.fn().mockResolvedValueOnce(null);
195-
const buildCreateMock = jest.fn().mockResolvedValueOnce(build);
196-
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
197-
const projectUpdateMock = jest.fn().mockResolvedValueOnce(project);
198-
const eventsBuildCreatedMock = jest.fn();
199-
mocked(BuildDto).mockReturnValue(buildDto);
200-
service = await initService({
201-
buildCreateMock,
202-
buildFindUniqueMock,
203-
eventsBuildCreatedMock,
204-
projectFindOneMock,
205-
projectUpdateMock,
206-
});
207-
208-
const result = await service.create(createBuildDto);
209-
210-
expect(projectFindOneMock).toHaveBeenCalledWith(createBuildDto.project);
211-
expect(buildFindUniqueMock).toHaveBeenCalledWith({
212-
where: {
213-
projectId_ciBuildId: {
214-
projectId: project.id,
215-
ciBuildId: createBuildDto.ciBuildId,
216-
},
217-
},
218-
});
219-
expect(projectUpdateMock).toHaveBeenCalledWith({
220-
where: { id: project.id },
221-
data: {
222-
buildsCounter: {
223-
increment: 1,
224-
},
225-
},
226-
});
227-
expect(buildCreateMock).toHaveBeenCalledWith({
228-
data: {
229-
branchName: createBuildDto.branchName,
230-
ciBuildId: createBuildDto.ciBuildId,
231-
isRunning: true,
232-
number: project.buildsCounter,
233-
project: {
234-
connect: {
235-
id: project.id,
236-
},
237-
},
238-
},
239-
});
240-
expect(eventsBuildCreatedMock).toHaveBeenCalledWith(buildDto);
241-
expect(result).toBe(buildDto);
242-
});
243-
244-
it('should reuse by ciBuildId', async () => {
245-
const buildFindUniqueMock = jest.fn().mockResolvedValueOnce(build);
246-
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
247-
mocked(BuildDto).mockReturnValue(buildDto);
248-
service = await initService({
249-
buildFindUniqueMock,
250-
projectFindOneMock,
251-
});
252-
253-
const result = await service.create(createBuildDto);
254-
255-
expect(result).toBe(buildDto);
256-
});
257-
});
258-
259177
it('delete', async () => {
260178
const buildFindUniqueMock = jest.fn().mockResolvedValueOnce(build);
261179
const buildDeleteMock = jest.fn();

0 commit comments

Comments
 (0)