@aloma.io/integration-sdk 3.8.68 → 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/build/transform/index.d.mts +2 -2
- package/build/transform/index.mjs +493 -78
- package/package.json +2 -2
- package/src/transform/index.mts +530 -96
- 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/transform.test.mjs +68 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {AbstractController} from './abstract.mjs';
|
|
2
|
+
|
|
3
|
+
export default class Controller extends AbstractController {
|
|
4
|
+
/**
|
|
5
|
+
* greet somebody
|
|
6
|
+
* @param name - who to greet, e.g. Ann
|
|
7
|
+
* @param times - how often
|
|
8
|
+
* @param opts - extra \@options
|
|
9
|
+
* @namespace demo
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```javascript
|
|
13
|
+
* greet({name: 'Ann'})
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
async greet({name, times = 2, loud = false, label = 'x', neg = -1, nothing = null}, other = 1) {
|
|
17
|
+
return {ok: true, greeting: name + times};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** no params */
|
|
21
|
+
async ping() {
|
|
22
|
+
return {ok: true};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async list(): Promise<string[]> {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async nothing(): Promise<void> {}
|
|
30
|
+
}
|
|
@@ -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,12 @@
|
|
|
1
|
+
import {AbstractController} from './abstract.mjs';
|
|
2
|
+
|
|
3
|
+
interface Opts {
|
|
4
|
+
id: string;
|
|
5
|
+
n?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default class Controller extends AbstractController {
|
|
9
|
+
async viaInterface({id, n}: Opts): Promise<{id: string; n: number | undefined}> {
|
|
10
|
+
return {id, n};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -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,11 @@
|
|
|
1
|
+
import {AbstractController} from './abstract.mjs';
|
|
2
|
+
|
|
3
|
+
export default class Controller extends AbstractController {
|
|
4
|
+
/** first form */
|
|
5
|
+
async over({a}: {a: string}): Promise<string>;
|
|
6
|
+
/** second form */
|
|
7
|
+
async over({b}: {b: number}): Promise<number>;
|
|
8
|
+
async over(args: any): Promise<any> {
|
|
9
|
+
return args;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -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,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "\n/**\n * Returns recorded calls scoped to the current task.\n *\n * \n * @example usage\n```javascript\nconst log = await connectors.testing.getCallLog({})\n// log.calls = [{method, args, timestamp}, ...] for THIS task only\n```\n **/ \ndeclare function getCallLog(): { calls: CallRecord[]; count: number; };\n \n/**\n * Resets the call log for the current task only.\n *\n * \n * @example usage\n```javascript\nawait connectors.testing.clearCallLog({})\n```\n **/ \ndeclare function clearCallLog(): { cleared: boolean; };\n \n/**\n * Updates the response map for the current task at runtime.\n *\n * \n * @example usage\n```javascript\nawait connectors.testing.setResponses({\n responses: { \"someConnector.create\": { id: \"456\" } }\n})\n```\n **/ \ndeclare function setResponses(): { updated: boolean; responseCount: number; };\n ",
|
|
3
|
+
"methods": [
|
|
4
|
+
"getCallLog",
|
|
5
|
+
"clearCallLog",
|
|
6
|
+
"setResponses"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { AbstractController } from "./abstract.mjs";
|
|
2
|
+
|
|
3
|
+
interface CallRecord {
|
|
4
|
+
method: string;
|
|
5
|
+
args: any;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Per-task scoped mock connector for ALOMA test workflows.
|
|
11
|
+
*
|
|
12
|
+
* State (responseMap, callLog) is keyed by ctx.taskId so concurrent
|
|
13
|
+
* test tasks against the same Testing pod do not contaminate each
|
|
14
|
+
* other's stubbed responses or recorded calls.
|
|
15
|
+
*
|
|
16
|
+
* Requires @aloma.io/integration-sdk ^3.8.67, which forwards
|
|
17
|
+
* { taskId, namespace, sticky } as the second arg to controller
|
|
18
|
+
* method handlers and to the __default fallback.
|
|
19
|
+
*
|
|
20
|
+
* Backward compatibility: when ctx is missing (legacy invocation or
|
|
21
|
+
* non-task call from admin UI), state falls into the "__no_ctx__"
|
|
22
|
+
* bucket so the connector does not crash. Isolation is NOT guaranteed
|
|
23
|
+
* in that mode — it is degraded compatibility only.
|
|
24
|
+
*/
|
|
25
|
+
export default class Controller extends AbstractController {
|
|
26
|
+
// Per-task scoped state — Maps keyed by taskId (insertion order = LRU order)
|
|
27
|
+
private responseMapByTask: Map<string, Record<string, any>> = new Map();
|
|
28
|
+
private callLogByTask: Map<string, CallRecord[]> = new Map();
|
|
29
|
+
private recording: boolean = true;
|
|
30
|
+
private defaultResponses: Record<string, any> = {};
|
|
31
|
+
// Memory cap (LRU). Configurable via config.maxTasks; default 1000.
|
|
32
|
+
private maxTasks: number = 1000;
|
|
33
|
+
|
|
34
|
+
protected async start() {
|
|
35
|
+
this.loadConfig();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private loadConfig() {
|
|
39
|
+
const config = this.config;
|
|
40
|
+
|
|
41
|
+
// Parse default responses from connector config — these seed each
|
|
42
|
+
// task's bucket when first touched.
|
|
43
|
+
if (config.responses) {
|
|
44
|
+
try {
|
|
45
|
+
this.defaultResponses =
|
|
46
|
+
typeof config.responses === "string"
|
|
47
|
+
? JSON.parse(config.responses)
|
|
48
|
+
: config.responses;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
this.defaultResponses = {};
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
this.defaultResponses = {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.recording = config.recordCalls !== "false";
|
|
57
|
+
|
|
58
|
+
// Allow config-driven override of LRU cap. Default 1000 if not set
|
|
59
|
+
// or not a positive number.
|
|
60
|
+
const configured = Number(config.maxTasks);
|
|
61
|
+
if (Number.isFinite(configured) && configured > 0) {
|
|
62
|
+
this.maxTasks = Math.floor(configured);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the bucket key for the current invocation. When ctx is
|
|
68
|
+
* absent or lacks taskId, falls back to a single shared bucket so
|
|
69
|
+
* legacy / hand-rolled callers do not crash. Isolation is not
|
|
70
|
+
* guaranteed in that mode.
|
|
71
|
+
*/
|
|
72
|
+
private bucketKey(ctx?: { taskId?: string }): string {
|
|
73
|
+
return ctx && ctx.taskId ? ctx.taskId : "__no_ctx__";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* LRU touch — ensures the bucket exists for the given key, evicting
|
|
78
|
+
* the least-recently-used entry if the cap would be exceeded. Map
|
|
79
|
+
* iteration order is insertion order; reinserting promotes a key to
|
|
80
|
+
* "most recently used" position.
|
|
81
|
+
*/
|
|
82
|
+
private touchTask(key: string) {
|
|
83
|
+
// If not present and at cap, evict the oldest (insertion order)
|
|
84
|
+
if (!this.responseMapByTask.has(key)) {
|
|
85
|
+
if (this.responseMapByTask.size >= this.maxTasks) {
|
|
86
|
+
const firstKey = this.responseMapByTask.keys().next().value as
|
|
87
|
+
| string
|
|
88
|
+
| undefined;
|
|
89
|
+
if (firstKey !== undefined) {
|
|
90
|
+
this.responseMapByTask.delete(firstKey);
|
|
91
|
+
this.callLogByTask.delete(firstKey);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.responseMapByTask.set(key, { ...this.defaultResponses });
|
|
95
|
+
this.callLogByTask.set(key, []);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Catch-all handler — registered as __default by the SDK via the
|
|
101
|
+
* fallback override below. Looks up the response in the task-scoped
|
|
102
|
+
* map, records the call in the task-scoped log, and returns the
|
|
103
|
+
* configured response or a default stub acknowledgement.
|
|
104
|
+
*/
|
|
105
|
+
private async handleStubCall(
|
|
106
|
+
arg: any,
|
|
107
|
+
ctx?: { taskId?: string },
|
|
108
|
+
): Promise<any> {
|
|
109
|
+
const key = this.bucketKey(ctx);
|
|
110
|
+
this.touchTask(key);
|
|
111
|
+
|
|
112
|
+
const method = Array.isArray(arg?.__method)
|
|
113
|
+
? arg.__method.join(".")
|
|
114
|
+
: arg?.__method || "unknown";
|
|
115
|
+
|
|
116
|
+
if (this.recording) {
|
|
117
|
+
const log = this.callLogByTask.get(key)!;
|
|
118
|
+
log.push({
|
|
119
|
+
method,
|
|
120
|
+
args: { ...arg, __method: undefined },
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const responseMap = this.responseMapByTask.get(key)!;
|
|
126
|
+
if (responseMap[method] !== undefined) {
|
|
127
|
+
return responseMap[method];
|
|
128
|
+
}
|
|
129
|
+
if (responseMap["default"] !== undefined) {
|
|
130
|
+
return responseMap["default"];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { stubbed: true, method };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* SDK override hook — invoked by AbstractController.__default with
|
|
138
|
+
* the per-invocation ctx (taskId, namespace, sticky) as second arg.
|
|
139
|
+
*/
|
|
140
|
+
protected fallback(arg: any, ctx?: { taskId?: string }): Promise<any> {
|
|
141
|
+
return this.handleStubCall(arg, ctx);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns recorded calls scoped to the current task.
|
|
146
|
+
* @example
|
|
147
|
+
* ```javascript
|
|
148
|
+
* const log = await connectors.testing.getCallLog({})
|
|
149
|
+
* // log.calls = [{method, args, timestamp}, ...] for THIS task only
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
async getCallLog(_args: any, ctx?: { taskId?: string }) {
|
|
153
|
+
const key = this.bucketKey(ctx);
|
|
154
|
+
const calls = this.callLogByTask.get(key) || [];
|
|
155
|
+
return { calls: [...calls], count: calls.length };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resets the call log for the current task only.
|
|
160
|
+
* @example
|
|
161
|
+
* ```javascript
|
|
162
|
+
* await connectors.testing.clearCallLog({})
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
async clearCallLog(_args: any, ctx?: { taskId?: string }) {
|
|
166
|
+
const key = this.bucketKey(ctx);
|
|
167
|
+
if (this.callLogByTask.has(key)) {
|
|
168
|
+
this.callLogByTask.set(key, []);
|
|
169
|
+
}
|
|
170
|
+
return { cleared: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Updates the response map for the current task at runtime.
|
|
175
|
+
* @example
|
|
176
|
+
* ```javascript
|
|
177
|
+
* await connectors.testing.setResponses({
|
|
178
|
+
* responses: { "someConnector.create": { id: "456" } }
|
|
179
|
+
* })
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
async setResponses(args: any, ctx?: { taskId?: string }) {
|
|
183
|
+
const key = this.bucketKey(ctx);
|
|
184
|
+
this.touchTask(key);
|
|
185
|
+
|
|
186
|
+
if (args?.responses) {
|
|
187
|
+
try {
|
|
188
|
+
const newResponses =
|
|
189
|
+
typeof args.responses === "string"
|
|
190
|
+
? JSON.parse(args.responses)
|
|
191
|
+
: args.responses;
|
|
192
|
+
const existing = this.responseMapByTask.get(key)!;
|
|
193
|
+
this.responseMapByTask.set(key, { ...existing, ...newResponses });
|
|
194
|
+
} catch (e) {
|
|
195
|
+
// ignore parse errors — leave existing map untouched
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
updated: true,
|
|
201
|
+
responseCount: Object.keys(this.responseMapByTask.get(key)!).length,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
{"compilerOptions":{"target":"es2022","module":"node16","moduleResolution":"node16","skipLibCheck":true,"strict":true,"noImplicitAny":false,"experimentalDecorators":true,"esModuleInterop":true},"include":["src/**/*"]}
|
|
@@ -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,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/**/*"]}
|
|
@@ -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
|
+
});
|