Skip to content

Commit f9fa245

Browse files
authored
Add CDCT for all interactions between UI and Pipeline (#356)
1 parent 45f31fe commit f9fa245

14 files changed

+1409
-87
lines changed

pacts/ui-pipeline.json

Lines changed: 462 additions & 1 deletion
Large diffs are not rendered by default.

pipeline/src/api/rest/pipelineConfig.provider.pact.test.ts

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,69 @@
11
import { Verifier } from '@pact-foundation/pact'
22
import path from 'path'
3-
import { PipelineConfig } from '../../pipeline-config/model/pipelineConfig'
3+
import { HealthStatus, PipelineConfig, PipelineConfigDTO } from '../../pipeline-config/model/pipelineConfig'
44
import { port, server } from '../../index' // the main method is automatically called due to this import
5+
import { PipelineTransformedData } from 'src/pipeline-config/model/pipelineTransformedData'
56

67
jest.mock('../../env', () => ({ }))
78

89
const pipelineConfigs: PipelineConfig[] = []
10+
let nextPipelineConfigId: number
911

1012
jest.mock('../../pipeline-config/pipelineConfigManager', () => {
1113
return {
1214
PipelineConfigManager: jest.fn().mockImplementation(() => {
1315
return {
14-
getAll: jest.fn().mockResolvedValue(pipelineConfigs)
16+
getAll: jest.fn().mockResolvedValue(pipelineConfigs),
17+
18+
get: jest.fn().mockImplementation(async (id: number) => {
19+
const result = pipelineConfigs.find((config) => config.id === id)
20+
return Promise.resolve(result)
21+
}),
22+
23+
getByDatasourceId: jest.fn().mockImplementation(async (datasourceId: number) => {
24+
const result = pipelineConfigs.filter((config) => config.datasourceId === datasourceId)
25+
return Promise.resolve(result)
26+
}),
27+
28+
create: jest.fn().mockImplementation(async (config: PipelineConfigDTO) => {
29+
const result: PipelineConfig = {
30+
...config,
31+
metadata: {
32+
...config.metadata,
33+
creationTimestamp: new Date(2022, 1)
34+
},
35+
id: ++nextPipelineConfigId
36+
}
37+
pipelineConfigs.push(result)
38+
return await Promise.resolve(result)
39+
}),
40+
41+
update: jest.fn(async (id: number, config: PipelineConfigDTO) => {
42+
const configToUpdate = pipelineConfigs.find((config) => config.id === id)
43+
Object.assign(configToUpdate, config)
44+
}),
45+
46+
delete: jest.fn(async (id: number) => {
47+
const indexOfConfigToDelete = pipelineConfigs.findIndex((config) => config.id === id)
48+
if (indexOfConfigToDelete !== -1) {
49+
pipelineConfigs.splice(indexOfConfigToDelete, 1)
50+
}
51+
})
52+
}
53+
})
54+
}
55+
})
56+
57+
const pipelineTransformedData: PipelineTransformedData[] = []
58+
59+
jest.mock('../../pipeline-config/pipelineTransformedDataManager', () => {
60+
return {
61+
PipelineTransformedDataManager: jest.fn().mockImplementation(() => {
62+
return {
63+
getLatest: jest.fn(async (id: number) => {
64+
const result = pipelineTransformedData.find((data) => data.id === id)
65+
return Promise.resolve(result)
66+
})
1567
}
1668
})
1769
}
@@ -42,14 +94,89 @@ describe('Pact Provider Verification', () => {
4294
pactUrls: [
4395
path.resolve(process.cwd(), '..', 'pacts', 'ui-pipeline.json')
4496
],
97+
logDir: path.resolve(process.cwd(), '..', 'pacts', 'logs'),
4598
stateHandlers: {
46-
'no pipelines registered': async () => {
47-
pipelineConfigs.splice(0, pipelineConfigs.length)
48-
}
99+
'any state': setupEmptyState,
100+
'no pipelines exist': setupEmptyState,
101+
'some pipelines exist': setupSomePipelineConfigs,
102+
'pipeline with id 1 exists': setupSomePipelineConfigs,
103+
'pipeline with id 1 does not exist': setupEmptyState,
104+
'pipelines with datasource id 2 exist': setupSomePipelineConfigs,
105+
'pipelines with datasource id 2 do not exist': setupEmptyState,
106+
'transformed data with id 1 exists': setupSomePipelineTransformedData,
107+
'transformed data with id 1 does not exist': setupEmptyState
49108
}
50109
})
51110
await verifier.verifyProvider().finally(() => {
52111
server?.close()
53112
})
54113
})
55114
})
115+
116+
async function setupEmptyState (): Promise<void> {
117+
clearState()
118+
}
119+
120+
async function setupSomePipelineConfigs (): Promise<void> {
121+
clearState()
122+
addSamplePipelineConfig(++nextPipelineConfigId, 2, true)
123+
addSamplePipelineConfig(++nextPipelineConfigId, 3, false)
124+
addSamplePipelineConfig(++nextPipelineConfigId, 2, false)
125+
}
126+
127+
async function setupSomePipelineTransformedData (): Promise<void> {
128+
clearState()
129+
addSamplePipelineTransformedData(1)
130+
addSamplePipelineTransformedData(2)
131+
addSamplePipelineTransformedData(3)
132+
}
133+
134+
function clearState (): void {
135+
nextPipelineConfigId = 0
136+
clearPipelineConfigs()
137+
138+
clearPipelineTransformedData()
139+
}
140+
141+
function clearPipelineConfigs (): void {
142+
pipelineConfigs.splice(0, pipelineConfigs.length)
143+
}
144+
145+
function addSamplePipelineConfig (id: number, datasourceId: number, withSchema: boolean): void {
146+
const pipelineConfig: PipelineConfig = {
147+
id: id,
148+
datasourceId: datasourceId,
149+
metadata: {
150+
author: 'some author',
151+
description: 'some description',
152+
displayName: 'some display name',
153+
license: 'some license',
154+
creationTimestamp: new Date(2021, 5)
155+
},
156+
transformation: {
157+
func: 'some function'
158+
}
159+
}
160+
if (withSchema) {
161+
pipelineConfig.schema = { }
162+
}
163+
pipelineConfigs.push(pipelineConfig)
164+
}
165+
166+
function clearPipelineTransformedData (): void {
167+
pipelineTransformedData.splice(0, pipelineTransformedData.length)
168+
}
169+
170+
function addSamplePipelineTransformedData (id: number): void {
171+
const data: PipelineTransformedData = {
172+
id: id,
173+
pipelineId: 42,
174+
healthStatus: HealthStatus.OK,
175+
data: { },
176+
177+
/* TODO without this 'createdAt' field, the UI would not be able to fully interpret the response
178+
because it expects the 'timestamp' field to be present which is derived from 'createdAt' */
179+
createdAt: 'some creation date'
180+
}
181+
pipelineTransformedData.push(data)
182+
}

ui/Dockerfile.consumer

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ RUN npm ci
1919
COPY ./src ./src
2020
COPY ./*.js ./
2121
COPY ./*.json ./
22+
COPY ./pact ./pact
2223

2324
# run consumer tests
2425
CMD ["npm", "run", "test:pact-consumer"]

ui/jest.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const {defaults} = require('jest-config')
2+
13
module.exports = {
24
preset: 'ts-jest',
35
testEnvironment: 'node',
@@ -33,6 +35,11 @@ module.exports = {
3335
'**/*.test.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
3436
],
3537

38+
testPathIgnorePatterns: [
39+
...defaults.testPathIgnorePatterns,
40+
'.*\\.pact\\.test\\.ts$'
41+
],
42+
3643
testURL: 'http://localhost/',
3744

3845
watchPlugins: [

ui/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"build": "vue-cli-service build",
77
"lint": "eslint src --fix",
88
"lint-ci": "eslint src --max-warnings=0",
9-
"test": "jest --colors --verbose --passWithNoTests --testPathIgnorePatterns \".*\\.pact\\.test\\.ts$\"",
10-
"test:pact-consumer": "jest --colors --verbose --testTimeout 30000 --testPathPattern \".*\\.consumer\\.pact\\.test\\.ts$\""
9+
"test": "jest --colors --verbose --passWithNoTests",
10+
"test:pact-consumer": "jest --config ./pact/consumer.jest.config.js --colors --verbose"
1111
},
1212
"dependencies": {
1313
"axios": "^0.21.1",

ui/pact/consumer.jest.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const {defaults} = require('jest-config')
2+
const regularJestConfig = require('../jest.config')
3+
4+
module.exports = {
5+
...regularJestConfig,
6+
rootDir: '..',
7+
testMatch: ['**/*.consumer.pact.test.(js|jsx|ts|tsx)'],
8+
testPathIgnorePatterns: defaults.testPathIgnorePatterns,
9+
testTimeout: 30000
10+
};

ui/src/pipeline/edit/schema/PipelineSchemaEdit.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import Vue from 'vue'
3333
import Component from 'vue-class-component'
3434
import * as SchemaSuggestionREST from './../../../datasource/schemaSuggestionRest'
3535
import * as DatasourceRest from './../../../datasource/datasourceRest'
36-
import * as TransformationRest from './../transformation/transformationRest'
36+
import { TransformationRest } from './../transformation/transformationRest'
37+
import { PIPELINE_SERVICE_URL } from './../../../env'
3738
import VueSlider from 'vue-slider-component'
3839
import 'vue-slider-component/theme/default.css'
3940
@@ -79,7 +80,7 @@ export default class PipelineSchemaEdit extends Vue {
7980
private async onGenerate (): Promise<void> {
8081
const data = await DatasourceRest.getDatasourceData(this.pipeline.datasourceId)
8182
const request: TransformationRequest = { data: data, func: this.pipeline.transformation.func }
82-
const result: JobResult = await TransformationRest.transformData(request)
83+
const result: JobResult = await new TransformationRest(PIPELINE_SERVICE_URL).transformData(request)
8384
this.pipeline.schema =
8485
await SchemaSuggestionREST.getSchema(JSON.stringify(result.data), this.currentSliderValue)
8586
this.schemaAsText = JSON.stringify(this.pipeline.schema)

ui/src/pipeline/edit/transformation/transformation.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
22

3-
import * as TransformationRest from './transformationRest'
3+
import { TransformationRest } from './transformationRest'
44
import { TransformationRequest, JobResult } from './transformation'
55

66
import * as DatasourceRest from '@/datasource/datasourceRest'
77
import { Data } from '@/datasource/datasource'
8+
import { PIPELINE_SERVICE_URL } from '@/env'
89

910
@Module({ namespaced: true })
1011
export default class TransformationModule extends VuexModule {
@@ -17,6 +18,8 @@ export default class TransformationModule extends VuexModule {
1718

1819
private timeoutHandle: number | null = null
1920

21+
private readonly transformationRest = new TransformationRest(PIPELINE_SERVICE_URL)
22+
2023
@Mutation public setData (value: Data): void {
2124
this.data = value
2225
this.isLoadingData = false
@@ -88,7 +91,7 @@ export default class TransformationModule extends VuexModule {
8891
// reset timeout handle
8992
this.context.commit('setTimeoutHandle', null)
9093
const request: TransformationRequest = { data: this.data, func: this.function }
91-
const result = await TransformationRest.transformData(request)
94+
const result = await this.transformationRest.transformData(request)
9295
// save the result in the module state
9396
this.context.commit('setResult', result)
9497
}
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
import axios from 'axios'
1+
import axios, { AxiosInstance } from 'axios'
22

33
import { TransformationRequest, JobResult } from './transformation'
4-
import { PIPELINE_SERVICE_URL } from '@/env'
54

6-
const http = axios.create({
7-
baseURL: PIPELINE_SERVICE_URL,
8-
headers: {
9-
'Content-Type': 'application/json'
5+
export class TransformationRest {
6+
private readonly http: AxiosInstance
7+
8+
constructor (pipelineServiceUrl: string) {
9+
this.http = axios.create({
10+
baseURL: pipelineServiceUrl,
11+
headers: {
12+
'Content-Type': 'application/json'
13+
}
14+
})
1015
}
11-
})
1216

13-
export async function transformData (request: TransformationRequest): Promise<JobResult> {
14-
const response = await http.post('/job', request, {
15-
validateStatus: status => (status >= 200 && status <= 400)
16-
})
17+
async transformData (request: TransformationRequest): Promise<JobResult> {
18+
const response = await this.http.post('/job', request, {
19+
validateStatus: status => (status >= 200 && status <= 400)
20+
})
1721

18-
return response.data
22+
return response.data
23+
}
1924
}

0 commit comments

Comments
 (0)