@compassdigital/sdk.typescript 3.68.0 → 3.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +4 -0
- package/.eslintignore +4 -0
- package/.eslintrc.js +71 -0
- package/lib/base.d.ts +36 -35
- package/lib/base.d.ts.map +1 -1
- package/lib/base.js +68 -66
- package/lib/base.js.map +1 -1
- package/package.json +56 -52
- package/prettier.config.js +10 -0
- package/src/base.ts +425 -410
- package/test/client.test.ts +223 -226
package/test/client.test.ts
CHANGED
|
@@ -1,233 +1,230 @@
|
|
|
1
|
+
import { parse as parseURL } from 'url';
|
|
2
|
+
import { ServiceClient, ResponseData, ServiceError, InterceptFn } from '../src';
|
|
3
|
+
import { Task } from '../src/interface/task';
|
|
4
|
+
|
|
5
|
+
describe('ServiceClient', () => {
|
|
6
|
+
function response(status: number, body?: any, err?: any): ResponseData {
|
|
7
|
+
const res = { ok: status === 200, status, body: '', err: undefined };
|
|
8
|
+
if (body) {
|
|
9
|
+
res.body = JSON.stringify(body);
|
|
10
|
+
}
|
|
11
|
+
if (err) {
|
|
12
|
+
res.err = err;
|
|
13
|
+
}
|
|
14
|
+
return res;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function interceptor(status: number, body?: any, err?: any): InterceptFn {
|
|
18
|
+
return () => Promise.resolve(response(status, body, err));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('makes basic request', async () => {
|
|
22
|
+
const fake: Task = { id: 'some_id' };
|
|
23
|
+
const intercept = jest.fn(interceptor(200, fake));
|
|
24
|
+
const api = new ServiceClient({ intercept });
|
|
25
|
+
const task = await api.get_task('some_id');
|
|
26
|
+
expect(task).toEqual(fake);
|
|
27
|
+
const req = intercept.mock.calls[0][0];
|
|
28
|
+
expect(req).toEqual({
|
|
29
|
+
name: 'get_task',
|
|
30
|
+
service: 'task',
|
|
31
|
+
url: 'https://api.compassdigital.org/dev/task/some_id',
|
|
32
|
+
method: 'get',
|
|
33
|
+
headers: {
|
|
34
|
+
'User-Agent': 'CDL/ServiceClient',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('token gets passed', async () => {
|
|
40
|
+
const token = 'this is a token';
|
|
41
|
+
const intercept = jest.fn(interceptor(200));
|
|
42
|
+
const api = new ServiceClient({ intercept });
|
|
43
|
+
await api.get_task('', { token });
|
|
44
|
+
const req = intercept.mock.calls[0][0];
|
|
45
|
+
expect(req.headers).toHaveProperty('Authorization', `Bearer ${token}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('headers get merged', async () => {
|
|
49
|
+
const intercept = jest.fn(interceptor(200));
|
|
50
|
+
const api = new ServiceClient({
|
|
51
|
+
intercept,
|
|
52
|
+
headers: {
|
|
53
|
+
a: 'a from constructor',
|
|
54
|
+
b: 'b from constructor',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
await api.get_task('', {
|
|
58
|
+
headers: {
|
|
59
|
+
b: 'b from method',
|
|
60
|
+
c: 'c from method',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const req = intercept.mock.calls[0][0];
|
|
64
|
+
expect(req.headers).toEqual({
|
|
65
|
+
'User-Agent': 'CDL/ServiceClient',
|
|
66
|
+
a: 'a from constructor',
|
|
67
|
+
b: 'b from method',
|
|
68
|
+
c: 'c from method',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('client retries on bad status codes', async () => {
|
|
73
|
+
const intercept = jest.fn(async () => {
|
|
74
|
+
if (intercept.mock.calls.length === 3) {
|
|
75
|
+
return response(200, '{}');
|
|
76
|
+
}
|
|
77
|
+
return response(500, 'something went wrong');
|
|
78
|
+
});
|
|
79
|
+
const api = new ServiceClient({ intercept });
|
|
80
|
+
await api.get_task('', { retry: 2 });
|
|
81
|
+
expect(intercept.mock.calls.length).toBe(3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('client retries on network error', async () => {
|
|
85
|
+
const intercept = jest.fn(async () => {
|
|
86
|
+
if (intercept.mock.calls.length === 3) {
|
|
87
|
+
return response(200, '{}');
|
|
88
|
+
}
|
|
89
|
+
return response(0, '', new Error('network error'));
|
|
90
|
+
});
|
|
91
|
+
const api = new ServiceClient({ intercept });
|
|
92
|
+
await api.get_task('', { retry: 2 });
|
|
93
|
+
expect(intercept.mock.calls.length).toBe(3);
|
|
94
|
+
});
|
|
1
95
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function response(status: number, body?: any, err?: any): ResponseData {
|
|
9
|
-
const res = { ok: status === 200, status: status, body: "", err: undefined };
|
|
10
|
-
if (body) {
|
|
11
|
-
res.body = JSON.stringify(body);
|
|
12
|
-
}
|
|
13
|
-
if (err) {
|
|
14
|
-
res.err = err;
|
|
15
|
-
}
|
|
16
|
-
return res;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function interceptor(status: number, body?: any, err?: any): InterceptFn {
|
|
20
|
-
return () => Promise.resolve(response(status, body, err));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
test("makes basic request", async () => {
|
|
24
|
-
const fake: Task = { id: "some_id" };
|
|
25
|
-
const intercept = jest.fn(interceptor(200, fake));
|
|
26
|
-
const api = new ServiceClient({ intercept });
|
|
27
|
-
const task = await api.get_task("some_id");
|
|
28
|
-
expect(task).toEqual(fake);
|
|
29
|
-
const req = intercept.mock.calls[0][0];
|
|
30
|
-
expect(req).toEqual({
|
|
31
|
-
name: "get_task",
|
|
32
|
-
service: "task",
|
|
33
|
-
url: "https://api.compassdigital.org/dev/task/some_id",
|
|
34
|
-
method: "get",
|
|
35
|
-
headers: {
|
|
36
|
-
"User-Agent": "CDL/ServiceClient",
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("token gets passed", async () => {
|
|
42
|
-
const token = "this is a token";
|
|
43
|
-
const intercept = jest.fn(interceptor(200));
|
|
44
|
-
const api = new ServiceClient({ intercept });
|
|
45
|
-
await api.get_task("", { token });
|
|
46
|
-
const req = intercept.mock.calls[0][0];
|
|
47
|
-
expect(req.headers).toHaveProperty("Authorization", `Bearer ${token}`);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("headers get merged", async () => {
|
|
51
|
-
const intercept = jest.fn(interceptor(200));
|
|
52
|
-
const api = new ServiceClient({
|
|
53
|
-
intercept,
|
|
54
|
-
headers: {
|
|
55
|
-
"a": "a from constructor",
|
|
56
|
-
"b": "b from constructor",
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
await api.get_task("", {
|
|
60
|
-
headers: {
|
|
61
|
-
"b": "b from method",
|
|
62
|
-
"c": "c from method",
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
const req = intercept.mock.calls[0][0];
|
|
66
|
-
expect(req.headers).toEqual({
|
|
67
|
-
"User-Agent": "CDL/ServiceClient",
|
|
68
|
-
"a": "a from constructor",
|
|
69
|
-
"b": "b from method",
|
|
70
|
-
"c": "c from method",
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("client retries on bad status codes", async () => {
|
|
75
|
-
const intercept = jest.fn(async () => {
|
|
76
|
-
if (intercept.mock.calls.length === 3) {
|
|
77
|
-
return response(200, "{}");
|
|
78
|
-
}
|
|
79
|
-
return response(500, "something went wrong");
|
|
80
|
-
});
|
|
81
|
-
const api = new ServiceClient({ intercept });
|
|
82
|
-
await api.get_task("", { retry: 2 });
|
|
83
|
-
expect(intercept.mock.calls.length).toBe(3);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("client retries on network error", async () => {
|
|
87
|
-
const intercept = jest.fn(async () => {
|
|
88
|
-
if (intercept.mock.calls.length === 3) {
|
|
89
|
-
return response(200, "{}");
|
|
90
|
-
}
|
|
91
|
-
return response(0, "", new Error("network error"));
|
|
92
|
-
});
|
|
93
|
-
const api = new ServiceClient({ intercept });
|
|
94
|
-
await api.get_task("", { retry: 2 });
|
|
95
|
-
expect(intercept.mock.calls.length).toBe(3);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("client gives up after retry limit", async () => {
|
|
99
|
-
const intercept = jest.fn(interceptor(500));
|
|
100
|
-
const api = new ServiceClient({ intercept });
|
|
101
|
-
const promise = api.get_task("", { retry: 5 });
|
|
102
|
-
await expect(promise).rejects.toBeInstanceOf(ServiceError);
|
|
103
|
-
expect(intercept.mock.calls.length).toEqual(6);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("array query parameters are passed correctly", async () => {
|
|
107
|
-
const intercept = jest.fn(interceptor(200, {}));
|
|
108
|
-
const api = new ServiceClient({ intercept });
|
|
109
|
-
await api.get_menus({ query: { foo: "test", things: [1, 2, 3] }} as any);
|
|
110
|
-
expect(intercept.mock.calls.length).toBe(1);
|
|
111
|
-
const req = intercept.mock.calls[0][0];
|
|
112
|
-
const { query } = parseURL(req.url, true);
|
|
113
|
-
expect(query).toEqual({ foo: "test", things: ["1","2","3"] });
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("ServiceError", () => {
|
|
117
|
-
|
|
118
|
-
// see: https://github.com/microsoft/TypeScript/issues/13965
|
|
119
|
-
test("is instance of itself", async () => {
|
|
120
|
-
const err = new ServiceError(0, 0, "");
|
|
121
|
-
expect(err).toBeInstanceOf(ServiceError);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("returns null for ignored http status", async () => {
|
|
125
|
-
const api = new ServiceClient({
|
|
126
|
-
intercept: interceptor(501),
|
|
127
|
-
});
|
|
128
|
-
const task = await api.get_task("").ignore(501);
|
|
129
|
-
expect(task).toBeNull();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("returns null for ignored service error code", async () => {
|
|
133
|
-
const api = new ServiceClient({
|
|
134
|
-
intercept: interceptor(500, { code: 500.4, message: "uh oh" }),
|
|
135
|
-
});
|
|
136
|
-
const task = await api.get_task("").ignore(500.4);
|
|
137
|
-
expect(task).toBeNull();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("throws ServiceError for non-ignored errors", async () => {
|
|
141
|
-
const api = new ServiceClient({
|
|
142
|
-
intercept: interceptor(500, { code: 500.4, message: "hello world"}),
|
|
143
|
-
});
|
|
144
|
-
try {
|
|
145
|
-
await api.get_task("");
|
|
146
|
-
} catch (err) {
|
|
147
|
-
expect(err).toBeInstanceOf(ServiceError);
|
|
148
|
-
expect(ServiceError.status(err)).toBe(500);
|
|
149
|
-
expect(ServiceError.code(err)).toBe(500.4);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("throws ServiceError for network errors", async () => {
|
|
154
|
-
const api = new ServiceClient({
|
|
155
|
-
intercept: interceptor(0, null, new Error("Network Error")),
|
|
156
|
-
});
|
|
157
|
-
try {
|
|
158
|
-
await api.get_task("");
|
|
159
|
-
} catch (err) {
|
|
160
|
-
expect(err).toBeInstanceOf(ServiceError);
|
|
161
|
-
expect(ServiceError.status(err)).toBe(0);
|
|
162
|
-
expect(ServiceError.code(err)).toBe(0);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("either should be true with 200 response", async () => {
|
|
167
|
-
const api = new ServiceClient({
|
|
168
|
-
intercept: interceptor(200, "{}"),
|
|
169
|
-
});
|
|
170
|
-
const res = await api.get_task("{}").combine();
|
|
171
|
-
expect(res.ok).toBe(true);
|
|
172
|
-
expect(res.data).toBeTruthy();
|
|
173
|
-
expect(res.err).toBeNull();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("either should be false with non-200 response", async () => {
|
|
177
|
-
const api = new ServiceClient({
|
|
178
|
-
intercept: interceptor(400, "{}"),
|
|
179
|
-
});
|
|
180
|
-
const res = await api.get_task("{}").combine();
|
|
181
|
-
expect(res.ok).toBe(false);
|
|
182
|
-
expect(res.data).toBeNull();
|
|
183
|
-
expect(res.err).toBeInstanceOf(ServiceError);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("either should be false with matching code", async () => {
|
|
187
|
-
const api = new ServiceClient({
|
|
188
|
-
intercept: interceptor(400, "{}"),
|
|
189
|
-
});
|
|
190
|
-
const res = await api.get_task("{}").combine(400);
|
|
191
|
-
expect(res.ok).toBe(false);
|
|
192
|
-
expect(res.err).toBeInstanceOf(ServiceError);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("method should throw if code is not specified", async () => {
|
|
196
|
-
const api = new ServiceClient({
|
|
197
|
-
intercept: interceptor(400, "{}"),
|
|
198
|
-
});
|
|
199
|
-
const promise = api.get_task("").combine(500);
|
|
200
|
-
expect(promise).rejects.toThrow();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("method should throw custom error", async () => {
|
|
204
|
-
const err = new Error("my error");
|
|
205
|
-
const api = new ServiceClient({
|
|
206
|
-
intercept: interceptor(400, "{}"),
|
|
207
|
-
throws: err,
|
|
208
|
-
});
|
|
209
|
-
const promise = api.get_task("");
|
|
210
|
-
expect(promise).rejects.toThrow(err);
|
|
96
|
+
test('client gives up after retry limit', async () => {
|
|
97
|
+
const intercept = jest.fn(interceptor(500));
|
|
98
|
+
const api = new ServiceClient({ intercept });
|
|
99
|
+
const promise = api.get_task('', { retry: 5 });
|
|
100
|
+
await expect(promise).rejects.toBeInstanceOf(ServiceError);
|
|
101
|
+
expect(intercept.mock.calls.length).toEqual(6);
|
|
211
102
|
});
|
|
212
103
|
|
|
213
|
-
test(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
104
|
+
test('array query parameters are passed correctly', async () => {
|
|
105
|
+
const intercept = jest.fn(interceptor(200, {}));
|
|
106
|
+
const api = new ServiceClient({ intercept });
|
|
107
|
+
await api.get_menus({ query: { foo: 'test', things: [1, 2, 3] } } as any);
|
|
108
|
+
expect(intercept.mock.calls.length).toBe(1);
|
|
109
|
+
const req = intercept.mock.calls[0][0];
|
|
110
|
+
const { query } = parseURL(req.url, true);
|
|
111
|
+
expect(query).toEqual({ foo: 'test', things: ['1', '2', '3'] });
|
|
221
112
|
});
|
|
222
113
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
114
|
+
describe('ServiceError', () => {
|
|
115
|
+
// see: https://github.com/microsoft/TypeScript/issues/13965
|
|
116
|
+
test('is instance of itself', async () => {
|
|
117
|
+
const err = new ServiceError(0, 0, '');
|
|
118
|
+
expect(err).toBeInstanceOf(ServiceError);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('returns null for ignored http status', async () => {
|
|
122
|
+
const api = new ServiceClient({
|
|
123
|
+
intercept: interceptor(501),
|
|
124
|
+
});
|
|
125
|
+
const task = await api.get_task('').ignore(501);
|
|
126
|
+
expect(task).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('returns null for ignored service error code', async () => {
|
|
130
|
+
const api = new ServiceClient({
|
|
131
|
+
intercept: interceptor(500, { code: 500.4, message: 'uh oh' }),
|
|
132
|
+
});
|
|
133
|
+
const task = await api.get_task('').ignore(500.4);
|
|
134
|
+
expect(task).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('throws ServiceError for non-ignored errors', async () => {
|
|
138
|
+
const api = new ServiceClient({
|
|
139
|
+
intercept: interceptor(500, { code: 500.4, message: 'hello world' }),
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
await api.get_task('');
|
|
143
|
+
} catch (err) {
|
|
144
|
+
expect(err).toBeInstanceOf(ServiceError);
|
|
145
|
+
expect(ServiceError.status(err)).toBe(500);
|
|
146
|
+
expect(ServiceError.code(err)).toBe(500.4);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('throws ServiceError for network errors', async () => {
|
|
151
|
+
const api = new ServiceClient({
|
|
152
|
+
intercept: interceptor(0, null, new Error('Network Error')),
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
await api.get_task('');
|
|
156
|
+
} catch (err) {
|
|
157
|
+
expect(err).toBeInstanceOf(ServiceError);
|
|
158
|
+
expect(ServiceError.status(err)).toBe(0);
|
|
159
|
+
expect(ServiceError.code(err)).toBe(0);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('either should be true with 200 response', async () => {
|
|
164
|
+
const api = new ServiceClient({
|
|
165
|
+
intercept: interceptor(200, '{}'),
|
|
166
|
+
});
|
|
167
|
+
const res = await api.get_task('{}').combine();
|
|
168
|
+
expect(res.ok).toBe(true);
|
|
169
|
+
expect(res.data).toBeTruthy();
|
|
170
|
+
expect(res.err).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('either should be false with non-200 response', async () => {
|
|
174
|
+
const api = new ServiceClient({
|
|
175
|
+
intercept: interceptor(400, '{}'),
|
|
176
|
+
});
|
|
177
|
+
const res = await api.get_task('{}').combine();
|
|
178
|
+
expect(res.ok).toBe(false);
|
|
179
|
+
expect(res.data).toBeNull();
|
|
180
|
+
expect(res.err).toBeInstanceOf(ServiceError);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('either should be false with matching code', async () => {
|
|
184
|
+
const api = new ServiceClient({
|
|
185
|
+
intercept: interceptor(400, '{}'),
|
|
186
|
+
});
|
|
187
|
+
const res = await api.get_task('{}').combine(400);
|
|
188
|
+
expect(res.ok).toBe(false);
|
|
189
|
+
expect(res.err).toBeInstanceOf(ServiceError);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('method should throw if code is not specified', async () => {
|
|
193
|
+
const api = new ServiceClient({
|
|
194
|
+
intercept: interceptor(400, '{}'),
|
|
195
|
+
});
|
|
196
|
+
const promise = api.get_task('').combine(500);
|
|
197
|
+
expect(promise).rejects.toThrow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('method should throw custom error', async () => {
|
|
201
|
+
const err = new Error('my error');
|
|
202
|
+
const api = new ServiceClient({
|
|
203
|
+
intercept: interceptor(400, '{}'),
|
|
204
|
+
throws: err,
|
|
205
|
+
});
|
|
206
|
+
const promise = api.get_task('');
|
|
207
|
+
expect(promise).rejects.toThrow(err);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('combine and should work with custom error', async () => {
|
|
211
|
+
const api = new ServiceClient({
|
|
212
|
+
intercept: interceptor(400, '{}'),
|
|
213
|
+
throws: new Error('custom'),
|
|
214
|
+
});
|
|
215
|
+
const res = await api.get_task('{}').combine(400);
|
|
216
|
+
expect(res.ok).toBe(false);
|
|
217
|
+
expect(res.err).toBeInstanceOf(ServiceError);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('ignore and should work with custom error', async () => {
|
|
221
|
+
const err = new Error('custom');
|
|
222
|
+
const api = new ServiceClient({
|
|
223
|
+
intercept: interceptor(400, '{}'),
|
|
224
|
+
throws: err,
|
|
225
|
+
});
|
|
226
|
+
const res = await api.get_task('{}').ignore(400);
|
|
227
|
+
expect(res).toBeNull();
|
|
228
|
+
});
|
|
231
229
|
});
|
|
232
|
-
});
|
|
233
230
|
});
|