diff --git a/package-lock.json b/package-lock.json index 549b6ca7..ab1cf5a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1333,15 +1333,18 @@ } }, "@prisma/cli": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.6.tgz", - "integrity": "sha512-qawcjLN5c26T+IVZij5WjQocfsV6b1BtJIL3CqMQfMkDCNGo/zXsu+NB+uyZ+QRqctEuJQ36lemnY44+k6L8XA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.2.2.tgz", + "integrity": "sha512-pk18QyfTHsAGctqoBu4pd9k/pAJ+uaD5GfrAE1gZkGuhmUGjh2hXmgJEKeQK+n1J4fvcstmDi38oHwskYyBv9w==", "dev": true }, "@prisma/client": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.6.tgz", - "integrity": "sha512-08AokCpMzL6SdxQVfShD7937t+sHntr6FJBpVKq5E/UFPVh0B2lUwU9YrA/MIAdRpGMg1wA+rdwRivtFIs5Law==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.2.2.tgz", + "integrity": "sha512-//Ji25pubuXCJDcXaE7Es/ytRB7ojdj8H2YCIkziQVWY62Nl/j9vXYM7WzA4z1lysOlbKFlH9e0W1sy6LQpUpg==", + "requires": { + "pkg-up": "^3.1.0" + } }, "@schematics/schematics": { "version": "0.901.0", @@ -4838,7 +4841,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -7533,7 +7535,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -8554,7 +8555,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -8563,7 +8563,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -8571,8 +8570,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "packet-reader": { "version": "1.0.0", @@ -8745,8 +8743,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -8926,6 +8923,14 @@ "find-up": "^3.0.0" } }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + } + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", diff --git a/package.json b/package.json index 085d67a5..e0367785 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@nestjs/platform-socket.io": "^7.1.3", "@nestjs/swagger": "^4.5.1", "@nestjs/websockets": "^7.1.3", - "@prisma/client": "^2.0.0-beta.6", + "@prisma/client": "^2.2.2", "bcryptjs": "^2.4.3", "class-transformer": "^0.2.3", "class-validator": "^0.11.1", @@ -52,7 +52,7 @@ "@nestjs/cli": "^7.0.0", "@nestjs/schematics": "^7.0.0", "@nestjs/testing": "^7.0.0", - "@prisma/cli": "^2.0.0-beta.6", + "@prisma/cli": "^2.2.2", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.3", "@types/jest": "25.1.4", diff --git a/prisma/migrations/20200711125803-branch-strategy/README.md b/prisma/migrations/20200715232608-branch-strategy/README.md similarity index 52% rename from prisma/migrations/20200711125803-branch-strategy/README.md rename to prisma/migrations/20200715232608-branch-strategy/README.md index 35b7a575..b50ba188 100644 --- a/prisma/migrations/20200711125803-branch-strategy/README.md +++ b/prisma/migrations/20200715232608-branch-strategy/README.md @@ -1,24 +1,19 @@ -# Migration `20200711125803-branch-strategy` +# Migration `20200715232608-branch-strategy` -This migration has been generated by Pavel Strunkin at 7/11/2020, 12:58:03 PM. +This migration has been generated by Pavel Strunkin at 7/15/2020, 11:26:08 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 "baselineBranchName" text , -ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; - -ALTER TABLE "public"."TestVariation" ADD COLUMN "branchName" text NOT NULL DEFAULT E'master'; +CREATE UNIQUE INDEX "TestVariation.name_browser_device_os_viewport_branchName" ON "public"."TestVariation"("name","browser","device","os","viewport","branchName") ``` ## Changes ```diff diff --git schema.prisma schema.prisma -migration 20200707182652-project-name-unique-constraint..20200711125803-branch-strategy +migration 20200707182652-project-name-unique-constraint..20200715232608-branch-strategy --- datamodel.dml +++ datamodel.dml @@ -3,9 +3,9 @@ @@ -26,7 +21,7 @@ migration 20200707182652-project-name-unique-constraint..20200711125803-branch-s datasource db { provider = "postgresql" - url = "***" -+ url = env("DATABASE_URL") ++ url = "***" } model Build { id String @default(uuid()) @id @@ -39,7 +34,17 @@ migration 20200707182652-project-name-unique-constraint..20200711125803-branch-s testVariations TestVariation[] updatedAt DateTime @updatedAt createdAt DateTime @default(now()) -@@ -55,8 +56,10 @@ +@@ -43,8 +44,9 @@ + buildId String + build Build @relation(fields: [buildId], references: [id]) + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) ++ merge Boolean @default(false) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // Test variation data + name String @default("") +@@ -55,13 +57,16 @@ baselineName String? ignoreAreas String @default("[]") comment String? @@ -49,15 +54,22 @@ migration 20200707182652-project-name-unique-constraint..20200711125803-branch-s } model TestVariation { id String @default(uuid()) @id -@@ -71,8 +74,9 @@ - project Project @relation(fields: [projectId], references: [id]) - testRuns TestRun[] + name String ++ branchName String @default("master") + browser String? + device String? + os String? + viewport String? +@@ -73,8 +78,10 @@ baselines Baseline[] comment String? -+ branchName String @default("master") updatedAt DateTime @updatedAt createdAt DateTime @default(now()) ++ ++ @@unique([name, browser, device, os, viewport, branchName]) } + model Baseline { + id String @default(uuid()) @id ``` diff --git a/prisma/migrations/20200711125803-branch-strategy/schema.prisma b/prisma/migrations/20200715232608-branch-strategy/schema.prisma similarity index 96% rename from prisma/migrations/20200711125803-branch-strategy/schema.prisma rename to prisma/migrations/20200715232608-branch-strategy/schema.prisma index 87feafce..aaaed347 100644 --- a/prisma/migrations/20200711125803-branch-strategy/schema.prisma +++ b/prisma/migrations/20200715232608-branch-strategy/schema.prisma @@ -45,6 +45,7 @@ model TestRun { build Build @relation(fields: [buildId], references: [id]) testVariationId String testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + merge Boolean @default(false) updatedAt DateTime @updatedAt createdAt DateTime @default(now()) // Test variation data @@ -64,6 +65,7 @@ model TestRun { model TestVariation { id String @default(uuid()) @id name String + branchName String @default("master") browser String? device String? os String? @@ -75,9 +77,10 @@ model TestVariation { testRuns TestRun[] baselines Baseline[] comment String? - branchName String @default("master") updatedAt DateTime @updatedAt createdAt DateTime @default(now()) + + @@unique([name, browser, device, os, viewport, branchName]) } model Baseline { diff --git a/prisma/migrations/20200711125803-branch-strategy/steps.json b/prisma/migrations/20200715232608-branch-strategy/steps.json similarity index 68% rename from prisma/migrations/20200711125803-branch-strategy/steps.json rename to prisma/migrations/20200715232608-branch-strategy/steps.json index d12b143b..8260e776 100644 --- a/prisma/migrations/20200711125803-branch-strategy/steps.json +++ b/prisma/migrations/20200715232608-branch-strategy/steps.json @@ -33,6 +33,38 @@ "argument": "", "value": "\"master\"" }, + { + "tag": "CreateField", + "model": "TestRun", + "field": "merge", + "type": "Boolean", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "TestRun", + "field": "merge" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "TestRun", + "field": "merge" + }, + "directive": "default" + }, + "argument": "", + "value": "false" + }, { "tag": "CreateField", "model": "TestRun", @@ -103,6 +135,22 @@ }, "argument": "", "value": "\"master\"" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Model", + "model": "TestVariation", + "arguments": [ + { + "name": "", + "value": "[name, browser, device, os, viewport, branchName]" + } + ] + }, + "directive": "unique" + } } ] } \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 4d670fd2..1db1b94d 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 -20200711125803-branch-strategy \ No newline at end of file +20200715232608-branch-strategy \ No newline at end of file diff --git a/prisma/package-lock.json b/prisma/package-lock.json index 439a5b88..c1dc5044 100644 --- a/prisma/package-lock.json +++ b/prisma/package-lock.json @@ -1,19 +1,22 @@ { "name": "vrt-migration", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@prisma/cli": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.6.tgz", - "integrity": "sha512-qawcjLN5c26T+IVZij5WjQocfsV6b1BtJIL3CqMQfMkDCNGo/zXsu+NB+uyZ+QRqctEuJQ36lemnY44+k6L8XA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.2.2.tgz", + "integrity": "sha512-pk18QyfTHsAGctqoBu4pd9k/pAJ+uaD5GfrAE1gZkGuhmUGjh2hXmgJEKeQK+n1J4fvcstmDi38oHwskYyBv9w==", "dev": true }, "@prisma/client": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.6.tgz", - "integrity": "sha512-08AokCpMzL6SdxQVfShD7937t+sHntr6FJBpVKq5E/UFPVh0B2lUwU9YrA/MIAdRpGMg1wA+rdwRivtFIs5Law==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.2.2.tgz", + "integrity": "sha512-//Ji25pubuXCJDcXaE7Es/ytRB7ojdj8H2YCIkziQVWY62Nl/j9vXYM7WzA4z1lysOlbKFlH9e0W1sy6LQpUpg==", + "requires": { + "pkg-up": "^3.1.0" + } }, "@types/bcryptjs": { "version": "2.4.2", @@ -65,12 +68,63 @@ "resolved": "https://registry.npmjs.org/encode32/-/encode32-1.1.0.tgz", "integrity": "sha1-DFS0X7MUrVUC48Iwy5Ws3F5c0d0=" }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/prisma/package.json b/prisma/package.json index a75bd155..a61afa96 100644 --- a/prisma/package.json +++ b/prisma/package.json @@ -5,18 +5,17 @@ "author": "", "private": true, "license": "UNLICENSED", - "scripts": { - }, + "scripts": {}, "dependencies": { - "@prisma/client": "^2.0.0-beta.6", + "@prisma/client": "^2.2.2", "bcryptjs": "^2.4.3", "uuid-apikey": "^1.4.6" }, "devDependencies": { - "ts-node": "^8.8.2", - "@prisma/cli": "^2.0.0-beta.6", + "@prisma/cli": "^2.2.2", "@types/bcryptjs": "^2.4.2", "@types/uuid-apikey": "^1.4.0", + "ts-node": "^8.8.2", "typescript": "^3.7.4" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 78c0bb0b..9067b5d2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -45,6 +45,7 @@ model TestRun { build Build @relation(fields: [buildId], references: [id]) testVariationId String testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + merge Boolean @default(false) updatedAt DateTime @updatedAt createdAt DateTime @default(now()) // Test variation data @@ -64,6 +65,7 @@ model TestRun { model TestVariation { id String @default(uuid()) @id name String + branchName String @default("master") browser String? device String? os String? @@ -75,9 +77,10 @@ model TestVariation { testRuns TestRun[] baselines Baseline[] comment String? - branchName String @default("master") updatedAt DateTime @updatedAt createdAt DateTime @default(now()) + + @@unique([name, browser, device, os, viewport, branchName]) } model Baseline { diff --git a/src/builds/builds.module.ts b/src/builds/builds.module.ts index a45fc86a..6187a0b1 100644 --- a/src/builds/builds.module.ts +++ b/src/builds/builds.module.ts @@ -4,11 +4,11 @@ import { BuildsController } from './builds.controller'; import { UsersModule } from '../users/users.module'; import { PrismaService } from '../prisma/prisma.service'; import { TestRunsModule } from '../test-runs/test-runs.module'; -import { EventsGateway } from '../events/events.gateway'; +import { SharedModule } from '../shared/shared.module'; @Module({ - imports: [UsersModule, TestRunsModule], - providers: [BuildsService, PrismaService, EventsGateway], + imports: [SharedModule, UsersModule, TestRunsModule], + providers: [BuildsService, PrismaService], controllers: [BuildsController], exports: [BuildsService], }) diff --git a/src/builds/builds.service.spec.ts b/src/builds/builds.service.spec.ts index 95d24c8f..6160e21d 100644 --- a/src/builds/builds.service.spec.ts +++ b/src/builds/builds.service.spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { BuildsService } from './builds.service'; import { PrismaService } from '../prisma/prisma.service'; import { TestRunsService } from '../test-runs/test-runs.service'; -import { EventsGateway } from '../events/events.gateway'; +import { EventsGateway } from '../shared/events/events.gateway'; import { CreateBuildDto } from './dto/build-create.dto'; import { Build, TestRun, Project } from '@prisma/client'; import { mocked } from 'ts-jest/utils'; @@ -18,7 +18,7 @@ const initService = async ({ buildDeleteMock = jest.fn(), testRunDeleteMock = jest.fn(), eventsBuildCreatedMock = jest.fn(), - projectFindManyMock = jest.fn(), + projectFindOneMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -27,7 +27,7 @@ const initService = async ({ provide: PrismaService, useValue: { project: { - findMany: projectFindManyMock, + findOne: projectFindOneMock, }, build: { findMany: buildFindManyMock, @@ -91,7 +91,8 @@ describe('BuildsService', () => { ignoreAreas: '[]', comment: 'some comment', branchName: 'develop', - baselineBranchName: 'master' + baselineBranchName: 'master', + merge: false, }, ], }; @@ -127,12 +128,12 @@ describe('BuildsService', () => { }); describe('create', () => { - const createBuildDto: CreateBuildDto = { - branchName: 'branchName', - project: 'project id or name', - }; + it('should create by name', async () => { + const createBuildDto: CreateBuildDto = { + branchName: 'branchName', + project: 'name', + }; - it('should create', async () => { const project: Project = { id: 'project id', name: 'name', @@ -141,18 +142,54 @@ describe('BuildsService', () => { createdAt: new Date(), }; const buildCreateMock = jest.fn().mockResolvedValueOnce(build); - const projectFindManyMock = jest.fn().mockResolvedValueOnce([project]); + const projectFindOneMock = jest.fn().mockResolvedValueOnce(project); const eventsBuildCreatedMock = jest.fn(); mocked(BuildDto).mockReturnValueOnce(buildDto); - service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindManyMock }); + service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock }); const result = await service.create(createBuildDto); - expect(projectFindManyMock).toHaveBeenCalledWith({ - where: { - OR: [{ id: createBuildDto.project }, { name: createBuildDto.project }], + expect(projectFindOneMock).toHaveBeenCalledWith({ + where: { name: createBuildDto.project }, + }); + expect(buildCreateMock).toHaveBeenCalledWith({ + data: { + branchName: createBuildDto.branchName, + project: { + connect: { + id: project.id, + }, + }, }, }); + expect(eventsBuildCreatedMock).toHaveBeenCalledWith(buildDto); + expect(result).toBe(buildDto); + }); + + it('should create by UUID', async () => { + const createBuildDto: CreateBuildDto = { + branchName: 'branchName', + project: '6bdd3704-90af-4b1b-94cb-f183e500f5cb', + }; + + const project: Project = { + id: '6bdd3704-90af-4b1b-94cb-f183e500f5cb', + name: 'name', + mainBranchName: 'master', + updatedAt: new Date(), + createdAt: new Date(), + }; + const buildCreateMock = jest.fn().mockResolvedValueOnce(build); + const projectFindOneMock = jest.fn().mockResolvedValueOnce(project); + const eventsBuildCreatedMock = jest.fn(); + mocked(BuildDto).mockReturnValueOnce(buildDto); + service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock }); + + const result = await service.create(createBuildDto); + + expect(projectFindOneMock).toHaveBeenCalledWith({ + where: { id: createBuildDto.project }, + }); expect(buildCreateMock).toHaveBeenCalledWith({ data: { branchName: createBuildDto.branchName, @@ -168,8 +205,12 @@ describe('BuildsService', () => { }); it('should throw exception if not found', async () => { - const projectFindManyMock = jest.fn().mockResolvedValueOnce([]); - service = await initService({ projectFindManyMock }); + const createBuildDto: CreateBuildDto = { + branchName: 'branchName', + project: 'nonexisting', + }; + const projectFindOneMock = jest.fn().mockResolvedValueOnce(undefined); + service = await initService({ projectFindOneMock }); await expect(service.create(createBuildDto)).rejects.toThrowError( new HttpException('Project not found', HttpStatus.NOT_FOUND) diff --git a/src/builds/builds.service.ts b/src/builds/builds.service.ts index f5b0508e..a9a50038 100644 --- a/src/builds/builds.service.ts +++ b/src/builds/builds.service.ts @@ -1,10 +1,11 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { CreateBuildDto } from './dto/build-create.dto'; import { PrismaService } from '../prisma/prisma.service'; -import { Build } from '@prisma/client'; +import { Build, Project } from '@prisma/client'; import { TestRunsService } from '../test-runs/test-runs.service'; -import { EventsGateway } from '../events/events.gateway'; +import { EventsGateway } from '../shared/events/events.gateway'; import { BuildDto } from './dto/build.dto'; +import uuidAPIKey from 'uuid-apikey'; @Injectable() export class BuildsService { @@ -27,13 +28,21 @@ export class BuildsService { } async create(createBuildDto: CreateBuildDto): Promise { - const projects = await this.prismaService.project.findMany({ - where: { - OR: [{ id: createBuildDto.project }, { name: createBuildDto.project }], - }, - }); + let project: Project; + + if (uuidAPIKey.isUUID(createBuildDto.project)) { + project = await this.prismaService.project.findOne({ + where: { id: createBuildDto.project }, + }); + } else { + project = await this.prismaService.project.findOne({ + where: { + name: createBuildDto.project, + }, + }); + } - if (projects.length <= 0) { + if (!project) { throw new HttpException(`Project not found`, HttpStatus.NOT_FOUND); } @@ -42,7 +51,7 @@ export class BuildsService { branchName: createBuildDto.branchName, project: { connect: { - id: projects[0].id, + id: project.id, }, }, }, diff --git a/src/builds/dto/build.dto.spec.ts b/src/builds/dto/build.dto.spec.ts index ae366886..5db0ff22 100644 --- a/src/builds/dto/build.dto.spec.ts +++ b/src/builds/dto/build.dto.spec.ts @@ -87,6 +87,7 @@ describe('BuildDto', () => { comment: 'some comment1', branchName: 'develop', baselineBranchName: 'master', + merge: true, }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -110,6 +111,7 @@ describe('BuildDto', () => { comment: 'some comment2', branchName: 'develop', baselineBranchName: 'master', + merge: false }, ], }; @@ -165,6 +167,7 @@ describe('BuildDto', () => { comment: 'some comment', branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -188,6 +191,7 @@ describe('BuildDto', () => { comment: 'some comment1', branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -211,6 +215,7 @@ describe('BuildDto', () => { comment: 'some comment2', branchName: 'develop', baselineBranchName: 'master', + merge: false }, ], }; @@ -266,6 +271,7 @@ describe('BuildDto', () => { comment: 'some comment1', branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -289,6 +295,7 @@ describe('BuildDto', () => { comment: 'some comment2', branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -312,6 +319,7 @@ describe('BuildDto', () => { comment: null, branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -335,6 +343,7 @@ describe('BuildDto', () => { comment: 'some comment', branchName: 'develop', baselineBranchName: 'master', + merge: false }, { id: '10fb5e02-64e0-4cf5-9f17-c00ab3c96658', @@ -358,6 +367,7 @@ describe('BuildDto', () => { comment: 'some comment', branchName: 'develop', baselineBranchName: 'master', + merge: false }, ], }; diff --git a/src/events/events.gateway.ts b/src/shared/events/events.gateway.ts similarity index 90% rename from src/events/events.gateway.ts rename to src/shared/events/events.gateway.ts index 6069f3e8..16775414 100644 --- a/src/events/events.gateway.ts +++ b/src/shared/events/events.gateway.ts @@ -1,7 +1,7 @@ import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; import { Server } from 'socket.io'; import { TestRun } from '@prisma/client'; -import { BuildDto } from '../builds/dto/build.dto'; +import { BuildDto } from '../../builds/dto/build.dto'; @WebSocketGateway() export class EventsGateway { diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts index a1adbe0f..1838a6f0 100644 --- a/src/shared/shared.module.ts +++ b/src/shared/shared.module.ts @@ -1,10 +1,11 @@ import { Global, Module } from '@nestjs/common'; import { StaticService } from './static/static.service'; +import { EventsGateway } from '../shared/events/events.gateway'; @Global() @Module({ - providers: [StaticService], - exports: [StaticService], + providers: [StaticService, EventsGateway], + exports: [StaticService, EventsGateway], imports: [], controllers: [], }) diff --git a/src/test-runs/dto/create-test-request.dto.ts b/src/test-runs/dto/create-test-request.dto.ts index 44c0c0d2..c11ae547 100644 --- a/src/test-runs/dto/create-test-request.dto.ts +++ b/src/test-runs/dto/create-test-request.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsBase64, IsUUID, IsNumber } from 'class-validator'; +import { IsOptional, IsBase64, IsUUID, IsNumber, IsBoolean } from 'class-validator'; import { BaselineDataDto } from '../../shared/dto/baseline-data.dto'; export class CreateTestRequestDto extends BaselineDataDto { @@ -19,4 +19,9 @@ export class CreateTestRequestDto extends BaselineDataDto { @IsOptional() @IsNumber() diffTollerancePercent?: number; + + @ApiProperty() + @IsBoolean() + @IsOptional() + merge?: boolean; } diff --git a/src/test-runs/dto/testRunResult.dto.ts b/src/test-runs/dto/testRunResult.dto.ts index 18bcb34d..5e854618 100644 --- a/src/test-runs/dto/testRunResult.dto.ts +++ b/src/test-runs/dto/testRunResult.dto.ts @@ -1,23 +1,25 @@ import { TestRun, TestStatus, TestVariation } from '@prisma/client'; export class TestRunResultDto { - id: string - imageName: string - diffName?: string - diffPercent: number + id: string; + imageName: string; + diffName?: string; + diffPercent: number; diffTollerancePercent?: number; pixelMisMatchCount?: number; status: TestStatus; url: string; + merge: boolean; constructor(testRun: TestRun, testVariation: TestVariation) { - this.id = testRun.id - this.imageName = testRun.imageName - this.diffName = testRun.diffName - this.diffPercent = testRun.diffPercent - this.diffTollerancePercent = testRun.diffTollerancePercent - this.pixelMisMatchCount = testRun.pixelMisMatchCount - this.status = testRun.status - this.url = `${process.env.APP_FRONTEND_URL}/${testVariation.projectId}?buildId=${testRun.buildId}&testId=${testRun.id}` + this.id = testRun.id; + this.imageName = testRun.imageName; + this.diffName = testRun.diffName; + this.diffPercent = testRun.diffPercent; + this.diffTollerancePercent = testRun.diffTollerancePercent; + this.pixelMisMatchCount = testRun.pixelMisMatchCount; + this.status = testRun.status; + this.merge = testRun.merge; + this.url = `${process.env.APP_FRONTEND_URL}/${testVariation.projectId}?buildId=${testRun.buildId}&testId=${testRun.id}`; } } diff --git a/src/test-runs/test-runs.controller.ts b/src/test-runs/test-runs.controller.ts index e2e226ef..f83685d2 100644 --- a/src/test-runs/test-runs.controller.ts +++ b/src/test-runs/test-runs.controller.ts @@ -1,4 +1,16 @@ -import { Controller, Delete, UseGuards, Param, ParseUUIDPipe, Put, Body, Get, Query, Post } from '@nestjs/common'; +import { + Controller, + Delete, + UseGuards, + Param, + ParseUUIDPipe, + Put, + Body, + Get, + Query, + Post, + ParseBoolPipe, +} from '@nestjs/common'; import { ApiTags, ApiParam, ApiBearerAuth, ApiQuery, ApiSecurity, ApiOkResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/auth.guard'; import { TestRun } from '@prisma/client'; @@ -30,12 +42,16 @@ export class TestRunsController { return this.testRunsService.recalculateDiff(id); } - @Get('approve/:id') - @ApiParam({ name: 'id', required: true }) + @Get('approve') + @ApiQuery({ name: 'id', required: true }) + @ApiQuery({ name: 'merge', required: false }) @ApiBearerAuth() @UseGuards(JwtAuthGuard) - approveTestRun(@Param('id', new ParseUUIDPipe()) id: string): Promise { - return this.testRunsService.approve(id); + approveTestRun( + @Query('id', new ParseUUIDPipe()) id: string, + @Query('merge', new ParseBoolPipe()) merge: boolean + ): Promise { + return this.testRunsService.approve(id, merge); } @Get('reject/:id') diff --git a/src/test-runs/test-runs.module.ts b/src/test-runs/test-runs.module.ts index 87349b1b..579466d0 100644 --- a/src/test-runs/test-runs.module.ts +++ b/src/test-runs/test-runs.module.ts @@ -3,12 +3,11 @@ import { TestRunsService } from './test-runs.service'; import { SharedModule } from '../shared/shared.module'; import { PrismaService } from '../prisma/prisma.service'; import { TestRunsController } from './test-runs.controller'; -import { EventsGateway } from '../events/events.gateway'; import { TestVariationsModule } from '../test-variations/test-variations.module'; @Module({ imports: [SharedModule, TestVariationsModule], - providers: [TestRunsService, PrismaService, EventsGateway], + providers: [TestRunsService, PrismaService], exports: [TestRunsService], controllers: [TestRunsController] }) diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index 08535c9d..50df32a4 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -4,13 +4,13 @@ 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, TestVariation } from '@prisma/client'; +import { TestStatus, Build, TestRun, TestVariation, Project } 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 { EventsGateway } from '../shared/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'; @@ -30,10 +30,14 @@ const initService = async ({ deleteImageMock = jest.fn(), eventNewTestRunMock = jest.fn(), eventBuildUpdatedMock = jest.fn(), + eventBuildCreatedMock = jest.fn(), buildFindOneMock = jest.fn(), + buildCreateMock = jest.fn(), testVariationCreateMock = jest.fn(), + testVariationFindManyMock = jest.fn(), baselineCreateMock = jest.fn(), testVariationFindOrCreateMock = jest.fn(), + projectFindOneMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -50,13 +54,18 @@ const initService = async ({ }, build: { findOne: buildFindOneMock, + create: buildCreateMock, }, testVariation: { create: testVariationCreateMock, + findMany: testVariationFindManyMock, }, baseline: { create: baselineCreateMock, }, + project: { + findOne: projectFindOneMock, + }, }, }, { @@ -72,6 +81,7 @@ const initService = async ({ useValue: { newTestRun: eventNewTestRunMock, buildUpdated: eventBuildUpdatedMock, + buildCreated: eventBuildCreatedMock, }, }, { @@ -159,6 +169,79 @@ describe('TestRunsService', () => { branchName: 'master', baselineBranchName: 'master', comment: 'some comment', + merge: false, + }; + 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, + }) + ); + service = await initService({ + testRunUpdateMock, + saveImageMock, + getImageMock, + }); + service.findOne = testRunFindOneMock; + service.emitUpdateBuildEvent = jest.fn(); + + await service.approve(testRun.id, false); + + expect(testRunFindOneMock).toHaveBeenCalledWith(testRun.id); + expect(getImageMock).toHaveBeenCalledWith(testRun.imageName); + expect(saveImageMock).toHaveBeenCalledTimes(1); + expect(testRunUpdateMock).toHaveBeenCalledWith({ + where: { id: testRun.id }, + data: { + status: TestStatus.approved, + testVariation: { + update: { + baselineName, + baselines: { + create: { + baselineName, + testRun: { + connect: { + id: testRun.id, + }, + }, + }, + }, + }, + }, + }, + }); + expect(service.emitUpdateBuildEvent).toBeCalledWith(testRun.buildId); + }); + + it('should approve merge', async () => { + const testRun: TestRun = { + 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', + merge: false, }; const testRunUpdateMock = jest.fn(); const testRunFindOneMock = jest.fn().mockResolvedValueOnce(testRun); @@ -178,7 +261,7 @@ describe('TestRunsService', () => { service.findOne = testRunFindOneMock; service.emitUpdateBuildEvent = jest.fn(); - await service.approve(testRun.id); + await service.approve(testRun.id, true); expect(testRunFindOneMock).toHaveBeenCalledWith(testRun.id); expect(getImageMock).toHaveBeenCalledWith(testRun.imageName); @@ -232,6 +315,7 @@ describe('TestRunsService', () => { branchName: 'develop', baselineBranchName: 'master', comment: 'some comment', + merge: false, testVariation: { id: '123', projectId: 'project Id', @@ -285,7 +369,7 @@ describe('TestRunsService', () => { service.findOne = testRunFindOneMock; service.emitUpdateBuildEvent = jest.fn(); - await service.approve(testRun.id); + await service.approve(testRun.id, false); expect(testRunFindOneMock).toHaveBeenCalledWith(testRun.id); expect(getImageMock).toHaveBeenCalledWith(testRun.imageName); @@ -342,6 +426,7 @@ describe('TestRunsService', () => { device: 'device', diffTollerancePercent: undefined, branchName: 'develop', + merge: true, }; const testRunWithResult = { id: 'id', @@ -435,6 +520,7 @@ describe('TestRunsService', () => { baselineBranchName: testVariation.branchName, branchName: createTestRequestDto.branchName, diffTollerancePercent: createTestRequestDto.diffTollerancePercent, + merge: createTestRequestDto.merge, status: TestStatus.new, }, }); @@ -773,6 +859,7 @@ describe('TestRunsService', () => { comment: 'some comment', baselineBranchName: 'master', branchName: 'develop', + merge: false, }, ], }; @@ -845,6 +932,7 @@ describe('TestRunsService', () => { comment: 'some comment', baselineBranchName: 'master', branchName: 'develop', + merge: false, }; const testVariationFindOrCreateMock = jest.fn().mockResolvedValueOnce(testVariation); const testRunFindManyMock = jest.fn().mockResolvedValueOnce([testRun]); diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index dc2fbcb7..c21107db 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -7,7 +7,7 @@ import { StaticService } from '../shared/static/static.service'; import { PrismaService } from '../prisma/prisma.service'; import { TestRun, TestStatus, TestVariation } from '@prisma/client'; import { DiffResult } from './diffResult'; -import { EventsGateway } from '../events/events.gateway'; +import { EventsGateway } from '../shared/events/events.gateway'; import { CommentDto } from '../shared/dto/comment.dto'; import { BuildDto } from '../builds/dto/build.dto'; import { TestRunResultDto } from '../test-runs/dto/testRunResult.dto'; @@ -81,14 +81,14 @@ export class TestRunsService { this.eventsGateway.buildUpdated(buildDto); } - async approve(id: string): Promise { + async approve(id: string, merge: boolean): Promise { const testRun = await this.findOne(id); // save new baseline const baseline = this.staticService.getImage(testRun.imageName); const baselineName = this.staticService.saveImage('baseline', PNG.sync.write(baseline)); let testRunUpdated: TestRun; - if (testRun.branchName === testRun.baselineBranchName) { + if (merge || testRun.branchName === testRun.baselineBranchName) { testRunUpdated = await this.prismaService.testRun.update({ where: { id }, data: { @@ -219,6 +219,7 @@ export class TestRunsService { comment: testVariation.comment, diffTollerancePercent: createTestRequestDto.diffTollerancePercent, branchName: createTestRequestDto.branchName, + merge: createTestRequestDto.merge, status: TestStatus.new, }, }); diff --git a/src/test-variations/test-variations.controller.ts b/src/test-variations/test-variations.controller.ts index 450b6e85..5f2f265c 100644 --- a/src/test-variations/test-variations.controller.ts +++ b/src/test-variations/test-variations.controller.ts @@ -1,11 +1,12 @@ import { Controller, ParseUUIDPipe, Get, UseGuards, Param, Query, Put, Body } from '@nestjs/common'; -import { ApiTags, ApiParam, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { ApiTags, ApiParam, ApiBearerAuth, ApiQuery, ApiOkResponse } from '@nestjs/swagger'; import { TestVariationsService } from './test-variations.service'; import { TestVariation, Baseline } from '@prisma/client'; import { JwtAuthGuard } from '../auth/guards/auth.guard'; import { PrismaService } from '../prisma/prisma.service'; import { IgnoreAreaDto } from '../test-runs/dto/ignore-area.dto'; import { CommentDto } from '../shared/dto/comment.dto'; +import { BuildDto } from '../builds/dto/build.dto'; @ApiTags('test-variations') @Controller('test-variations') @@ -16,17 +17,17 @@ export class TestVariationsController { @ApiQuery({ name: 'projectId', required: true }) @ApiBearerAuth() @UseGuards(JwtAuthGuard) - getList(@Query('projectId', new ParseUUIDPipe()) projectId): Promise { + getList(@Query('projectId', new ParseUUIDPipe()) projectId: string): Promise { return this.prismaService.testVariation.findMany({ where: { projectId }, }); } - @Get(':id') + @Get('details/:id') @ApiQuery({ name: 'id', required: true }) @ApiBearerAuth() @UseGuards(JwtAuthGuard) - getDetails(@Param('id', new ParseUUIDPipe()) id): Promise { + getDetails(@Param('id', new ParseUUIDPipe()) id: string): Promise { return this.testVariations.getDetails(id); } @@ -48,4 +49,14 @@ export class TestVariationsController { updateComment(@Param('id', new ParseUUIDPipe()) id: string, @Body() body: CommentDto): Promise { return this.testVariations.updateComment(id, body); } + + @Get('merge/') + @ApiQuery({ name: 'projectId', required: true }) + @ApiQuery({ name: 'branchName', required: true }) + @ApiOkResponse({ type: BuildDto }) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + merge(@Query('projectId') projectId: string, @Query('branchName') branchName: string): Promise { + return this.testVariations.merge(projectId, branchName); + } } diff --git a/src/test-variations/test-variations.module.ts b/src/test-variations/test-variations.module.ts index 337832e8..0e8c23f3 100644 --- a/src/test-variations/test-variations.module.ts +++ b/src/test-variations/test-variations.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { TestVariationsService } from './test-variations.service'; import { TestVariationsController } from './test-variations.controller'; import { PrismaService } from '../prisma/prisma.service'; +import { TestRunsService } from '../test-runs/test-runs.service'; +import { BuildsService } from '../builds/builds.service'; @Module({ - providers: [TestVariationsService, PrismaService], + providers: [TestVariationsService, PrismaService, TestRunsService, BuildsService], controllers: [TestVariationsController], - exports: [TestVariationsService] + exports: [TestVariationsService], }) export class TestVariationsModule {} diff --git a/src/test-variations/test-variations.service.spec.ts b/src/test-variations/test-variations.service.spec.ts index c2b023c9..8c612f4d 100644 --- a/src/test-variations/test-variations.service.spec.ts +++ b/src/test-variations/test-variations.service.spec.ts @@ -4,12 +4,16 @@ 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, Project } from '@prisma/client'; +import { TestVariation, Baseline, Project, Build } from '@prisma/client'; import { CommentDto } from '../shared/dto/comment.dto'; import { convertBaselineDataToQuery } from '../shared/dto/baseline-data.dto'; +import { PNG } from 'pngjs'; +import { BuildsService } from '../builds/builds.service'; +import { TestRunsService } from '../test-runs/test-runs.service'; const initModule = async ({ imageDeleteMock = jest.fn(), + getImageMock = jest.fn(), variationFindOneMock = jest.fn, variationFindManyMock = jest.fn().mockReturnValue([]), variationCreateMock = jest.fn(), @@ -17,6 +21,8 @@ const initModule = async ({ variationDeleteMock = jest.fn(), baselineDeleteMock = jest.fn(), projectFindOneMock = jest.fn(), + buildCreateMock = jest.fn(), + testRunCreateMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -24,9 +30,22 @@ const initModule = async ({ { provide: StaticService, useValue: { + getImage: getImageMock, deleteImage: imageDeleteMock, }, }, + { + provide: BuildsService, + useValue: { + create: buildCreateMock, + }, + }, + { + provide: TestRunsService, + useValue: { + create: testRunCreateMock, + }, + }, { provide: PrismaService, useValue: { @@ -353,4 +372,149 @@ describe('TestVariationsService', () => { }, }); }); + + it('merge', async () => { + const mergedBranch = 'develop'; + const project: Project = { + id: 'some id', + name: 'some name', + mainBranchName: 'master', + updatedAt: new Date(), + createdAt: new Date(), + }; + const build: Build = { + id: 'a9385fc1-884d-4f9f-915e-40da0e7773d5', + number: null, + branchName: project.mainBranchName, + status: null, + projectId: project.id, + updatedAt: new Date(), + createdAt: new Date(), + userId: null, + }; + 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: mergedBranch, + }; + const testVariationSecond: TestVariation = { + id: '123', + projectId: project.id, + name: 'Test name second', + baselineName: 'baselineName', + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + createdAt: new Date(), + updatedAt: new Date(), + branchName: mergedBranch, + }; + const testVariationNoBaseline: TestVariation = { + id: '123', + projectId: project.id, + name: 'Test name', + baselineName: null, + os: 'OS', + browser: 'browser', + viewport: 'viewport', + device: 'device', + ignoreAreas: '[]', + comment: 'some comment', + createdAt: new Date(), + updatedAt: new Date(), + branchName: mergedBranch, + }; + const testVariationMainBranch: 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: project.mainBranchName, + }; + const projectFindOneMock = jest.fn().mockResolvedValueOnce(project); + const buildCreateMock = jest.fn().mockResolvedValueOnce(build); + const variationFindManyMock = jest + .fn() + .mockResolvedValueOnce([testVariation, testVariationSecond, testVariationNoBaseline]); + const image = new PNG({ + width: 10, + height: 10, + }); + const getImageMock = jest + .fn() + .mockReturnValueOnce(image) + .mockReturnValueOnce(image) + .mockReturnValueOnce(null); + const findOrCreateMock = jest + .fn() + .mockResolvedValueOnce(testVariationMainBranch) + .mockResolvedValueOnce(testVariationMainBranch); + const testRunCreateMock = jest.fn(); + const service = await initModule({ + projectFindOneMock, + buildCreateMock, + testRunCreateMock, + variationFindManyMock, + getImageMock, + }); + service.findOrCreate = findOrCreateMock; + + await service.merge(project.id, mergedBranch); + + expect(projectFindOneMock).toHaveBeenCalledWith({ where: { id: project.id } }); + expect(buildCreateMock).toHaveBeenCalledWith({ + branchName: project.mainBranchName, + project: project.id, + }); + expect(variationFindManyMock).toHaveBeenCalledWith({ + where: { projectId: project.id, branchName: mergedBranch }, + }); + expect(getImageMock).toHaveBeenCalledWith(testVariation.baselineName); + expect(service.findOrCreate).toHaveBeenCalledWith(project.id, { + name: testVariation.name, + os: testVariation.os, + device: testVariation.device, + browser: testVariation.browser, + viewport: testVariation.viewport, + branchName: project.mainBranchName, + }); + + await new Promise(r => setTimeout(r, 1)); + expect(testRunCreateMock).toHaveBeenNthCalledWith(1, testVariationMainBranch, { + ...testVariation, + buildId: build.id, + imageBase64: PNG.sync.write(image).toString('base64'), + diffTollerancePercent: 0, + merge: true, + }); + expect(testRunCreateMock).toHaveBeenNthCalledWith(2, testVariationMainBranch, { + ...testVariationSecond, + buildId: build.id, + imageBase64: PNG.sync.write(image).toString('base64'), + diffTollerancePercent: 0, + merge: true, + }); + expect(testRunCreateMock).toHaveBeenCalledTimes(2); + }); }); diff --git a/src/test-variations/test-variations.service.ts b/src/test-variations/test-variations.service.ts index 8e4b9cc4..cdcb543e 100644 --- a/src/test-variations/test-variations.service.ts +++ b/src/test-variations/test-variations.service.ts @@ -1,14 +1,26 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject, forwardRef } from '@nestjs/common'; import { IgnoreAreaDto } from '../test-runs/dto/ignore-area.dto'; import { PrismaService } from '../prisma/prisma.service'; -import { TestVariation, Baseline } from '@prisma/client'; +import { TestVariation, Baseline, Project, Build } from '@prisma/client'; import { StaticService } from '../shared/static/static.service'; import { CommentDto } from '../shared/dto/comment.dto'; -import { BaselineDataDto } from '../shared/dto/baseline-data.dto'; +import { BaselineDataDto, convertBaselineDataToQuery } from '../shared/dto/baseline-data.dto'; +import { BuildsService } from '../builds/builds.service'; +import { TestRunsService } from '../test-runs/test-runs.service'; +import { PNG } from 'pngjs'; +import { CreateTestRequestDto } from 'src/test-runs/dto/create-test-request.dto'; +import { BuildDto } from 'src/builds/dto/build.dto'; @Injectable() export class TestVariationsService { - constructor(private prismaService: PrismaService, private staticService: StaticService) {} + constructor( + private prismaService: PrismaService, + private staticService: StaticService, + @Inject(forwardRef(() => BuildsService)) + private buildsService: BuildsService, + @Inject(forwardRef(() => TestRunsService)) + private testRunsService: TestRunsService + ) {} async getDetails(id: string): Promise { return this.prismaService.testVariation.findOne({ @@ -106,4 +118,51 @@ export class TestVariationsService { where: { id }, }); } + + async merge(projectId: string, branchName: string): Promise { + const project: Project = await this.prismaService.project.findOne({ where: { id: projectId } }); + + // create build + const build: BuildDto = await this.buildsService.create({ + branchName: project.mainBranchName, + project: projectId, + }); + + // find side branch variations + const testVariations: TestVariation[] = await this.prismaService.testVariation.findMany({ + where: { projectId, branchName }, + }); + + // compare to main branch variations + testVariations.map(async sideBranchTestVariation => { + const baseline = this.staticService.getImage(sideBranchTestVariation.baselineName); + if (baseline) { + try { + let imageBase64 = PNG.sync.write(baseline).toString('base64'); + + // get main branch variation + const baselineData = convertBaselineDataToQuery({ + ...sideBranchTestVariation, + branchName: project.mainBranchName, + }); + const mainBranchTestVariation = await this.findOrCreate(projectId, baselineData); + + // get side branch request + const createTestRequestDto: CreateTestRequestDto = { + ...sideBranchTestVariation, + buildId: build.id, + imageBase64, + diffTollerancePercent: 0, + merge: true, + }; + + return this.testRunsService.create(mainBranchTestVariation, createTestRequestDto); + } catch (err) { + console.log(err); + } + } + }); + + return build; + } }