Test server v2 using a simplified api optimized for a mock api (#251)
This commit is contained in:
Родитель
84ab536f3c
Коммит
da7d2d8b79
|
@ -0,0 +1,31 @@
|
|||
name: Autorest Testserver CI
|
||||
trigger: none
|
||||
pr:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: "ubuntu-latest"
|
||||
|
||||
jobs:
|
||||
- job: main
|
||||
displayName: "Build and test"
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
displayName: "Install Node.js"
|
||||
|
||||
- script: |
|
||||
npm install -g npm
|
||||
npm install
|
||||
displayName: "Install dependencies"
|
||||
|
||||
- script: |
|
||||
npm run build
|
||||
displayName: "Build"
|
||||
|
||||
- script: npm run test:ci
|
||||
displayName: Test
|
||||
|
||||
- script: npm run lint
|
||||
displayName: Lint
|
|
@ -0,0 +1,30 @@
|
|||
root: true
|
||||
plugins:
|
||||
- "@typescript-eslint"
|
||||
- prettier
|
||||
- import
|
||||
- unicorn
|
||||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
node: true
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:@typescript-eslint/recommended
|
||||
rules:
|
||||
no-console: warn
|
||||
|
||||
"@typescript-eslint/no-empty-interface": off
|
||||
"@typescript-eslint/no-unused-vars": off
|
||||
import/no-default-export: warn
|
||||
import/no-self-import: warn # This cause many circular dependency issues with index files.
|
||||
import/no-internal-modules: off
|
||||
import/order:
|
||||
- warn
|
||||
- groups: ["builtin", "external", "parent", "sibling", "index"]
|
||||
alphabetize:
|
||||
order: "asc"
|
||||
caseInsensitive: true
|
||||
unicorn/filename-case: warn
|
||||
prettier/prettier: warn
|
||||
parser: "@typescript-eslint/parser"
|
|
@ -291,3 +291,6 @@ package-lock.json
|
|||
*.tgz
|
||||
legacy/coverage/report-*.json
|
||||
__files/
|
||||
|
||||
dist/
|
||||
coverage/
|
|
@ -0,0 +1,5 @@
|
|||
trailingComma: "all"
|
||||
printWidth: 120
|
||||
quoteProps: "consistent"
|
||||
endOfLine: "auto"
|
||||
arrowParens: always
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
const { existsSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
const { execute, queryUser, httpPost } = require('./process');
|
||||
const { yellow, red, green, white, gray, cyan} = require('chalk');
|
||||
const { execute, queryUser } = require('./process');
|
||||
const { yellow, gray, cyan} = require('chalk');
|
||||
|
||||
function prepend( color,pText ,text) {
|
||||
return text.replace( /^/gm, `${color(pText)} `)
|
||||
|
@ -22,7 +20,7 @@ function prepend( color,pText ,text) {
|
|||
let wrappedExitCode = 0;
|
||||
|
||||
async function main() {
|
||||
|
||||
console.log(yellow("WARNING: start-autorest-express is deprecated use autorest-testserver instead."));
|
||||
const cmdArgs = [];
|
||||
const switches = [];
|
||||
let switchChecking = true;
|
||||
|
@ -53,7 +51,7 @@ async function main() {
|
|||
})
|
||||
|
||||
try {
|
||||
await execute(process.execPath, [`${__dirname}/../legacy/startup/shutdown.js`]);
|
||||
await execute(process.execPath, [`${__dirname}/../dist/cli/cli.js`, "stop"]);
|
||||
verbose('Shutting down existing Express instance.')
|
||||
} catch (e) {
|
||||
verbose('Express was not running previously.')
|
||||
|
@ -62,33 +60,33 @@ async function main() {
|
|||
|
||||
// start the express process
|
||||
verbose('Starting Express Server.')
|
||||
const spResult = execute(process.execPath, [`${__dirname}/../legacy/startup/www.js`], {
|
||||
const spResult = execute(process.execPath, [`${__dirname}/../dist/cli/cli.js`], {
|
||||
onCreate: (proc) => {
|
||||
serverProc = proc;
|
||||
proc.on('close', ()=>{
|
||||
proc.on("close", () => {
|
||||
if (cmdProc && cmdProc.status === null) {
|
||||
cmdProc.kill();
|
||||
}
|
||||
if (interactive) {
|
||||
process.exit(0);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
onStdOutData: (chunk) => {
|
||||
const c = chunk.toString().replace(/\s*$/, '');
|
||||
const c = chunk.toString().replace(/\s*$/, "");
|
||||
if (showMessages) {
|
||||
console.log(prepend( gray, '[Express]', c));
|
||||
console.log(prepend(gray, "[Express]", c));
|
||||
}
|
||||
if (/Server started/.exec(c)) {
|
||||
spResolve();
|
||||
}
|
||||
},
|
||||
onStdErrData: (chunk) => {
|
||||
const c = chunk.toString().replace(/\s*$/, '');
|
||||
const c = chunk.toString().replace(/\s*$/, "");
|
||||
if (showMessages) {
|
||||
console.log(prepend( yellow.dim, '[Express]', c ));
|
||||
}
|
||||
console.log(prepend(yellow.dim, "[Express]", c));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// when it's ready, run the command line
|
||||
|
@ -125,7 +123,7 @@ async function main() {
|
|||
// shutdown server process
|
||||
verbose('Shutting down Express.');
|
||||
try {
|
||||
await execute(process.execPath, [`${__dirname}/../legacy/startup/shutdown.js`]);
|
||||
await execute(process.execPath, [`${__dirname}/../dist/cli/cli.js`, "stop"]);
|
||||
verbose('Shutting down existing Express instance.')
|
||||
} catch (e) {
|
||||
// who cares.
|
||||
|
|
|
@ -29,6 +29,8 @@ function getEntrypoint() {
|
|||
}
|
||||
|
||||
async function main() {
|
||||
console.log(yellow("WARNING: start-autorest-wiremock is deprecated use autorest-testserver instead."));
|
||||
|
||||
const wmFolderPath = getEntrypoint();
|
||||
|
||||
const cmdArgs = [];
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Start Server",
|
||||
"program": "${workspaceRoot}/legacy/startup/www.js",
|
||||
"env": {
|
||||
"PORT": "3000"
|
||||
}
|
||||
"name": "Run server",
|
||||
"program": "${workspaceFolder}/src/cli/cli.ts",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"dist": true
|
||||
}
|
||||
}
|
29
README.md
29
README.md
|
@ -1,20 +1,23 @@
|
|||
# Test Server
|
||||
# Test server V2
|
||||
|
||||
This project contains a set of OpenAPI definitions and a server implementing the corresponding API.
|
||||
Use this to test compliance of AutoRest generators.
|
||||
## Requirements
|
||||
|
||||
# Contributing
|
||||
- `Node.js`
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
## Getting started
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
# Start for dev: Will start the server and automatically restart in case there is changes in the files.
|
||||
npm run start:dev
|
||||
|
||||
# Start for running: Will build and start the server
|
||||
npm run start
|
||||
```
|
||||
|
||||
|
||||
## Writing mock apis
|
||||
|
||||
See [docs](./docs/writing-mock-apis)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Yargs is missing helpers definitions.
|
||||
*/
|
||||
declare module "yargs/helpers" {
|
||||
export const hideBin: (argv: string[]) => string[];
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# Writing mock apis
|
||||
|
||||
1. First step is to create a new file typescript file in the [src/test-routes](../src/test-routes) folder:
|
||||
1. All the needed imports are from the `api` folder
|
||||
1. Define the category for your apis using `app.category("vanilla" | "azure" | "optional", () => {})`
|
||||
1. Start writing mock apis inside the `category` callback
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { app, json } from "../../api";
|
||||
|
||||
app.category("vanilla", () => {
|
||||
app.get("/test", "GetMyTest", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
foo: "succeeded",
|
||||
bar: "wut",
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/test", "PostMyTest", (req) => {
|
||||
req.bodyEquals({ foo: "123", bar: "456" });
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
succeeded: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## How to build response
|
||||
|
||||
Return the reponse object. [See type](../src/api/mock-response.ts)
|
||||
|
||||
```ts
|
||||
// Minimum requirement is the status code.
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
```
|
||||
|
||||
### Return a body
|
||||
|
||||
```ts
|
||||
// Return json
|
||||
return {
|
||||
status: 200,
|
||||
body: json({ foo: 123 }),
|
||||
};
|
||||
|
||||
// Return raw content
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/text",
|
||||
rawContent: "foobar",
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Return headers
|
||||
|
||||
```ts
|
||||
// Return json
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
MyHeader: "value-1"
|
||||
MyHeaderOther: req.headers.MyRequestHeader
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## How to validate the request:
|
||||
|
||||
### Validate the body
|
||||
|
||||
- With `req.bodyEquals`
|
||||
|
||||
This will do a deep equals of the body to make sure it match.
|
||||
|
||||
```ts
|
||||
app.post("/example", "Example", (req) => {
|
||||
req.bodyEquals({ foo: "123", bar: "456" });
|
||||
});
|
||||
```
|
||||
|
||||
- With `req.rawBodyEquals`
|
||||
|
||||
This will compare the raw body sent.
|
||||
|
||||
```ts
|
||||
app.post("/example", "Example", (req) => {
|
||||
req.rawBodyEquals('"foo"');
|
||||
});
|
||||
```
|
||||
|
||||
### Custom validation
|
||||
|
||||
You can do any kind of validation accessing the `req: MockRequest` object and deciding to return a different response in some cases.
|
||||
You can also always `throw` a `ValidationError`
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
app.post("/example", "Example", (req) => {
|
||||
if (req.headers.MyCustomHeader.startsWith("x-foo")) {
|
||||
throw new ValidationError("MyCustomHeader shouldn't start with x-foo", null, req.headers.MyCustomHeader);
|
||||
}
|
||||
});
|
||||
```
|
|
@ -0,0 +1,25 @@
|
|||
// @ts-check
|
||||
|
||||
const config = {
|
||||
transform: {
|
||||
"^.+\\.ts$": "ts-jest",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js", "json", "node"],
|
||||
moduleNameMapper: {},
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ["**/*.ts", "!**/node_modules/**", "!dist/**"],
|
||||
coverageReporters: ["json", "lcov", "cobertura", "text", "html", "clover"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/", ".*/test/.*"],
|
||||
modulePathIgnorePatterns: ["<rootDir>/sdk"],
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsconfig: "tsconfig.json",
|
||||
},
|
||||
},
|
||||
testMatch: ["<rootDir>/src/**/*.test.ts", "<rootDir>/test/**/*.test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/test/setup-jest.ts"],
|
||||
verbose: true,
|
||||
testEnvironment: "node",
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -3,6 +3,8 @@ var router = express.Router();
|
|||
var util = require('util');
|
||||
var utils = require('../util/utils');
|
||||
|
||||
const getRequestBaseUrl = (request) => `${request.protocol}://${request.get("host")}`;
|
||||
|
||||
var getLRORetryPutScenarioName = function (initialCode, initialState, finalState, finalCode) {
|
||||
if (initialCode === 201 && initialState === 'Creating' && finalState === 'Succeeded' && finalCode === 200) {
|
||||
return 'LRORetryPutSucceededWithBody';
|
||||
|
@ -64,7 +66,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPut202Retry200'] = 0;
|
||||
router.put('/put/202/retry/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/put/202/retry/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/put/202/retry/operationResults/200';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -152,7 +154,7 @@ var lros = function (coverage) {
|
|||
|
||||
var scenario = getLROAsyncScenarioName("putasync", retry, finalState);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -189,7 +191,7 @@ var lros = function (coverage) {
|
|||
var scenario = getLROAsyncScenarioName(operation, retry, finalState);
|
||||
console.log('In scenario: ' + scenario + '\n');
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -212,7 +214,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutNoHeaderInRetry'] = 0;
|
||||
router.put('/put/noheader/202/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/put/noheader/operationresults';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/put/noheader/operationresults';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -234,7 +236,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutAsyncNoHeaderInRetry'] = 0;
|
||||
router.put('/putasync/noheader/201/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putasync/noheader/operationresults/123';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putasync/noheader/operationresults/123';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'somethingBadWhichShouldNotBeUsed'
|
||||
|
@ -261,7 +263,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRODeleteNoHeaderInRetry'] = 0;
|
||||
router.delete('/delete/noheader', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/delete/noheader/operationresults/123';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/delete/noheader/operationresults/123';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -283,7 +285,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRODeleteAsyncNoHeaderInRetry'] = 0;
|
||||
router.delete('/deleteasync/noheader/202/204', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/deleteasync/noheader/operationresults/123';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/deleteasync/noheader/operationresults/123';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'somethingBadWhichShouldNotBeUsed'
|
||||
|
@ -306,7 +308,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutSubResourceInRetry'] = 0;
|
||||
router.put('/putsubresource/202/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putsubresource/operationresults';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putsubresource/operationresults';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -328,7 +330,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutSubResourceAsyncInRetry'] = 0;
|
||||
router.put('/putsubresourceasync/202/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putsubresourceasync/operationresults/123';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putsubresourceasync/operationresults/123';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'somethingBadWhichShouldNotBeUsed'
|
||||
|
@ -355,7 +357,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutNonResourceInRetry'] = 0;
|
||||
router.put('/putnonresource/202/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putnonresource/operationresults';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putnonresource/operationresults';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -377,7 +379,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPutNonResourceAsyncInRetry'] = 0;
|
||||
router.put('/putnonresourceasync/202/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/putnonresourceasync/operationresults/123';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/putnonresourceasync/operationresults/123';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'somethingBadWhichShouldNotBeUsed'
|
||||
|
@ -425,7 +427,7 @@ var lros = function (coverage) {
|
|||
var finalCode = JSON.parse(req.params.finalCode);
|
||||
var scenario = getLRODeleteProvisioningScenarioName(initialCode, initialState, finalCode, finalState);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/delete/provisioning/' + initialCode + '/' + initialState.toLowerCase() + '/' + finalCode + '/' + finalState.toLowerCase();
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/delete/provisioning/' + initialCode + '/' + initialState.toLowerCase() + '/' + finalCode + '/' + finalState.toLowerCase();
|
||||
var headers = {
|
||||
'Location': pollingUri,
|
||||
'Retry-After': 0
|
||||
|
@ -475,7 +477,7 @@ var lros = function (coverage) {
|
|||
var finalCode = JSON.parse(req.params.finalCode);
|
||||
var scenario = getLRODeleteScenarioName(initialCode, retry, finalCode);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/delete/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/delete/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -511,7 +513,7 @@ var lros = function (coverage) {
|
|||
|
||||
var scenario = getLROAsyncScenarioName("deleteasync", retry, finalState);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/deleteasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/deleteasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -536,7 +538,7 @@ var lros = function (coverage) {
|
|||
var scenario = getLROAsyncScenarioName(operation, retry, finalState);
|
||||
console.log('In scenario: ' + scenario + '\n');
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -574,7 +576,7 @@ var lros = function (coverage) {
|
|||
var finalCode = JSON.parse(req.params.finalCode);
|
||||
var scenario = getLROPostScenarioName(initialCode, retry, finalCode);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/post/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/post/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -594,7 +596,7 @@ var lros = function (coverage) {
|
|||
var finalCode = JSON.parse(req.params.finalCode);
|
||||
var scenario = getLROPostScenarioName(initialCode, retry, finalCode);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/post/newuri/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/post/newuri/' + initialCode + '/' + retry + '/' + finalCode;
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -627,7 +629,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROPost200'] = 0;
|
||||
router.post('/post/payload/200', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/post/payload/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/post/payload/200';
|
||||
var headers = {
|
||||
'Location': pollingUri,
|
||||
'Retry-After': 0
|
||||
|
@ -642,27 +644,6 @@ var lros = function (coverage) {
|
|||
res.status(200).type('json').end('{"id":"1", "name":"product"}');
|
||||
});
|
||||
|
||||
// Initial call is 202 with no body and Location and Azure-AsyncOperation
|
||||
// Configured to follow Location
|
||||
// Then, should poll Azure-AsyncOperation and see it's done
|
||||
// Then, should do final GET on the initial Location
|
||||
// ARM guidance ok, and implemented in VM capture after 2018-04-01
|
||||
coverage['LROPostDoubleHeadersFinalLocationGet'] = 0;
|
||||
router.post('/LROPostDoubleHeadersFinalLocationGet', function (req, res, next) {
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl',
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalLocationGet/location'
|
||||
};
|
||||
res.set(headers).status(202).end('');
|
||||
});
|
||||
router.get('/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl', function (req, res, next) {
|
||||
res.status(200).type('json').end('{ "status": "succeeded"} ');
|
||||
});
|
||||
router.get('/LROPostDoubleHeadersFinalLocationGet/location', function (req, res, next) {
|
||||
res.status(200).type('json').end('{ "id": "100", "name": "foo" }');
|
||||
coverage['LROPostDoubleHeadersFinalLocationGet']++;
|
||||
});
|
||||
|
||||
// Initial call is 202 with no body and Location and Azure-AsyncOperation
|
||||
// Configured to follow Azure-AsyncOperation
|
||||
// Then, should poll Azure-AsyncOperation and see it's done
|
||||
|
@ -670,8 +651,8 @@ var lros = function (coverage) {
|
|||
coverage['LROPostDoubleHeadersFinalAzureHeaderGet'] = 0;
|
||||
router.post('/LROPostDoubleHeadersFinalAzureHeaderGet', function (req, res, next) {
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalAzureHeaderGet/asyncOperationUrl',
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalAzureHeaderGet/location'
|
||||
'Azure-AsyncOperation': getRequestBaseUrl(req) + '/lro/LROPostDoubleHeadersFinalAzureHeaderGet/asyncOperationUrl',
|
||||
'Location': getRequestBaseUrl(req) + '/lro/LROPostDoubleHeadersFinalAzureHeaderGet/location'
|
||||
};
|
||||
res.set(headers).status(202).end('');
|
||||
});
|
||||
|
@ -691,8 +672,8 @@ var lros = function (coverage) {
|
|||
coverage['LROPostDoubleHeadersFinalAzureHeaderGetDefault'] = 0;
|
||||
router.post('/LROPostDoubleHeadersFinalAzureHeaderGetDefault', function (req, res, next) {
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalAzureHeaderGetDefault/asyncOperationUrl',
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/LROPostDoubleHeadersFinalAzureHeaderGetDefault/location'
|
||||
'Azure-AsyncOperation': getRequestBaseUrl(req) + '/lro/LROPostDoubleHeadersFinalAzureHeaderGetDefault/asyncOperationUrl',
|
||||
'Location': getRequestBaseUrl(req) + '/lro/LROPostDoubleHeadersFinalAzureHeaderGetDefault/location'
|
||||
};
|
||||
res.set(headers).status(202).end('');
|
||||
});
|
||||
|
@ -707,8 +688,8 @@ var lros = function (coverage) {
|
|||
coverage['LROPostAndGetList'] = 0;
|
||||
router.post('/list', function (req, res, next) {
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': 'http://localhost:' + utils.getPort() + '/lro/list/pollingGet',
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/list/finalGet'
|
||||
'Azure-AsyncOperation': getRequestBaseUrl(req) + '/lro/list/pollingGet',
|
||||
'Location': getRequestBaseUrl(req) + '/lro/list/finalGet'
|
||||
};
|
||||
res.set(headers).status(202).end();
|
||||
});
|
||||
|
@ -732,10 +713,10 @@ var lros = function (coverage) {
|
|||
|
||||
var scenario = getLROAsyncScenarioName("postasync", retry, finalState);
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/postasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/postasync/' + retry + '/' + finalState.toLowerCase() + '/operationResults/200/';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/postasync/' + retry + '/Succeeded/operationResults/foo/200/'
|
||||
'Location': getRequestBaseUrl(req) + '/lro/postasync/' + retry + '/Succeeded/operationResults/foo/200/'
|
||||
};
|
||||
if (retry === 'retry') {
|
||||
headers['Retry-After'] = 0;
|
||||
|
@ -757,10 +738,10 @@ var lros = function (coverage) {
|
|||
var scenario = getLROAsyncScenarioName(operation, retry, finalState);
|
||||
console.log('In scenario: ' + scenario + '\n');
|
||||
if (scenario) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/' + operation + '/' + retry + '/' + finalState.toLowerCase() + '/operationResults/' + code;
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/postasync/' + retry + '/Succeeded/operationResults/foo/200/'
|
||||
'Location': getRequestBaseUrl(req) + '/lro/postasync/' + retry + '/Succeeded/operationResults/foo/200/'
|
||||
};
|
||||
if (retry === 'retry') {
|
||||
headers['Retry-After'] = 0;
|
||||
|
@ -848,7 +829,7 @@ var lros = function (coverage) {
|
|||
coverage['LRORetryErrorPutAsyncSucceededPolling'] = 0;
|
||||
router.put('/retryerror/putasync/retry/succeeded', function (req, res, next) {
|
||||
var scenario = 'LRORetryErrorPutAsyncSucceeded';
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/putasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/putasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -894,7 +875,7 @@ var lros = function (coverage) {
|
|||
addScenarioCookie(res, scenario);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/delete/provisioning/202/accepted/200/succeeded';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/delete/provisioning/202/accepted/200/succeeded';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -923,7 +904,7 @@ var lros = function (coverage) {
|
|||
addScenarioCookie(res, scenario);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/delete/202/retry/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/delete/202/retry/200';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -952,9 +933,9 @@ var lros = function (coverage) {
|
|||
addScenarioCookie(res, scenario);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/deleteasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/deleteasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/retryerror/deleteasync/retry/succeeded/operationResults/foo/200',
|
||||
'Location': getRequestBaseUrl(req) + '/lro/retryerror/deleteasync/retry/succeeded/operationResults/foo/200',
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Retry-After': 0
|
||||
};
|
||||
|
@ -995,7 +976,7 @@ var lros = function (coverage) {
|
|||
addScenarioCookie(res, scenario);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/post/202/retry/200/operationResults';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/post/202/retry/200/operationResults';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -1024,10 +1005,10 @@ var lros = function (coverage) {
|
|||
addScenarioCookie(res, scenario);
|
||||
res.status(500).end();
|
||||
} else {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/retryerror/postasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/retryerror/postasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
/*Passing absolute uri */
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/lro/retryerror/postasync/retry/succeeded/operationResults/foo/200',
|
||||
'Location': getRequestBaseUrl(req) + '/lro/retryerror/postasync/retry/succeeded/operationResults/foo/200',
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Retry-After': 0
|
||||
};
|
||||
|
@ -1090,7 +1071,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRONonRetryPutAsyncRetry400'] = 0;
|
||||
router.put('/nonretryerror/putasync/retry/400', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/nonretryerror/putasync/retry/failed/operationResults/400';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/nonretryerror/putasync/retry/failed/operationResults/400';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1112,7 +1093,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRONonRetryDelete202Retry400'] = 0;
|
||||
router.delete('/nonretryerror/delete/202/retry/400', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/nonretryerror/delete/202/retry/400';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/nonretryerror/delete/202/retry/400';
|
||||
var headers = {
|
||||
'Location': pollingUri,
|
||||
'Retry-After': 0
|
||||
|
@ -1127,7 +1108,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRONonRetryDeleteAsyncRetry400'] = 0;
|
||||
router.delete('/nonretryerror/deleteasync/retry/400', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/nonretryerror/deleteasync/retry/failed/operationResults/400';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/nonretryerror/deleteasync/retry/failed/operationResults/400';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1149,7 +1130,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRONonRetryPost202Retry400'] = 0;
|
||||
router.post('/nonretryerror/post/202/retry/400', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/nonretryerror/post/202/retry/400';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/nonretryerror/post/202/retry/400';
|
||||
var headers = {
|
||||
'Location': pollingUri,
|
||||
'Retry-After': 0
|
||||
|
@ -1164,7 +1145,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LRONonRetryPostAsyncRetry400'] = 0;
|
||||
router.post('/nonretryerror/postasync/retry/400', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/nonretryerror/postasync/retry/failed/operationResults/400';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/nonretryerror/postasync/retry/failed/operationResults/400';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1191,7 +1172,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorPutAsyncNoPollingStatus'] = 0;
|
||||
router.put('/error/putasync/retry/nostatus', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/putasync/retry/failed/operationResults/nostatus';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/putasync/retry/failed/operationResults/nostatus';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1211,7 +1192,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorPutAsyncNoPollingStatusPayload'] = 0;
|
||||
router.put('/error/putasync/retry/nostatuspayload', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/putasync/retry/failed/operationResults/nostatuspayload';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/putasync/retry/failed/operationResults/nostatuspayload';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1253,7 +1234,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorPutAsyncInvalidJsonPolling'] = 0;
|
||||
router.put('/error/putasync/retry/invalidjsonpolling', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/putasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/putasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1286,7 +1267,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorDeleteAsyncNoPollingStatus'] = 0;
|
||||
router.delete('/error/deleteasync/retry/nostatus', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/deleteasync/retry/failed/operationResults/nostatus';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/deleteasync/retry/failed/operationResults/nostatus';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1314,7 +1295,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorDeleteAsyncInvalidJsonPolling'] = 0;
|
||||
router.delete('/error/deleteasync/retry/invalidjsonpolling', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/deleteasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/deleteasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1347,7 +1328,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorPostAsyncNoPollingPayload'] = 0;
|
||||
router.post('/error/postasync/retry/nopayload', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/postasync/retry/failed/operationResults/nopayload';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/postasync/retry/failed/operationResults/nopayload';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1375,7 +1356,7 @@ var lros = function (coverage) {
|
|||
|
||||
coverage['LROErrorPostAsyncInvalidJsonPolling'] = 0;
|
||||
router.post('/error/postasync/retry/invalidjsonpolling', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/error/postasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/error/postasync/retry/failed/operationResults/invalidjsonpolling';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri,
|
||||
|
@ -1392,7 +1373,7 @@ var lros = function (coverage) {
|
|||
router.put('/customheader/putasync/retry/succeeded', function (req, res, next) {
|
||||
var header = req.get("x-ms-client-request-id");
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/putasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/putasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -1418,7 +1399,7 @@ var lros = function (coverage) {
|
|||
var header = req.get("x-ms-client-request-id");
|
||||
var scenario = 'CustomHeaderPutAsyncSucceded';
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/putasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/putasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': pollingUri
|
||||
|
@ -1440,7 +1421,7 @@ var lros = function (coverage) {
|
|||
router.post('/customheader/postasync/retry/succeeded', function (req, res, next) {
|
||||
var header = req.get("x-ms-client-request-id");
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/postasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/postasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri
|
||||
};
|
||||
|
@ -1455,7 +1436,7 @@ var lros = function (coverage) {
|
|||
var header = req.get("x-ms-client-request-id");
|
||||
var scenario = 'CustomHeaderPostAsyncSucceded';
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/postasync/retry/succeeded/operationResults/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/postasync/retry/succeeded/operationResults/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri
|
||||
};
|
||||
|
@ -1499,7 +1480,7 @@ var lros = function (coverage) {
|
|||
var scenario = 'CustomHeaderPostSucceeded';
|
||||
var header = req.get("x-ms-client-request-id");
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/post/202/retry/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/post/202/retry/200';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
@ -1514,7 +1495,7 @@ var lros = function (coverage) {
|
|||
var scenario = 'CustomHeaderPostSucceeded';
|
||||
var header = req.get("x-ms-client-request-id");
|
||||
if (header && header.toLowerCase() === "9C4D50EE-2D56-4CD3-8152-34347DC9F2B0".toLowerCase()) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/lro/customheader/post/newuri/202/retry/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/lro/customheader/post/newuri/202/retry/200';
|
||||
var headers = {
|
||||
'Location': pollingUri
|
||||
};
|
||||
|
|
|
@ -3,6 +3,8 @@ var router = express.Router();
|
|||
var util = require('util');
|
||||
var utils = require('../util/utils');
|
||||
|
||||
const getRequestBaseUrl = (request) => `${request.protocol}://${request.get("host")}`;
|
||||
|
||||
var hasScenarioCookie = function(req, scenario) {
|
||||
var cookies = req.headers['cookie'];
|
||||
var cookieMatch;
|
||||
|
@ -55,7 +57,7 @@ var paging = function(coverage) {
|
|||
|
||||
router.get('/nullnextlink', function(req, res, next) {
|
||||
coverage["PagingNextLinkNameNull"]++;
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":"http://localhost:" + utils.getPort() + "/paging/idontexistraise404" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":getRequestBaseUrl(req) + "/paging/idontexistraise404" });
|
||||
});
|
||||
|
||||
router.get('/single', function(req, res, next) {
|
||||
|
@ -66,13 +68,13 @@ var paging = function(coverage) {
|
|||
router.get('/multiple', function(req, res, next) {
|
||||
|
||||
coverage["PagingMultiple"]++;
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":"http://localhost:" + utils.getPort() + "/paging/multiple/page/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":getRequestBaseUrl(req) + "/paging/multiple/page/2" });
|
||||
});
|
||||
|
||||
router.get('/multiple/getWithQueryParams', function(req, res, next) {
|
||||
// No coverage added here, gets added in next operation nextOperationWithQueryParams
|
||||
if (req.query['requiredQueryParameter'] == '100' && req.query['queryConstant'] == 'true') {
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":"http://localhost:" + utils.getPort() + "/paging/multiple/nextOperationWithQueryParams" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":getRequestBaseUrl(req) + "/paging/multiple/nextOperationWithQueryParams" });
|
||||
}
|
||||
else{
|
||||
utils.send400(res, next, 'The query parameters to getWithQueryParams were not passed correctly');
|
||||
|
@ -91,7 +93,7 @@ var paging = function(coverage) {
|
|||
|
||||
router.get('/multiple/page/:pagenumber', function(req, res, next) {
|
||||
if (req.params.pagenumber < 10) {
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/page/" + (++req.params.pagenumber) });
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/page/" + (++req.params.pagenumber) });
|
||||
} else {
|
||||
res.status(200).json({"values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ]});
|
||||
}
|
||||
|
@ -100,12 +102,12 @@ var paging = function(coverage) {
|
|||
router.get('/multiple/odata', function(req, res, next) {
|
||||
|
||||
coverage["PagingOdataMultiple"]++;
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "odata.nextLink":"http://localhost:" + utils.getPort() + "/paging/multiple/odata/page/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "odata.nextLink":getRequestBaseUrl(req) + "/paging/multiple/odata/page/2" });
|
||||
});
|
||||
|
||||
router.get('/multiple/odata/page/:pagenumber', function(req, res, next) {
|
||||
if (req.params.pagenumber < 10) {
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "odata.nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/odata/page/" + (++req.params.pagenumber) });
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "odata.nextLink": getRequestBaseUrl(req) + "/paging/multiple/odata/page/" + (++req.params.pagenumber) });
|
||||
} else {
|
||||
res.status(200).json({"values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ]});
|
||||
}
|
||||
|
@ -114,12 +116,12 @@ var paging = function(coverage) {
|
|||
router.get('/multiple/withpath/:offset', function(req, res, next) {
|
||||
|
||||
coverage["PagingMultiplePath"]++;
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":"http://localhost:" + utils.getPort() + "/paging/multiple/withpath/page/" + req.params.offset + "/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink":getRequestBaseUrl(req) + "/paging/multiple/withpath/page/" + req.params.offset + "/2" });
|
||||
});
|
||||
|
||||
router.get('/multiple/withpath/page/:offset/:pagenumber', function(req, res, next) {
|
||||
if (req.params.pagenumber < 10) {
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber) + parseInt(req.params.offset), "name": "product"}} ], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/withpath/page/" + req.params.offset + "/" + ++req.params.pagenumber});
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber) + parseInt(req.params.offset), "name": "product"}} ], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/withpath/page/" + req.params.offset + "/" + ++req.params.pagenumber});
|
||||
} else {
|
||||
res.status(200).json({"values": [ {"properties":{"id" : parseInt(req.params.pagenumber) + parseInt(req.params.offset), "name": "product"}} ]});
|
||||
}
|
||||
|
@ -133,12 +135,12 @@ var paging = function(coverage) {
|
|||
} else {
|
||||
coverage[scenario]++;
|
||||
removeScenarioCookie(res);
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/page/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/page/2" });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/multiple/retrysecond', function(req, res, next) {
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/retrysecond/page/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/retrysecond/page/2" });
|
||||
});
|
||||
|
||||
router.get('/multiple/retrysecond/page/:pagenumber', function(req, res, next) {
|
||||
|
@ -150,10 +152,10 @@ var paging = function(coverage) {
|
|||
} else {
|
||||
coverage[scenario]++;
|
||||
removeScenarioCookie(res);
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/retrysecond/page/3" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/retrysecond/page/3" });
|
||||
}
|
||||
} else if (req.params.pagenumber < 10) {
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/retrysecond/page/" + (++req.params.pagenumber)});
|
||||
res.status(200).json({ "values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/retrysecond/page/" + (++req.params.pagenumber)});
|
||||
} else {
|
||||
res.status(200).json({"values": [ {"properties":{"id" : parseInt(req.params.pagenumber), "name": "product"}} ]});
|
||||
}
|
||||
|
@ -221,10 +223,10 @@ var paging = function(coverage) {
|
|||
|
||||
/*** PAGEABLE LROs ***/
|
||||
router.post('/multiple/lro', function (req, res, next) {
|
||||
var pollingUri = 'http://localhost:' + utils.getPort() + '/paging/multiple/lro/200';
|
||||
var pollingUri = getRequestBaseUrl(req) + '/paging/multiple/lro/200';
|
||||
var headers = {
|
||||
'Azure-AsyncOperation': pollingUri,
|
||||
'Location': 'http://localhost:' + utils.getPort() + '/paging/multiple',
|
||||
'Location': getRequestBaseUrl(req) + '/paging/multiple',
|
||||
'Retry-After': 0
|
||||
};
|
||||
res.set(headers).status(202).json({ "status": "Accepted"});
|
||||
|
@ -243,7 +245,7 @@ var paging = function(coverage) {
|
|||
|
||||
router.get('/multiple/failure', function(req, res, next) {
|
||||
coverage["PagingMultipleFailure"]++;
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": "http://localhost:" + utils.getPort() + "/paging/multiple/failure/page/2" });
|
||||
res.status(200).json({ "values" : [ {"properties":{"id": 1, "name": "Product" }}], "nextLink": getRequestBaseUrl(req) + "/paging/multiple/failure/page/2" });
|
||||
});
|
||||
|
||||
router.get('/multiple/failure/page/:pagenumber', function(req, res, next) {
|
||||
|
|
|
@ -45,7 +45,7 @@ const report = function(coverage, azureCoverage, optionalCoverage) {
|
|||
Object.getOwnPropertyNames(coverage).forEach(function(val, idx, array) {
|
||||
optionalCoverage[val] = 0;
|
||||
});
|
||||
res.status(200).send().end();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var io = require('socket.io-client');
|
||||
var Constants = require('../util/constants')
|
||||
|
||||
/**
|
||||
* Get HTTP server port and socket client.
|
||||
*/
|
||||
var port = process.env.PORT || Constants.DEFAULT_SERVER_PORT;
|
||||
var socketClient = io.connect('http://localhost:' + port);
|
||||
|
||||
/**
|
||||
* Set a timeout so if we can't connect to the socket self-terminate.
|
||||
*/
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 3000);
|
||||
|
||||
/**
|
||||
* Send the npmStop signal to the server process.
|
||||
*/
|
||||
socketClient.on('connect', () => {
|
||||
socketClient.emit('npmStop');
|
||||
});
|
|
@ -1,105 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var utils = require('../util/utils.js');
|
||||
var debug = require('debug')('server:server');
|
||||
var http = require('http');
|
||||
var Constants = require('../util/constants')
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || Constants.DEFAULT_SERVER_PORT);
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Listen on a socket for the npmStop signal, this will terminate the HTTP server.
|
||||
*/
|
||||
let io = require('socket.io')(server);
|
||||
io.on('connection', (socketServer) => {
|
||||
socketServer.on('npmStop', () => {
|
||||
console.log('Received npmStop signal, server will exit');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
console.log('Server started at port ' + addr.port);
|
||||
utils.setPort(addr.port);
|
||||
}
|
|
@ -22,15 +22,6 @@ exports.coerceDate = function(targetObject) {
|
|||
return JSON.parse(stringRep);
|
||||
};
|
||||
|
||||
exports.setPort = function(port)
|
||||
{
|
||||
serverPort = port;
|
||||
}
|
||||
|
||||
exports.getPort = function () {
|
||||
return serverPort;
|
||||
}
|
||||
|
||||
exports.toPascalCase = function(input) {
|
||||
return '' + input.charAt(0).toUpperCase() + input.slice(1);
|
||||
}
|
93
package.json
93
package.json
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"name": "@microsoft.azure/autorest.testserver",
|
||||
"version": "2.10.68",
|
||||
"main": "./legacy/startup/www.js",
|
||||
"version": "3.0.0",
|
||||
"description": "Autorest test server.",
|
||||
"main": "dist/cli/cli.js",
|
||||
"bin": {
|
||||
"autorest-testserver": "./dist/cli/cli.js",
|
||||
"start-autorest-express": "./.scripts/start-autorest-express.js",
|
||||
"start-autorest-wiremock": "./.scripts/start-autorest-wiremock.js"
|
||||
},
|
||||
|
@ -13,40 +15,73 @@
|
|||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "node ./legacy/startup/www",
|
||||
"stop": "node ./legacy/startup/shutdown",
|
||||
"testci": "echo no tests",
|
||||
"publish-preview": "npm publish",
|
||||
"dev": "npm run watch",
|
||||
"start": "npm run build && node ./dist/cli/cli.js",
|
||||
"start:prod": "node ./dist/cli/cli.js",
|
||||
"start:dev": "concurrently \"npm run watch\" \"nodemon ./dist/cli/cli.js\"",
|
||||
"stop": "node ./dist/cli/cli.js stop",
|
||||
"fix": "eslint . --fix --ext .ts",
|
||||
"lint": "eslint ./src --ext .ts --max-warnings=0",
|
||||
"test": "jest --watchAll --coverage=false",
|
||||
"test:ci": "jest --ci",
|
||||
"watch": "tsc -p ./tsconfig.build.json --watch",
|
||||
"build": "tsc -p ./tsconfig.build.json",
|
||||
"clean": "rimraf dist/",
|
||||
"coverage-push": "node ./legacy/coverage/app-immediate-push",
|
||||
"coverage-show": "node ./legacy/coverage/app-show",
|
||||
"show-results": "node ./legacy/coverage/app-show",
|
||||
"build": "rimraf ./__files && cpy ./swagger/* ./__files && cd __files && shx ls *.json > files.txt",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpy-cli": "~2.0.0",
|
||||
"mkdirp": "~0.5.1",
|
||||
"rimraf": "~3.0.0",
|
||||
"shx": "0.3.2"
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/commonmark": "^0.27.0",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/express": "~4.17.9",
|
||||
"@types/express-promise-router": "^3.0.0",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/js-yaml": "~4.0.0",
|
||||
"@types/morgan": "^1.9.2",
|
||||
"@types/mustache": "^4.1.0",
|
||||
"@types/node": "~14.14.20",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"@types/xml2js": "^0.4.7",
|
||||
"@types/yargs": "^15.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^4.12.0",
|
||||
"@typescript-eslint/parser": "^4.12.0",
|
||||
"concurrently": "^5.3.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-plugin-import": "~2.22.1",
|
||||
"eslint-plugin-prettier": "~3.1.4",
|
||||
"eslint-plugin-unicorn": "~25.0.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"prettier": "~2.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"supertest": "^6.0.1",
|
||||
"typescript": "~4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"azure-storage": "^2.4.0",
|
||||
"body-parser": "^1.18.3",
|
||||
"busboy": "*",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cors": "^2.8.4",
|
||||
"debug": "^3.1.0",
|
||||
"express": "^4.16.3",
|
||||
"pug": "^2.0.3",
|
||||
"morgan": "^1.9.0",
|
||||
"request": "^2.87.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"socket.io": "^2.1.1",
|
||||
"socket.io-client": "^2.1.1",
|
||||
"underscore": "*",
|
||||
"xml2js": "^0.4.19",
|
||||
"wiremock": "2.25.0",
|
||||
"chalk": "3.0.0"
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"busboy": "^0.3.1",
|
||||
"commonmark": "^0.27.0",
|
||||
"deep-equal": "^2.0.5",
|
||||
"express": "~4.17.1",
|
||||
"express-promise-router": "^4.0.1",
|
||||
"jest": "^26.6.3",
|
||||
"js-yaml": "~4.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"mustache": "^4.1.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"ts-jest": "^26.4.4",
|
||||
"underscore": "^1.12.0",
|
||||
"winston": "~3.3.3",
|
||||
"wiremock": "^2.27.2",
|
||||
"xml2js": "^0.4.23",
|
||||
"yargs": "^16.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export * from "./response-content-utils";
|
||||
export * from "./mock-api-router";
|
||||
export * from "./validation-error";
|
||||
import { MockApiRouter } from "./mock-api-router";
|
||||
|
||||
export const app = new MockApiRouter();
|
|
@ -0,0 +1,120 @@
|
|||
import { Response, Router } from "express";
|
||||
import PromiseRouter from "express-promise-router";
|
||||
import { logger } from "../logger";
|
||||
import { RequestExt } from "../server";
|
||||
import { coverageService } from "../services";
|
||||
import { MockRequestHandler, processRequest } from "./request-processor";
|
||||
|
||||
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head";
|
||||
|
||||
export type Category = "vanilla" | "azure" | "optional";
|
||||
|
||||
export class MockApiRouter {
|
||||
public router: Router;
|
||||
private currentCategory: Category | undefined;
|
||||
|
||||
public constructor() {
|
||||
this.router = PromiseRouter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the category for the route definition inside of the provided function.
|
||||
* @param category Category.
|
||||
* @param callback Callback where are defined the mock routes.
|
||||
*/
|
||||
public category(category: Category, callback: () => void): void {
|
||||
this.currentCategory = category;
|
||||
callback();
|
||||
this.currentCategory = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a GET request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public get(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("get", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a POST request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public post(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("post", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a PUT request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public put(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("put", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a PATCH request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public patch(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("patch", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a DELETE request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public delete(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("delete", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a HEAD request for the provided uri.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*/
|
||||
public head(uri: string, name: string, func: MockRequestHandler): void {
|
||||
this.request("head", uri, name, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a request for the provided uri.
|
||||
* @param method Method to use.
|
||||
* @param uri URI to match.
|
||||
* @param name Name of the scenario(For coverage).
|
||||
* @param func Request handler.
|
||||
*
|
||||
* @note prefer to use the coresponding method method directly instead of `#request()`(i.e `#get(), #post()`)
|
||||
*/
|
||||
public request(method: HttpMethod, uri: string, name: string, func: MockRequestHandler): void {
|
||||
logger.info(`Registering route ${method} ${uri} (${name})`);
|
||||
if (this.currentCategory === undefined) {
|
||||
throw new Error(
|
||||
[
|
||||
`Cannot register route ${method} ${uri} (${name}), missing category.`,
|
||||
`Please wrap it in:`,
|
||||
`app.category("vanilla" | "azure", () => {`,
|
||||
` // app.get(...`,
|
||||
`});`,
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
const category = this.currentCategory;
|
||||
coverageService.register(category, name);
|
||||
this.router.route(uri)[method](async (req: RequestExt, res: Response) => {
|
||||
await processRequest(category, name, req, res, func);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { RequestExt } from "../server";
|
||||
import { getRequestBaseUrl } from "../utils";
|
||||
import { validateBodyEquals, validateRawBodyEquals } from "./request-validations";
|
||||
|
||||
export const BODY_NOT_EQUAL_ERROR_MESSAGE = "Body provided doesn't match expected body.";
|
||||
|
||||
export class MockRequest {
|
||||
public readonly baseUrl: string;
|
||||
public readonly headers: { [key: string]: string };
|
||||
|
||||
public constructor(private originalRequest: RequestExt) {
|
||||
this.baseUrl = getRequestBaseUrl(originalRequest);
|
||||
this.headers = originalRequest.headers as { [key: string]: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect the raw body of the request to match the given string.
|
||||
* @param rawBody Raw request body.
|
||||
* @throws {ValidationError} if there is an error.
|
||||
*/
|
||||
public rawBodyEquals(expectedRawBody: string | undefined): void {
|
||||
validateRawBodyEquals(this.originalRequest, expectedRawBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect the body of the request to match the given object.
|
||||
* @param rawBody Raw request body.
|
||||
* @throws {ValidationError} if there is an error.
|
||||
*/
|
||||
public bodyEquals(expectedRawBody: unknown | undefined): void {
|
||||
validateBodyEquals(this.originalRequest, expectedRawBody);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
export interface MockResponse {
|
||||
status: number;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
body?: MockResponseBody;
|
||||
}
|
||||
|
||||
export interface MockResponseBody {
|
||||
contentType: string;
|
||||
rawContent: string | undefined;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import { Response } from "express";
|
||||
import { logger } from "../logger";
|
||||
import { RequestExt } from "../server";
|
||||
import { coverageService } from "../services";
|
||||
import { MockRequest } from "./mock-request";
|
||||
import { MockResponse } from "./mock-response";
|
||||
import { ValidationError } from "./validation-error";
|
||||
|
||||
export type MockRequestHandler = (req: MockRequest) => MockResponse | Promise<MockResponse>;
|
||||
|
||||
export const processRequest = async (
|
||||
category: string,
|
||||
name: string,
|
||||
request: RequestExt,
|
||||
response: Response,
|
||||
func: MockRequestHandler,
|
||||
): Promise<void> => {
|
||||
const mockRequest = new MockRequest(request);
|
||||
const mockResponse = await callHandler(mockRequest, response, func);
|
||||
if (mockResponse === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mockResponse.status >= 200 && mockResponse.status < 300) {
|
||||
await coverageService.track(category, name);
|
||||
}
|
||||
processResponse(response, mockResponse);
|
||||
};
|
||||
|
||||
const processResponse = (response: Response, mockResponse: MockResponse) => {
|
||||
response.status(mockResponse.status);
|
||||
|
||||
if (mockResponse.headers) {
|
||||
response.set(mockResponse.headers);
|
||||
}
|
||||
|
||||
if (mockResponse.body) {
|
||||
response.contentType(mockResponse.body.contentType).send(mockResponse.body.rawContent);
|
||||
}
|
||||
|
||||
response.end();
|
||||
};
|
||||
|
||||
const callHandler = async (
|
||||
mockRequest: MockRequest,
|
||||
response: Response,
|
||||
func: MockRequestHandler,
|
||||
): Promise<MockResponse | undefined> => {
|
||||
try {
|
||||
return func(mockRequest);
|
||||
} catch (e) {
|
||||
if (!(e instanceof ValidationError)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
[`Request validation failed: ${e.message}:`, ` Expected:\n ${e.expected}`, ` Actual: \n${e.actual}`].join("\n"),
|
||||
);
|
||||
response
|
||||
.status(400)
|
||||
.contentType("application/json")
|
||||
.send(e.toJSON ? e.toJSON() : JSON.stringify(e.message))
|
||||
.end();
|
||||
return undefined;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
import deepEqual from "deep-equal";
|
||||
import { RequestExt } from "../server";
|
||||
import { ValidationError } from "./validation-error";
|
||||
|
||||
export const BODY_NOT_EQUAL_ERROR_MESSAGE = "Body provided doesn't match expected body";
|
||||
|
||||
export const validateRawBodyEquals = (request: RequestExt, expectedRawBody: string | undefined): void => {
|
||||
const actualRawBody = request.rawBody;
|
||||
|
||||
if (expectedRawBody == null) {
|
||||
if (!isBodyEmpty(actualRawBody)) {
|
||||
throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedRawBody, actualRawBody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (actualRawBody !== expectedRawBody) {
|
||||
throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedRawBody, actualRawBody);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateBodyEquals = (request: RequestExt, expectedBody: unknown | undefined): void => {
|
||||
if (expectedBody == null) {
|
||||
if (!isBodyEmpty(request.rawBody)) {
|
||||
throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedBody, request.rawBody);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deepEqual(request.body, expectedBody, { strict: true })) {
|
||||
throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedBody, request.body);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the provided body is empty.
|
||||
* @param body express.js request body.
|
||||
*/
|
||||
const isBodyEmpty = (body: string | undefined | null) => {
|
||||
return body == null || body === "";
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
import { MockResponseBody } from "./mock-response";
|
||||
|
||||
/**
|
||||
* Serialize the provided content as json to use in a MockResponse body.
|
||||
* @content Object to return as json.
|
||||
* @returns {MockResponseBody} response body with application/json content type.
|
||||
*/
|
||||
export const json = (content: unknown): MockResponseBody => {
|
||||
return {
|
||||
contentType: "application/json",
|
||||
rawContent: JSON.stringify(content),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
export class ValidationError extends Error {
|
||||
/**
|
||||
* Error thrown there there is a validation issue.
|
||||
* @param message Message describing the error.
|
||||
* @param expected expected value.
|
||||
* @param actual actual value.
|
||||
*/
|
||||
constructor(message: string, public expected: unknown | undefined, public actual: unknown | undefined) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public toJSON(): string {
|
||||
return JSON.stringify({ message: this.message, expected: this.expected, actual: this.actual });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import path from "path";
|
||||
import { app } from "../api";
|
||||
import { registerLegacyRoutes } from "../legacy";
|
||||
import { logger } from "../logger";
|
||||
import { internalRouter } from "../routes";
|
||||
import { MockApiServer } from "../server";
|
||||
import { coverageService } from "../services";
|
||||
import { findFilesFromPattern } from "../utils";
|
||||
import { ApiMockAppConfig } from "./config";
|
||||
|
||||
const ROUTE_FOLDER = path.join(__dirname, "../test-routes");
|
||||
|
||||
export class ApiMockApp {
|
||||
private server: MockApiServer;
|
||||
|
||||
constructor(private config: ApiMockAppConfig) {
|
||||
coverageService.coverageDirectory = config.coverageDirectory;
|
||||
this.server = new MockApiServer({ port: config.port });
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
this.server.use("/", internalRouter);
|
||||
|
||||
await requireMockRoutes(ROUTE_FOLDER);
|
||||
registerLegacyRoutes(this.server);
|
||||
|
||||
const apiRouter = app;
|
||||
this.server.use("/", apiRouter.router);
|
||||
this.server.start();
|
||||
}
|
||||
}
|
||||
|
||||
const requireMockRoutes = async (routesFolder: string) => {
|
||||
const files = await findFilesFromPattern(path.join(routesFolder, "/**/*.js"));
|
||||
logger.debug("Detected routes:", files);
|
||||
for (const file of files) {
|
||||
require(path.resolve(file));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export interface ApiMockAppConfig {
|
||||
port: number;
|
||||
coverageDirectory: string;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./app";
|
||||
export * from "./config";
|
|
@ -0,0 +1,44 @@
|
|||
import { join } from "path";
|
||||
import yargs, { CommandModule } from "yargs";
|
||||
import { CliConfig } from "./cli-config";
|
||||
|
||||
export const DEFAULT_PORT = 3000;
|
||||
|
||||
export const parseArgs = (argv: string[]): CliConfig => {
|
||||
const cli = yargs(argv)
|
||||
.help()
|
||||
.strict()
|
||||
.command("$0", "Run the autorest test server.")
|
||||
.command("stop", "Stop the autorest test server running at the provided port.")
|
||||
.option("verbose", {
|
||||
alias: "v",
|
||||
type: "boolean",
|
||||
description: "Run with verbose logging level.",
|
||||
})
|
||||
.option("debug", {
|
||||
type: "boolean",
|
||||
description: "Run with debug logging level.",
|
||||
})
|
||||
.option("level", {
|
||||
type: "string",
|
||||
description: "Run with given logging level.",
|
||||
})
|
||||
.option("port", {
|
||||
alias: "p",
|
||||
type: "number",
|
||||
description: "Port where to host the server",
|
||||
default: DEFAULT_PORT,
|
||||
})
|
||||
.option("coverageDirectory", {
|
||||
type: "string",
|
||||
description: "Path of the directory where the coverage reports should be saved.",
|
||||
default: join(process.cwd(), "coverage"),
|
||||
});
|
||||
|
||||
const options = cli.argv;
|
||||
return {
|
||||
...options,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
command: (options._[0] as any) ?? "run",
|
||||
};
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
export interface CliConfig {
|
||||
// Logging
|
||||
verbose?: boolean;
|
||||
debug?: boolean;
|
||||
level?: string;
|
||||
|
||||
/**
|
||||
* Port to serve the application.
|
||||
*/
|
||||
port: number;
|
||||
|
||||
/**
|
||||
* Directory where the coverage reports should be saved.
|
||||
*/
|
||||
coverageDirectory: string;
|
||||
|
||||
/**
|
||||
* Command to use
|
||||
*/
|
||||
command: "run" | "stop";
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("source-map-support").install();
|
||||
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { logger, setLoggingLevelFromConfig } from "../logger";
|
||||
import { parseArgs } from "./args-parser";
|
||||
import { stopCommand, runCommand } from "./commands";
|
||||
|
||||
const run = async () => {
|
||||
const cliConfig = parseArgs(hideBin(process.argv));
|
||||
setLoggingLevelFromConfig(cliConfig);
|
||||
switch (cliConfig.command) {
|
||||
case "run":
|
||||
await runCommand(cliConfig);
|
||||
break;
|
||||
case "stop":
|
||||
await stopCommand(cliConfig);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
run().catch((e) => {
|
||||
logger.error("Error", e);
|
||||
process.exit(1);
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./run-command";
|
||||
export * from "./stop-command";
|
|
@ -0,0 +1,14 @@
|
|||
import { ApiMockApp, ApiMockAppConfig } from "../../app";
|
||||
import { CliConfig } from "../cli-config";
|
||||
|
||||
const getAppConfig = (cliConfig: CliConfig): ApiMockAppConfig => {
|
||||
return {
|
||||
coverageDirectory: cliConfig.coverageDirectory,
|
||||
port: cliConfig.port,
|
||||
};
|
||||
};
|
||||
|
||||
export const runCommand = async (cliConfig: CliConfig): Promise<void> => {
|
||||
const app = new ApiMockApp(getAppConfig(cliConfig));
|
||||
await app.start();
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
import axios from "axios";
|
||||
import { AdminUrls } from "../../constants";
|
||||
import { logger } from "../../logger";
|
||||
import { CliConfig } from "../cli-config";
|
||||
|
||||
export const stopCommand = async ({ port }: CliConfig): Promise<void> => {
|
||||
logger.info("Stopping server at port {port}", port);
|
||||
try {
|
||||
const url = `http://localhost:${port}${AdminUrls.stop}`;
|
||||
const response = await axios.post(url);
|
||||
logger.debug(`Call success: ${url} ${response.status}`);
|
||||
logger.info(`Successfuly stopped server at port ${port}`);
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
logger.error("Error while trying to stop server", e);
|
||||
process.exit(-1);
|
||||
}
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./args-parser";
|
|
@ -0,0 +1,7 @@
|
|||
import { join, resolve } from "path";
|
||||
|
||||
export const ProjectRoot = resolve(join(__dirname), "..");
|
||||
|
||||
export const AdminUrls = {
|
||||
stop: "/.admin/stop",
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./legacy";
|
|
@ -0,0 +1,550 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
/**
|
||||
* This file handles loading the legacy test server routes.
|
||||
*/
|
||||
import { Category } from "../api";
|
||||
import { ProjectRoot } from "../constants";
|
||||
import { MockApiServer } from "../server";
|
||||
import { CoverageMap, coverageService } from "../services";
|
||||
|
||||
const legacyRoutePath = `${ProjectRoot}/legacy/routes`;
|
||||
|
||||
const requireLegacy = (filename: string) => require(`${legacyRoutePath}/${filename}`);
|
||||
|
||||
const additionalProperties = requireLegacy("additionalProperties.js");
|
||||
const array = requireLegacy("array");
|
||||
const parameterGrouping = requireLegacy("azureParameterGrouping.js");
|
||||
const azureSpecial = requireLegacy("azureSpecials");
|
||||
const azureUrl = requireLegacy("azureUrl");
|
||||
const bool = requireLegacy("bool");
|
||||
const byte = requireLegacy("byte");
|
||||
const complex = requireLegacy("complex");
|
||||
const customUri = requireLegacy("customUri.js");
|
||||
const date = requireLegacy("date");
|
||||
const datetime = requireLegacy("datetime");
|
||||
const datetimeRfc1123 = requireLegacy("datetime-rfc1123");
|
||||
const dictionary = requireLegacy("dictionary");
|
||||
const duration = requireLegacy("duration");
|
||||
const errorStatusCodes = requireLegacy("errorStatusCodes.js");
|
||||
const extensibleEnums = requireLegacy("extensibleEnums.js");
|
||||
const files = requireLegacy("files");
|
||||
const formData = requireLegacy("formData");
|
||||
const header = requireLegacy("header");
|
||||
const httpResponses = requireLegacy("httpResponses");
|
||||
const routes = requireLegacy("index");
|
||||
const integer = requireLegacy("int");
|
||||
const lroParameterizedEndpoints = requireLegacy("lroParameterizedEndpoints.js");
|
||||
const lros = requireLegacy("lros");
|
||||
const mediatypes = requireLegacy("mediatypes");
|
||||
const modelFlatten = requireLegacy("model-flatten");
|
||||
const multiapi = requireLegacy("multiapi");
|
||||
const multiapiCustomBaseUrl = requireLegacy("multiapiCustomBaseUrl.js");
|
||||
const multipleInheritance = requireLegacy("multipleInheritance.js");
|
||||
const nonStringEnums = requireLegacy("nonStringEnums.js");
|
||||
const number = requireLegacy("number");
|
||||
const objectType = requireLegacy("objectType.js");
|
||||
const paging = requireLegacy("paging");
|
||||
const pathitem = requireLegacy("pathitem");
|
||||
const paths = requireLegacy("paths");
|
||||
const queries = requireLegacy("queries");
|
||||
const reqopt = requireLegacy("reqopt");
|
||||
const time = requireLegacy("time.js");
|
||||
const validation = requireLegacy("validation.js");
|
||||
const xml = requireLegacy("xml.js");
|
||||
|
||||
const proxyCoverage = (category: Category, coverage: CoverageMap) => {
|
||||
for (const key of Object.keys(coverage)) {
|
||||
coverageService.legacyTrack(category, key as string, 0);
|
||||
}
|
||||
|
||||
return new Proxy(coverage, {
|
||||
get: (_, key) => coverageService.getAllForCategory(category)[key as string],
|
||||
set: (obj, key, value) => {
|
||||
coverageService.legacyTrack(category, key as string, value);
|
||||
obj[key as string] = value;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const registerLegacyRoutes = (app: MockApiServer): void => {
|
||||
const azurecoverage = proxyCoverage("azure", {});
|
||||
const optionalCoverage = proxyCoverage("optional", {
|
||||
getDecimalInvalid: 0,
|
||||
getDecimalBig: 0,
|
||||
getDecimalSmall: 0,
|
||||
getDecimalBigPositiveDecimal: 0,
|
||||
getDecimalBigNegativeDecimal: 0,
|
||||
putDecimalBig: 0,
|
||||
putDecimalSmall: 0,
|
||||
putDecimalBigPositiveDecimal: 0,
|
||||
putDecimalBigNegativeDecimal: 0,
|
||||
});
|
||||
|
||||
const coverage = proxyCoverage("vanilla", {
|
||||
additionalPropertiesTrue: 0,
|
||||
additionalPropertiesSubclass: 0,
|
||||
additionalPropertiesTypeObject: 0,
|
||||
additionalPropertiesTypeString: 0,
|
||||
additionalPropertiesInProperties: 0,
|
||||
additionalPropertiesInPropertiesWithAPTypeString: 0,
|
||||
getArrayNull: 0,
|
||||
getArrayEmpty: 0,
|
||||
putArrayEmpty: 0,
|
||||
getArrayInvalid: 0,
|
||||
getArrayBooleanValid: 0,
|
||||
putArrayBooleanValid: 0,
|
||||
getArrayBooleanWithNull: 0,
|
||||
getArrayBooleanWithString: 0,
|
||||
getArrayIntegerValid: 0,
|
||||
putArrayIntegerValid: 0,
|
||||
getArrayIntegerWithNull: 0,
|
||||
getArrayIntegerWithString: 0,
|
||||
getArrayLongValid: 0,
|
||||
putArrayLongValid: 0,
|
||||
getArrayLongWithNull: 0,
|
||||
getArrayLongWithString: 0,
|
||||
getArrayFloatValid: 0,
|
||||
putArrayFloatValid: 0,
|
||||
getArrayFloatWithNull: 0,
|
||||
getArrayFloatWithString: 0,
|
||||
getArrayDoubleValid: 0,
|
||||
putArrayDoubleValid: 0,
|
||||
getArrayDoubleWithNull: 0,
|
||||
getArrayDoubleWithString: 0,
|
||||
getArrayStringValid: 0,
|
||||
putArrayStringValid: 0,
|
||||
getArrayEnumValid: 0,
|
||||
putArrayEnumValid: 0,
|
||||
getArrayStringEnumValid: 0,
|
||||
putArrayStringEnumValid: 0,
|
||||
getArrayStringWithNull: 0,
|
||||
getArrayStringWithNumber: 0,
|
||||
getArrayDateValid: 0,
|
||||
putArrayDateValid: 0,
|
||||
getArrayDateWithNull: 0,
|
||||
getArrayDateWithInvalidChars: 0,
|
||||
getArrayDateTimeValid: 0,
|
||||
putArrayDateTimeValid: 0,
|
||||
getArrayDateTimeWithNull: 0,
|
||||
getArrayDateTimeWithInvalidChars: 0,
|
||||
getArrayDateTimeRfc1123Valid: 0,
|
||||
putArrayDateTimeRfc1123Valid: 0,
|
||||
getArrayDurationValid: 0,
|
||||
putArrayDurationValid: 0,
|
||||
getArrayUuidValid: 0,
|
||||
getArrayUuidWithInvalidChars: 0,
|
||||
putArrayUuidValid: 0,
|
||||
getArrayByteValid: 0,
|
||||
putArrayByteValid: 0,
|
||||
getArrayByteWithNull: 0,
|
||||
getArrayArrayNull: 0,
|
||||
getArrayArrayEmpty: 0,
|
||||
getArrayArrayItemNull: 0,
|
||||
getArrayArrayItemEmpty: 0,
|
||||
getArrayArrayValid: 0,
|
||||
putArrayArrayValid: 0,
|
||||
getArrayComplexNull: 0,
|
||||
getArrayComplexEmpty: 0,
|
||||
getArrayComplexItemNull: 0,
|
||||
getArrayComplexItemEmpty: 0,
|
||||
getArrayComplexValid: 0,
|
||||
putArrayComplexValid: 0,
|
||||
getArrayDictionaryNull: 0,
|
||||
getArrayDictionaryEmpty: 0,
|
||||
getArrayDictionaryItemNull: 0,
|
||||
getArrayDictionaryItemEmpty: 0,
|
||||
getArrayDictionaryValid: 0,
|
||||
putArrayDictionaryValid: 0,
|
||||
getBoolTrue: 0,
|
||||
putBoolTrue: 0,
|
||||
getBoolFalse: 0,
|
||||
putBoolFalse: 0,
|
||||
getBoolInvalid: 0,
|
||||
getBoolNull: 0,
|
||||
getByteNull: 0,
|
||||
getByteEmpty: 0,
|
||||
getByteNonAscii: 0,
|
||||
putByteNonAscii: 0,
|
||||
getByteInvalid: 0,
|
||||
getDateNull: 0,
|
||||
getDateInvalid: 0,
|
||||
getDateOverflow: 0,
|
||||
getDateUnderflow: 0,
|
||||
getDateMax: 0,
|
||||
putDateMax: 0,
|
||||
getDateMin: 0,
|
||||
putDateMin: 0,
|
||||
getDateTimeNull: 0,
|
||||
getDateTimeInvalid: 0,
|
||||
getDateTimeOverflow: 0,
|
||||
getDateTimeUnderflow: 0,
|
||||
putDateTimeMaxUtc: 0,
|
||||
getDateTimeMaxUtcLowercase: 0,
|
||||
getDateTimeMaxUtcUppercase: 0,
|
||||
getDateTimeMaxLocalPositiveOffsetLowercase: 0,
|
||||
getDateTimeMaxLocalPositiveOffsetUppercase: 0,
|
||||
getDateTimeMaxLocalNegativeOffsetLowercase: 0,
|
||||
getDateTimeMaxLocalNegativeOffsetUppercase: 0,
|
||||
getDateTimeMinUtc: 0,
|
||||
putDateTimeMinUtc: 0,
|
||||
getDateTimeMinLocalPositiveOffset: 0,
|
||||
getDateTimeMinLocalNegativeOffset: 0,
|
||||
getDateTimeRfc1123Null: 0,
|
||||
getDateTimeRfc1123Invalid: 0,
|
||||
getDateTimeRfc1123Overflow: 0,
|
||||
getDateTimeRfc1123Underflow: 0,
|
||||
getDateTimeRfc1123MinUtc: 0,
|
||||
putDateTimeRfc1123Max: 0,
|
||||
putDateTimeRfc1123Min: 0,
|
||||
getDateTimeRfc1123MaxUtcLowercase: 0,
|
||||
getDateTimeRfc1123MaxUtcUppercase: 0,
|
||||
getIntegerNull: 0,
|
||||
getIntegerInvalid: 0,
|
||||
getIntegerOverflow: 0,
|
||||
getIntegerUnderflow: 0,
|
||||
getLongOverflow: 0,
|
||||
getLongUnderflow: 0,
|
||||
putIntegerMax: 0,
|
||||
putLongMax: 0,
|
||||
putIntegerMin: 0,
|
||||
putLongMin: 0,
|
||||
getNumberNull: 0,
|
||||
getFloatInvalid: 0,
|
||||
getDoubleInvalid: 0,
|
||||
getFloatBigScientificNotation: 0,
|
||||
putFloatBigScientificNotation: 0,
|
||||
getDoubleBigScientificNotation: 0,
|
||||
putDoubleBigScientificNotation: 0,
|
||||
getDoubleBigPositiveDecimal: 0,
|
||||
putDoubleBigPositiveDecimal: 0,
|
||||
getDoubleBigNegativeDecimal: 0,
|
||||
putDoubleBigNegativeDecimal: 0,
|
||||
getFloatSmallScientificNotation: 0,
|
||||
putFloatSmallScientificNotation: 0,
|
||||
getDoubleSmallScientificNotation: 0,
|
||||
putDoubleSmallScientificNotation: 0,
|
||||
getStringNull: 0,
|
||||
putStringNull: 0,
|
||||
getStringEmpty: 0,
|
||||
putStringEmpty: 0,
|
||||
getStringMultiByteCharacters: 0,
|
||||
putStringMultiByteCharacters: 0,
|
||||
getStringWithLeadingAndTrailingWhitespace: 0,
|
||||
putStringWithLeadingAndTrailingWhitespace: 0,
|
||||
getStringNotProvided: 0,
|
||||
getEnumNotExpandable: 0,
|
||||
putEnumNotExpandable: 0,
|
||||
putComplexBasicValid: 0,
|
||||
getComplexBasicValid: 0,
|
||||
getComplexBasicEmpty: 0,
|
||||
getComplexBasicNotProvided: 0,
|
||||
getComplexBasicNull: 0,
|
||||
getComplexBasicInvalid: 0,
|
||||
putComplexPrimitiveInteger: 0,
|
||||
putComplexPrimitiveLong: 0,
|
||||
putComplexPrimitiveFloat: 0,
|
||||
putComplexPrimitiveDouble: 0,
|
||||
putComplexPrimitiveBool: 0,
|
||||
putComplexPrimitiveString: 0,
|
||||
putComplexPrimitiveDate: 0,
|
||||
putComplexPrimitiveDateTime: 0,
|
||||
putComplexPrimitiveDateTimeRfc1123: 0,
|
||||
putComplexPrimitiveDuration: 0,
|
||||
putComplexPrimitiveByte: 0,
|
||||
getComplexPrimitiveInteger: 0,
|
||||
getComplexPrimitiveLong: 0,
|
||||
getComplexPrimitiveFloat: 0,
|
||||
getComplexPrimitiveDouble: 0,
|
||||
getComplexPrimitiveBool: 0,
|
||||
getComplexPrimitiveString: 0,
|
||||
getComplexPrimitiveDate: 0,
|
||||
getComplexPrimitiveDateTime: 0,
|
||||
getComplexPrimitiveDateTimeRfc1123: 0,
|
||||
getComplexPrimitiveDuration: 0,
|
||||
getComplexPrimitiveByte: 0,
|
||||
putComplexArrayValid: 0,
|
||||
putComplexArrayEmpty: 0,
|
||||
getComplexArrayValid: 0,
|
||||
getComplexArrayEmpty: 0,
|
||||
getComplexArrayNotProvided: 0,
|
||||
putComplexDictionaryValid: 0,
|
||||
putComplexDictionaryEmpty: 0,
|
||||
getComplexDictionaryValid: 0,
|
||||
getComplexDictionaryEmpty: 0,
|
||||
getComplexDictionaryNull: 0,
|
||||
getComplexDictionaryNotProvided: 0,
|
||||
putComplexInheritanceValid: 0,
|
||||
getComplexInheritanceValid: 0,
|
||||
putComplexPolymorphismValid: 0,
|
||||
getComplexPolymorphismValid: 0,
|
||||
putComplexPolymorphismComplicated: 0,
|
||||
getComplexPolymorphismComplicated: 0,
|
||||
putComplexPolymorphismNoDiscriminator: 0,
|
||||
putComplexPolymorphicRecursiveValid: 0,
|
||||
getComplexPolymorphicRecursiveValid: 0,
|
||||
putComplexReadOnlyPropertyValid: 0,
|
||||
getComplexReadOnlyPropertyValid: 0,
|
||||
UrlPathsBoolFalse: 0,
|
||||
UrlPathsBoolTrue: 0,
|
||||
UrlPathsIntPositive: 0,
|
||||
UrlPathsIntNegative: 0,
|
||||
UrlPathsLongPositive: 0,
|
||||
UrlPathsLongNegative: 0,
|
||||
UrlPathsFloatPositive: 0,
|
||||
UrlPathsFloatNegative: 0,
|
||||
UrlPathsDoublePositive: 0,
|
||||
UrlPathsDoubleNegative: 0,
|
||||
UrlPathsStringUrlEncoded: 0,
|
||||
UrlPathsStringUrlNonEncoded: 0,
|
||||
UrlPathsStringEmpty: 0,
|
||||
UrlPathsStringUnicode: 0,
|
||||
UrlPathsEnumValid: 0,
|
||||
UrlPathsByteMultiByte: 0,
|
||||
UrlPathsByteEmpty: 0,
|
||||
UrlPathsDateValid: 0,
|
||||
UrlPathsDateTimeValid: 0,
|
||||
UrlQueriesBoolFalse: 0,
|
||||
UrlQueriesBoolTrue: 0,
|
||||
UrlQueriesBoolNull: 0,
|
||||
UrlQueriesIntPositive: 0,
|
||||
UrlQueriesIntNegative: 0,
|
||||
UrlQueriesIntNull: 0,
|
||||
UrlQueriesLongPositive: 0,
|
||||
UrlQueriesLongNegative: 0,
|
||||
UrlQueriesLongNull: 0,
|
||||
UrlQueriesFloatPositive: 0,
|
||||
UrlQueriesFloatNegative: 0,
|
||||
UrlQueriesFloatNull: 0,
|
||||
UrlQueriesDoublePositive: 0,
|
||||
UrlQueriesDoubleNegative: 0,
|
||||
UrlQueriesDoubleNull: 0,
|
||||
UrlQueriesStringUrlEncoded: 0,
|
||||
UrlQueriesStringEmpty: 0,
|
||||
UrlQueriesStringNull: 0,
|
||||
UrlQueriesStringUnicode: 0,
|
||||
UrlQueriesEnumValid: 0,
|
||||
UrlQueriesEnumNull: 0,
|
||||
UrlQueriesByteMultiByte: 0,
|
||||
UrlQueriesByteEmpty: 0,
|
||||
UrlQueriesByteNull: 0,
|
||||
UrlQueriesDateValid: 0,
|
||||
UrlQueriesDateNull: 0,
|
||||
UrlQueriesDateTimeValid: 0,
|
||||
UrlQueriesDateTimeNull: 0,
|
||||
UrlQueriesArrayCsvNull: 0,
|
||||
UrlQueriesArrayCsvEmpty: 0,
|
||||
UrlQueriesArrayCsvValid: 0,
|
||||
UrlQueriesArrayMultiNull: 0,
|
||||
UrlQueriesArrayMultiEmpty: 0,
|
||||
UrlQueriesArrayMultiValid: 0,
|
||||
UrlQueriesArraySsvValid: 0,
|
||||
UrlQueriesArrayPipesValid: 0,
|
||||
UrlQueriesArrayTsvValid: 0,
|
||||
UrlQueriesArrayNoCollectionFormatValid: 0,
|
||||
UrlPathItemGetAll: 0,
|
||||
UrlPathItemGetGlobalNull: 0,
|
||||
UrlPathItemGetGlobalAndLocalNull: 0,
|
||||
UrlPathItemGetPathItemAndLocalNull: 0,
|
||||
putDictionaryEmpty: 0,
|
||||
getDictionaryNull: 0,
|
||||
getDictionaryEmpty: 0,
|
||||
getDictionaryInvalid: 0,
|
||||
getDictionaryNullValue: 0,
|
||||
getDictionaryNullkey: 0,
|
||||
getDictionaryKeyEmptyString: 0,
|
||||
getDictionaryBooleanValid: 0,
|
||||
getDictionaryBooleanWithNull: 0,
|
||||
getDictionaryBooleanWithString: 0,
|
||||
getDictionaryIntegerValid: 0,
|
||||
getDictionaryIntegerWithNull: 0,
|
||||
getDictionaryIntegerWithString: 0,
|
||||
getDictionaryLongValid: 0,
|
||||
getDictionaryLongWithNull: 0,
|
||||
getDictionaryLongWithString: 0,
|
||||
getDictionaryFloatValid: 0,
|
||||
getDictionaryFloatWithNull: 0,
|
||||
getDictionaryFloatWithString: 0,
|
||||
getDictionaryDoubleValid: 0,
|
||||
getDictionaryDoubleWithNull: 0,
|
||||
getDictionaryDoubleWithString: 0,
|
||||
getDictionaryStringValid: 0,
|
||||
getDictionaryStringWithNull: 0,
|
||||
getDictionaryStringWithNumber: 0,
|
||||
getDictionaryDateValid: 0,
|
||||
getDictionaryDateWithNull: 0,
|
||||
getDictionaryDateWithInvalidChars: 0,
|
||||
getDictionaryDateTimeValid: 0,
|
||||
getDictionaryDateTimeWithNull: 0,
|
||||
getDictionaryDateTimeWithInvalidChars: 0,
|
||||
getDictionaryDateTimeRfc1123Valid: 0,
|
||||
getDictionaryDurationValid: 0,
|
||||
getDictionaryByteValid: 0,
|
||||
getDictionaryByteWithNull: 0,
|
||||
putDictionaryBooleanValid: 0,
|
||||
putDictionaryIntegerValid: 0,
|
||||
putDictionaryLongValid: 0,
|
||||
putDictionaryFloatValid: 0,
|
||||
putDictionaryDoubleValid: 0,
|
||||
putDictionaryStringValid: 0,
|
||||
putDictionaryDateValid: 0,
|
||||
putDictionaryDateTimeValid: 0,
|
||||
putDictionaryDateTimeRfc1123Valid: 0,
|
||||
putDictionaryDurationValid: 0,
|
||||
putDictionaryByteValid: 0,
|
||||
getDictionaryComplexNull: 0,
|
||||
getDictionaryComplexEmpty: 0,
|
||||
getDictionaryComplexItemNull: 0,
|
||||
getDictionaryComplexItemEmpty: 0,
|
||||
getDictionaryComplexValid: 0,
|
||||
putDictionaryComplexValid: 0,
|
||||
getDictionaryArrayNull: 0,
|
||||
getDictionaryArrayEmpty: 0,
|
||||
getDictionaryArrayItemNull: 0,
|
||||
getDictionaryArrayItemEmpty: 0,
|
||||
getDictionaryArrayValid: 0,
|
||||
putDictionaryArrayValid: 0,
|
||||
getDictionaryDictionaryNull: 0,
|
||||
getDictionaryDictionaryEmpty: 0,
|
||||
getDictionaryDictionaryItemNull: 0,
|
||||
getDictionaryDictionaryItemEmpty: 0,
|
||||
getDictionaryDictionaryValid: 0,
|
||||
putDictionaryDictionaryValid: 0,
|
||||
putDurationPositive: 0,
|
||||
getDurationNull: 0,
|
||||
getDurationInvalid: 0,
|
||||
getDurationPositive: 0,
|
||||
HeaderParameterExistingKey: 0,
|
||||
HeaderResponseExistingKey: 0,
|
||||
HeaderResponseProtectedKey: 0,
|
||||
HeaderParameterIntegerPositive: 0,
|
||||
HeaderParameterIntegerNegative: 0,
|
||||
HeaderParameterLongPositive: 0,
|
||||
HeaderParameterLongNegative: 0,
|
||||
HeaderParameterFloatPositive: 0,
|
||||
HeaderParameterFloatNegative: 0,
|
||||
HeaderParameterDoublePositive: 0,
|
||||
HeaderParameterDoubleNegative: 0,
|
||||
HeaderParameterBoolTrue: 0,
|
||||
HeaderParameterBoolFalse: 0,
|
||||
HeaderParameterStringValid: 0,
|
||||
HeaderParameterStringNull: 0,
|
||||
HeaderParameterStringEmpty: 0,
|
||||
HeaderParameterDateValid: 0,
|
||||
HeaderParameterDateMin: 0,
|
||||
HeaderParameterDateTimeValid: 0,
|
||||
HeaderParameterDateTimeMin: 0,
|
||||
HeaderParameterDateTimeRfc1123Valid: 0,
|
||||
HeaderParameterDateTimeRfc1123Min: 0,
|
||||
HeaderParameterBytesValid: 0,
|
||||
HeaderParameterDurationValid: 0,
|
||||
HeaderResponseIntegerPositive: 0,
|
||||
HeaderResponseIntegerNegative: 0,
|
||||
HeaderResponseLongPositive: 0,
|
||||
HeaderResponseLongNegative: 0,
|
||||
HeaderResponseFloatPositive: 0,
|
||||
HeaderResponseFloatNegative: 0,
|
||||
HeaderResponseDoublePositive: 0,
|
||||
HeaderResponseDoubleNegative: 0,
|
||||
HeaderResponseBoolTrue: 0,
|
||||
HeaderResponseBoolFalse: 0,
|
||||
HeaderResponseStringValid: 0,
|
||||
HeaderResponseStringNull: 0,
|
||||
HeaderResponseStringEmpty: 0,
|
||||
HeaderParameterEnumValid: 0,
|
||||
HeaderParameterEnumNull: 0,
|
||||
HeaderResponseEnumValid: 0,
|
||||
HeaderResponseEnumNull: 0,
|
||||
HeaderResponseDateValid: 0,
|
||||
HeaderResponseDateMin: 0,
|
||||
HeaderResponseDateTimeValid: 0,
|
||||
HeaderResponseDateTimeMin: 0,
|
||||
HeaderResponseDateTimeRfc1123Valid: 0,
|
||||
HeaderResponseDateTimeRfc1123Min: 0,
|
||||
HeaderResponseBytesValid: 0,
|
||||
HeaderResponseDurationValid: 0,
|
||||
ConstantsInPath: 0,
|
||||
ConstantsInBody: 0,
|
||||
CustomBaseUri: 0,
|
||||
CustomBaseUriMoreOptions: 0,
|
||||
getModelFlattenArray: 0,
|
||||
putModelFlattenArray: 0,
|
||||
getModelFlattenDictionary: 0,
|
||||
putModelFlattenDictionary: 0,
|
||||
getModelFlattenResourceCollection: 0,
|
||||
putModelFlattenResourceCollection: 0,
|
||||
putModelFlattenCustomBase: 0,
|
||||
postModelFlattenCustomParameter: 0,
|
||||
putModelFlattenCustomGroupedParameter: 0,
|
||||
getStringBase64Encoded: 0,
|
||||
getStringBase64UrlEncoded: 0,
|
||||
putStringBase64UrlEncoded: 0,
|
||||
getStringNullBase64UrlEncoding: 0,
|
||||
getArrayBase64Url: 0,
|
||||
getDictionaryBase64Url: 0,
|
||||
UrlPathsStringBase64Url: 0,
|
||||
UrlPathsArrayCSVInPath: 0,
|
||||
getUnixTime: 0,
|
||||
getInvalidUnixTime: 0,
|
||||
getNullUnixTime: 0,
|
||||
putUnixTime: 0,
|
||||
UrlPathsIntUnixTime: 0,
|
||||
expectedEnum: 0,
|
||||
unexpectedEnum: 0,
|
||||
allowedValueEnum: 0,
|
||||
roundTripEnum: 0,
|
||||
getEnumReferenced: 0,
|
||||
putEnumReferenced: 0,
|
||||
getEnumReferencedConstant: 0,
|
||||
putEnumReferencedConstant: 0,
|
||||
expectedNoErrors: 0,
|
||||
expectedPetSadError: 0,
|
||||
expectedPetHungryError: 0,
|
||||
intError: 0,
|
||||
stringError: 0,
|
||||
animalNotFoundError: 0,
|
||||
linkNotFoundError: 0,
|
||||
});
|
||||
|
||||
app.use("/", routes);
|
||||
app.use("/bool", new bool(coverage).router);
|
||||
app.use("/int", new integer(coverage).router);
|
||||
app.use("/number", new number(coverage, optionalCoverage).router);
|
||||
app.use("/byte", new byte(coverage).router);
|
||||
app.use("/date", new date(coverage).router);
|
||||
app.use("/datetime", new datetime(coverage, optionalCoverage).router);
|
||||
app.use("/datetimeRfc1123", new datetimeRfc1123(coverage).router);
|
||||
app.use("/duration", new duration(coverage, optionalCoverage).router);
|
||||
app.use("/array", new array(coverage).router);
|
||||
app.use("/complex", new complex(coverage).router);
|
||||
app.use("/dictionary", new dictionary(coverage).router);
|
||||
app.use("/paths", new paths(coverage).router);
|
||||
app.use("/queries", new queries(coverage).router);
|
||||
app.use("/pathitem", new pathitem(coverage).router);
|
||||
app.use("/header", new header(coverage, optionalCoverage).router);
|
||||
app.use("/reqopt", new reqopt(coverage).router);
|
||||
app.use("/files", new files(coverage).router);
|
||||
app.use("/formdata", new formData(optionalCoverage).router);
|
||||
app.use("/http", new httpResponses(coverage, optionalCoverage).router);
|
||||
app.use("/model-flatten", new modelFlatten(coverage).router);
|
||||
app.use("/lro", new lros(azurecoverage).router);
|
||||
app.use("/lroParameterizedEndpoints", new lroParameterizedEndpoints(azurecoverage).router);
|
||||
app.use("/paging", new paging(azurecoverage).router);
|
||||
app.use("/azurespecials", new azureSpecial(azurecoverage).router);
|
||||
app.use("/subscriptions", new azureUrl(azurecoverage).router);
|
||||
app.use("/parameterGrouping", new parameterGrouping(azurecoverage).router);
|
||||
app.use("/validation", new validation(coverage).router);
|
||||
app.use("/customUri", new customUri(coverage).router);
|
||||
app.use("/extensibleEnums", new extensibleEnums(coverage).router);
|
||||
app.use("/errorStatusCodes", new errorStatusCodes(coverage, optionalCoverage).router);
|
||||
app.use("/additionalProperties", new additionalProperties(coverage).router);
|
||||
app.use("/mediatypes", new mediatypes(coverage).router);
|
||||
app.use("/xml", new xml(coverage).router);
|
||||
app.use("/multiapi", new multiapi(optionalCoverage).router);
|
||||
app.use("/objectType", new objectType(coverage).router);
|
||||
app.use("/nonStringEnums", new nonStringEnums(coverage).router);
|
||||
app.use("/time", new time(coverage).router);
|
||||
app.use("/multipleInheritance", new multipleInheritance(coverage).router);
|
||||
app.use("/multiapiCustomBaseUrl", new multiapiCustomBaseUrl(optionalCoverage).router);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
import winston from "winston";
|
||||
import { CliConfig } from "./cli/cli-config";
|
||||
|
||||
export const logger = winston.createLogger({
|
||||
level: "info",
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const setLoggingLevelFromConfig = (config: CliConfig): void => {
|
||||
const newLevel = config.level ?? (config.debug ? "debug" : config.verbose ? "verbose" : "info");
|
||||
logger.level = newLevel;
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
import { Router } from "express";
|
||||
import { AdminUrls } from "../constants";
|
||||
import { logger } from "../logger";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post(AdminUrls.stop, (_req, res) => {
|
||||
logger.info("Recieved signal to stop server. Exiting...");
|
||||
res.status(202).end();
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
export const adminRoutes = router;
|
|
@ -0,0 +1,15 @@
|
|||
import { Router } from "express";
|
||||
import { coverageService } from "../services";
|
||||
|
||||
const router = Router();
|
||||
export const coverageRouter = router;
|
||||
|
||||
router.get("/report/clear", (_req, res) => {
|
||||
coverageService.reset();
|
||||
return res.status(200).send().end();
|
||||
});
|
||||
|
||||
router.get("/report/:category?", (req, res) => {
|
||||
const category = req.params.category?.toString() ?? "vanilla";
|
||||
return res.json(coverageService.getAllForCategory(category)).end();
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import { Router } from "express";
|
||||
import { adminRoutes } from "./admin";
|
||||
import { coverageRouter } from "./coverage";
|
||||
|
||||
const router = Router();
|
||||
router.use("/", adminRoutes);
|
||||
router.use("/", coverageRouter);
|
||||
|
||||
export const internalRouter = router;
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./server";
|
||||
export * from "./request-ext";
|
|
@ -0,0 +1,8 @@
|
|||
import { Request } from "express";
|
||||
|
||||
/**
|
||||
* Extension of the express.js request which include a rawBody.
|
||||
*/
|
||||
export interface RequestExt extends Request {
|
||||
rawBody?: string;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { ServerResponse } from "http";
|
||||
import bodyParser from "body-parser";
|
||||
import express, { ErrorRequestHandler, RequestHandler, Response } from "express";
|
||||
import morgan from "morgan";
|
||||
import { logger } from "../logger";
|
||||
import { cleanupBody } from "../utils";
|
||||
import { RequestExt } from "./request-ext";
|
||||
|
||||
export interface MockApiServerConfig {
|
||||
port: number;
|
||||
}
|
||||
|
||||
const errorHandler: ErrorRequestHandler = (err, _req, res, _next) => {
|
||||
logger.error("Error", err);
|
||||
res.status(err.status || 500);
|
||||
res
|
||||
.contentType("application/json")
|
||||
.send(err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : JSON.stringify(err))
|
||||
.end();
|
||||
};
|
||||
|
||||
const rawBodySaver = (req: RequestExt, res: ServerResponse, buf: Buffer, encoding: BufferEncoding) => {
|
||||
if (buf && buf.length) {
|
||||
req.rawBody = cleanupBody(buf.toString(encoding || "utf8"));
|
||||
}
|
||||
};
|
||||
|
||||
const loggerstream = {
|
||||
write: (message: string) => logger.info(message),
|
||||
};
|
||||
|
||||
export class MockApiServer {
|
||||
private app: express.Application;
|
||||
|
||||
constructor(private config: MockApiServerConfig) {
|
||||
this.app = express();
|
||||
this.app.use(morgan("dev", { stream: loggerstream }));
|
||||
this.app.use(bodyParser.json({ verify: rawBodySaver, strict: false }));
|
||||
this.app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
|
||||
this.app.use(bodyParser.text({ type: "*/pdf", verify: rawBodySaver }));
|
||||
}
|
||||
|
||||
public use(route: string, ...handlers: RequestHandler[]): void {
|
||||
this.app.use(route, ...handlers);
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.app.use(errorHandler);
|
||||
|
||||
this.app.listen(this.config.port, () => {
|
||||
logger.info(`Started server on port ${this.config.port}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type ServerRequestHandler = (request: RequestExt, response: Response) => void;
|
|
@ -0,0 +1,92 @@
|
|||
import fs from "fs";
|
||||
import { join } from "path";
|
||||
import { logger } from "../logger";
|
||||
import { ensureDir } from "../utils";
|
||||
|
||||
export interface CoverageMap {
|
||||
[name: string]: number;
|
||||
}
|
||||
|
||||
export class CoverageService {
|
||||
public coverageDirectory = "./coverage";
|
||||
|
||||
private coverage: { [category: string]: CoverageMap } = {
|
||||
defaultCategoryName: {},
|
||||
};
|
||||
|
||||
public getAllForCategory(category: string): CoverageMap {
|
||||
return this.coverage[category] ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Track usage of a scenario.
|
||||
* @param category Category for the coverage
|
||||
* @param name Name of the scenario.
|
||||
* @param value {Optional} For legacy test set the value of the usage.
|
||||
*/
|
||||
public async track(category: string, name: string, value?: number): Promise<void> {
|
||||
const map = this.coverage[category];
|
||||
if (!map) {
|
||||
throw new Error(`Unknown category '${category}'`);
|
||||
}
|
||||
|
||||
if (!(name in map)) {
|
||||
throw new Error(`Unknown coverage name '${name}' in '${category}'`);
|
||||
}
|
||||
|
||||
map[name] += 1;
|
||||
await this.saveCoverage(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* For LEGACY test only.
|
||||
* @depreacted
|
||||
*/
|
||||
public legacyTrack(category: string, name: string, value: number): void {
|
||||
let map = this.coverage[category];
|
||||
if (!map) {
|
||||
map = this.coverage[category] = {};
|
||||
}
|
||||
|
||||
if (!(name in map)) {
|
||||
map[name] = 0;
|
||||
}
|
||||
|
||||
map[name] = value;
|
||||
}
|
||||
|
||||
public register(category: string, name: string): void {
|
||||
let map = this.coverage[category];
|
||||
if (!map) {
|
||||
map = this.coverage[category] = {};
|
||||
}
|
||||
|
||||
if (name in map) {
|
||||
throw new Error(`Name '${name}' already exists in category '${category}' make sure it is unique.`);
|
||||
}
|
||||
|
||||
map[name] = 0;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
for (const categoryMap of Object.values(this.coverage)) {
|
||||
for (const name of Object.keys(categoryMap)) {
|
||||
categoryMap[name] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async saveCoverage(category: string) {
|
||||
const categoryMap = this.coverage[category];
|
||||
await ensureDir(this.coverageDirectory);
|
||||
const path = join(this.coverageDirectory, `report-${category}.json`);
|
||||
|
||||
try {
|
||||
await fs.promises.writeFile(path, JSON.stringify(categoryMap, null, 2));
|
||||
} catch (e) {
|
||||
logger.warn("Error while saving coverage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const coverageService = new CoverageService();
|
|
@ -0,0 +1 @@
|
|||
export * from "./coverage-service";
|
|
@ -0,0 +1,41 @@
|
|||
import { app, json } from "../../api";
|
||||
|
||||
app.category("azure", () => {
|
||||
// Initial call is 202 with no body and Location and Azure-AsyncOperation
|
||||
// Configured to follow Location
|
||||
// Then, should poll Azure-AsyncOperation and see it's done
|
||||
// Then, should do final GET on the initial Location
|
||||
// ARM guidance ok, and implemented in VM capture after 2018-04-01
|
||||
app.post("/lro/LROPostDoubleHeadersFinalLocationGet", "LROPostDoubleHeadersFinalLocationPost", (req) => {
|
||||
return {
|
||||
status: 202,
|
||||
headers: {
|
||||
"Azure-AsyncOperation": `${req.baseUrl}/lro/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl`,
|
||||
"Location": `${req.baseUrl}/lro/LROPostDoubleHeadersFinalLocationGet/location`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/lro/LROPostDoubleHeadersFinalLocationGet/asyncOperationUrl",
|
||||
"LROPostDoubleHeadersFinalLocationAsync",
|
||||
(req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
status: "succeeded",
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
app.get("/lro/LROPostDoubleHeadersFinalLocationGet/location", "LROPostDoubleHeadersFinalLocationGet", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
id: "100",
|
||||
name: "foo",
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { app, json } from "../../api";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
app.category("example" as any, () => {
|
||||
app.get("/test", "GetMyTest", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
foo: "succeeded",
|
||||
bar: "wut",
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/test", "PostMyTest", (req) => {
|
||||
req.bodyEquals({ foo: "123", bar: "456" });
|
||||
return {
|
||||
status: 200,
|
||||
body: json({
|
||||
succeeded: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
|
@ -0,0 +1,142 @@
|
|||
import { app, json } from "../api";
|
||||
|
||||
app.category("vanilla", () => {
|
||||
app.get("/string/null", "getStringNull", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: undefined,
|
||||
},
|
||||
};
|
||||
});
|
||||
app.put("/string/null", "putStringNull", (req) => {
|
||||
req.rawBodyEquals(undefined);
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/empty", "getStringEmpty", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: `""`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/empty", "putStringEmpty", (req) => {
|
||||
req.rawBodyEquals(`""`);
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/notProvided", "getStringNotProvided", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
};
|
||||
});
|
||||
|
||||
app.get("/string/whitespace", "getStringWithLeadingAndTrailingWhitespace", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: '" Now is the time for all good men to come to the aid of their country "',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/whitespace", "putStringWithLeadingAndTrailingWhitespace", (req) => {
|
||||
req.rawBodyEquals('" Now is the time for all good men to come to the aid of their country "');
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/base64UrlEncoding", "getStringBase64UrlEncoded", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: '"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw"',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/base64UrlEncoding", "putStringBase64UrlEncoded", (req) => {
|
||||
req.rawBodyEquals('"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw"');
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/base64Encoding", "getStringBase64Encoded", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: '"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjQ="',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.get("/string/nullBase64UrlEncoding", "getStringNullBase64UrlEncoding", (req) => {
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
const MULTIBYTE_BUFFER_BODY =
|
||||
"啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€";
|
||||
|
||||
app.get("/string/mbcs", "getStringMultiByteCharacters", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: `"${MULTIBYTE_BUFFER_BODY}"`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/mbcs", "putStringMultiByteCharacters", (req) => {
|
||||
req.bodyEquals(MULTIBYTE_BUFFER_BODY);
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/enum/notExpandable", "getEnumNotExpandable", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: '"red color"',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/enum/notExpandable", "putEnumNotExpandable", (req) => {
|
||||
req.rawBodyEquals('"red color"');
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/enum/Referenced", "getEnumReferenced", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
rawContent: '"red color"',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/enum/Referenced", "putEnumReferenced", (req) => {
|
||||
req.rawBodyEquals('"red color"');
|
||||
return { status: 200 };
|
||||
});
|
||||
|
||||
app.get("/string/enum/ReferencedConstant", "getEnumReferencedConstant", (req) => {
|
||||
return {
|
||||
status: 200,
|
||||
body: json({ field1: "Sample String" }),
|
||||
};
|
||||
});
|
||||
|
||||
app.put("/string/enum/ReferencedConstant", "putEnumReferencedConstant", (req) => {
|
||||
req.bodyEquals({ ColorConstant: "green-color" });
|
||||
return { status: 200 };
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import { cleanupBody } from "./body-utils";
|
||||
|
||||
describe("BodyUtils", () => {
|
||||
describe("cleanupBody()", () => {
|
||||
it("remove trailing whitespaces", () => {
|
||||
expect(cleanupBody(" foo ")).toEqual("foo");
|
||||
});
|
||||
|
||||
it("remove trailing new lines", () => {
|
||||
expect(cleanupBody("\nfoo\nbar\n")).toEqual("foo\nbar");
|
||||
});
|
||||
|
||||
it("replace windows line endings", () => {
|
||||
expect(cleanupBody("foo\r\nbar")).toEqual("foo\nbar");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Cleanup the raw content:
|
||||
* - trim whitespaces
|
||||
* - replace \r\n with \n
|
||||
* @param rawContent: raw content to clean.
|
||||
*/
|
||||
export const cleanupBody = (rawContent: string): string => rawContent.trim().replace(/\r?\n|\r/g, "\n");
|
|
@ -0,0 +1,21 @@
|
|||
import fs from "fs";
|
||||
import glob from "glob";
|
||||
|
||||
export const findFilesFromPattern = async (pattern: string): Promise<string[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(pattern, (err, matches) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(matches);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure the given dir exists.
|
||||
* @param path Path to the dir.
|
||||
*/
|
||||
export const ensureDir = async (path: string): Promise<void> => {
|
||||
await fs.promises.mkdir(path, { recursive: true });
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./body-utils";
|
||||
export * from "./file-utils";
|
||||
export * from "./request-utils";
|
|
@ -0,0 +1,3 @@
|
|||
import { Request } from "express";
|
||||
|
||||
export const getRequestBaseUrl = (request: Request): string => `${request.protocol}://${request.get("host")}`;
|
|
@ -0,0 +1,3 @@
|
|||
import { logger } from "../src/logger";
|
||||
|
||||
logger.silent = true;
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["**/*.test.*"]
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
"lib": ["ESNext"] /* Specify library files to be included in the compilation. */,
|
||||
// "allowJs": true /* Allow javascript files to be compiled. */,
|
||||
// "checkJs": true /* Report errors in .js files. */,
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist" /* Redirect output structure to the directory. */,
|
||||
// "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "definitions/**/*.d.ts"]
|
||||
}
|
Загрузка…
Ссылка в новой задаче