Skip to content

Commit

Permalink
mock-service-host: use discriminatorMaps (#2311)
Browse files Browse the repository at this point in the history
* use discriminatorMaps

* fix boolean response

* fix arrary response

* use snapshot
  • Loading branch information
changlong-liu authored Nov 23, 2021
1 parent 1408568 commit 6ad560d
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 42 deletions.
3 changes: 2 additions & 1 deletion tools/mock-service-host/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/mock-service-host",
"version": "0.1.4",
"version": "0.1.5",
"description": "Azure Mock Service Host",
"main": "index.js",
"scripts": {
Expand All @@ -9,6 +9,7 @@
"build": "tsc && npm run copyfiles",
"copyfiles": "copyfiles -a \"./test/testData/**/*.json\" ./dist",
"unit-test": "npm run build && cross-env NODE_ENV=test jest --ci --reporters=default --reporters=jest-junit --config ./jest.unittest.config.js",
"unittest-update": "npm run build && cross-env NODE_ENV=test jest --ci --reporters=default --reporters=jest-junit --updateSnapshot --config ./jest.unittest.config.js",
"test": "npm run build && cross-env NODE_ENV=test jest --ci --reporters=default --reporters=jest-junit --runInBand",
"eslint-fix": "eslint . --ext .ts --ignore-pattern node_modules/ --ignore-pattern dist/ --fix",
"eslint": "eslint . --ext .ts --ignore-pattern node_modules/ --ignore-pattern dist/ --quiet",
Expand Down
40 changes: 24 additions & 16 deletions tools/mock-service-host/src/mid/coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,25 +276,33 @@ export class Coordinator {
const [code, _ret] = this.findResponse(exampleResponses, HttpStatusCode.OK)

let ret = _ret
// simplified paging
ret = lodash.omit(ret, 'nextLink')
if (typeof ret === 'object') {
if (!Array.isArray(ret)) {
// simplified paging
ret = lodash.omit(ret, 'nextLink')
}

// simplified LRO
ret = replacePropertyValue('provisioningState', 'Succeeded', ret)
// simplified LRO
ret = replacePropertyValue('provisioningState', 'Succeeded', ret)

if (code !== HttpStatusCode.OK && code !== HttpStatusCode.NO_CONTENT && code < 300) {
res.setHeader('Azure-AsyncOperation', await this.findLROGet(req))
res.setHeader('Retry-After', 1)
}
if (req.query?.[LRO_CALLBACK] === 'true') {
ret.status = 'Succeeded'
}
if (
code !== HttpStatusCode.OK &&
code !== HttpStatusCode.NO_CONTENT &&
code < 300
) {
res.setHeader('Azure-AsyncOperation', await this.findLROGet(req))
res.setHeader('Retry-After', 1)
}
if (req.query?.[LRO_CALLBACK] === 'true') {
ret.status = 'Succeeded'
}

//set name
const path = getPath(getPureUrl(req.url))
ret = replacePropertyValue('name', path[path.length - 1], ret, (v) => {
return typeof v === 'string' && v.match(/^a+$/) !== null
})
//set name
const path = getPath(getPureUrl(req.url))
ret = replacePropertyValue('name', path[path.length - 1], ret, (v) => {
return typeof v === 'string' && v.match(/^a+$/) !== null
})
}

res.set(code, ret)
}
Expand Down
7 changes: 5 additions & 2 deletions tools/mock-service-host/src/mid/oav/swaggerMocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default class SwaggerMocker {
}
}
// get(list)
if (responses[key]?.body?.value?.length) {
if (Array.isArray(responses[key]?.body?.value) && responses[key]?.body?.value?.length) {
responses[key]?.body?.value?.forEach((item: any) => {
if (item.id) {
const resourceName = item.name || 'resourceName'
Expand Down Expand Up @@ -369,7 +369,10 @@ export default class SwaggerMocker {
example = this.mocker.mock(definitionSpec, objName, arrItem)
} else {
/** type === number or integer */
example = example ? example : this.mocker.mock(definitionSpec, objName)
example =
example && typeof example !== 'object'
? example
: this.mocker.mock(definitionSpec, objName)
}
// return value for primary type: string, number, integer, boolean
// "aaaa"
Expand Down
14 changes: 14 additions & 0 deletions tools/mock-service-host/src/mid/responser.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import * as _ from 'lodash'
import * as fs from 'fs'
import * as path from 'path'
import { AjvSchemaValidator } from 'oav/dist/lib/swaggerValidator/ajvSchemaValidator'
import { Config } from '../common/config'
import { ExampleNotFound, ExampleNotMatch } from '../common/errors'
import { Headers, ParameterType, SWAGGER_ENCODING, useREF } from '../common/constants'
import { JsonLoader } from 'oav/dist/lib/swagger/jsonLoader'
import { LiveRequest } from 'oav/dist/lib/liveValidation/operationValidator'
import { MockerCache, PayloadCache } from 'oav/dist/lib/generator/exampleCache'
import { Operation, SwaggerExample, SwaggerSpec } from 'oav/dist/lib/swagger/swaggerTypes'
import { TransformContext, getTransformContext } from 'oav/dist/lib/transform/context'
import { applyGlobalTransformers, applySpecTransformers } from 'oav/dist/lib/transform/transformer'
import { discriminatorTransformer } from 'oav/dist/lib/transform/discriminatorTransformer'
import { injectable } from 'inversify'
import { inversifyGetInstance } from 'oav/dist/lib/inversifyUtils'
import { isNullOrUndefined } from '../common/utils'
import { resolveNestedDefinitionTransformer } from 'oav/dist/lib/transform/resolveNestedDefinitionTransformer'
import SwaggerMocker from './oav/swaggerMocker'

export interface SwaggerExampleParameter {
Expand All @@ -30,12 +35,18 @@ export class ResponseGenerator {
private mockerCache: MockerCache
private payloadCache: PayloadCache
private swaggerMocker: SwaggerMocker
public readonly transformContext: TransformContext

constructor() {
this.jsonLoader = inversifyGetInstance(JsonLoader, {})
this.mockerCache = new MockerCache()
this.payloadCache = new PayloadCache()
this.swaggerMocker = new SwaggerMocker(this.jsonLoader, this.mockerCache, this.payloadCache)
const schemaValidator = new AjvSchemaValidator(this.jsonLoader)
this.transformContext = getTransformContext(this.jsonLoader, schemaValidator, [
resolveNestedDefinitionTransformer,
discriminatorTransformer
])
}

private getSpecItem(spec: any, operationId: string): SpecItem | undefined {
Expand Down Expand Up @@ -147,6 +158,9 @@ export class ResponseGenerator {
public async generate(operation: Operation, config: Config, liveRequest: LiveRequest) {
const specFile = this.getSpecFileByOperation(operation, config)
const spec = (await (this.jsonLoader.load(specFile) as unknown)) as SwaggerSpec
applySpecTransformers(spec, this.transformContext)
applyGlobalTransformers(this.transformContext)

const specItem = this.getSpecItem(spec, operation.operationId as string)
if (!specItem) {
throw Error(`operation ${operation.operationId} can't be found in ${specFile}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
},
"liveResponse": {
"statusCode": "200",
"headers": {},
"body": {}
"headers": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"liveRequest": {
"headers": {
"strict-Transport-Security": "max-age=31536000; includeSubDomains",
"x-ms-request-id": "8e3485b6-c8a7-45c2-a9f5-59b826e42880",
"x-ms-correlation-request-id": "8e3485b6-c8a7-45c2-a9f5-59b826e42880",
"date": "Tue, 11 Sep 2018 18:45:41 GMT",
"eTag": "\"AAAAAAAAjAIAAAAAAACL/A==\"",
"server": "Microsoft-HTTPAPI/2.0",
"Content-Type": "application/json",
"If-Match": "dddd"
},
"method": "DELETE",
"url": "/subscriptions/randomSub/resourceGroups/randomRG/providers/Microsoft.ApiManagement/service/randomService/users/randomUsers?api-version=2018-01-01",
"query": {
"api-version": "2018-01-01"
}
},
"liveResponse": {
"statusCode": "200",
"headers": {}
}
}
16 changes: 0 additions & 16 deletions tools/mock-service-host/test/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,6 @@ export function mockDefaultResponse(): VirtualServerResponse {
return new VirtualServerResponse('500', createErrorBody(500, 'Default Response'))
}

export function storeAndCompare(
pair: RequestResponsePair,
response: VirtualServerResponse,
path: string
) {
const expected = lodash.cloneDeep(pair)
pair.liveResponse.statusCode = response.statusCode
pair.liveResponse.body = response.body
pair.liveResponse.headers = response.headers

const newFile = path + '.new'
fs.writeFileSync(newFile, JSON.stringify(pair, null, 2)) // save new response for trouble shooting
assert.deepStrictEqual(pair, expected)
fs.unlinkSync(newFile) // remove the new file if pass the assert
}

export function createLiveRequestForCreateRG(sub = 'randomSub', rg = 'randomRG'): LiveRequest {
return {
url: `/subscriptions/${sub}/resourceGroups/${rg}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generateResponse() special rule of GET locations 1`] = `
VirtualServerResponse {
"body": Object {
"id": "/subscriptions/randomSub/resourceGroups/randomRG",
"location": "eastus",
"managedBy": null,
"name": "randomRG",
"properties": Object {
"provisioningState": "Succeeded",
},
"tags": Object {},
"type": "Microsoft.Resources/resourceGroups",
},
"headers": Object {},
"statusCode": "200",
}
`;

exports[`generateResponse() special rule of GET resourceGroup 1`] = `
VirtualServerResponse {
"body": Object {
"id": "/subscriptions/randomSub/resourceGroups/randomRG",
"location": "eastus",
"managedBy": null,
"name": "randomRG",
"properties": Object {
"provisioningState": "Succeeded",
},
"tags": Object {},
"type": "Microsoft.Resources/resourceGroups",
},
"headers": Object {},
"statusCode": "200",
}
`;

exports[`generateResponse() validate DELETE input 1`] = `
VirtualServerResponse {
"body": undefined,
"headers": Object {},
"statusCode": "200",
}
`;
9 changes: 4 additions & 5 deletions tools/mock-service-host/test/unittest/testcoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ import {
createLiveRequestForDeleteApiManagementService,
genFakeResponses,
mockDefaultResponse,
mockRequest,
storeAndCompare
mockRequest
} from '../tools'

const statefulProfile = {
Expand Down Expand Up @@ -102,7 +101,7 @@ describe('generateResponse()', () => {
const response = mockDefaultResponse()
await coordinator.generateResponse(request, response, statelessProfile)
assert.strictEqual(response.statusCode, pair.liveResponse.statusCode)
storeAndCompare(pair, response, fileName)
expect(response).toMatchSnapshot()
pair.liveResponse.body = response.body
pair.liveResponse.headers = response.headers
pair.liveResponse.headers['Content-Type'] = 'application/json'
Expand Down Expand Up @@ -134,7 +133,7 @@ describe('generateResponse()', () => {
const response = mockDefaultResponse()
await coordinator.generateResponse(request, response, statelessProfile)
assert.strictEqual(response.statusCode, pair.liveResponse.statusCode)
storeAndCompare(pair, response, fileName)
expect(response).toMatchSnapshot()
})

it('special rule of GET locations', async () => {
Expand All @@ -150,7 +149,7 @@ describe('generateResponse()', () => {
const response = mockDefaultResponse()
await coordinator.generateResponse(request, response, statelessProfile)
assert.strictEqual(response.statusCode, pair.liveResponse.statusCode)
storeAndCompare(pair, response, fileName)
expect(response).toMatchSnapshot()
if (!pair.liveResponse.headers) pair.liveResponse.headers = {}
const result = await coordinator.Validator.validateLiveRequestResponse(pair)
assert.strictEqual(result.requestValidationResult.isSuccessful, undefined) // since this is a special URI not handled by oav
Expand Down

0 comments on commit 6ad560d

Please sign in to comment.