@aloma.io/integration-sdk 3.8.67 → 3.8.69
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/README.md +60 -0
- package/build/testing/fake-aloma.d.mts +87 -0
- package/build/testing/fake-aloma.mjs +248 -0
- package/build/testing/harness.d.mts +51 -0
- package/build/testing/harness.mjs +139 -0
- package/build/testing/index.d.mts +15 -0
- package/build/testing/index.mjs +13 -0
- package/build/testing/keys.d.mts +9 -0
- package/build/testing/keys.mjs +15 -0
- package/build/testing/network-guard.d.mts +3 -0
- package/build/testing/network-guard.mjs +49 -0
- package/build/testing/runtime-connector.d.mts +11 -0
- package/build/testing/runtime-connector.mjs +38 -0
- package/build/testing/vendor-mock.d.mts +59 -0
- package/build/testing/vendor-mock.mjs +121 -0
- package/build/transform/index.d.mts +2 -2
- package/build/transform/index.mjs +493 -78
- package/package.json +8 -4
- package/src/testing/fake-aloma.mts +306 -0
- package/src/testing/harness.mts +172 -0
- package/src/testing/index.mts +15 -0
- package/src/testing/keys.mts +19 -0
- package/src/testing/network-guard.mts +57 -0
- package/src/testing/runtime-connector.mts +50 -0
- package/src/testing/vendor-mock.mts +166 -0
- package/src/transform/index.mts +530 -96
- package/test/MUTATION-entry.md +21 -0
- package/test/MUTATION.md +43 -0
- package/test/fixtures/transform/basic/expected.json +9 -0
- package/test/fixtures/transform/basic/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/basic/src/controller/index.mts +30 -0
- package/test/fixtures/transform/basic/tsconfig.json +1 -0
- package/test/fixtures/transform/interface-param/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/interface-param/src/controller/index.mts +12 -0
- package/test/fixtures/transform/interface-param/tsconfig.json +1 -0
- package/test/fixtures/transform/overloads/expected.json +6 -0
- package/test/fixtures/transform/overloads/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/overloads/src/controller/index.mts +11 -0
- package/test/fixtures/transform/overloads/tsconfig.json +1 -0
- package/test/fixtures/transform/tdd-stub/expected.json +8 -0
- package/test/fixtures/transform/tdd-stub/src/controller/abstract.mts +6 -0
- package/test/fixtures/transform/tdd-stub/src/controller/index.mts +204 -0
- package/test/fixtures/transform/tdd-stub/tsconfig.json +1 -0
- package/test/fixtures/transform/two-exports/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/two-exports/src/controller/index.mts +9 -0
- package/test/fixtures/transform/two-exports/tsconfig.json +1 -0
- package/test/fixtures/transform/type-error/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/type-error/src/controller/index.mts +7 -0
- package/test/fixtures/transform/type-error/tsconfig.json +1 -0
- package/test/fixtures/transform/typed/expected.json +8 -0
- package/test/fixtures/transform/typed/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/typed/src/controller/index.mts +13 -0
- package/test/fixtures/transform/typed/tsconfig.json +1 -0
- package/test/fixtures/transform/visibility/expected.json +9 -0
- package/test/fixtures/transform/visibility/src/controller/abstract.mts +5 -0
- package/test/fixtures/transform/visibility/src/controller/index.mts +19 -0
- package/test/fixtures/transform/visibility/tsconfig.json +1 -0
- package/test/harness-connector-smoke.test.mjs +72 -0
- package/test/harness.test.mjs +375 -0
- package/test/openapi-to-connector.test.mts +25 -7
- package/test/scenarios.test.mts +15 -7
- package/test/transform.test.mjs +68 -0
- package/tools/mutation-run-entry.mjs +52 -0
- package/tools/mutation-run.mjs +120 -0
- package/tools/run-tests.mjs +64 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {installNetworkGuard, uninstallNetworkGuard} from './network-guard.mjs';
|
|
2
|
+
|
|
3
|
+
export type VendorRequest = {
|
|
4
|
+
method: string;
|
|
5
|
+
url: string;
|
|
6
|
+
/** the url exactly as the connector passed it to fetch */
|
|
7
|
+
rawUrl: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type VendorResponse = {
|
|
13
|
+
status?: number;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** sent as JSON with a json content-type */
|
|
16
|
+
json?: unknown;
|
|
17
|
+
/** sent as is */
|
|
18
|
+
text?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type VendorResponder = VendorResponse | ((request: VendorRequest) => VendorResponse | Promise<VendorResponse>);
|
|
22
|
+
export type UrlMatcher = string | RegExp | ((url: string) => boolean);
|
|
23
|
+
|
|
24
|
+
type Route = {
|
|
25
|
+
method: string;
|
|
26
|
+
matcher: UrlMatcher;
|
|
27
|
+
respond: VendorResponder;
|
|
28
|
+
times: number;
|
|
29
|
+
used: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// "https:/host/x" (one slash, which the SDK Fetcher produces) and "https://host/x" are the same URL to fetch
|
|
33
|
+
const canonical = (url: string): string => {
|
|
34
|
+
try {
|
|
35
|
+
return new URL(url).href;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const matches = (matcher: UrlMatcher, url: string): boolean => {
|
|
42
|
+
if (typeof matcher === 'string') return canonical(matcher) === url;
|
|
43
|
+
if (matcher instanceof RegExp) return matcher.test(url);
|
|
44
|
+
return matcher(url);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mock for the HTTP calls a connector makes to its vendor API.
|
|
49
|
+
*
|
|
50
|
+
* - intercepts global fetch (what the SDK Fetcher and OAuth code use)
|
|
51
|
+
* - answers from routes you register with on()/get()/post()...
|
|
52
|
+
* - any request without a route is recorded in `unmocked` and rejected,
|
|
53
|
+
* verify() then fails the test even if the connector swallowed the error
|
|
54
|
+
* - also installs the network guard, so nothing can reach a non-loopback host
|
|
55
|
+
*/
|
|
56
|
+
export class VendorMock {
|
|
57
|
+
readonly requests: VendorRequest[] = [];
|
|
58
|
+
readonly unmocked: VendorRequest[] = [];
|
|
59
|
+
private routes: Route[] = [];
|
|
60
|
+
private originalFetch?: typeof fetch;
|
|
61
|
+
private passthrough: (url: URL) => boolean = () => false;
|
|
62
|
+
|
|
63
|
+
/** urls for which the real fetch is used (the fake ALOMA endpoint); everything else is mocked */
|
|
64
|
+
allowPassthrough(fn: (url: URL) => boolean) {
|
|
65
|
+
this.passthrough = fn;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
on(method: string, matcher: UrlMatcher, respond: VendorResponder, options: {times?: number} = {}) {
|
|
70
|
+
this.routes.push({method: method.toUpperCase(), matcher, respond, times: options.times ?? Infinity, used: 0});
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get(matcher: UrlMatcher, respond: VendorResponder, options?: {times?: number}) {
|
|
75
|
+
return this.on('GET', matcher, respond, options);
|
|
76
|
+
}
|
|
77
|
+
post(matcher: UrlMatcher, respond: VendorResponder, options?: {times?: number}) {
|
|
78
|
+
return this.on('POST', matcher, respond, options);
|
|
79
|
+
}
|
|
80
|
+
put(matcher: UrlMatcher, respond: VendorResponder, options?: {times?: number}) {
|
|
81
|
+
return this.on('PUT', matcher, respond, options);
|
|
82
|
+
}
|
|
83
|
+
delete(matcher: UrlMatcher, respond: VendorResponder, options?: {times?: number}) {
|
|
84
|
+
return this.on('DELETE', matcher, respond, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
install() {
|
|
88
|
+
if (this.originalFetch) return this;
|
|
89
|
+
|
|
90
|
+
installNetworkGuard();
|
|
91
|
+
this.originalFetch = globalThis.fetch;
|
|
92
|
+
globalThis.fetch = ((input: any, init?: any) => this.handle(input, init)) as typeof fetch;
|
|
93
|
+
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
uninstall() {
|
|
98
|
+
if (!this.originalFetch) return;
|
|
99
|
+
|
|
100
|
+
globalThis.fetch = this.originalFetch;
|
|
101
|
+
this.originalFetch = undefined;
|
|
102
|
+
uninstallNetworkGuard();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** throws if the connector made a request that had no mock, even if it caught the error */
|
|
106
|
+
verify() {
|
|
107
|
+
if (this.unmocked.length) {
|
|
108
|
+
const list = this.unmocked.map((r) => `${r.method} ${r.url}`).join('\n ');
|
|
109
|
+
throw new Error(`UNMOCKED vendor requests were made (no network is allowed in tests):\n ${list}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** throws if a registered route was never used */
|
|
114
|
+
verifyAllRoutesUsed() {
|
|
115
|
+
const unused = this.routes.filter((r) => r.used === 0);
|
|
116
|
+
if (unused.length) {
|
|
117
|
+
throw new Error(`mocked routes never called: ${unused.map((r) => `${r.method} ${String(r.matcher)}`).join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async handle(input: any, init: any = {}): Promise<Response> {
|
|
122
|
+
const rawUrl: string = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
123
|
+
const url = canonical(rawUrl);
|
|
124
|
+
const method = String(init.method || (typeof input === 'object' && input.method) || 'GET').toUpperCase();
|
|
125
|
+
|
|
126
|
+
const headers: Record<string, string> = {};
|
|
127
|
+
new Headers(init.headers || (typeof input === 'object' && (input as any).headers) || {}).forEach((value, key) => {
|
|
128
|
+
headers[key] = value;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const request: VendorRequest = {method, url, rawUrl, headers};
|
|
132
|
+
if (init.body != null) request.body = typeof init.body === 'string' ? init.body : String(init.body);
|
|
133
|
+
|
|
134
|
+
const route = this.routes.find((r) => r.method === method && r.used < r.times && matches(r.matcher, url));
|
|
135
|
+
|
|
136
|
+
if (route) {
|
|
137
|
+
route.used++;
|
|
138
|
+
this.requests.push(request);
|
|
139
|
+
const spec = typeof route.respond === 'function' ? await route.respond(request) : route.respond;
|
|
140
|
+
|
|
141
|
+
const responseHeaders = {...spec.headers};
|
|
142
|
+
let body: string | null = null;
|
|
143
|
+
if (spec.json !== undefined) {
|
|
144
|
+
body = JSON.stringify(spec.json);
|
|
145
|
+
responseHeaders['content-type'] ||= 'application/json';
|
|
146
|
+
} else if (spec.text !== undefined) {
|
|
147
|
+
body = spec.text;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const status = spec.status ?? 200;
|
|
151
|
+
return new Response([204, 205, 304].includes(status) ? null : body, {status, headers: responseHeaders});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (this.originalFetch && this.passthrough(new URL(url))) {
|
|
155
|
+
return this.originalFetch(input, init);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.unmocked.push(request);
|
|
159
|
+
|
|
160
|
+
// status 400 makes the SDK Fetcher give up at once instead of retrying for seconds
|
|
161
|
+
const error: any = new Error(`UNMOCKED vendor request: ${method} ${url} (add a route with vendor.on(...))`);
|
|
162
|
+
error.status = 400;
|
|
163
|
+
error.alomaHarnessUnmocked = true;
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|