@aloma.io/integration-sdk 3.8.68 → 3.8.70
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/FINDINGS-PLAT-47.md +253 -0
- package/OPENAPI_TO_CONNECTOR.md +4 -1
- package/build/internal/fetcher/fetcher.d.mts +8 -0
- package/build/internal/fetcher/fetcher.mjs +47 -3
- package/build/openapi-to-connector.mjs +5 -2
- package/build/transform/index.d.mts +2 -2
- package/build/transform/index.mjs +493 -78
- package/package.json +3 -3
- package/src/internal/fetcher/fetcher.mts +52 -3
- package/src/openapi-to-connector.mts +6 -2
- package/src/transform/index.mts +530 -96
- package/test/fetcher-query-params.test.mjs +294 -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/openapi-to-connector.test.mts +7 -0
- package/test/transform.test.mjs +68 -0
- package/tools/mutation-run.mjs +23 -5
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "\n/**\n * \n *\n * \n * \n **/ \ndeclare function typed({email; count; tags; mode; either; nested}: {\n/**\n\n */\n email: string; \n/**\n\n */\n count: any; \n/**\n\n */\n tags: string[]; \n/**\n\n */\n mode: any; \n/**\n\n */\n either: any; \n/**\n\n */\n nested: any}): { ok: boolean; tags: number[]; };\n \n/**\n * \n *\n * \n * \n **/ \ndeclare function tuple(): [string, number];\n \n/**\n * \n *\n * \n * \n **/ \ndeclare function maybe(): string | undefined;\n ",
|
|
3
|
+
"methods": [
|
|
4
|
+
"typed",
|
|
5
|
+
"tuple",
|
|
6
|
+
"maybe"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {AbstractController} from './abstract.mjs';
|
|
2
|
+
|
|
3
|
+
export default class Controller extends AbstractController {
|
|
4
|
+
async typed({email, count, tags, mode, either, nested}: {email: string; count?: number; tags: string[]; mode: 'a' | 'b'; either: string | number; nested: {x: number}}) {
|
|
5
|
+
return {ok: true, tags: [1, 2]};
|
|
6
|
+
}
|
|
7
|
+
async tuple(): Promise<[string, number]> {
|
|
8
|
+
return ['a', 1];
|
|
9
|
+
}
|
|
10
|
+
async maybe(): Promise<string | undefined> {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"compilerOptions":{"target":"es2022","module":"node16","moduleResolution":"node16","skipLibCheck":true,"strict":true,"noImplicitAny":false,"experimentalDecorators":true,"esModuleInterop":true},"include":["src/**/*"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "\n/**\n * \n *\n * \n * \n **/ \ndeclare function visible({a}: {\n/**\n\n */\n a: string}): string;\n \n/**\n * \n *\n * \n * \n **/ \ndeclare function quoted-name({a}: {\n/**\n\n */\n a: string}): void;\n \n/**\n * \n *\n * \n * \n **/ \ndeclare function arrow({a}: {\n/**\n\n */\n a: string}): string;\n \n/**\n * \n *\n * \n * \n **/ \ndeclare function inheritedPublic(): void;\n ",
|
|
3
|
+
"methods": [
|
|
4
|
+
"visible",
|
|
5
|
+
"quoted-name",
|
|
6
|
+
"arrow",
|
|
7
|
+
"inheritedPublic"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {AbstractController} from './abstract.mjs';
|
|
2
|
+
|
|
3
|
+
export default class Controller extends AbstractController {
|
|
4
|
+
async visible({a}: {a: string}) {
|
|
5
|
+
return a;
|
|
6
|
+
}
|
|
7
|
+
private async hidden({a}: {a: string}) {}
|
|
8
|
+
protected async guarded({a}: {a: string}) {}
|
|
9
|
+
static async stat({a}: {a: string}) {}
|
|
10
|
+
async _underscore({a}: {a: string}) {}
|
|
11
|
+
async 'quoted-name'({a}: {a: string}) {}
|
|
12
|
+
async '_quoted_private'({a}: {a: string}) {}
|
|
13
|
+
arrow = async ({a}: {a: string}) => a;
|
|
14
|
+
notAMethod = 5;
|
|
15
|
+
get getter() {
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
override async inheritedPublic() {}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"compilerOptions":{"target":"es2022","module":"node16","moduleResolution":"node16","skipLibCheck":true,"strict":true,"noImplicitAny":false,"experimentalDecorators":true,"esModuleInterop":true},"include":["src/**/*"]}
|
|
@@ -138,6 +138,13 @@ paths:
|
|
|
138
138
|
}).to.throw('Invalid OpenAPI 3.x specification');
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
it('names the field that fails validation (zod 4 reports issues, not errors)', () => {
|
|
142
|
+
const missingTitle = {openapi: '3.0.0', info: {version: '1.0.0'}, paths: {}};
|
|
143
|
+
expect(() => {
|
|
144
|
+
OpenAPIToConnector.parseSpec(JSON.stringify(missingTitle));
|
|
145
|
+
}).to.throw(/Invalid OpenAPI 3\.x specification: info\.title: /);
|
|
146
|
+
});
|
|
147
|
+
|
|
141
148
|
it('should reject invalid JSON/YAML', () => {
|
|
142
149
|
expect(() => {
|
|
143
150
|
OpenAPIToConnector.parseSpec('invalid json {');
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// The transform reads a connector's controller source and returns the method declarations (`text`) and names
|
|
2
|
+
// (`methods`) that `cli build` writes to build/.controller.json. It replaced @ts-ast-parser/core (engines stop at
|
|
3
|
+
// Node 20), so most cases below are PARITY cases: expected.json was produced by the previous implementation (the
|
|
4
|
+
// ts-ast-parser one, SDK 3.8.68) on the same source, and the new one must print exactly the same. The same check
|
|
5
|
+
// ran on 58 real connector controllers (all of aloma-io/connector-*) with no difference.
|
|
6
|
+
// Needs `yarn build` first. Offline.
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import {fileURLToPath} from 'node:url';
|
|
11
|
+
import {describe, it} from 'mocha';
|
|
12
|
+
import transform from '../build/transform/index.mjs';
|
|
13
|
+
|
|
14
|
+
const fixtures = path.join(path.dirname(fileURLToPath(import.meta.url)), 'fixtures', 'transform');
|
|
15
|
+
|
|
16
|
+
// the transform reads tsconfig.json from the working directory, as `cli build` runs it from the connector folder
|
|
17
|
+
const run = async (name) => {
|
|
18
|
+
const before = process.cwd();
|
|
19
|
+
process.chdir(path.join(fixtures, name));
|
|
20
|
+
try {
|
|
21
|
+
return await transform('./src/controller/index.mts');
|
|
22
|
+
} finally {
|
|
23
|
+
process.chdir(before);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const expected = (name) => JSON.parse(fs.readFileSync(path.join(fixtures, name, 'expected.json'), 'utf8'));
|
|
28
|
+
|
|
29
|
+
describe('transform (controller source to declarations)', function () {
|
|
30
|
+
this.timeout(60000);
|
|
31
|
+
|
|
32
|
+
for (const [name, what] of [
|
|
33
|
+
['basic', 'JSDoc description, @param, @example, @namespace, defaults, return types'],
|
|
34
|
+
['visibility', 'public, private, protected, static, underscore, quoted names, arrow property, getter, inherited'],
|
|
35
|
+
['typed', 'typed object parameters, unions, arrays, tuples, optional'],
|
|
36
|
+
['overloads', 'one declaration per overload signature'],
|
|
37
|
+
['tdd-stub', 'the real connector-tdd-stub controller'],
|
|
38
|
+
]) {
|
|
39
|
+
it(`prints exactly what the previous implementation printed: ${name} (${what})`, async () => {
|
|
40
|
+
assert.deepEqual(await run(name), expected(name));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
it('lists the public methods the class declares or overrides, not inherited, private, static or underscore ones', async () => {
|
|
45
|
+
const {methods} = await run('visibility');
|
|
46
|
+
assert.deepEqual(methods, ['visible', 'quoted-name', 'arrow', 'inheritedPublic']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('lists the properties of a parameter typed with an interface (the previous implementation crashed)', async () => {
|
|
50
|
+
const {text, methods} = await run('interface-param');
|
|
51
|
+
assert.deepEqual(methods, ['viaInterface']);
|
|
52
|
+
assert.match(text, /declare function viaInterface\(\{id; n\}: \{/);
|
|
53
|
+
assert.match(text, /\n id: string; /);
|
|
54
|
+
assert.match(text, /\n n: number \| undefined\}\): \{ id: string; n: number \| undefined; \};/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('fails when the file exports more than the default class', async () => {
|
|
58
|
+
await assert.rejects(run('two-exports'), /connector file needs to export default class/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('fails with the TypeScript error text when the source does not compile (the previous one died on a circular JSON error)', async () => {
|
|
62
|
+
await assert.rejects(run('type-error'), (err) => {
|
|
63
|
+
assert.match(err.message, /has TypeScript errors/);
|
|
64
|
+
assert.match(err.message, /index\.mts\(5,5\).*TS2322.*'string' is not assignable to type 'number'/);
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
package/tools/mutation-run.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// yarn build && yarn test:mutation
|
|
4
4
|
//
|
|
5
|
-
// Applies each deliberate single-point bug below to src/, rebuilds, runs the harness tests
|
|
6
|
-
// to FAIL. The original file is restored after every mutant (and on Ctrl-C). Writes test/MUTATION.md.
|
|
5
|
+
// Applies each deliberate single-point bug below to src/, rebuilds, runs the harness tests (or, for the F mutants,
|
|
6
|
+
// test/fetcher-query-params.test.mjs) and expects them to FAIL. The original file is restored after every mutant (and on Ctrl-C). Writes test/MUTATION.md.
|
|
7
7
|
// Not part of CI: it rebuilds the SDK ~30 times. Run it when the harness or the SDK paths it covers change.
|
|
8
8
|
import {spawnSync} from 'node:child_process';
|
|
9
9
|
import fs from 'node:fs';
|
|
@@ -47,6 +47,19 @@ const MUTANTS = [
|
|
|
47
47
|
['M30', 'harness fake ALOMA', 'testing/fake-aloma.mts', "{query: method, variables, ...ctx}", "{query: method, variables}", 'call() drops the ctx fields'],
|
|
48
48
|
['M31', 'harness', 'testing/harness.mts', 'this.vendor.verify();', '', 'stop() no longer fails on unmocked requests'],
|
|
49
49
|
['M32', 'harness', 'testing/harness.mts', "await aloma.setConfig(options.config || {});", "", 'the harness never sends configuration'],
|
|
50
|
+
// PLAT-47: Fetcher query parameters, checked by test/fetcher-query-params.test.mjs
|
|
51
|
+
['F01', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'const fullURL = withQuery(theURL, toQuery(options0.params));', 'const fullURL = theURL;', 'params are not sent at all (the PLAT-47 bug)'],
|
|
52
|
+
['F02', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (Array.isArray(value)) value.forEach((item) => add(key, item));\n else add(key, value);', 'add(key, value);', 'array handling dropped (arrays are refused as objects)'],
|
|
53
|
+
['F03', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (Array.isArray(value)) value.forEach((item) => add(key, item));', "if (Array.isArray(value)) add(key, value.join(','));", 'arrays are comma-joined instead of repeated keys'],
|
|
54
|
+
['F04', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (value == null) return;', 'if (value === null) return;', 'undefined values are not skipped'],
|
|
55
|
+
['F05', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (value == null) return;', 'if (value === undefined) return;', 'null values are not skipped'],
|
|
56
|
+
['F06', 'sdk fetcher', 'internal/fetcher/fetcher.mts', " if (typeof value === 'object') {", ' if (false) {', 'nested objects are sent instead of refused'],
|
|
57
|
+
['F07', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (e instanceof ParamsError) throw e;', '', 'a refused params object is retried'],
|
|
58
|
+
['F08', 'sdk fetcher', 'internal/fetcher/fetcher.mts', "const sep = !base.includes('?') ? '?'", "const sep = true ? '?'", 'a query already in the url gets a second "?"'],
|
|
59
|
+
['F09', 'sdk fetcher', 'internal/fetcher/fetcher.mts', ' ...init,\n', ' ...options0,\n', 'params is passed on to native fetch'],
|
|
60
|
+
['F10', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'const {params, ...init} = options0;', 'const {params, ...init} = options0;\n delete options0.params;', 'params are dropped from options0, so a retry loses the query'],
|
|
61
|
+
['F11', 'sdk fetcher', 'internal/fetcher/fetcher.mts', "console.log(theURL.split('?')[0], e);", 'console.log(theURL, e);', 'a failed attempt logs query values'],
|
|
62
|
+
['F12', 'sdk fetcher', 'internal/fetcher/fetcher.mts', 'if (params instanceof URLSearchParams) return params.toString();', '', 'a URLSearchParams instance is silently dropped'],
|
|
50
63
|
];
|
|
51
64
|
|
|
52
65
|
const run = (cmd, args, timeout) =>
|
|
@@ -57,6 +70,8 @@ const build = () => {
|
|
|
57
70
|
if (r.status !== 0) throw new Error('build failed:\n' + r.stdout + r.stderr);
|
|
58
71
|
};
|
|
59
72
|
const mocha = () => run('node', ['./node_modules/mocha/bin/_mocha', '--bail', '--exit', 'test/harness.test.mjs', 'test/harness-connector-smoke.test.mjs'], 150000);
|
|
73
|
+
const fetcherTests = () => run('node', ['test/fetcher-query-params.test.mjs'], 150000);
|
|
74
|
+
const testsFor = (id) => (id.startsWith('F') ? fetcherTests() : mocha());
|
|
60
75
|
|
|
61
76
|
let current = null;
|
|
62
77
|
const restore = () => {
|
|
@@ -73,6 +88,8 @@ try {
|
|
|
73
88
|
build();
|
|
74
89
|
const baseline = mocha();
|
|
75
90
|
if (baseline.status !== 0) throw new Error('baseline run must pass before mutating:\n' + baseline.stdout.slice(-2000));
|
|
91
|
+
const fetcherBaseline = fetcherTests();
|
|
92
|
+
if (fetcherBaseline.status !== 0) throw new Error('fetcher baseline must pass:\n' + fetcherBaseline.stdout.slice(-2000));
|
|
76
93
|
|
|
77
94
|
for (const [id, area, rel, find, replace, bug] of MUTANTS) {
|
|
78
95
|
const file = src(rel);
|
|
@@ -85,8 +102,8 @@ try {
|
|
|
85
102
|
try {
|
|
86
103
|
fs.writeFileSync(file, original.replace(find, () => replace));
|
|
87
104
|
build();
|
|
88
|
-
const r =
|
|
89
|
-
const failing = (r.stdout.match(/^\s+\d+\) .+$/gm) || [])[0]?.trim() || '';
|
|
105
|
+
const r = testsFor(id);
|
|
106
|
+
const failing = (r.stdout.match(/^\s+\d+\) .+$|FAIL: .+$/gm) || [])[0]?.trim() || '';
|
|
90
107
|
outcome = r.error?.code === 'ETIMEDOUT' ? 'killed (timeout)' : r.status !== 0 ? 'killed' : 'SURVIVED';
|
|
91
108
|
results.push({id, area, rel, bug, outcome, by: failing});
|
|
92
109
|
} finally {
|
|
@@ -107,7 +124,8 @@ const md = [
|
|
|
107
124
|
'',
|
|
108
125
|
'Each row is one deliberate single-point bug in the SDK code paths the harness exercises (handshake, transport,',
|
|
109
126
|
'dispatch, result return, config) or in the harness itself. `tools/mutation-run.mjs` applies it to `src/`, rebuilds,',
|
|
110
|
-
'runs `test/harness.test.mjs` with `--bail
|
|
127
|
+
'runs `test/harness.test.mjs` with `--bail` (F rows: `test/fetcher-query-params.test.mjs`), expects a failure, and',
|
|
128
|
+
'restores the file. Re-run with',
|
|
111
129
|
'`yarn build && yarn test:mutation` after changing the harness or those SDK paths.',
|
|
112
130
|
'',
|
|
113
131
|
'| # | Area | File | Bug | Result |',
|