@opensip-cli/external-tool-adapter 0.1.15
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +33 -0
- package/dist/__tests__/acceptance-harness.test.d.ts +2 -0
- package/dist/__tests__/acceptance-harness.test.d.ts.map +1 -0
- package/dist/__tests__/acceptance-harness.test.js +106 -0
- package/dist/__tests__/acceptance-harness.test.js.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.js +19 -0
- package/dist/__tests__/artifact-path.test.js.map +1 -0
- package/dist/__tests__/binary-resolver.test.d.ts +2 -0
- package/dist/__tests__/binary-resolver.test.d.ts.map +1 -0
- package/dist/__tests__/binary-resolver.test.js +64 -0
- package/dist/__tests__/binary-resolver.test.js.map +1 -0
- package/dist/__tests__/define-external-tool-adapter.test.d.ts +2 -0
- package/dist/__tests__/define-external-tool-adapter.test.d.ts.map +1 -0
- package/dist/__tests__/define-external-tool-adapter.test.js +165 -0
- package/dist/__tests__/define-external-tool-adapter.test.js.map +1 -0
- package/dist/__tests__/doctor-command.test.d.ts +2 -0
- package/dist/__tests__/doctor-command.test.d.ts.map +1 -0
- package/dist/__tests__/doctor-command.test.js +124 -0
- package/dist/__tests__/doctor-command.test.js.map +1 -0
- package/dist/__tests__/exit-model.test.d.ts +2 -0
- package/dist/__tests__/exit-model.test.d.ts.map +1 -0
- package/dist/__tests__/exit-model.test.js +30 -0
- package/dist/__tests__/exit-model.test.js.map +1 -0
- package/dist/__tests__/fingerprint.test.d.ts +2 -0
- package/dist/__tests__/fingerprint.test.d.ts.map +1 -0
- package/dist/__tests__/fingerprint.test.js +39 -0
- package/dist/__tests__/fingerprint.test.js.map +1 -0
- package/dist/__tests__/gate-render.test.d.ts +2 -0
- package/dist/__tests__/gate-render.test.d.ts.map +1 -0
- package/dist/__tests__/gate-render.test.js +82 -0
- package/dist/__tests__/gate-render.test.js.map +1 -0
- package/dist/__tests__/ingest-json.test.d.ts +2 -0
- package/dist/__tests__/ingest-json.test.d.ts.map +1 -0
- package/dist/__tests__/ingest-json.test.js +53 -0
- package/dist/__tests__/ingest-json.test.js.map +1 -0
- package/dist/__tests__/ingest-sarif.test.d.ts +2 -0
- package/dist/__tests__/ingest-sarif.test.d.ts.map +1 -0
- package/dist/__tests__/ingest-sarif.test.js +283 -0
- package/dist/__tests__/ingest-sarif.test.js.map +1 -0
- package/dist/__tests__/manifest-commands.test.d.ts +2 -0
- package/dist/__tests__/manifest-commands.test.d.ts.map +1 -0
- package/dist/__tests__/manifest-commands.test.js +67 -0
- package/dist/__tests__/manifest-commands.test.js.map +1 -0
- package/dist/__tests__/provenance.test.d.ts +2 -0
- package/dist/__tests__/provenance.test.d.ts.map +1 -0
- package/dist/__tests__/provenance.test.js +48 -0
- package/dist/__tests__/provenance.test.js.map +1 -0
- package/dist/__tests__/redact.test.d.ts +2 -0
- package/dist/__tests__/redact.test.d.ts.map +1 -0
- package/dist/__tests__/redact.test.js +37 -0
- package/dist/__tests__/redact.test.js.map +1 -0
- package/dist/__tests__/run-loop-artifact.test.d.ts +21 -0
- package/dist/__tests__/run-loop-artifact.test.d.ts.map +1 -0
- package/dist/__tests__/run-loop-artifact.test.js +186 -0
- package/dist/__tests__/run-loop-artifact.test.js.map +1 -0
- package/dist/__tests__/run-loop-exit.test.d.ts +21 -0
- package/dist/__tests__/run-loop-exit.test.d.ts.map +1 -0
- package/dist/__tests__/run-loop-exit.test.js +123 -0
- package/dist/__tests__/run-loop-exit.test.js.map +1 -0
- package/dist/__tests__/run-loop-gate.test.d.ts +10 -0
- package/dist/__tests__/run-loop-gate.test.d.ts.map +1 -0
- package/dist/__tests__/run-loop-gate.test.js +159 -0
- package/dist/__tests__/run-loop-gate.test.js.map +1 -0
- package/dist/__tests__/session-payload.test.d.ts +12 -0
- package/dist/__tests__/session-payload.test.d.ts.map +1 -0
- package/dist/__tests__/session-payload.test.js +131 -0
- package/dist/__tests__/session-payload.test.js.map +1 -0
- package/dist/__tests__/severity-map.test.d.ts +2 -0
- package/dist/__tests__/severity-map.test.d.ts.map +1 -0
- package/dist/__tests__/severity-map.test.js +57 -0
- package/dist/__tests__/severity-map.test.js.map +1 -0
- package/dist/acceptance-harness.d.ts +48 -0
- package/dist/acceptance-harness.d.ts.map +1 -0
- package/dist/acceptance-harness.js +78 -0
- package/dist/acceptance-harness.js.map +1 -0
- package/dist/adapter-config.d.ts +58 -0
- package/dist/adapter-config.d.ts.map +1 -0
- package/dist/adapter-config.js +73 -0
- package/dist/adapter-config.js.map +1 -0
- package/dist/adapter-manifest.d.ts +57 -0
- package/dist/adapter-manifest.d.ts.map +1 -0
- package/dist/adapter-manifest.js +68 -0
- package/dist/adapter-manifest.js.map +1 -0
- package/dist/artifact-path.d.ts +26 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/artifact-path.js +22 -0
- package/dist/artifact-path.js.map +1 -0
- package/dist/binary-resolver.d.ts +51 -0
- package/dist/binary-resolver.d.ts.map +1 -0
- package/dist/binary-resolver.js +66 -0
- package/dist/binary-resolver.js.map +1 -0
- package/dist/define-external-tool-adapter.d.ts +25 -0
- package/dist/define-external-tool-adapter.d.ts.map +1 -0
- package/dist/define-external-tool-adapter.js +149 -0
- package/dist/define-external-tool-adapter.js.map +1 -0
- package/dist/doctor-command.d.ts +81 -0
- package/dist/doctor-command.d.ts.map +1 -0
- package/dist/doctor-command.js +160 -0
- package/dist/doctor-command.js.map +1 -0
- package/dist/exit-model.d.ts +33 -0
- package/dist/exit-model.d.ts.map +1 -0
- package/dist/exit-model.js +35 -0
- package/dist/exit-model.js.map +1 -0
- package/dist/fingerprint.d.ts +26 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +32 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/gate-render.d.ts +18 -0
- package/dist/gate-render.d.ts.map +1 -0
- package/dist/gate-render.js +25 -0
- package/dist/gate-render.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-json.d.ts +32 -0
- package/dist/ingest-json.d.ts.map +1 -0
- package/dist/ingest-json.js +66 -0
- package/dist/ingest-json.js.map +1 -0
- package/dist/ingest-sarif.d.ts +113 -0
- package/dist/ingest-sarif.d.ts.map +1 -0
- package/dist/ingest-sarif.js +158 -0
- package/dist/ingest-sarif.js.map +1 -0
- package/dist/manifest-commands.d.ts +23 -0
- package/dist/manifest-commands.d.ts.map +1 -0
- package/dist/manifest-commands.js +47 -0
- package/dist/manifest-commands.js.map +1 -0
- package/dist/process-exec.d.ts +51 -0
- package/dist/process-exec.d.ts.map +1 -0
- package/dist/process-exec.js +99 -0
- package/dist/process-exec.js.map +1 -0
- package/dist/provenance.d.ts +19 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +31 -0
- package/dist/provenance.js.map +1 -0
- package/dist/redact.d.ts +24 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +38 -0
- package/dist/redact.js.map +1 -0
- package/dist/run-context.d.ts +24 -0
- package/dist/run-context.d.ts.map +1 -0
- package/dist/run-context.js +36 -0
- package/dist/run-context.js.map +1 -0
- package/dist/run-loop.d.ts +64 -0
- package/dist/run-loop.d.ts.map +1 -0
- package/dist/run-loop.js +320 -0
- package/dist/run-loop.js.map +1 -0
- package/dist/scan-emit.d.ts +81 -0
- package/dist/scan-emit.d.ts.map +1 -0
- package/dist/scan-emit.js +125 -0
- package/dist/scan-emit.js.map +1 -0
- package/dist/session-payload.d.ts +81 -0
- package/dist/session-payload.d.ts.map +1 -0
- package/dist/session-payload.js +86 -0
- package/dist/session-payload.js.map +1 -0
- package/dist/severity-map.d.ts +43 -0
- package/dist/severity-map.d.ts.map +1 -0
- package/dist/severity-map.js +84 -0
- package/dist/severity-map.js.map +1 -0
- package/dist/types.d.ts +228 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/version-command.d.ts +36 -0
- package/dist/version-command.d.ts.map +1 -0
- package/dist/version-command.js +74 -0
- package/dist/version-command.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact-lifecycle behavior of the scan run loop (External Tool Adapter
|
|
3
|
+
* A1 / A3 / A11). The run loop is the IO orchestration excluded from coverage;
|
|
4
|
+
* these tests drive it directly with a stub `ToolCliContext` + stub IO deps and
|
|
5
|
+
* assert the artifact-lifecycle guarantees the fake binaries' `mkdir -p` used to
|
|
6
|
+
* mask:
|
|
7
|
+
*
|
|
8
|
+
* - A1: the per-run artifact dir is ensured through the HOST seam BEFORE the
|
|
9
|
+
* scanner subprocess runs (a real scanner does a bare `open(--report-path)`).
|
|
10
|
+
* - A3: the substrate injects each adapter's declared exclusion (flag and/or a
|
|
11
|
+
* host-written config file) so the scanner never re-walks `.runtime/`.
|
|
12
|
+
* - A11: an invalid file-backed report (empty / garbage / oversize) FAULTS even
|
|
13
|
+
* under an `ok`/`findings` exit verdict, and is NEVER overwritten by the empty
|
|
14
|
+
* read buffer (the scanner's report on disk survives the fault).
|
|
15
|
+
*
|
|
16
|
+
* Each test FAILS without the fix: pre-fix the loop never calls `ensureArtifactDir`
|
|
17
|
+
* (A1), ignores `excludeScan` (A3), and `writeArtifact(path, '')` overwrites the
|
|
18
|
+
* report while emitting a clean 0-findings pass (A11).
|
|
19
|
+
*/
|
|
20
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
21
|
+
import { messageHashFingerprintStrategy } from '../fingerprint.js';
|
|
22
|
+
import { runScanLoop } from '../run-loop.js';
|
|
23
|
+
/** A `ToolCliContext` stub that records the ORDER of seam calls. */
|
|
24
|
+
function makeCli() {
|
|
25
|
+
const order = [];
|
|
26
|
+
const spies = {
|
|
27
|
+
ensureArtifactDir: vi.fn((path) => {
|
|
28
|
+
order.push(`ensureArtifactDir:${path}`);
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}),
|
|
31
|
+
writeArtifact: vi.fn((path) => {
|
|
32
|
+
order.push(`writeArtifact:${path}`);
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}),
|
|
35
|
+
deliverSignals: vi.fn(() => Promise.resolve({ cloudAccepted: 0 })),
|
|
36
|
+
emitEnvelope: vi.fn(),
|
|
37
|
+
render: vi.fn(() => Promise.resolve()),
|
|
38
|
+
};
|
|
39
|
+
const cli = {
|
|
40
|
+
...spies,
|
|
41
|
+
saveBaseline: vi.fn(() => Promise.resolve()),
|
|
42
|
+
compareBaseline: vi.fn(() => Promise.resolve({ added: [], resolved: [], unchanged: [], degraded: false })),
|
|
43
|
+
reportFailure: vi.fn(() => Promise.resolve()),
|
|
44
|
+
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
45
|
+
scope: {
|
|
46
|
+
toolConfig: {},
|
|
47
|
+
projectContext: { projectRoot: '/proj', configPath: '/proj/opensip-cli.config.yml' },
|
|
48
|
+
runId: 'RUN_test',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return { cli, order, spies };
|
|
52
|
+
}
|
|
53
|
+
const BINARY = { command: 'examplescan', versionArgs: ['version'] };
|
|
54
|
+
/** IO-deps stub: a found binary; `code`/`artifact`/sizes per case. */
|
|
55
|
+
function makeDeps(opts) {
|
|
56
|
+
return {
|
|
57
|
+
binaryDeps: { existsSync: () => true, which: () => '/usr/bin/examplescan' },
|
|
58
|
+
runProcess: () => {
|
|
59
|
+
opts.order?.push('runProcess');
|
|
60
|
+
return Promise.resolve({ code: opts.code, stdout: '', stderr: 'boom', timedOut: false });
|
|
61
|
+
},
|
|
62
|
+
probeVersion: () => '1.2.3',
|
|
63
|
+
readFile: () => opts.artifact,
|
|
64
|
+
fileSize: () => opts.fileSize ?? opts.artifact.length,
|
|
65
|
+
env: {},
|
|
66
|
+
...(opts.maxBuffer === undefined ? {} : { maxBuffer: opts.maxBuffer }),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function input(cli, cmd, opts = {}) {
|
|
70
|
+
return {
|
|
71
|
+
cli,
|
|
72
|
+
tool: 'examplescan',
|
|
73
|
+
adapterPackage: '@opensip-cli/tool-example',
|
|
74
|
+
command: cmd,
|
|
75
|
+
binary: BINARY,
|
|
76
|
+
fingerprintStrategy: messageHashFingerprintStrategy,
|
|
77
|
+
opts,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** gitleaks-shaped JSON command (0 clean, 1 findings, >=2 fault). */
|
|
81
|
+
function jsonCommand(extra = {}) {
|
|
82
|
+
return {
|
|
83
|
+
name: 'scan',
|
|
84
|
+
args: (ctx) => ['detect', '--report-path', ctx.artifactPath('scan.json')],
|
|
85
|
+
output: { kind: 'json', path: 'scan.json' },
|
|
86
|
+
exitCodes: { ok: [0], findings: [1], errorFrom: 2 },
|
|
87
|
+
parse: () => [],
|
|
88
|
+
...extra,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** trivy-shaped SARIF command: ONLY 0 is clean, no findings code, any nonzero faults. */
|
|
92
|
+
function sarifCommand(extra = {}) {
|
|
93
|
+
return {
|
|
94
|
+
name: 'scan',
|
|
95
|
+
args: (ctx) => ['fs', '--output', ctx.artifactPath('scan.sarif'), ctx.projectRoot],
|
|
96
|
+
output: { kind: 'sarif', path: 'scan.sarif' },
|
|
97
|
+
exitCodes: { ok: [0], findings: [], errorFrom: 1 },
|
|
98
|
+
...extra,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const VALID_SARIF = '{"version":"2.1.0","runs":[]}';
|
|
102
|
+
const ARTIFACT_JSON = '/proj/opensip-cli/.runtime/artifacts/examplescan/RUN_test/scan.json';
|
|
103
|
+
describe('runScanLoop — A1: per-run dir ensured via the host seam before the scan', () => {
|
|
104
|
+
it('calls cli.ensureArtifactDir(artifactPath) BEFORE runProcess', async () => {
|
|
105
|
+
const { cli, order, spies } = makeCli();
|
|
106
|
+
await runScanLoop(input(cli, jsonCommand()), makeDeps({ code: 0, artifact: '[]', order }));
|
|
107
|
+
expect(spies.ensureArtifactDir).toHaveBeenCalledWith(ARTIFACT_JSON);
|
|
108
|
+
// Ordering: the dir is created (host seam) strictly before the scanner runs and
|
|
109
|
+
// before the host re-writes the report at 0600.
|
|
110
|
+
const ensureIdx = order.findIndex((e) => e.startsWith('ensureArtifactDir:'));
|
|
111
|
+
const runIdx = order.indexOf('runProcess');
|
|
112
|
+
const writeIdx = order.findIndex((e) => e.startsWith('writeArtifact:'));
|
|
113
|
+
expect(ensureIdx).toBeGreaterThanOrEqual(0);
|
|
114
|
+
expect(runIdx).toBeGreaterThan(ensureIdx);
|
|
115
|
+
expect(writeIdx).toBeGreaterThan(runIdx);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('runScanLoop — A3: the substrate injects each adapter exclusion', () => {
|
|
119
|
+
it('appends excludeScan args to the scanner argv (run-context-supplied path)', async () => {
|
|
120
|
+
const { cli } = makeCli();
|
|
121
|
+
const runProcess = vi.fn((_input) => Promise.resolve({ code: 0, stdout: '', stderr: '', timedOut: false }));
|
|
122
|
+
await runScanLoop(input(cli, sarifCommand({
|
|
123
|
+
excludeScan: ({ excludePath }) => ({ args: ['--skip-dirs', excludePath] }),
|
|
124
|
+
})), { ...makeDeps({ code: 0, artifact: VALID_SARIF }), runProcess });
|
|
125
|
+
const args = runProcess.mock.calls[0][0].args;
|
|
126
|
+
// The exclusion targets the project's `.runtime` store, not a user flag.
|
|
127
|
+
expect(args).toContain('--skip-dirs');
|
|
128
|
+
expect(args).toContain('/proj/opensip-cli/.runtime');
|
|
129
|
+
});
|
|
130
|
+
it('writes an excludeScan configFile through the host writeArtifact seam and references it', async () => {
|
|
131
|
+
const { cli, order, spies } = makeCli();
|
|
132
|
+
const runProcess = vi.fn((_input) => {
|
|
133
|
+
order.push('runProcess');
|
|
134
|
+
return Promise.resolve({ code: 0, stdout: '', stderr: '', timedOut: false });
|
|
135
|
+
});
|
|
136
|
+
await runScanLoop(input(cli, jsonCommand({
|
|
137
|
+
excludeScan: ({ configPath }) => {
|
|
138
|
+
const path = configPath('exclude.toml');
|
|
139
|
+
return { args: ['--config', path], configFile: { path, contents: 'allowlist' } };
|
|
140
|
+
},
|
|
141
|
+
})), { ...makeDeps({ code: 0, artifact: '[]' }), runProcess });
|
|
142
|
+
// The config is written via the HOST seam (never a raw substrate fs write) and
|
|
143
|
+
// BEFORE the scanner runs.
|
|
144
|
+
expect(spies.writeArtifact).toHaveBeenCalledWith('/proj/opensip-cli/.runtime/artifacts/examplescan/RUN_test/exclude.toml', 'allowlist');
|
|
145
|
+
const args = runProcess.mock.calls[0][0].args;
|
|
146
|
+
expect(args).toContain('--config');
|
|
147
|
+
const cfgWriteIdx = order.indexOf('writeArtifact:/proj/opensip-cli/.runtime/artifacts/examplescan/RUN_test/exclude.toml');
|
|
148
|
+
expect(cfgWriteIdx).toBeGreaterThanOrEqual(0);
|
|
149
|
+
expect(cfgWriteIdx).toBeLessThan(order.indexOf('runProcess'));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe('runScanLoop — A11: invalid file-backed report ⇒ fault, report not destroyed', () => {
|
|
153
|
+
it('trivy-model exit 0 + EMPTY report faults (no silent clean pass) and never overwrites it', async () => {
|
|
154
|
+
const { cli, spies } = makeCli();
|
|
155
|
+
await expect(runScanLoop(input(cli, sarifCommand()), makeDeps({ code: 0, artifact: '' }))).rejects.toMatchObject({ code: 'ADAPTER.ARTIFACT.INVALID' });
|
|
156
|
+
// The empty read buffer is NEVER written back over the scanner's report (A11).
|
|
157
|
+
expect(spies.writeArtifact).not.toHaveBeenCalled();
|
|
158
|
+
expect(spies.deliverSignals).not.toHaveBeenCalled();
|
|
159
|
+
expect(spies.emitEnvelope).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
it('exit 0 + GARBAGE (unparseable) report faults', async () => {
|
|
162
|
+
const { cli, spies } = makeCli();
|
|
163
|
+
await expect(runScanLoop(input(cli, sarifCommand()), makeDeps({ code: 0, artifact: 'not json <<<' }))).rejects.toMatchObject({ code: 'ADAPTER.ARTIFACT.INVALID' });
|
|
164
|
+
expect(spies.writeArtifact).not.toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
it('OVERSIZE report (over the maxBuffer cap) faults with a size-distinguished message', async () => {
|
|
167
|
+
const { cli, spies } = makeCli();
|
|
168
|
+
await expect(runScanLoop(input(cli, sarifCommand()), makeDeps({
|
|
169
|
+
code: 0,
|
|
170
|
+
artifact: VALID_SARIF,
|
|
171
|
+
fileSize: 100 * 1024 * 1024,
|
|
172
|
+
maxBuffer: 1024 * 1024,
|
|
173
|
+
}))).rejects.toMatchObject({
|
|
174
|
+
code: 'ADAPTER.ARTIFACT.INVALID',
|
|
175
|
+
message: expect.stringContaining('MiB cap'),
|
|
176
|
+
});
|
|
177
|
+
expect(spies.writeArtifact).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
it('a VALID report under the same trivy model still passes (no false fault)', async () => {
|
|
180
|
+
const { cli, spies } = makeCli();
|
|
181
|
+
const completion = await runScanLoop(input(cli, sarifCommand()), makeDeps({ code: 0, artifact: VALID_SARIF }));
|
|
182
|
+
expect(completion?.envelope.verdict.passed).toBe(true);
|
|
183
|
+
expect(spies.writeArtifact).toHaveBeenCalledTimes(1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
//# sourceMappingURL=run-loop-artifact.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop-artifact.test.js","sourceRoot":"","sources":["../../src/__tests__/run-loop-artifact.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit-model REACTION branch of the scan run loop (ADR-0091 Phase-0 decision 4).
|
|
3
|
+
*
|
|
4
|
+
* `interpretExit`'s classification is unit-tested directly (exit-model.test.ts) and
|
|
5
|
+
* per-adapter (each tool.test.ts). What those do NOT cover is how `runScanLoop`
|
|
6
|
+
* REACTS: the §4.12 acceptance row "verdict/exit correct for clean / findings /
|
|
7
|
+
* scanner-error exit codes". The findings row is proven end-to-end by every
|
|
8
|
+
* adapter's worker E2E; this fills the other two at the substrate level (the run
|
|
9
|
+
* loop is substrate-owned — adapters only declare the exit-model data):
|
|
10
|
+
* - a scanner-ERROR exit FAULTS (throws ADAPTER.SCAN.FAULT) — it must NOT silently
|
|
11
|
+
* emit a clean 0-findings envelope (the highest-risk misclassification, §4.6);
|
|
12
|
+
* - the gitleaks disambiguation (exit 1 + missing/garbage artifact) also faults;
|
|
13
|
+
* - a CLEAN exit (0 findings) emits a passing envelope through the host seams,
|
|
14
|
+
* never a fault.
|
|
15
|
+
*
|
|
16
|
+
* Drives `runScanLoop` directly with a stub `ToolCliContext` + stub IO deps — the
|
|
17
|
+
* same substrate-unit pattern as run-loop-gate.test.ts (run-loop.ts is excluded
|
|
18
|
+
* from coverage as IO orchestration; this asserts behavior, not coverage).
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=run-loop-exit.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop-exit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/run-loop-exit.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit-model REACTION branch of the scan run loop (ADR-0091 Phase-0 decision 4).
|
|
3
|
+
*
|
|
4
|
+
* `interpretExit`'s classification is unit-tested directly (exit-model.test.ts) and
|
|
5
|
+
* per-adapter (each tool.test.ts). What those do NOT cover is how `runScanLoop`
|
|
6
|
+
* REACTS: the §4.12 acceptance row "verdict/exit correct for clean / findings /
|
|
7
|
+
* scanner-error exit codes". The findings row is proven end-to-end by every
|
|
8
|
+
* adapter's worker E2E; this fills the other two at the substrate level (the run
|
|
9
|
+
* loop is substrate-owned — adapters only declare the exit-model data):
|
|
10
|
+
* - a scanner-ERROR exit FAULTS (throws ADAPTER.SCAN.FAULT) — it must NOT silently
|
|
11
|
+
* emit a clean 0-findings envelope (the highest-risk misclassification, §4.6);
|
|
12
|
+
* - the gitleaks disambiguation (exit 1 + missing/garbage artifact) also faults;
|
|
13
|
+
* - a CLEAN exit (0 findings) emits a passing envelope through the host seams,
|
|
14
|
+
* never a fault.
|
|
15
|
+
*
|
|
16
|
+
* Drives `runScanLoop` directly with a stub `ToolCliContext` + stub IO deps — the
|
|
17
|
+
* same substrate-unit pattern as run-loop-gate.test.ts (run-loop.ts is excluded
|
|
18
|
+
* from coverage as IO orchestration; this asserts behavior, not coverage).
|
|
19
|
+
*/
|
|
20
|
+
import { createSignal } from '@opensip-cli/core';
|
|
21
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
22
|
+
import { messageHashFingerprintStrategy } from '../fingerprint.js';
|
|
23
|
+
import { runScanLoop } from '../run-loop.js';
|
|
24
|
+
function makeCli() {
|
|
25
|
+
const spies = {
|
|
26
|
+
deliverSignals: vi.fn(() => Promise.resolve({ cloudAccepted: 0 })),
|
|
27
|
+
render: vi.fn(() => Promise.resolve()),
|
|
28
|
+
emitEnvelope: vi.fn(),
|
|
29
|
+
reportFailure: vi.fn(() => Promise.resolve()),
|
|
30
|
+
writeArtifact: vi.fn(() => Promise.resolve()),
|
|
31
|
+
ensureArtifactDir: vi.fn(() => Promise.resolve()),
|
|
32
|
+
saveBaseline: vi.fn(() => Promise.resolve()),
|
|
33
|
+
compareBaseline: vi.fn(() => Promise.resolve({ added: [], resolved: [], unchanged: [], degraded: false })),
|
|
34
|
+
};
|
|
35
|
+
const cli = {
|
|
36
|
+
...spies,
|
|
37
|
+
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
38
|
+
scope: {
|
|
39
|
+
toolConfig: {},
|
|
40
|
+
projectContext: { projectRoot: '/proj', configPath: '/proj/opensip-cli.config.yml' },
|
|
41
|
+
runId: 'RUN_test',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
return { cli, spies };
|
|
45
|
+
}
|
|
46
|
+
/** gitleaks-shaped model: 0 clean, 1 findings, >=2 fault. */
|
|
47
|
+
const GITLEAKS_MODEL = { ok: [0], findings: [1], errorFrom: 2 };
|
|
48
|
+
function command(parse) {
|
|
49
|
+
return {
|
|
50
|
+
name: 'scan',
|
|
51
|
+
args: (ctx) => ['scan', ctx.projectRoot],
|
|
52
|
+
output: { kind: 'json', path: 'scan.json' },
|
|
53
|
+
exitCodes: GITLEAKS_MODEL,
|
|
54
|
+
parse,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const BINARY = { command: 'examplescan', versionArgs: ['version'] };
|
|
58
|
+
/** IO-deps stub: a found binary; `code`/`artifact` per case. */
|
|
59
|
+
function makeDeps(code, artifact) {
|
|
60
|
+
return {
|
|
61
|
+
binaryDeps: { existsSync: () => true, which: () => '/usr/bin/examplescan' },
|
|
62
|
+
runProcess: () => Promise.resolve({ code, stdout: '', stderr: 'boom', timedOut: false }),
|
|
63
|
+
probeVersion: () => '1.2.3',
|
|
64
|
+
readFile: () => artifact,
|
|
65
|
+
fileSize: () => artifact.length,
|
|
66
|
+
env: {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function input(cli, cmd, opts = {}) {
|
|
70
|
+
return {
|
|
71
|
+
cli,
|
|
72
|
+
tool: 'examplescan',
|
|
73
|
+
adapterPackage: '@opensip-cli/tool-example',
|
|
74
|
+
command: cmd,
|
|
75
|
+
binary: BINARY,
|
|
76
|
+
fingerprintStrategy: messageHashFingerprintStrategy,
|
|
77
|
+
opts,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const ONE_FINDING = [
|
|
81
|
+
createSignal({
|
|
82
|
+
source: 'examplescan',
|
|
83
|
+
severity: 'high',
|
|
84
|
+
ruleId: 'aws-key',
|
|
85
|
+
message: 'AWS Access Key',
|
|
86
|
+
code: { file: 'config/prod.env', line: 12 },
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
describe('runScanLoop — scanner-error exit ⇒ fault (never a silent clean scan)', () => {
|
|
90
|
+
it('a fault exit code (>= errorFrom) throws ADAPTER.SCAN.FAULT and emits nothing', async () => {
|
|
91
|
+
const { cli, spies } = makeCli();
|
|
92
|
+
// A parse that WOULD return zero findings — proving the fault path is taken on
|
|
93
|
+
// the exit code, not silently turned into a clean 0-findings emit.
|
|
94
|
+
await expect(runScanLoop(input(cli, command(() => [])), makeDeps(2, '[]'))).rejects.toMatchObject({ code: 'ADAPTER.SCAN.FAULT' });
|
|
95
|
+
expect(spies.writeArtifact).not.toHaveBeenCalled();
|
|
96
|
+
expect(spies.deliverSignals).not.toHaveBeenCalled();
|
|
97
|
+
expect(spies.emitEnvelope).not.toHaveBeenCalled();
|
|
98
|
+
expect(spies.render).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
it('the gitleaks disambiguation: exit 1 + missing/garbage artifact ⇒ fault', async () => {
|
|
101
|
+
const { cli, spies } = makeCli();
|
|
102
|
+
// Exit 1 is the `findings` code, but an empty/unparseable artifact downgrades
|
|
103
|
+
// it to a fault — it must NOT be reported as findings.
|
|
104
|
+
await expect(runScanLoop(input(cli, command(() => ONE_FINDING)), makeDeps(1, ''))).rejects.toMatchObject({ code: 'ADAPTER.SCAN.FAULT' });
|
|
105
|
+
expect(spies.writeArtifact).not.toHaveBeenCalled();
|
|
106
|
+
expect(spies.deliverSignals).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('runScanLoop — clean exit ⇒ passing envelope through the host seams', () => {
|
|
110
|
+
it('exit 0 with zero findings persists the artifact and delivers a passing verdict (no fault)', async () => {
|
|
111
|
+
const { cli, spies } = makeCli();
|
|
112
|
+
const completion = await runScanLoop(input(cli, command(() => [])), makeDeps(0, '[]'));
|
|
113
|
+
expect(spies.reportFailure).not.toHaveBeenCalled();
|
|
114
|
+
expect(spies.writeArtifact).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(completion?.envelope.tool).toBe('examplescan');
|
|
116
|
+
expect(completion?.envelope.verdict.passed).toBe(true);
|
|
117
|
+
expect(completion?.session.payload.findings).toBe(0);
|
|
118
|
+
// No-gate, no-json path: human summary, delivered without a runFailed override.
|
|
119
|
+
expect(spies.emitEnvelope).not.toHaveBeenCalled();
|
|
120
|
+
expect((spies.deliverSignals.mock.calls[0]?.[1]).runFailed).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=run-loop-exit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop-exit.test.js","sourceRoot":"","sources":["../../src/__tests__/run-loop-exit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate-ratchet branch of the scan run loop (ADR-0036). The run loop itself is the
|
|
3
|
+
* IO orchestration excluded from coverage; these tests drive its `--gate-save` /
|
|
4
|
+
* `--gate-compare` decision branch directly with a stub `ToolCliContext` + stub IO
|
|
5
|
+
* deps, asserting the host baseline seams + the runFailed exit override are wired
|
|
6
|
+
* the same way fit's `runGateMode` wires them. The full ratchet over a REAL forked
|
|
7
|
+
* worker lives in tool-gitleaks's worker E2E (§4.12).
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=run-loop-gate.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/run-loop-gate.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate-ratchet branch of the scan run loop (ADR-0036). The run loop itself is the
|
|
3
|
+
* IO orchestration excluded from coverage; these tests drive its `--gate-save` /
|
|
4
|
+
* `--gate-compare` decision branch directly with a stub `ToolCliContext` + stub IO
|
|
5
|
+
* deps, asserting the host baseline seams + the runFailed exit override are wired
|
|
6
|
+
* the same way fit's `runGateMode` wires them. The full ratchet over a REAL forked
|
|
7
|
+
* worker lives in tool-gitleaks's worker E2E (§4.12).
|
|
8
|
+
*/
|
|
9
|
+
import { EXIT_CODES } from '@opensip-cli/contracts';
|
|
10
|
+
import { createSignal, } from '@opensip-cli/core';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { messageHashFingerprintStrategy } from '../fingerprint.js';
|
|
13
|
+
import { runScanLoop } from '../run-loop.js';
|
|
14
|
+
const TWO_FINDINGS = [
|
|
15
|
+
createSignal({
|
|
16
|
+
source: 'examplescan',
|
|
17
|
+
severity: 'high',
|
|
18
|
+
ruleId: 'aws-key',
|
|
19
|
+
message: 'AWS Access Key',
|
|
20
|
+
code: { file: 'config/prod.env', line: 12 },
|
|
21
|
+
}),
|
|
22
|
+
createSignal({
|
|
23
|
+
source: 'examplescan',
|
|
24
|
+
severity: 'high',
|
|
25
|
+
ruleId: 'gitlab-pat',
|
|
26
|
+
message: 'GitLab PAT',
|
|
27
|
+
code: { file: 'src/client.ts', line: 4 },
|
|
28
|
+
}),
|
|
29
|
+
];
|
|
30
|
+
function makeCli(compareResult) {
|
|
31
|
+
const spies = {
|
|
32
|
+
saveBaseline: vi.fn(() => Promise.resolve()),
|
|
33
|
+
compareBaseline: vi.fn(() => Promise.resolve(compareResult ?? { added: [], resolved: [], unchanged: [], degraded: false })),
|
|
34
|
+
deliverSignals: vi.fn(() => Promise.resolve({ cloudAccepted: 0 })),
|
|
35
|
+
render: vi.fn(() => Promise.resolve()),
|
|
36
|
+
emitEnvelope: vi.fn(),
|
|
37
|
+
reportFailure: vi.fn(() => Promise.resolve()),
|
|
38
|
+
writeArtifact: vi.fn(() => Promise.resolve()),
|
|
39
|
+
ensureArtifactDir: vi.fn(() => Promise.resolve()),
|
|
40
|
+
};
|
|
41
|
+
const cli = {
|
|
42
|
+
...spies,
|
|
43
|
+
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
44
|
+
scope: {
|
|
45
|
+
toolConfig: {},
|
|
46
|
+
projectContext: { projectRoot: '/proj', configPath: '/proj/opensip-cli.config.yml' },
|
|
47
|
+
runId: 'RUN_test',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return { cli, spies };
|
|
51
|
+
}
|
|
52
|
+
const COMMAND = {
|
|
53
|
+
name: 'scan',
|
|
54
|
+
args: (ctx) => ['scan', ctx.projectRoot],
|
|
55
|
+
output: { kind: 'json', path: 'scan.json' },
|
|
56
|
+
exitCodes: { ok: [0], findings: [1], errorFrom: 2 },
|
|
57
|
+
parse: () => TWO_FINDINGS,
|
|
58
|
+
};
|
|
59
|
+
const BINARY = { command: 'examplescan', versionArgs: ['version'] };
|
|
60
|
+
/** IO-deps stub: a found binary, a findings exit, a valid (non-empty) artifact. */
|
|
61
|
+
function makeDeps() {
|
|
62
|
+
return {
|
|
63
|
+
binaryDeps: { existsSync: () => true, which: () => '/usr/bin/examplescan' },
|
|
64
|
+
runProcess: () => Promise.resolve({ code: 1, stdout: '', stderr: '', timedOut: false }),
|
|
65
|
+
probeVersion: () => '1.2.3',
|
|
66
|
+
readFile: () => '[{"RuleID":"aws-key"},{"RuleID":"gitlab-pat"}]',
|
|
67
|
+
fileSize: () => 42,
|
|
68
|
+
env: {},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function input(cli, opts) {
|
|
72
|
+
return {
|
|
73
|
+
cli,
|
|
74
|
+
tool: 'examplescan',
|
|
75
|
+
adapterPackage: '@opensip-cli/tool-example',
|
|
76
|
+
command: COMMAND,
|
|
77
|
+
binary: BINARY,
|
|
78
|
+
fingerprintStrategy: messageHashFingerprintStrategy,
|
|
79
|
+
opts,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
describe('runScanLoop — gate-ratchet branch (ADR-0036)', () => {
|
|
83
|
+
it('rejects --gate-save + --gate-compare together (mutual exclusion → reportFailure, no scan)', async () => {
|
|
84
|
+
const { cli, spies } = makeCli();
|
|
85
|
+
const runProcess = vi.fn(() => Promise.resolve({ code: 0, stdout: '', stderr: '', timedOut: false }));
|
|
86
|
+
const completion = await runScanLoop(input(cli, { gateSave: true, gateCompare: true }), {
|
|
87
|
+
...makeDeps(),
|
|
88
|
+
runProcess,
|
|
89
|
+
});
|
|
90
|
+
expect(completion).toBeUndefined();
|
|
91
|
+
expect(spies.reportFailure).toHaveBeenCalledWith(expect.objectContaining({ exitCode: EXIT_CODES.CONFIGURATION_ERROR }));
|
|
92
|
+
// Fail-fast: the scanner subprocess never ran, and no baseline seam was touched.
|
|
93
|
+
expect(runProcess).not.toHaveBeenCalled();
|
|
94
|
+
expect(spies.saveBaseline).not.toHaveBeenCalled();
|
|
95
|
+
expect(spies.compareBaseline).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
it('--gate-save saves the baseline, renders gate-done, and delivers without a runFailed override', async () => {
|
|
98
|
+
const { cli, spies } = makeCli();
|
|
99
|
+
const completion = await runScanLoop(input(cli, { gateSave: true }), makeDeps());
|
|
100
|
+
expect(spies.saveBaseline).toHaveBeenCalledWith('examplescan', expect.objectContaining({ tool: 'examplescan' }));
|
|
101
|
+
expect(spies.render).toHaveBeenCalledWith(expect.objectContaining({
|
|
102
|
+
type: 'gate-done',
|
|
103
|
+
lines: expect.arrayContaining([expect.stringContaining('baseline saved')]),
|
|
104
|
+
}));
|
|
105
|
+
// gate-save omits runFailed → the host derives the findings exit from the verdict.
|
|
106
|
+
expect(spies.deliverSignals).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(spies.deliverSignals.mock.calls[0][1].runFailed).toBeUndefined();
|
|
108
|
+
// A gate run still persists a session.
|
|
109
|
+
expect(completion?.session.tool).toBe('examplescan');
|
|
110
|
+
expect(completion?.session.payload.findings).toBe(2);
|
|
111
|
+
});
|
|
112
|
+
it('--gate-compare clean (not degraded) renders the diff and delivers runFailed=false', async () => {
|
|
113
|
+
const { cli, spies } = makeCli({
|
|
114
|
+
added: [],
|
|
115
|
+
resolved: [],
|
|
116
|
+
unchanged: [...TWO_FINDINGS],
|
|
117
|
+
degraded: false,
|
|
118
|
+
});
|
|
119
|
+
await runScanLoop(input(cli, { gateCompare: true }), makeDeps());
|
|
120
|
+
expect(spies.compareBaseline).toHaveBeenCalledWith('examplescan', expect.objectContaining({ tool: 'examplescan' }));
|
|
121
|
+
expect(spies.render).toHaveBeenCalledWith(expect.objectContaining({ type: 'gate-done', lines: expect.any(Array) }));
|
|
122
|
+
expect(spies.deliverSignals.mock.calls[0][1].runFailed).toBe(false);
|
|
123
|
+
expect(spies.saveBaseline).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
it('--gate-compare degraded (net-new finding) delivers runFailed=true', async () => {
|
|
126
|
+
const newFinding = createSignal({
|
|
127
|
+
source: 'examplescan',
|
|
128
|
+
severity: 'high',
|
|
129
|
+
ruleId: 'stripe-key',
|
|
130
|
+
message: 'Stripe key',
|
|
131
|
+
code: { file: 'src/pay.ts', line: 1 },
|
|
132
|
+
});
|
|
133
|
+
const { cli, spies } = makeCli({
|
|
134
|
+
added: [newFinding],
|
|
135
|
+
resolved: [],
|
|
136
|
+
unchanged: [...TWO_FINDINGS],
|
|
137
|
+
degraded: true,
|
|
138
|
+
});
|
|
139
|
+
await runScanLoop(input(cli, { gateCompare: true }), makeDeps());
|
|
140
|
+
expect(spies.deliverSignals.mock.calls[0][1].runFailed).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
it('the no-gate path is unaffected: emits the envelope (--json) and delivers without runFailed', async () => {
|
|
143
|
+
const { cli, spies } = makeCli();
|
|
144
|
+
const completion = await runScanLoop(input(cli, { json: true }), makeDeps());
|
|
145
|
+
expect(spies.emitEnvelope).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(spies.saveBaseline).not.toHaveBeenCalled();
|
|
147
|
+
expect(spies.compareBaseline).not.toHaveBeenCalled();
|
|
148
|
+
expect(spies.deliverSignals.mock.calls[0][1].runFailed).toBeUndefined();
|
|
149
|
+
expect(completion?.envelope.tool).toBe('examplescan');
|
|
150
|
+
});
|
|
151
|
+
it('the no-gate, no-json path renders the human summary and delivers without runFailed', async () => {
|
|
152
|
+
const { cli, spies } = makeCli();
|
|
153
|
+
await runScanLoop(input(cli, {}), makeDeps());
|
|
154
|
+
expect(spies.emitEnvelope).not.toHaveBeenCalled();
|
|
155
|
+
expect(spies.render).toHaveBeenCalledWith(expect.objectContaining({ type: 'text-lines', title: 'examplescan scan' }));
|
|
156
|
+
expect(spies.deliverSignals.mock.calls[0][1].runFailed).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=run-loop-gate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/run-loop-gate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-payload — the adapter-owned, dashboard-shaped session detail blob
|
|
3
|
+
* (A2: an adapter session must carry `summary` + grouped `checks[]` so the HTML
|
|
4
|
+
* report renders a secret/vuln scan's findings instead of falsely "clean").
|
|
5
|
+
*
|
|
6
|
+
* Proves the grouped shape AND the secret-hygiene invariant: every field is
|
|
7
|
+
* copied from an already-redacted signal, and `metadata` is narrowed to JSON
|
|
8
|
+
* scalars (the nested provenance object is dropped) — so NO raw credential reaches
|
|
9
|
+
* the persisted payload.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=session-payload.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-payload.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-payload.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|