From 15ba26553f16a22340b390e8735af4d4cbf215dd Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 11 Jul 2020 12:40:07 +0200 Subject: [PATCH 1/5] db modifyed --- .../20200711123937-branch-strategy/README.md | 61 ++++++++++ .../schema.prisma | 112 ++++++++++++++++++ .../20200711123937-branch-strategy/steps.json | 101 ++++++++++++++++ prisma/migrations/migrate.lock | 3 +- prisma/schema.prisma | 3 + 5 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20200711123937-branch-strategy/README.md create mode 100644 prisma/migrations/20200711123937-branch-strategy/schema.prisma create mode 100644 prisma/migrations/20200711123937-branch-strategy/steps.json diff --git a/prisma/migrations/20200711123937-branch-strategy/README.md b/prisma/migrations/20200711123937-branch-strategy/README.md new file mode 100644 index 00000000..7744027b --- /dev/null +++ b/prisma/migrations/20200711123937-branch-strategy/README.md @@ -0,0 +1,61 @@ +# Migration `20200711123937-branch-strategy` + +This migration has been generated by Pavel Strunkin at 7/11/2020, 12:39:37 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "public"."Project" ADD COLUMN "mainBranchName" text NOT NULL DEFAULT E'master'; + +ALTER TABLE "public"."TestRun" ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; + +ALTER TABLE "public"."TestVariation" ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200707182652-project-name-unique-constraint..20200711123937-branch-strategy +--- datamodel.dml ++++ datamodel.dml +@@ -3,9 +3,9 @@ + } + datasource db { + provider = "postgresql" +- url = "***" ++ url = env("DATABASE_URL") + } + model Build { + id String @default(uuid()) @id +@@ -23,8 +23,9 @@ + model Project { + id String @default(uuid()) @id + name String ++ mainBranchName String @default("master") + builds Build[] + testVariations TestVariation[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +@@ -55,8 +56,9 @@ + baselineName String? + ignoreAreas String @default("[]") + comment String? + baseline Baseline? ++ branchName String @default("master") + } + model TestVariation { + id String @default(uuid()) @id +@@ -71,8 +73,9 @@ + project Project @relation(fields: [projectId], references: [id]) + testRuns TestRun[] + baselines Baseline[] + comment String? ++ branchName String @default("master") + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + } +``` + + diff --git a/prisma/migrations/20200711123937-branch-strategy/schema.prisma b/prisma/migrations/20200711123937-branch-strategy/schema.prisma new file mode 100644 index 00000000..a6dc1c41 --- /dev/null +++ b/prisma/migrations/20200711123937-branch-strategy/schema.prisma @@ -0,0 +1,112 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = "***" +} + +model Build { + id String @default(uuid()) @id + number Int? + branchName String? + status String? + testRuns TestRun[] + projectId String + project Project @relation(fields: [projectId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id]) + userId String? +} + +model Project { + id String @default(uuid()) @id + name String + mainBranchName String @default("master") + builds Build[] + testVariations TestVariation[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@unique([name]) +} + +model TestRun { + id String @default(uuid()) @id + imageName String + diffName String? + diffPercent Float? + diffTollerancePercent Float @default(1.0) + pixelMisMatchCount Int? + status TestStatus + buildId String + build Build @relation(fields: [buildId], references: [id]) + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // Test variation data + name String @default("") + browser String? + device String? + os String? + viewport String? + baselineName String? + ignoreAreas String @default("[]") + comment String? + baseline Baseline? + branchName String @default("master") +} + +model TestVariation { + id String @default(uuid()) @id + name String + browser String? + device String? + os String? + viewport String? + baselineName String? + ignoreAreas String @default("[]") + projectId String + project Project @relation(fields: [projectId], references: [id]) + testRuns TestRun[] + baselines Baseline[] + comment String? + branchName String @default("master") + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +model Baseline { + id String @default(uuid()) @id + baselineName String + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + testRunId String? + testRun TestRun? @relation(fields: [testRunId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +model User { + id String @default(uuid()) @id + email String @unique + password String + firstName String? + lastName String? + apiKey String @unique + isActive Boolean @default(true) + builds Build[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +enum TestStatus { + failed + new + ok + unresolved + approved +} diff --git a/prisma/migrations/20200711123937-branch-strategy/steps.json b/prisma/migrations/20200711123937-branch-strategy/steps.json new file mode 100644 index 00000000..b3ea00b3 --- /dev/null +++ b/prisma/migrations/20200711123937-branch-strategy/steps.json @@ -0,0 +1,101 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "Project", + "field": "mainBranchName", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Project", + "field": "mainBranchName" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Project", + "field": "mainBranchName" + }, + "directive": "default" + }, + "argument": "", + "value": "\"master\"" + }, + { + "tag": "CreateField", + "model": "TestRun", + "field": "branchName", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "TestRun", + "field": "branchName" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "TestRun", + "field": "branchName" + }, + "directive": "default" + }, + "argument": "", + "value": "\"master\"" + }, + { + "tag": "CreateField", + "model": "TestVariation", + "field": "branchName", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "TestVariation", + "field": "branchName" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "TestVariation", + "field": "branchName" + }, + "directive": "default" + }, + "argument": "", + "value": "\"master\"" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 6984bffd..68625334 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -4,4 +4,5 @@ 20200524162125-baseline-history 20200526195312-approved-test-status-added 20200627134248-comment-added -20200707182652-project-name-unique-constraint \ No newline at end of file +20200707182652-project-name-unique-constraint +20200711123937-branch-strategy \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a3bdfc0f..91f8f4e3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,6 +24,7 @@ model Build { model Project { id String @default(uuid()) @id name String + mainBranchName String @default("master") builds Build[] testVariations TestVariation[] updatedAt DateTime @updatedAt @@ -56,6 +57,7 @@ model TestRun { ignoreAreas String @default("[]") comment String? baseline Baseline? + branchName String @default("master") } model TestVariation { @@ -72,6 +74,7 @@ model TestVariation { testRuns TestRun[] baselines Baseline[] comment String? + branchName String @default("master") updatedAt DateTime @updatedAt createdAt DateTime @default(now()) } From a3fa245c15b992215067eaf8d2c95ec98885c5af Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 11 Jul 2020 17:24:51 +0200 Subject: [PATCH 2/5] migration updated --- .../README.md | 14 ++++++++------ .../schema.prisma | 1 + .../steps.json | 7 +++++++ prisma/migrations/migrate.lock | 2 +- prisma/schema.prisma | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) rename prisma/migrations/{20200711123937-branch-strategy => 20200711125803-branch-strategy}/README.md (84%) rename prisma/migrations/{20200711123937-branch-strategy => 20200711125803-branch-strategy}/schema.prisma (99%) rename prisma/migrations/{20200711123937-branch-strategy => 20200711125803-branch-strategy}/steps.json (93%) diff --git a/prisma/migrations/20200711123937-branch-strategy/README.md b/prisma/migrations/20200711125803-branch-strategy/README.md similarity index 84% rename from prisma/migrations/20200711123937-branch-strategy/README.md rename to prisma/migrations/20200711125803-branch-strategy/README.md index 7744027b..35b7a575 100644 --- a/prisma/migrations/20200711123937-branch-strategy/README.md +++ b/prisma/migrations/20200711125803-branch-strategy/README.md @@ -1,6 +1,6 @@ -# Migration `20200711123937-branch-strategy` +# Migration `20200711125803-branch-strategy` -This migration has been generated by Pavel Strunkin at 7/11/2020, 12:39:37 PM. +This migration has been generated by Pavel Strunkin at 7/11/2020, 12:58:03 PM. You can check out the [state of the schema](./schema.prisma) after the migration. ## Database Steps @@ -8,7 +8,8 @@ You can check out the [state of the schema](./schema.prisma) after the migration ```sql ALTER TABLE "public"."Project" ADD COLUMN "mainBranchName" text NOT NULL DEFAULT E'master'; -ALTER TABLE "public"."TestRun" ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; +ALTER TABLE "public"."TestRun" ADD COLUMN "baselineBranchName" text , +ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; ALTER TABLE "public"."TestVariation" ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; ``` @@ -17,7 +18,7 @@ ALTER TABLE "public"."TestVariation" ADD COLUMN "branchName" text NOT NULL DEFA ```diff diff --git schema.prisma schema.prisma -migration 20200707182652-project-name-unique-constraint..20200711123937-branch-strategy +migration 20200707182652-project-name-unique-constraint..20200711125803-branch-strategy --- datamodel.dml +++ datamodel.dml @@ -3,9 +3,9 @@ @@ -38,16 +39,17 @@ migration 20200707182652-project-name-unique-constraint..20200711123937-branch-s testVariations TestVariation[] updatedAt DateTime @updatedAt createdAt DateTime @default(now()) -@@ -55,8 +56,9 @@ +@@ -55,8 +56,10 @@ baselineName String? ignoreAreas String @default("[]") comment String? baseline Baseline? + branchName String @default("master") ++ baselineBranchName String? } model TestVariation { id String @default(uuid()) @id -@@ -71,8 +73,9 @@ +@@ -71,8 +74,9 @@ project Project @relation(fields: [projectId], references: [id]) testRuns TestRun[] baselines Baseline[] diff --git a/prisma/migrations/20200711123937-branch-strategy/schema.prisma b/prisma/migrations/20200711125803-branch-strategy/schema.prisma similarity index 99% rename from prisma/migrations/20200711123937-branch-strategy/schema.prisma rename to prisma/migrations/20200711125803-branch-strategy/schema.prisma index a6dc1c41..87feafce 100644 --- a/prisma/migrations/20200711123937-branch-strategy/schema.prisma +++ b/prisma/migrations/20200711125803-branch-strategy/schema.prisma @@ -58,6 +58,7 @@ model TestRun { comment String? baseline Baseline? branchName String @default("master") + baselineBranchName String? } model TestVariation { diff --git a/prisma/migrations/20200711123937-branch-strategy/steps.json b/prisma/migrations/20200711125803-branch-strategy/steps.json similarity index 93% rename from prisma/migrations/20200711123937-branch-strategy/steps.json rename to prisma/migrations/20200711125803-branch-strategy/steps.json index b3ea00b3..d12b143b 100644 --- a/prisma/migrations/20200711123937-branch-strategy/steps.json +++ b/prisma/migrations/20200711125803-branch-strategy/steps.json @@ -65,6 +65,13 @@ "argument": "", "value": "\"master\"" }, + { + "tag": "CreateField", + "model": "TestRun", + "field": "baselineBranchName", + "type": "String", + "arity": "Optional" + }, { "tag": "CreateField", "model": "TestVariation", diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 68625334..4d670fd2 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -5,4 +5,4 @@ 20200526195312-approved-test-status-added 20200627134248-comment-added 20200707182652-project-name-unique-constraint -20200711123937-branch-strategy \ No newline at end of file +20200711125803-branch-strategy \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 91f8f4e3..78c0bb0b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -58,6 +58,7 @@ model TestRun { comment String? baseline Baseline? branchName String @default("master") + baselineBranchName String? } model TestVariation { From 8bad243ecd960a2934c1440f0d35f7433cd7c5e0 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 11 Jul 2020 17:27:16 +0200 Subject: [PATCH 3/5] branch stratedy added --- src/shared/dto/baseline-data.dto.ts | 5 + src/test-runs/test-runs.service.ts | 74 +++++-- .../test-variations.service.spec.ts | 207 +++++++++++++----- .../test-variations.service.ts | 45 ++-- 4 files changed, 247 insertions(+), 84 deletions(-) diff --git a/src/shared/dto/baseline-data.dto.ts b/src/shared/dto/baseline-data.dto.ts index 7c030986..2ff77dd0 100644 --- a/src/shared/dto/baseline-data.dto.ts +++ b/src/shared/dto/baseline-data.dto.ts @@ -25,11 +25,16 @@ export class BaselineDataDto { @IsOptional() @IsString() device?: string; + + @ApiProperty() + @IsString() + branchName: string; } export const convertBaselineDataToQuery = (data: BaselineDataDto) => { return { name: data.name, + branchName: data.branchName, os: data.os ? data.os : null, browser: data.browser ? data.browser : null, device: data.device ? data.device : null, diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index 69eec896..27b48595 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -83,32 +83,72 @@ export class TestRunsService { async approve(id: string): Promise { const testRun = await this.findOne(id); + // const project = await this.prismaService.project.findOne({ where: { id: testRun.testVariation.projectId } }); // save new baseline const baseline = this.staticService.getImage(testRun.imageName); const baselineName = this.staticService.saveImage('baseline', PNG.sync.write(baseline)); - - const testRunUpdated = await this.prismaService.testRun.update({ - where: { id }, - data: { - status: TestStatus.approved, - testVariation: { - update: { - baselineName, - baselines: { - create: { - baselineName, - testRun: { - connect: { - id: testRun.id, + let testRunUpdated: TestRun; + if (testRun.branchName === testRun.baselineBranchName) { + testRunUpdated = await this.prismaService.testRun.update({ + where: { id }, + data: { + status: TestStatus.approved, + testVariation: { + update: { + baselineName, + baselines: { + create: { + baselineName, + testRun: { + connect: { + id: testRun.id, + }, }, }, }, }, }, }, - }, - }); + }); + } else { + const newTestVariation = await this.prismaService.testVariation.create({ + data: { + project: { connect: { id: testRun.testVariation.projectId } }, + baselineName, + name: testRun.name, + browser: testRun.browser, + device: testRun.device, + os: testRun.os, + viewport: testRun.viewport, + ignoreAreas: testRun.ignoreAreas, + comment: testRun.comment, + branchName: testRun.branchName, + }, + }); + const newBaseline = await this.prismaService.baseline.create({ + data: { + baselineName, + testVariation: { + connect: { id: newTestVariation.id }, + }, + testRun: { + connect: { + id: testRun.id, + }, + }, + }, + }); + testRunUpdated = await this.prismaService.testRun.update({ + where: { id }, + data: { + status: TestStatus.approved, + testVariation: { + connect: { id: newTestVariation.id }, + }, + }, + }); + } this.emitUpdateBuildEvent(testRun.buildId); return testRunUpdated; @@ -175,9 +215,11 @@ export class TestRunsService { os: testVariation.os, viewport: testVariation.viewport, baselineName: testVariation.baselineName, + baselineBranchName: testVariation.branchName, ignoreAreas: testVariation.ignoreAreas, comment: testVariation.comment, diffTollerancePercent: createTestRequestDto.diffTollerancePercent, + branchName: createTestRequestDto.branchName, status: TestStatus.new, }, }); diff --git a/src/test-variations/test-variations.service.spec.ts b/src/test-variations/test-variations.service.spec.ts index 05e7c67c..c2b023c9 100644 --- a/src/test-variations/test-variations.service.spec.ts +++ b/src/test-variations/test-variations.service.spec.ts @@ -4,7 +4,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { CreateTestRequestDto } from '../test-runs/dto/create-test-request.dto'; import { StaticService } from '../shared/static/static.service'; import { IgnoreAreaDto } from '../test-runs/dto/ignore-area.dto'; -import { TestVariation, Baseline } from '@prisma/client'; +import { TestVariation, Baseline, Project } from '@prisma/client'; import { CommentDto } from '../shared/dto/comment.dto'; import { convertBaselineDataToQuery } from '../shared/dto/baseline-data.dto'; @@ -16,6 +16,7 @@ const initModule = async ({ variationUpdateMock = jest.fn(), variationDeleteMock = jest.fn(), baselineDeleteMock = jest.fn(), + projectFindOneMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -39,6 +40,9 @@ const initModule = async ({ baseline: { delete: baselineDeleteMock, }, + project: { + findOne: projectFindOneMock, + }, }, }, ], @@ -47,24 +51,6 @@ const initModule = async ({ return module.get(TestVariationsService); }; -const dataRequiredFields: CreateTestRequestDto = { - buildId: 'buildId', - projectId: 'projectId', - name: 'Test name', - imageBase64: 'Image', -}; - -const dataAllFields: CreateTestRequestDto = { - buildId: 'buildId', - projectId: 'projectId', - name: 'Test name', - imageBase64: 'Image', - os: 'OS', - browser: 'browser', - viewport: 'viewport', - device: 'device', -}; - describe('TestVariationsService', () => { let service: TestVariationsService; @@ -93,68 +79,178 @@ describe('TestVariationsService', () => { }); describe('findOrCreate', () => { - it('can find by required fields', async () => { - const data = dataRequiredFields; - const variationFindManyMock = jest.fn(); - service = await initModule({ variationFindManyMock: variationFindManyMock.mockResolvedValueOnce([data]) }); + const projectMock: Project = { + id: '12', + name: 'Project', + mainBranchName: 'master', + createdAt: new Date(), + updatedAt: new Date(), + }; - const result = await service.findOrCreate(data.projectId, convertBaselineDataToQuery(data)); + it('can find by main branch', async () => { + const createRequest: CreateTestRequestDto = { + buildId: 'buildId', + projectId: projectMock.id, + name: 'Test name', + imageBase64: 'Image', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + branchName: 'develop', + }; - expect(variationFindManyMock).toHaveBeenCalledWith({ + const variationMock: TestVariation = { + id: '123', + projectId: projectMock.id, + name: 'Test name', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + branchName: 'develop', + createdAt: new Date(), + updatedAt: new Date(), + }; + const variationFindManyMock = jest + .fn() + .mockResolvedValueOnce([variationMock]) + .mockResolvedValueOnce([undefined]); + const projectFindOneMock = jest.fn().mockReturnValueOnce(projectMock); + service = await initModule({ variationFindManyMock, projectFindOneMock }); + + const result = await service.findOrCreate(createRequest.projectId, convertBaselineDataToQuery(createRequest)); + + expect(projectFindOneMock).toHaveBeenCalledWith({ where: { id: createRequest.projectId } }); + expect(variationFindManyMock).toHaveBeenNthCalledWith(1, { + where: { + name: createRequest.name, + projectId: createRequest.projectId, + os: createRequest.os, + browser: createRequest.browser, + viewport: createRequest.viewport, + device: createRequest.device, + branchName: projectMock.mainBranchName, + }, + }); + expect(variationFindManyMock).toHaveBeenNthCalledWith(2, { where: { - name: data.name, - projectId: data.projectId, - os: null, - browser: null, - viewport: null, - device: null, + name: createRequest.name, + projectId: createRequest.projectId, + os: createRequest.os, + browser: createRequest.browser, + viewport: createRequest.viewport, + device: createRequest.device, + branchName: createRequest.branchName, }, }); - expect(result).toBe(data); + expect(result).toBe(variationMock); }); - it('can find by all fields', async () => { - const data = dataAllFields; - const variationFindManyMock = jest.fn(); - service = await initModule({ variationFindManyMock: variationFindManyMock.mockResolvedValueOnce([data]) }); + it('can find by current branch', async () => { + const createRequest: CreateTestRequestDto = { + buildId: 'buildId', + projectId: projectMock.id, + name: 'Test name', + imageBase64: 'Image', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + branchName: 'develop', + }; + + const variationMock: TestVariation = { + id: '123', + projectId: projectMock.id, + name: 'Test name', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + branchName: 'develop', + createdAt: new Date(), + updatedAt: new Date(), + }; + const variationFindManyMock = jest + .fn() + .mockResolvedValueOnce([undefined]) + .mockResolvedValueOnce([variationMock]); + const projectFindOneMock = jest.fn().mockReturnValueOnce(projectMock); + service = await initModule({ variationFindManyMock, projectFindOneMock }); - const result = await service.findOrCreate(data.projectId, convertBaselineDataToQuery(data)); + const result = await service.findOrCreate(createRequest.projectId, convertBaselineDataToQuery(createRequest)); - expect(variationFindManyMock).toHaveBeenCalledWith({ + expect(projectFindOneMock).toHaveBeenCalledWith({ where: { id: createRequest.projectId } }); + expect(variationFindManyMock).toHaveBeenNthCalledWith(1, { + where: { + name: createRequest.name, + projectId: createRequest.projectId, + os: createRequest.os, + browser: createRequest.browser, + viewport: createRequest.viewport, + device: createRequest.device, + branchName: projectMock.mainBranchName, + }, + }); + expect(variationFindManyMock).toHaveBeenNthCalledWith(2, { where: { - name: data.name, - projectId: data.projectId, - os: data.os, - browser: data.browser, - viewport: data.viewport, - device: data.device, + name: createRequest.name, + projectId: createRequest.projectId, + os: createRequest.os, + browser: createRequest.browser, + viewport: createRequest.viewport, + device: createRequest.device, + branchName: createRequest.branchName, }, }); - expect(result).toBe(data); + expect(result).toBe(variationMock); }); it('can create if not found', async () => { - const data = dataAllFields; + const createRequest: CreateTestRequestDto = { + buildId: 'buildId', + projectId: projectMock.id, + name: 'Test name', + imageBase64: 'Image', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + branchName: 'develop', + }; + + const variationFindManyMock = jest + .fn() + .mockResolvedValueOnce([undefined]) + .mockResolvedValueOnce([undefined]); + const projectFindOneMock = jest.fn().mockReturnValueOnce(projectMock); const variationCreateMock = jest.fn(); - service = await initModule({ variationCreateMock: variationCreateMock.mockResolvedValueOnce(data) }); + service = await initModule({ variationFindManyMock, projectFindOneMock, variationCreateMock }); - const result = await service.findOrCreate(data.projectId, convertBaselineDataToQuery(data)); + const result = await service.findOrCreate(createRequest.projectId, convertBaselineDataToQuery(createRequest)); expect(variationCreateMock).toHaveBeenCalledWith({ data: { - name: data.name, - os: data.os, - browser: data.browser, - viewport: data.viewport, - device: data.device, + name: createRequest.name, + os: createRequest.os, + browser: createRequest.browser, + viewport: createRequest.viewport, + device: createRequest.device, + branchName: createRequest.branchName, project: { connect: { - id: data.projectId, + id: createRequest.projectId, }, }, }, }); - expect(result).toBe(data); }); }); @@ -201,6 +297,7 @@ describe('TestVariationsService', () => { device: 'device', ignoreAreas: '[]', comment: 'some comment', + branchName: 'develop', createdAt: new Date(), updatedAt: new Date(), baselines: [ diff --git a/src/test-variations/test-variations.service.ts b/src/test-variations/test-variations.service.ts index a03e8291..8e4b9cc4 100644 --- a/src/test-variations/test-variations.service.ts +++ b/src/test-variations/test-variations.service.ts @@ -27,23 +27,42 @@ export class TestVariationsService { } async findOrCreate(projectId: string, baselineData: BaselineDataDto): Promise { - let [testVariation] = await this.prismaService.testVariation.findMany({ - where: { - projectId, - ...baselineData, - }, - }); + const project = await this.prismaService.project.findOne({ where: { id: projectId } }); - if (!testVariation) { - testVariation = await this.prismaService.testVariation.create({ - data: { - project: { connect: { id: projectId } }, + let [[mainBranchTestVariation], [currentBranchTestVariation]] = await Promise.all([ + // search main branch variation + this.prismaService.testVariation.findMany({ + where: { + projectId, + name: baselineData.name, + os: baselineData.os, + device: baselineData.device, + browser: baselineData.browser, + viewport: baselineData.viewport, + branchName: project.mainBranchName, + }, + }), + // search current branch variation + this.prismaService.testVariation.findMany({ + where: { + projectId, ...baselineData, }, - }); - } + }), + ]); - return testVariation; + if (!!currentBranchTestVariation) { + return currentBranchTestVariation; + } + if (!!mainBranchTestVariation) { + return mainBranchTestVariation; + } + return this.prismaService.testVariation.create({ + data: { + project: { connect: { id: projectId } }, + ...baselineData, + }, + }); } async updateIgnoreAreas(id: string, ignoreAreas: IgnoreAreaDto[]): Promise { From 1c6b270f5737d2591b8affad7e72a65bb35c2e6b Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 11 Jul 2020 19:11:08 +0200 Subject: [PATCH 4/5] approve covered with tests --- src/test-runs/test-runs.service.spec.ts | 157 +++++++++++++++++++++++- src/test-runs/test-runs.service.ts | 1 - 2 files changed, 152 insertions(+), 6 deletions(-) diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index 3be46d25..e99dc4c0 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -4,7 +4,7 @@ import { TestRunsService } from './test-runs.service'; import { PrismaService } from '../prisma/prisma.service'; import { StaticService } from '../shared/static/static.service'; import { PNG } from 'pngjs'; -import { TestStatus, Build, TestRun } from '@prisma/client'; +import { TestStatus, Build, TestRun, TestVariation } from '@prisma/client'; import Pixelmatch from 'pixelmatch'; import { CreateTestRequestDto } from './dto/create-test-request.dto'; import { DiffResult } from './diffResult'; @@ -28,6 +28,8 @@ const initService = async ({ eventNewTestRunMock = jest.fn(), eventBuildUpdatedMock = jest.fn(), buildFindOneMock = jest.fn(), + testVariationCreateMock = jest.fn(), + baselineCreateMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -45,6 +47,12 @@ const initService = async ({ build: { findOne: buildFindOneMock, }, + testVariation: { + create: testVariationCreateMock, + }, + baseline: { + create: baselineCreateMock, + }, }, }, { @@ -122,10 +130,14 @@ describe('TestRunsService', () => { }); describe('approve', () => { - it('can approve', async () => { - const testRun = { + it('should approve the same branch', async () => { + const testRun: TestRun = { id: 'id', imageName: 'imageName', + diffName: 'diffName', + baselineName: 'baselineName', + diffPercent: 1, + pixelMisMatchCount: 10, diffTollerancePercent: 12, status: TestStatus.new, buildId: 'buildId', @@ -134,6 +146,13 @@ describe('TestRunsService', () => { createdAt: new Date(), name: 'test run name', ignoreAreas: '[]', + browser: 'browser', + device: 'device', + os: 'os', + viewport: 'viewport', + branchName: 'master', + baselineBranchName: 'master', + comment: 'some comment', }; const testRunUpdateMock = jest.fn(); const testRunFindOneMock = jest.fn().mockResolvedValueOnce(testRun); @@ -181,6 +200,128 @@ describe('TestRunsService', () => { }); expect(service.emitUpdateBuildEvent).toBeCalledWith(testRun.buildId); }); + + it('should approve different branch', async () => { + const testRun: TestRun & { + testVariation: TestVariation; + } = { + id: 'id', + imageName: 'imageName', + diffName: 'diffName', + baselineName: 'baselineName', + diffPercent: 1, + pixelMisMatchCount: 10, + diffTollerancePercent: 12, + status: TestStatus.new, + buildId: 'buildId', + testVariationId: 'testVariationId', + updatedAt: new Date(), + createdAt: new Date(), + name: 'test run name', + ignoreAreas: '[]', + browser: 'browser', + device: 'device', + os: 'os', + viewport: 'viewport', + branchName: 'develop', + baselineBranchName: 'master', + comment: 'some comment', + testVariation: { + id: '123', + projectId: 'project Id', + name: 'Test name', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + createdAt: new Date(), + updatedAt: new Date(), + branchName: 'master', + }, + }; + const newTestVariation: TestVariation = { + id: '124', + projectId: 'project Id', + name: 'Test name', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + createdAt: new Date(), + updatedAt: new Date(), + branchName: 'develop', + }; + const testRunUpdateMock = jest.fn(); + const testRunFindOneMock = jest.fn().mockResolvedValueOnce(testRun); + const baselineName = 'some baseline name'; + const saveImageMock = jest.fn().mockReturnValueOnce(baselineName); + const getImageMock = jest.fn().mockReturnValueOnce( + new PNG({ + width: 10, + height: 10, + }) + ); + const testVariationCreateMock = jest.fn().mockResolvedValueOnce(newTestVariation); + const baselineCreateMock = jest.fn(); + service = await initService({ + testRunUpdateMock, + saveImageMock, + getImageMock, + testVariationCreateMock, + baselineCreateMock, + }); + service.findOne = testRunFindOneMock; + service.emitUpdateBuildEvent = jest.fn(); + + await service.approve(testRun.id); + + expect(testRunFindOneMock).toHaveBeenCalledWith(testRun.id); + expect(getImageMock).toHaveBeenCalledWith(testRun.imageName); + expect(saveImageMock).toHaveBeenCalledTimes(1); + expect(testVariationCreateMock).toBeCalledWith({ + data: { + project: { connect: { id: testRun.testVariation.projectId } }, + baselineName, + name: testRun.name, + browser: testRun.browser, + device: testRun.device, + os: testRun.os, + viewport: testRun.viewport, + ignoreAreas: testRun.ignoreAreas, + comment: testRun.comment, + branchName: testRun.branchName, + }, + }); + expect(baselineCreateMock).toHaveBeenCalledWith({ + data: { + baselineName, + testVariation: { + connect: { id: newTestVariation.id }, + }, + testRun: { + connect: { + id: testRun.id, + }, + }, + }, + }); + expect(testRunUpdateMock).toHaveBeenCalledWith({ + where: { id: testRun.id }, + data: { + status: TestStatus.approved, + testVariation: { + connect: { id: newTestVariation.id }, + }, + }, + }); + expect(service.emitUpdateBuildEvent).toBeCalledWith(testRun.buildId); + }); }); it('create', async () => { @@ -194,6 +335,7 @@ describe('TestRunsService', () => { viewport: 'viewport', device: 'device', diffTollerancePercent: undefined, + branchName: 'develop', }; const testRunWithResult = { id: 'id', @@ -226,6 +368,7 @@ describe('TestRunsService', () => { comment: 'some comment', createdAt: new Date(), updatedAt: new Date(), + branchName: 'master', }; const createTestRequestDto = initCreateTestRequestDto; const testRunCreateMock = jest.fn().mockResolvedValueOnce(testRun); @@ -283,6 +426,8 @@ describe('TestRunsService', () => { baselineName: testVariation.baselineName, ignoreAreas: testVariation.ignoreAreas, comment: testVariation.comment, + baselineBranchName: testVariation.branchName, + branchName: createTestRequestDto.branchName, diffTollerancePercent: createTestRequestDto.diffTollerancePercent, status: TestStatus.new, }, @@ -495,9 +640,9 @@ describe('TestRunsService', () => { service = await initService({ testRunUpdateMock, }); - + await service.saveDiffResult(id, diff); - + expect(testRunUpdateMock).toHaveBeenCalledWith({ where: { id }, data: { @@ -620,6 +765,8 @@ describe('TestRunsService', () => { baselineName: null, ignoreAreas: '[]', comment: 'some comment', + baselineBranchName: 'master', + branchName: 'develop', }, ], }; diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index 27b48595..dc2fbcb7 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -83,7 +83,6 @@ export class TestRunsService { async approve(id: string): Promise { const testRun = await this.findOne(id); - // const project = await this.prismaService.project.findOne({ where: { id: testRun.testVariation.projectId } }); // save new baseline const baseline = this.staticService.getImage(testRun.imageName); From 5f5b09f06a1dd703a93d505afe944b502043a706 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 11 Jul 2020 20:58:04 +0200 Subject: [PATCH 5/5] postTestRun covered with tests --- src/test-runs/test-runs.service.spec.ts | 84 ++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index e99dc4c0..08535c9d 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -7,14 +7,17 @@ import { PNG } from 'pngjs'; import { TestStatus, Build, TestRun, TestVariation } from '@prisma/client'; import Pixelmatch from 'pixelmatch'; import { CreateTestRequestDto } from './dto/create-test-request.dto'; +import { TestRunResultDto } from './dto/testRunResult.dto'; import { DiffResult } from './diffResult'; import { IgnoreAreaDto } from './dto/ignore-area.dto'; import { EventsGateway } from '../events/events.gateway'; import { CommentDto } from '../shared/dto/comment.dto'; import { BuildDto } from '../builds/dto/build.dto'; import { TestVariationsService } from '../test-variations/test-variations.service'; +import { convertBaselineDataToQuery } from '../shared/dto/baseline-data.dto'; jest.mock('pixelmatch'); +jest.mock('./dto/testRunResult.dto'); const initService = async ({ testRunDeleteMock = jest.fn(), @@ -30,6 +33,7 @@ const initService = async ({ buildFindOneMock = jest.fn(), testVariationCreateMock = jest.fn(), baselineCreateMock = jest.fn(), + testVariationFindOrCreateMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -72,7 +76,9 @@ const initService = async ({ }, { provide: TestVariationsService, - useValue: {}, + useValue: { + findOrCreate: testVariationFindOrCreateMock, + }, }, ], }).compile(); @@ -789,4 +795,80 @@ describe('TestRunsService', () => { }); expect(eventBuildUpdatedMock).toHaveBeenCalledWith(new BuildDto(build)); }); + + it('postTestRun', async () => { + const createTestRequestDto: CreateTestRequestDto = { + buildId: 'buildId', + projectId: 'projectId', + name: 'Test name', + imageBase64: 'Image', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + branchName: 'develop', + }; + const testVariation: TestVariation = { + id: '123', + projectId: 'project Id', + name: 'Test name', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + createdAt: new Date(), + updatedAt: new Date(), + branchName: 'master', + }; + const testRun: TestRun = { + id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', + imageName: '1592423768112.screenshot.png', + diffName: 'diffName', + diffPercent: 12, + diffTollerancePercent: 1, + pixelMisMatchCount: 123, + status: 'new', + buildId: '146e7a8d-89f0-4565-aa2c-e61efabb0afd', + testVariationId: '3bc4a5bc-006e-4d43-8e4e-eaa132627fca', + updatedAt: new Date(), + createdAt: new Date(), + name: 'ss2f77', + browser: 'chromium', + device: null, + os: null, + viewport: '1800x1600', + baselineName: null, + ignoreAreas: '[]', + comment: 'some comment', + baselineBranchName: 'master', + branchName: 'develop', + }; + const testVariationFindOrCreateMock = jest.fn().mockResolvedValueOnce(testVariation); + const testRunFindManyMock = jest.fn().mockResolvedValueOnce([testRun]); + const deleteMock = jest.fn(); + const createMock = jest.fn().mockResolvedValueOnce(testRun); + const service = await initService({ + testVariationFindOrCreateMock, + testRunFindManyMock, + }); + service.delete = deleteMock; + service.create = createMock; + const baselineData = convertBaselineDataToQuery(createTestRequestDto); + + await service.postTestRun(createTestRequestDto); + + expect(testVariationFindOrCreateMock).toHaveBeenCalledWith(createTestRequestDto.projectId, baselineData); + expect(testRunFindManyMock).toHaveBeenCalledWith({ + where: { + buildId: createTestRequestDto.buildId, + ...baselineData, + }, + }); + expect(deleteMock).toHaveBeenCalledWith(testRun.id); + expect(createMock).toHaveBeenCalledWith(testVariation, createTestRequestDto); + expect(mocked(TestRunResultDto)).toHaveBeenCalledWith(testRun, testVariation); + }); });