Skip to content

Commit

Permalink
feat: enhance cookie supports, add maxRedirects
Browse files Browse the repository at this point in the history
  • Loading branch information
sigoden committed Jun 17, 2021
1 parent b49dfeb commit d94196a
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 38 deletions.
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Read this in other languages: [中文](./README.zh-CN.md)
- [Echo](#echo)
- [Http](#http)
- [Options](#options)
- [cookie](#cookie)
- [x-www-form-urlencoded](#x-www-form-urlencoded)
- [multipart/form-data](#multipartform-data)
- [graphql](#graphql)
Expand Down Expand Up @@ -952,19 +953,59 @@ The `echo` client does not send any request, and directly returns the data in th
```js
{
// `baseURL` will be prepended to `url` unless `url` is absolute.
baseURL: 'https://some-domain.com/api/',
baseURL: '',
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout: 1000, // default is `0` (no timeout)
// `withCredentials` indicates whether or not cross-site Access-Control requests
// should be made using credentials
withCredentials: false, // default
timeout: 0,
// `withCredentials` Whether to provide credentials
withCredentials: true,
// `maxRedirects` defines the maximum number of redirects to follow in node.js. If set to 0, no redirects will be followed.
maxRedirects: 0,
// `headers` is default request headers
headers: {
}
}
```

#### cookie

```js
{
test1: {
req: {
url: "https://httpbin.org/cookies/set",
query: {
k1: "v1",
k2: "v2",
},
},
res: {
status: 302,
headers: { @partial
'set-cookie': [], @type
},
body: "", @type
}
},
test2: {
req: {
url: "https://httpbin.org/cookies",
headers: {
Cookie: `test1.res.headers["set-cookie"]`, @eval
}
},
res: {
body: { @partial
cookies: {
k1: "v1",
k2: "v2",
}
}
},
},
}
```

#### x-www-form-urlencoded

Add the request header `"content-type": "application/x-www-form-urlencoded"`
Expand Down
53 changes: 47 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Apitest 是一款使用类JSON的DSL编写测试用例的自动化测试工具
- [Echo](#echo)
- [Http](#http)
- [配置](#配置)
- [cookie](#cookie)
- [x-www-form-urlencoded](#x-www-form-urlencoded)
- [multipart/form-data](#multipartform-data)
- [graphql](#graphql)
Expand Down Expand Up @@ -943,14 +944,54 @@ Apitest 提供两种客户端。
```js
{
// `baseURL` 相对路径
baseURL: 'https://some-domain.com/api/',
baseURL: '',
// `timeout` 指定请求超时前的毫秒数。 如果请求时间超过`timeout`,请求将被中止。
timeout: 1000, // default is `0` (no timeout)
// `withCredentials` 表示是否跨站访问控制请求
withCredentials: false, // default
timeout: 0,
// `withCredentials` 是否提供凭据信息
withCredentials: true,
// `maxRedirects` 最大重定向数。如果设置为 0,则不会遵循重定向。
maxRedirects: 0,
// `headers` 默认请求头
headers: {
}
headers: {}
}
```

#### cookie

```
{
test1: {
req: {
url: "https://httpbin.org/cookies/set",
query: {
k1: "v1",
k2: "v2",
},
},
res: {
status: 302,
headers: { @partial
'set-cookie': [], @type
},
body: "", @type
}
},
test2: {
req: {
url: "https://httpbin.org/cookies",
headers: {
Cookie: `test1.res.headers["set-cookie"]`, @eval
}
},
res: {
body: { @partial
cookies: {
k1: "v1",
k2: "v2",
}
}
},
},
}
```

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
"@sigodenjs/fake": "^0.2.0",
"@types/lodash": "^4.14.170",
"axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1",
"chalk": "^4.1.1",
"form-data": "^4.0.0",
"jsona-js": "^0.5.1",
"lodash": "^4.17.21",
"tough-cookie": "^4.0.0",
"yargs": "^17.0.1"
},
"devDependencies": {
Expand Down
49 changes: 25 additions & 24 deletions src/Clients/HttpClient.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import axios, { AxiosRequestConfig } from "axios";
import axiosCookieJarSupport from "axios-cookiejar-support";
import * as _ from "lodash";
import * as qs from "querystring";
import * as FormData from "form-data";
import { Client } from ".";
import { Unit } from "../Cases";
import { checkValue, existAnno, schemaValidate, toPosString } from "../utils";
import { checkValue, createAnno, existAnno, schemaValidate, toPosString } from "../utils";
import { JsonaObject, JsonaString, JsonaValue } from "jsona-js";

axiosCookieJarSupport(axios);

export interface HttpClientOptions {
baseUrl?: string;
timeout?: number;
withCredentials?: boolean;
maxRedirects?: number;
headers?: Record<string, string>;
}

Expand All @@ -26,6 +30,9 @@ export const HTTP_OPTIONS_SCHEMA = {
withCredentials: {
type: "boolean",
},
maxRedirects: {
type: "integer",
},
headers: {
type: "object",
anyProperties: {
Expand All @@ -35,19 +42,26 @@ export const HTTP_OPTIONS_SCHEMA = {
},
};

export const DEFAULT_OPTIONS: HttpClientOptions = {
timeout: 0,
withCredentials: true,
maxRedirects: 0,
};


export default class HttpClient implements Client {
private options: HttpClientOptions;
public constructor(name: string, options: any) {
if (options) {
try {
schemaValidate(options, [], HTTP_OPTIONS_SCHEMA, true);
this.options = _.pick(options, ["baseURL", "timeout", "withCredentials", "headers"]);
this.options = _.pick(options, ["baseURL", "timeout", "withCredentials", "maxRedirects", "headers"]);
this.options = _.merge({}, DEFAULT_OPTIONS, this.options);
} catch (err) {
throw new Error(`[main@client(${name})[${err.paths.join(".")}] ${err.message}`);
}
} else {
this.options = {};
this.options = _.merge({}, DEFAULT_OPTIONS);
}
}

Expand All @@ -66,6 +80,9 @@ export default class HttpClient implements Client {
...(unit.client.options || {}),
url: req.url,
method: req.method,
validateStatus: () => true,
jar: true,
ignoreCookieErrors: true,
};
if (req.query) {
opts.params = req.query;
Expand Down Expand Up @@ -99,29 +116,12 @@ export default class HttpClient implements Client {
opts.data = req.body;
}
}
const result = {} as any;
let needHeader = false;
let needStatus = false;
if (unit.res) {
const res_ = unit.res as JsonaObject;
needHeader = !!res_.properties.find(v => v.key === "headers");
needStatus = !!res_.properties.find(v => v.key === "status");
}
try {
const axiosRes = await axios(opts);
if (needHeader) result.headers = axiosRes.headers;
if (needStatus) result.status = axiosRes.status;
result.body = axiosRes.data;
const { headers, status, data } = await axios(opts);
return { headers, status, body: data };
} catch (err) {
if (err.response) {
if (needHeader) result.headers = err.response.headers;
if (needStatus) result.status = err.response.status;
result.body = err.response.data;
} else {
throw err;
}
throw err;
}
return result;
}

private validateReq(paths: string[], req: JsonaValue) {
Expand Down Expand Up @@ -169,11 +169,12 @@ export default class HttpClient implements Client {

private validateRes(paths: string[], res: JsonaValue) {
if (!res) return;
res.annotations.push(createAnno("partial", null));
checkValue(paths, res, [
{ paths: [], type: "Object", required: true },
{ paths: ["status"], type: "Integer" },
{ paths: ["headers"], type: "Object" },
{ paths: ["headers", "*"], type: "Scalar", required: true },
{ paths: ["headers", "*"], type: "Header", required: true },
]);
}
}
8 changes: 6 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as _ from "lodash";
import * as fs from "fs/promises";
import * as crypto from "crypto";
import { parse } from "jsona-js";
import { JsonaAnnotation, parse } from "jsona-js";
import * as vm from "vm";
import { JsonaValue, JsonaObject, Position } from "jsona-js";
import { RunCaseError } from "./Reporter";
Expand Down Expand Up @@ -68,6 +68,10 @@ export function toPosString(position: Position) {
return ` at line ${position.line} col ${position.col}`;
}

export function createAnno(name: string, value: any): JsonaAnnotation {
return { name, value, position: {col:0,index:0,line:0} };
}

export function getType(value) {
if (value === null) {
return "null";
Expand Down Expand Up @@ -172,7 +176,7 @@ export function checkValue(paths: string[], value: JsonaValue, rules: CheckValue
export function ensureType(paths: string[], value: JsonaValue, type: string) {
if (value.type !== type) {
if (type === "Scalar" && (value.type !== "Object" && value.type !== "Array")) {

} else if (type === "Header" && (value.type === "String" || value.type === "Array")) {
} else {
throw new Error(`${[paths.join(".")]}: should be ${type.toLowerCase()} value${toPosString(value.position)}`);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/http.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ exports[`http invalid req value 1`] = `
`;

exports[`http invalid res headers prop type 1`] = `
"main.test1.res.headers.key: should be scalar value at line 8 col 14
"main.test1.res.headers.key: should be header value at line 8 col 14
"
`;

Expand Down
52 changes: 52 additions & 0 deletions tests/fixtures/http/cookie.jsona
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
test1: {
req: {
url: "https://httpbin.org/cookies/set",
query: {
k1: "v1",
k2: "v2",
},
},
res: {
status: 302,
headers: { @partial
'set-cookie': [], @type
},
body: "", @type
}
},
test2: { @client({options:{maxRedirects:1}})
req: {
url: "https://httpbin.org/cookies/set",
query: {
k1: "v1",
k2: "v2",
},
},
res: {
status: 200,
body: { @partial
cookies: {
k1: "v1",
k2: "v2",
}
}
}
},
test3: {
req: {
url: "https://httpbin.org/cookies",
headers: {
Cookie: `test1.res.headers["set-cookie"]`, @eval
}
},
res: {
body: { @partial
cookies: {
k1: "v1",
k2: "v2",
}
}
},
},
}
7 changes: 7 additions & 0 deletions tests/http.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,10 @@ describe("http form", () => {
expect(code).toEqual(0);
}, 60000);
});

describe("http cookie", () => {
test("cookie", async () => {
const { code } = await spwanTest("http/cookie.jsona", ["--ci"]);
expect(code).toEqual(0);
}, 60000);
});

0 comments on commit d94196a

Please sign in to comment.