forked from mxx1111/sparepack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterfaces.test.mjs
More file actions
197 lines (171 loc) · 7.46 KB
/
Copy pathinterfaces.test.mjs
File metadata and controls
197 lines (171 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { stripFile, UnsupportedLanguageError } from '../src/interfaces.mjs'
import { findSurvivingBodies } from '../src/verify.mjs'
const strip = (src, path = 'src/thing.ts') => stripFile(src, path)
/**
* The single most important assertion in this package: a secret sitting inside a function
* body must not appear anywhere in the output. Every stripping test routes through here.
*/
function assertNotLeaked(output, ...secrets) {
for (const secret of secrets) {
assert.ok(!output.includes(secret), `implementation leaked into the stripped output: "${secret}"\n---\n${output}`)
}
}
// --- the core promise -----------------------------------------------------
test('a function keeps its signature and loses its body', () => {
const { code } = strip(`
export function chargeCard(amount: number, token: string): Promise<Receipt> {
const gateway = new AcmeGateway("live_key_do_not_ship")
return gateway.charge(amount, token)
}`)
assert.match(code, /export function chargeCard\(amount: number, token: string\): Promise<Receipt>/)
assert.match(code, /sparepack stub: not implemented/)
assertNotLeaked(code, 'live_key_do_not_ship', 'AcmeGateway', 'gateway.charge')
})
test('JSDoc survives because it documents the contract', () => {
const { code } = strip(`
/**
* Charge a card. Throws on a declined transaction.
*/
export function charge(n: number): void {
secretInternalCall(n)
}`)
assert.match(code, /Charge a card\. Throws on a declined transaction\./)
assertNotLeaked(code, 'secretInternalCall')
})
test('types, interfaces and enums are kept verbatim — they are the contract', () => {
const { code } = strip(`
export interface Receipt { id: string; total: number }
export type Currency = 'USD' | 'EUR'
export enum Status { Pending, Settled }`)
assert.match(code, /interface Receipt \{ id: string; total: number \}/)
assert.match(code, /type Currency = 'USD' \| 'EUR'/)
assert.match(code, /enum Status \{ Pending, Settled \}/)
})
test('imports are kept so the stub can still typecheck', () => {
const { code } = strip(`import { Receipt } from './types'\nexport function f(): Receipt { return realWork() }`)
assert.match(code, /import \{ Receipt \} from '\.\/types'/)
assertNotLeaked(code, 'realWork')
})
// --- classes --------------------------------------------------------------
test('class methods are stubbed and property initialisers are dropped', () => {
const { code } = strip(`
export class PaymentClient {
private endpoint = "https://billing.acme-corp.internal/v2"
public retries: number = 3
constructor(private token: string) {
this.validate(token)
}
async charge(amount: number): Promise<void> {
await fetch(this.endpoint, { body: JSON.stringify({ amount, secret: this.token }) })
}
get isReady(): boolean {
return Boolean(this.token)
}
}`)
assert.match(code, /class PaymentClient/)
assert.match(code, /charge\(amount: number\): Promise<void>/)
assert.match(code, /get isReady\(\): boolean/)
assertNotLeaked(code, 'billing.acme-corp.internal', 'JSON.stringify', 'this.validate', 'Boolean(this.token)')
})
test('a static initialiser block is dropped with a warning', () => {
const { code, warnings } = strip(`
export class Config {
static { loadFromVault("prod") }
}`)
assertNotLeaked(code, 'loadFromVault', 'prod')
assert.ok(warnings.some((w) => /static initialiser/.test(w)))
})
// --- values are not contract ---------------------------------------------
test('an untyped top-level constant is dropped rather than published', () => {
const { code, warnings } = strip(`export const API_BASE = "https://internal.acme-corp.lan/api"`)
assertNotLeaked(code, 'internal.acme-corp.lan')
assert.ok(warnings.some((w) => /API_BASE/.test(w) && /no type annotation/.test(w)))
})
test('a typed top-level constant becomes a declaration without its value', () => {
const { code } = strip(`export const TIMEOUT_MS: number = 30_000`)
assert.match(code, /export declare const TIMEOUT_MS: number;/)
assertNotLeaked(code, '30_000')
})
// --- failure direction ----------------------------------------------------
test('an unrecognised top-level statement is dropped, not passed through', () => {
const { code, warnings, dropped } = strip(`
console.log(process.env.INTERNAL_WEBHOOK)
if (isProd) { bootstrapSecrets() }
export function safe(): void { doThing() }`)
assertNotLeaked(code, 'INTERNAL_WEBHOOK', 'bootstrapSecrets', 'isProd', 'doThing')
assert.match(code, /export function safe\(\): void/)
assert.ok(dropped >= 2, `expected the unrecognised statements to be counted as dropped, got ${dropped}`)
assert.ok(warnings.length >= 2)
})
test('an unsupported language is refused rather than emitted unchanged', () => {
assert.throws(
() => stripFile('def charge(amount):\n return gateway.charge(amount)\n', 'src/billing.py'),
(err) => {
assert.ok(err instanceof UnsupportedLanguageError)
assert.match(err.message, /only understands/)
assert.match(err.message, /include/)
return true
},
)
})
test('every supported extension is actually handled', () => {
for (const ext of ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs']) {
const { code } = stripFile('export function f() { secretBody() }', `src/a${ext}`)
assertNotLeaked(code, 'secretBody')
}
})
// --- cross-check against the independent verifier ------------------------
test('stripped output passes the verifier that looks for surviving bodies', () => {
const source = `
import { A } from './a'
export interface Shape { n: number }
export function one(a: number): number {
return compute(a) * SECRET_FACTOR
}
export class C {
method(): void { doSomethingReal() }
get v(): number { return this.internal }
}
export const T: number = 5
`
const { code } = strip(source)
assertNotLeaked(code, 'compute', 'SECRET_FACTOR', 'doSomethingReal', 'this.internal')
assert.deepEqual(findSurvivingBodies(code, 'src/thing.ts'), [], 'verifier should find no surviving implementation')
})
test('the verifier catches a body the stripper would have missed', () => {
// Guards the guard: if this ever passes, findSurvivingBodies has stopped working.
const notStripped = `export function f(): number { return 42 }`
const survivors = findSurvivingBodies(notStripped, 'src/x.ts')
assert.equal(survivors.length, 1)
assert.equal(survivors[0].name, 'f')
})
test('the verifier sees through a nested function body', () => {
const sneaky = `
export function outer(): void {
throw new Error('sparepack stub: not implemented')
}
const inner = () => { realLogic() }
`
const survivors = findSurvivingBodies(sneaky, 'src/x.ts')
assert.equal(survivors.length, 1, 'the arrow function body should be reported')
})
// --- overloads and edge shapes -------------------------------------------
test('an overload signature without a body is left alone', () => {
const { code } = strip(`
export function parse(x: string): number
export function parse(x: number): string
export function parse(x: any): any { return realParse(x) }`)
assert.match(code, /export function parse\(x: string\): number/)
assertNotLeaked(code, 'realParse')
})
test('an empty file produces a header and nothing else', () => {
const { code, kept } = strip('')
assert.equal(kept, 0)
assert.match(code, /Stripped by sparepack/)
})
test('the output names its source file', () => {
const { code } = strip('export function f(): void { x() }', 'src/payment/gateway.ts')
assert.match(code, /Source file: src\/payment\/gateway\.ts/)
})