@in2grate/testing 0.1.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/dist/cassette-clients.d.ts +26 -0
- package/dist/cassette-clients.d.ts.map +1 -0
- package/dist/cassette-clients.js +174 -0
- package/dist/cassette-clients.js.map +1 -0
- package/dist/cassette-store.d.ts +16 -0
- package/dist/cassette-store.d.ts.map +1 -0
- package/dist/cassette-store.js +34 -0
- package/dist/cassette-store.js.map +1 -0
- package/dist/cassette.test.d.ts +2 -0
- package/dist/cassette.test.d.ts.map +1 -0
- package/dist/cassette.test.js +414 -0
- package/dist/cassette.test.js.map +1 -0
- package/dist/error-helpers.test.d.ts +2 -0
- package/dist/error-helpers.test.d.ts.map +1 -0
- package/dist/error-helpers.test.js +66 -0
- package/dist/error-helpers.test.js.map +1 -0
- package/dist/http-mocks.test.d.ts +2 -0
- package/dist/http-mocks.test.d.ts.map +1 -0
- package/dist/http-mocks.test.js +89 -0
- package/dist/http-mocks.test.js.map +1 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +333 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.test.d.ts +2 -0
- package/dist/oauth.test.d.ts.map +1 -0
- package/dist/oauth.test.js +53 -0
- package/dist/oauth.test.js.map +1 -0
- package/dist/poll.test.d.ts +2 -0
- package/dist/poll.test.d.ts.map +1 -0
- package/dist/poll.test.js +78 -0
- package/dist/poll.test.js.map +1 -0
- package/dist/redact.d.ts +20 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +83 -0
- package/dist/redact.js.map +1 -0
- package/dist/replay.test.d.ts +2 -0
- package/dist/replay.test.d.ts.map +1 -0
- package/dist/replay.test.js +169 -0
- package/dist/replay.test.js.map +1 -0
- package/dist/secrets.test.d.ts +2 -0
- package/dist/secrets.test.d.ts.map +1 -0
- package/dist/secrets.test.js +30 -0
- package/dist/secrets.test.js.map +1 -0
- package/dist/state.test.d.ts +2 -0
- package/dist/state.test.d.ts.map +1 -0
- package/dist/state.test.js +66 -0
- package/dist/state.test.js.map +1 -0
- package/package.json +32 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { FlowDefinition, PollFunction, PollItem } from '@in2grate/sdk';
|
|
2
|
+
export interface MockHttpResponse {
|
|
3
|
+
status: number;
|
|
4
|
+
data: unknown;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface FlowTestOptions<TInput> {
|
|
8
|
+
/** Defaults to 'test-tenant' when omitted. */
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
input: TInput;
|
|
11
|
+
/**
|
|
12
|
+
* Exact-URL keyed map of mock HTTP responses.
|
|
13
|
+
* Any ctx.http call to an unmocked URL throws — keep tests hermetic.
|
|
14
|
+
* Mutually exclusive with cassette — providing both throws immediately.
|
|
15
|
+
* When set, takes precedence over IN2_CASSETTE_MODE.
|
|
16
|
+
*/
|
|
17
|
+
httpMocks?: Record<string, MockHttpResponse>;
|
|
18
|
+
/** Pre-populate ctx.state before the handler runs. */
|
|
19
|
+
initialState?: Record<string, unknown>;
|
|
20
|
+
/** Pre-populate ctx.secrets before the handler runs. */
|
|
21
|
+
initialSecrets?: Record<string, string>;
|
|
22
|
+
/** Pre-populate ctx.oauth tokens by provider ID. */
|
|
23
|
+
oauthTokens?: Record<string, string>;
|
|
24
|
+
/** Pre-populate ctx.config for config-aware flow tests. */
|
|
25
|
+
initialConfig?: Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Path to a recorded cassette file (.json) — legacy FIFO replay.
|
|
28
|
+
* Replays responses in FIFO order — first call gets first entry.
|
|
29
|
+
* Mutually exclusive with httpMocks — providing both throws immediately.
|
|
30
|
+
* Paths resolve relative to process.cwd().
|
|
31
|
+
* When set, takes precedence over IN2_CASSETTE_MODE.
|
|
32
|
+
*/
|
|
33
|
+
cassette?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Name of the cassette to use when IN2_CASSETTE_MODE is set.
|
|
36
|
+
* Falls back to IN2_CASSETTE_TEST_NAME env var, then flow.name.
|
|
37
|
+
* Has no effect when httpMocks or cassette (legacy) is provided.
|
|
38
|
+
*/
|
|
39
|
+
cassetteName?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface FlowTestResult<TOutput> {
|
|
42
|
+
output: TOutput;
|
|
43
|
+
/** All outbound HTTP calls made by the handler, in order. */
|
|
44
|
+
httpCalls: Array<{
|
|
45
|
+
url: string;
|
|
46
|
+
method: string;
|
|
47
|
+
}>;
|
|
48
|
+
logs: Array<{
|
|
49
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
50
|
+
message: string;
|
|
51
|
+
data?: Record<string, unknown>;
|
|
52
|
+
}>;
|
|
53
|
+
/** Final state of ctx.state after the handler completes. */
|
|
54
|
+
stateValues: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
export interface PollTestOptions {
|
|
57
|
+
/** Cursor value passed to poll(). Defaults to undefined (first-ever poll). */
|
|
58
|
+
initialCursor?: unknown;
|
|
59
|
+
/** Defaults to 'test-tenant' when omitted. */
|
|
60
|
+
tenantId?: string;
|
|
61
|
+
/** Pre-populate ctx.oauth tokens by provider ID for poll() tests. */
|
|
62
|
+
oauthTokens?: Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Exact-URL or method-qualified mock HTTP responses.
|
|
65
|
+
* Method-qualified keys (e.g. "GET https://...") take precedence over plain URL keys.
|
|
66
|
+
*/
|
|
67
|
+
httpMocks?: Record<string, MockHttpResponse>;
|
|
68
|
+
/** Pre-populate ctx.state before poll() runs. */
|
|
69
|
+
initialState?: Record<string, unknown>;
|
|
70
|
+
/** Pre-populate ctx.config for poll() tests. */
|
|
71
|
+
initialConfig?: Record<string, unknown>;
|
|
72
|
+
/** Cassette name for IN2_CASSETTE_MODE. Defaults to 'poll-default'. */
|
|
73
|
+
cassetteName?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface PollTestResult {
|
|
76
|
+
items: PollItem[];
|
|
77
|
+
/** undefined when the poll function returned no nextCursor. */
|
|
78
|
+
nextCursor: unknown;
|
|
79
|
+
httpCalls: Array<{
|
|
80
|
+
url: string;
|
|
81
|
+
method: string;
|
|
82
|
+
}>;
|
|
83
|
+
logs: Array<{
|
|
84
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
85
|
+
message: string;
|
|
86
|
+
data?: Record<string, unknown>;
|
|
87
|
+
}>;
|
|
88
|
+
/** Final state of ctx.state after poll() completes. */
|
|
89
|
+
stateValues: Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run a poll() function in-process with a mock context.
|
|
93
|
+
* Equivalent to runFlowTest but for the named poll() export of a polling flow.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const result = await runPollTest(myPollFn, {
|
|
97
|
+
* initialCursor: 'last-seen-id',
|
|
98
|
+
* httpMocks: { 'https://api.example.com/items': { status: 200, data: [] } },
|
|
99
|
+
* });
|
|
100
|
+
* assert.equal(result.items.length, 0);
|
|
101
|
+
*/
|
|
102
|
+
export declare function runPollTest(pollFn: PollFunction, options?: PollTestOptions): Promise<PollTestResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Assert that an async function throws. Returns the caught Error for further assertions.
|
|
105
|
+
* Optionally accepts a string (message includes), RegExp (message matches),
|
|
106
|
+
* or predicate function (called with the error, must return true).
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* const err = await assertThrows(() => runFlowTest(flow, opts), 'invalid input');
|
|
110
|
+
* assert.equal(err.name, 'ValidationError');
|
|
111
|
+
*/
|
|
112
|
+
export declare function assertThrows(fn: () => Promise<unknown>, matcher?: string | RegExp | ((err: unknown) => boolean)): Promise<Error>;
|
|
113
|
+
/**
|
|
114
|
+
* Assert that an async function throws a PermanentError (duck-typed by name).
|
|
115
|
+
* Works across module boundaries since it checks err.name rather than instanceof.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* await assertPermanentError(() => runFlowTest(flow, { input: { badSig: true } }));
|
|
119
|
+
*/
|
|
120
|
+
export declare function assertPermanentError(fn: () => Promise<unknown>): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Run a flow handler in-process with a mock context.
|
|
123
|
+
* Network calls are intercepted — no real HTTP is made.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const result = await runFlowTest(myFlow, {
|
|
127
|
+
* input: { orderId: '123' },
|
|
128
|
+
* httpMocks: { 'https://api.example.com/orders/123': { status: 200, data: { ... } } },
|
|
129
|
+
* });
|
|
130
|
+
* assert.strictEqual(result.output.status, 'processed');
|
|
131
|
+
*/
|
|
132
|
+
export declare function runFlowTest<TInput, TOutput>(flow: FlowDefinition<TInput, TOutput>, options: FlowTestOptions<TInput>): Promise<FlowTestResult<TOutput>>;
|
|
133
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { CassetteStore, resolveCassetteDir, resolveCassetteName } from './cassette-store.js';
|
|
6
|
+
import { createFetchHttpClient, createCassetteReplayClient, createCassetteRecordClient, } from './cassette-clients.js';
|
|
7
|
+
/**
|
|
8
|
+
* Run a poll() function in-process with a mock context.
|
|
9
|
+
* Equivalent to runFlowTest but for the named poll() export of a polling flow.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const result = await runPollTest(myPollFn, {
|
|
13
|
+
* initialCursor: 'last-seen-id',
|
|
14
|
+
* httpMocks: { 'https://api.example.com/items': { status: 200, data: [] } },
|
|
15
|
+
* });
|
|
16
|
+
* assert.equal(result.items.length, 0);
|
|
17
|
+
*/
|
|
18
|
+
export async function runPollTest(pollFn, options) {
|
|
19
|
+
const httpCalls = [];
|
|
20
|
+
const logs = [];
|
|
21
|
+
const { stateClient, getStateValues } = buildStateClient(options?.initialState);
|
|
22
|
+
const secrets = buildSecretsClient();
|
|
23
|
+
const oauth = buildOAuthClient(options?.oauthTokens);
|
|
24
|
+
const cassetteName = options?.cassetteName ?? 'poll-default';
|
|
25
|
+
const httpClient = resolveHttpClientForPoll(cassetteName, options?.httpMocks, httpCalls);
|
|
26
|
+
const resolvedEnvId = (options?.tenantId ?? 'test-env');
|
|
27
|
+
const ctx = {
|
|
28
|
+
tenantId: resolvedEnvId,
|
|
29
|
+
envId: resolvedEnvId,
|
|
30
|
+
orgId: resolvedEnvId,
|
|
31
|
+
flowName: 'poll-test',
|
|
32
|
+
runId: randomUUID(),
|
|
33
|
+
config: Object.freeze({ ...(options?.initialConfig ?? {}) }),
|
|
34
|
+
logger: buildLogger(logs),
|
|
35
|
+
http: httpClient,
|
|
36
|
+
state: stateClient,
|
|
37
|
+
secrets,
|
|
38
|
+
oauth,
|
|
39
|
+
};
|
|
40
|
+
const result = await pollFn(ctx, options?.initialCursor);
|
|
41
|
+
return {
|
|
42
|
+
items: result.items,
|
|
43
|
+
nextCursor: result.nextCursor,
|
|
44
|
+
httpCalls,
|
|
45
|
+
logs,
|
|
46
|
+
stateValues: getStateValues(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Assert that an async function throws. Returns the caught Error for further assertions.
|
|
51
|
+
* Optionally accepts a string (message includes), RegExp (message matches),
|
|
52
|
+
* or predicate function (called with the error, must return true).
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const err = await assertThrows(() => runFlowTest(flow, opts), 'invalid input');
|
|
56
|
+
* assert.equal(err.name, 'ValidationError');
|
|
57
|
+
*/
|
|
58
|
+
export async function assertThrows(fn, matcher) {
|
|
59
|
+
let caught;
|
|
60
|
+
let threw = false;
|
|
61
|
+
try {
|
|
62
|
+
await fn();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
threw = true;
|
|
66
|
+
caught = err;
|
|
67
|
+
}
|
|
68
|
+
if (!threw) {
|
|
69
|
+
assert.fail('Expected function to throw, but it resolved successfully');
|
|
70
|
+
}
|
|
71
|
+
const err = caught instanceof Error ? caught : new Error(String(caught));
|
|
72
|
+
if (typeof matcher === 'string') {
|
|
73
|
+
if (!err.message.includes(matcher)) {
|
|
74
|
+
assert.fail(`Expected error message to include "${matcher}", but got: "${err.message}"`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (matcher instanceof RegExp) {
|
|
78
|
+
if (!matcher.test(err.message)) {
|
|
79
|
+
assert.fail(`Expected error message to match ${String(matcher)}, but got: "${err.message}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (typeof matcher === 'function') {
|
|
83
|
+
if (!matcher(caught)) {
|
|
84
|
+
assert.fail(`Matcher predicate returned false for error: ${String(caught)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return err;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Assert that an async function throws a PermanentError (duck-typed by name).
|
|
91
|
+
* Works across module boundaries since it checks err.name rather than instanceof.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* await assertPermanentError(() => runFlowTest(flow, { input: { badSig: true } }));
|
|
95
|
+
*/
|
|
96
|
+
export async function assertPermanentError(fn) {
|
|
97
|
+
const err = await assertThrows(fn);
|
|
98
|
+
if (err.name !== 'PermanentError') {
|
|
99
|
+
assert.fail(`Expected a PermanentError (err.name === "PermanentError"), but got: ${err.name}: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Run a flow handler in-process with a mock context.
|
|
104
|
+
* Network calls are intercepted — no real HTTP is made.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* const result = await runFlowTest(myFlow, {
|
|
108
|
+
* input: { orderId: '123' },
|
|
109
|
+
* httpMocks: { 'https://api.example.com/orders/123': { status: 200, data: { ... } } },
|
|
110
|
+
* });
|
|
111
|
+
* assert.strictEqual(result.output.status, 'processed');
|
|
112
|
+
*/
|
|
113
|
+
export async function runFlowTest(flow, options) {
|
|
114
|
+
if (options.cassette !== undefined && options.httpMocks !== undefined) {
|
|
115
|
+
throw new Error('runFlowTest: cannot use both `cassette` and `httpMocks` at the same time. ' +
|
|
116
|
+
'Provide one or the other — cassette path for replay, httpMocks for manual mocking.');
|
|
117
|
+
}
|
|
118
|
+
const httpCalls = [];
|
|
119
|
+
const logs = [];
|
|
120
|
+
const { stateClient, getStateValues } = buildStateClient(options.initialState);
|
|
121
|
+
const secrets = buildSecretsClient(options.initialSecrets);
|
|
122
|
+
const oauth = buildOAuthClient(options.oauthTokens);
|
|
123
|
+
const resolvedEnvId = (options.tenantId ?? 'test-env');
|
|
124
|
+
const ctx = {
|
|
125
|
+
tenantId: resolvedEnvId,
|
|
126
|
+
envId: resolvedEnvId,
|
|
127
|
+
orgId: resolvedEnvId,
|
|
128
|
+
flowName: flow.name,
|
|
129
|
+
runId: randomUUID(),
|
|
130
|
+
config: Object.freeze({ ...(options.initialConfig ?? {}) }),
|
|
131
|
+
logger: buildLogger(logs),
|
|
132
|
+
http: resolveHttpClient(flow, options, httpCalls),
|
|
133
|
+
state: stateClient,
|
|
134
|
+
secrets,
|
|
135
|
+
oauth,
|
|
136
|
+
};
|
|
137
|
+
const output = await flow.handler(ctx, options.input);
|
|
138
|
+
return { output, httpCalls, logs, stateValues: getStateValues() };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Determine which HttpClient to use for this test invocation.
|
|
142
|
+
*
|
|
143
|
+
* Priority (highest → lowest):
|
|
144
|
+
* 1. options.cassette — legacy FIFO replay (backward compat)
|
|
145
|
+
* 2. options.httpMocks — explicit mock map (backward compat)
|
|
146
|
+
* 3. IN2_CASSETTE_MODE — env-var-driven cassette system (record / replay / live)
|
|
147
|
+
* 4. default — mock client that throws on every unmocked URL
|
|
148
|
+
*/
|
|
149
|
+
function resolveHttpClient(flow, options, httpCalls) {
|
|
150
|
+
// 1. Legacy FIFO cassette path
|
|
151
|
+
if (options.cassette !== undefined) {
|
|
152
|
+
return buildReplayHttpClient(options.cassette, httpCalls);
|
|
153
|
+
}
|
|
154
|
+
// 2. Explicit http mocks
|
|
155
|
+
if (options.httpMocks !== undefined) {
|
|
156
|
+
return buildHttpClient(options.httpMocks, httpCalls);
|
|
157
|
+
}
|
|
158
|
+
// 3. Env-var-driven cassette mode
|
|
159
|
+
const mode = process.env['IN2_CASSETTE_MODE'];
|
|
160
|
+
if (mode === 'live') {
|
|
161
|
+
return createFetchHttpClient();
|
|
162
|
+
}
|
|
163
|
+
if (mode === 'record' || mode === 'replay') {
|
|
164
|
+
const cassetteDir = resolveCassetteDir();
|
|
165
|
+
const store = new CassetteStore(cassetteDir);
|
|
166
|
+
const cassetteName = resolveCassetteName({
|
|
167
|
+
...(options.cassetteName !== undefined ? { cassetteName: options.cassetteName } : {}),
|
|
168
|
+
flowName: flow.name,
|
|
169
|
+
});
|
|
170
|
+
if (mode === 'record') {
|
|
171
|
+
return createCassetteRecordClient(createFetchHttpClient(), store, cassetteName, httpCalls);
|
|
172
|
+
}
|
|
173
|
+
// replay — lazy: only throw if an HTTP call is actually attempted
|
|
174
|
+
const cassette = store.load(cassetteName);
|
|
175
|
+
if (!cassette) {
|
|
176
|
+
return createNoCassetteThrowingClient(cassetteName, cassetteDir);
|
|
177
|
+
}
|
|
178
|
+
return createCassetteReplayClient(cassette, cassetteName, cassetteDir, httpCalls);
|
|
179
|
+
}
|
|
180
|
+
// 4. Explicit cassetteName defaults to replay semantics, even without env vars.
|
|
181
|
+
if (options.cassetteName !== undefined) {
|
|
182
|
+
const cassetteDir = resolveCassetteDir();
|
|
183
|
+
const store = new CassetteStore(cassetteDir);
|
|
184
|
+
const cassette = store.load(options.cassetteName);
|
|
185
|
+
if (!cassette) {
|
|
186
|
+
return createNoCassetteThrowingClient(options.cassetteName, cassetteDir);
|
|
187
|
+
}
|
|
188
|
+
return createCassetteReplayClient(cassette, options.cassetteName, cassetteDir, httpCalls);
|
|
189
|
+
}
|
|
190
|
+
// 5. Default — throws on any unmocked call
|
|
191
|
+
return buildHttpClient(undefined, httpCalls);
|
|
192
|
+
}
|
|
193
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
194
|
+
/** Resolve an HttpClient for runPollTest (no FlowDefinition available). */
|
|
195
|
+
function resolveHttpClientForPoll(cassetteName, httpMocks, httpCalls) {
|
|
196
|
+
if (httpMocks !== undefined) {
|
|
197
|
+
return buildHttpClient(httpMocks, httpCalls);
|
|
198
|
+
}
|
|
199
|
+
const mode = process.env['IN2_CASSETTE_MODE'];
|
|
200
|
+
if (mode === 'live') {
|
|
201
|
+
return createFetchHttpClient();
|
|
202
|
+
}
|
|
203
|
+
if (mode === 'record' || mode === 'replay') {
|
|
204
|
+
const cassetteDir = resolveCassetteDir();
|
|
205
|
+
const store = new CassetteStore(cassetteDir);
|
|
206
|
+
const name = resolveCassetteName({ cassetteName, flowName: cassetteName });
|
|
207
|
+
if (mode === 'record') {
|
|
208
|
+
return createCassetteRecordClient(createFetchHttpClient(), store, name, httpCalls);
|
|
209
|
+
}
|
|
210
|
+
const cassette = store.load(name);
|
|
211
|
+
if (!cassette) {
|
|
212
|
+
return createNoCassetteThrowingClient(name, cassetteDir);
|
|
213
|
+
}
|
|
214
|
+
return createCassetteReplayClient(cassette, name, cassetteDir, httpCalls);
|
|
215
|
+
}
|
|
216
|
+
return buildHttpClient(undefined, httpCalls);
|
|
217
|
+
}
|
|
218
|
+
function buildLogger(logs) {
|
|
219
|
+
const push = (level) => (message, data) => {
|
|
220
|
+
logs.push({ level, message, ...(data !== undefined ? { data } : {}) });
|
|
221
|
+
};
|
|
222
|
+
return { debug: push('debug'), info: push('info'), warn: push('warn'), error: push('error') };
|
|
223
|
+
}
|
|
224
|
+
function buildStateClient(initialState) {
|
|
225
|
+
const store = new Map(Object.entries(initialState ?? {}));
|
|
226
|
+
const stateClient = {
|
|
227
|
+
async get(key) {
|
|
228
|
+
return store.get(key);
|
|
229
|
+
},
|
|
230
|
+
async set(key, value) {
|
|
231
|
+
store.set(key, value);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
stateClient,
|
|
236
|
+
getStateValues: () => Object.fromEntries(store),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function buildSecretsClient(initialSecrets) {
|
|
240
|
+
const store = new Map(Object.entries(initialSecrets ?? {}));
|
|
241
|
+
return {
|
|
242
|
+
async get(key) {
|
|
243
|
+
return store.get(key);
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function buildOAuthClient(initialTokens) {
|
|
248
|
+
const store = new Map(Object.entries(initialTokens ?? {}));
|
|
249
|
+
return {
|
|
250
|
+
async getToken(providerId) {
|
|
251
|
+
const token = store.get(providerId);
|
|
252
|
+
if (token === undefined) {
|
|
253
|
+
throw new Error(`OAuth provider not connected: ${providerId}`);
|
|
254
|
+
}
|
|
255
|
+
return token;
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function buildHttpClient(httpMocks, httpCalls) {
|
|
260
|
+
async function request(url, opts) {
|
|
261
|
+
const method = opts?.method ?? 'GET';
|
|
262
|
+
httpCalls.push({ url, method });
|
|
263
|
+
// Method-qualified key takes precedence over plain URL key.
|
|
264
|
+
const methodKey = `${method} ${url}`;
|
|
265
|
+
const mock = httpMocks?.[methodKey] ?? httpMocks?.[url];
|
|
266
|
+
if (mock === undefined) {
|
|
267
|
+
throw new Error(`No HTTP mock registered for ${method} ${url}.\n` +
|
|
268
|
+
`Add it to httpMocks as '${methodKey}' or '${url}'.`);
|
|
269
|
+
}
|
|
270
|
+
return { status: mock.status, data: mock.data, headers: mock.headers ?? {} };
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
request,
|
|
274
|
+
get: (url, opts) => request(url, { ...opts, method: 'GET' }),
|
|
275
|
+
post: (url, body, opts) => request(url, { ...opts, method: 'POST', body }),
|
|
276
|
+
put: (url, body, opts) => request(url, { ...opts, method: 'PUT', body }),
|
|
277
|
+
patch: (url, body, opts) => request(url, { ...opts, method: 'PATCH', body }),
|
|
278
|
+
delete: (url, opts) => request(url, { ...opts, method: 'DELETE' }),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function buildReplayHttpClient(cassettePath, httpCalls) {
|
|
282
|
+
const resolved = path.resolve(cassettePath);
|
|
283
|
+
const raw = fs.readFileSync(resolved, 'utf-8');
|
|
284
|
+
const entries = JSON.parse(raw);
|
|
285
|
+
let cursor = 0;
|
|
286
|
+
async function request(url, opts) {
|
|
287
|
+
const method = opts?.method ?? 'GET';
|
|
288
|
+
if (cursor >= entries.length) {
|
|
289
|
+
throw new Error('Cassette exhausted: no more recorded responses available.\n' +
|
|
290
|
+
` Attempted: ${method} ${url}\n` +
|
|
291
|
+
` Cassette: ${resolved}\n` +
|
|
292
|
+
` Recorded entries: ${entries.length}`);
|
|
293
|
+
}
|
|
294
|
+
const entry = entries[cursor];
|
|
295
|
+
cursor += 1;
|
|
296
|
+
httpCalls.push({ url, method });
|
|
297
|
+
return {
|
|
298
|
+
status: entry.responseStatus,
|
|
299
|
+
headers: entry.responseHeaders,
|
|
300
|
+
data: entry.responseBody,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
request,
|
|
305
|
+
get: (url, opts) => request(url, { ...opts, method: 'GET' }),
|
|
306
|
+
post: (url, body, opts) => request(url, { ...opts, method: 'POST', body }),
|
|
307
|
+
put: (url, body, opts) => request(url, { ...opts, method: 'PUT', body }),
|
|
308
|
+
patch: (url, body, opts) => request(url, { ...opts, method: 'PATCH', body }),
|
|
309
|
+
delete: (url, opts) => request(url, { ...opts, method: 'DELETE' }),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Returns a client that throws on any HTTP attempt — used in replay mode when
|
|
314
|
+
* no cassette file has been recorded yet. The error is raised lazily so tests
|
|
315
|
+
* that make no HTTP calls (e.g. pure-logic flows) still pass.
|
|
316
|
+
*/
|
|
317
|
+
function createNoCassetteThrowingClient(cassetteName, cassetteDir) {
|
|
318
|
+
async function request(url, opts) {
|
|
319
|
+
const method = opts?.method ?? 'GET';
|
|
320
|
+
throw new Error(`Cassette miss: no cassette recorded for "${cassetteName}" in ${cassetteDir}.\n` +
|
|
321
|
+
` Attempted: ${method} ${url}\n` +
|
|
322
|
+
` Run \`in2 test --record\` to record a cassette first.`);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
request,
|
|
326
|
+
get: (url, opts) => request(url, { ...opts, method: 'GET' }),
|
|
327
|
+
post: (url, body, opts) => request(url, { ...opts, method: 'POST', body }),
|
|
328
|
+
put: (url, body, opts) => request(url, { ...opts, method: 'PUT', body }),
|
|
329
|
+
patch: (url, body, opts) => request(url, { ...opts, method: 'PATCH', body }),
|
|
330
|
+
delete: (url, opts) => request(url, { ...opts, method: 'DELETE' }),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.test.d.ts","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { runFlowTest, runPollTest } from './index.js';
|
|
4
|
+
const oauthFlow = {
|
|
5
|
+
name: 'oauth-flow',
|
|
6
|
+
trigger: { type: 'webhook' },
|
|
7
|
+
async handler(ctx, input) {
|
|
8
|
+
return { token: await ctx.oauth.getToken(input.providerId) };
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
describe('ctx.oauth in runFlowTest', () => {
|
|
12
|
+
it('oauthTokens pre-populates ctx.oauth', async () => {
|
|
13
|
+
const result = await runFlowTest(oauthFlow, {
|
|
14
|
+
input: { providerId: 'samsara' },
|
|
15
|
+
oauthTokens: { samsara: 'oauth-token-123' },
|
|
16
|
+
});
|
|
17
|
+
assert.deepEqual(result.output, { token: 'oauth-token-123' });
|
|
18
|
+
});
|
|
19
|
+
it('throws when the requested provider is not configured', async () => {
|
|
20
|
+
await assert.rejects(() => runFlowTest(oauthFlow, {
|
|
21
|
+
input: { providerId: 'samsara' },
|
|
22
|
+
}), /not connected/i);
|
|
23
|
+
});
|
|
24
|
+
it('oauth tokens are isolated per invocation', async () => {
|
|
25
|
+
await runFlowTest(oauthFlow, {
|
|
26
|
+
input: { providerId: 'samsara' },
|
|
27
|
+
oauthTokens: { samsara: 'first-token' },
|
|
28
|
+
});
|
|
29
|
+
await assert.rejects(() => runFlowTest(oauthFlow, {
|
|
30
|
+
input: { providerId: 'samsara' },
|
|
31
|
+
}), /not connected/i);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('ctx.oauth in runPollTest', () => {
|
|
35
|
+
it('oauthTokens pre-populates ctx.oauth for polling tests', async () => {
|
|
36
|
+
const pollFn = async (ctx) => {
|
|
37
|
+
const token = await ctx.oauth.getToken('samsara');
|
|
38
|
+
return { items: [{ id: token }] };
|
|
39
|
+
};
|
|
40
|
+
const result = await runPollTest(pollFn, {
|
|
41
|
+
oauthTokens: { samsara: 'poll-token-123' },
|
|
42
|
+
});
|
|
43
|
+
assert.equal(result.items[0]?.id, 'poll-token-123');
|
|
44
|
+
});
|
|
45
|
+
it('throws when the requested polling provider token is not configured', async () => {
|
|
46
|
+
const pollFn = async (ctx) => {
|
|
47
|
+
await ctx.oauth.getToken('samsara');
|
|
48
|
+
return { items: [] };
|
|
49
|
+
};
|
|
50
|
+
await assert.rejects(() => runPollTest(pollFn), /not connected/i);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=oauth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.test.d.ts","sourceRoot":"","sources":["../src/poll.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { runPollTest } from './index.js';
|
|
4
|
+
const simplePollFn = async (_ctx, cursor) => ({
|
|
5
|
+
items: [{ id: '1', value: 'a' }],
|
|
6
|
+
nextCursor: cursor === undefined ? 'page-2' : 'page-3',
|
|
7
|
+
});
|
|
8
|
+
const emptyPollFn = async (_ctx, _cursor) => ({
|
|
9
|
+
items: [],
|
|
10
|
+
});
|
|
11
|
+
describe('runPollTest', () => {
|
|
12
|
+
it('returns items and nextCursor from the poll function', async () => {
|
|
13
|
+
const result = await runPollTest(simplePollFn);
|
|
14
|
+
assert.equal(result.items.length, 1);
|
|
15
|
+
assert.equal(result.items[0].id, '1');
|
|
16
|
+
assert.equal(result.nextCursor, 'page-2');
|
|
17
|
+
});
|
|
18
|
+
it('passes initialCursor through to the poll function', async () => {
|
|
19
|
+
const result = await runPollTest(simplePollFn, { initialCursor: 'page-2' });
|
|
20
|
+
assert.equal(result.nextCursor, 'page-3');
|
|
21
|
+
});
|
|
22
|
+
it('empty poll returns empty items and undefined nextCursor', async () => {
|
|
23
|
+
const result = await runPollTest(emptyPollFn);
|
|
24
|
+
assert.equal(result.items.length, 0);
|
|
25
|
+
assert.equal(result.nextCursor, undefined);
|
|
26
|
+
});
|
|
27
|
+
it('populates httpCalls for HTTP calls made during poll', async () => {
|
|
28
|
+
const pollFn = async (ctx, _cursor) => {
|
|
29
|
+
await ctx.http.get('https://api.example.com/items');
|
|
30
|
+
return { items: [{ id: 'x' }] };
|
|
31
|
+
};
|
|
32
|
+
const result = await runPollTest(pollFn, {
|
|
33
|
+
httpMocks: { 'https://api.example.com/items': { status: 200, data: [] } },
|
|
34
|
+
});
|
|
35
|
+
assert.equal(result.httpCalls.length, 1);
|
|
36
|
+
assert.equal(result.httpCalls[0].url, 'https://api.example.com/items');
|
|
37
|
+
assert.equal(result.httpCalls[0].method, 'GET');
|
|
38
|
+
});
|
|
39
|
+
it('throws for unmocked HTTP URLs (hermetic)', async () => {
|
|
40
|
+
const pollFn = async (ctx, _cursor) => {
|
|
41
|
+
await ctx.http.get('https://api.example.com/unmocked');
|
|
42
|
+
return { items: [] };
|
|
43
|
+
};
|
|
44
|
+
await assert.rejects(() => runPollTest(pollFn), /No HTTP mock/);
|
|
45
|
+
});
|
|
46
|
+
it('pre-populates ctx.state from initialState and stateValues reflects writes', async () => {
|
|
47
|
+
const pollFn = async (ctx, _cursor) => {
|
|
48
|
+
const prev = (await ctx.state.get('counter'));
|
|
49
|
+
await ctx.state.set('counter', (prev ?? 0) + 1);
|
|
50
|
+
return { items: [] };
|
|
51
|
+
};
|
|
52
|
+
const result = await runPollTest(pollFn, { initialState: { counter: 5 } });
|
|
53
|
+
assert.equal(result.stateValues['counter'], 6);
|
|
54
|
+
});
|
|
55
|
+
it('captures ctx.logger calls in logs array', async () => {
|
|
56
|
+
const pollFn = async (ctx, _cursor) => {
|
|
57
|
+
ctx.logger.info('polling started', { page: 1 });
|
|
58
|
+
ctx.logger.warn('slow API');
|
|
59
|
+
return { items: [] };
|
|
60
|
+
};
|
|
61
|
+
const result = await runPollTest(pollFn);
|
|
62
|
+
assert.equal(result.logs.length, 2);
|
|
63
|
+
assert.equal(result.logs[0].level, 'info');
|
|
64
|
+
assert.equal(result.logs[0].message, 'polling started');
|
|
65
|
+
assert.equal(result.logs[1].level, 'warn');
|
|
66
|
+
assert.equal(result.logs[1].message, 'slow API');
|
|
67
|
+
});
|
|
68
|
+
it('uses provided tenantId in ctx', async () => {
|
|
69
|
+
let capturedTenantId;
|
|
70
|
+
const pollFn = async (ctx, _cursor) => {
|
|
71
|
+
capturedTenantId = ctx.tenantId;
|
|
72
|
+
return { items: [] };
|
|
73
|
+
};
|
|
74
|
+
await runPollTest(pollFn, { tenantId: 'acme-corp' });
|
|
75
|
+
assert.equal(capturedTenantId, 'acme-corp');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=poll.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.test.js","sourceRoot":"","sources":["../src/poll.test.ts"],"names":[],"mappings":"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"}
|