@bongos/core 1.19.1078 → 1.19.1079

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.
@@ -0,0 +1,224 @@
1
+ // tests/module_install.mjs — `bongos module install <key>` (task 1003785, ADR 0338 D1).
2
+ // No network, no database: cmdInstall runs against a fake store built from a REAL
3
+ // packed tarball (scripts/gds/module-artifact.js packModule), so verification is the
4
+ // real verifyModuleArtifact. The store's read side (src/bongos/module-store.js) runs
5
+ // against a fake pg client, and the three routes are pinned in their source.
6
+ //
7
+ // Run: node tests/module_install.mjs
8
+
9
+ import { test } from 'node:test';
10
+ import { strict as assert } from 'node:assert';
11
+ import { createRequire } from 'node:module';
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+ const { packModule, verifyModuleArtifact } = require('../scripts/gds/module-artifact.js');
20
+ const { readTar, buildTar } = require('../scripts/gds/artifact-format.js');
21
+ const { cmdInstall } = require('../scripts/gds/module.js');
22
+ const { isFreePrice, resolveAcquirable, versionArtifactFile, relativeArtifactPath } = require('../src/bongos/module-store.js');
23
+
24
+ const NOW = () => new Date('2026-09-29T00:00:00Z');
25
+
26
+ // A real packed tarball for one module, built from a temp source tree.
27
+ async function packed(mj = {}) {
28
+ const src = fs.mkdtempSync(path.join(os.tmpdir(), 'mod-install-src-'));
29
+ const manifest = { key: 'weather', title: 'Weather', description: 'Shows the weather.', version: '1.2.0', coreVersion: '^1.0.0', contributes: {}, ...mj };
30
+ const dir = path.join(src, manifest.key);
31
+ fs.mkdirSync(path.join(dir, 'routes'), { recursive: true });
32
+ fs.writeFileSync(path.join(dir, 'module.json'), JSON.stringify(manifest, null, 2));
33
+ fs.writeFileSync(path.join(dir, 'routes', 'weather.js'), 'module.exports = () => null;\n');
34
+ const { tgz } = packModule(manifest.key, { modulesDir: src, now: NOW, modeOf: () => '644' });
35
+ const v = await verifyModuleArtifact(tgz, { key: manifest.key });
36
+ assert.ok(v.ok, 'the fixture packs cleanly');
37
+ return { tgz, v };
38
+ }
39
+
40
+ // A fake store: answers the three install calls and records what it was asked.
41
+ function fakeStore({ tgz, v }, over = {}) {
42
+ const calls = [];
43
+ const describe = {
44
+ ok: true, module_key: v.moduleJson.key, version: v.version, core_version: v.coreVersion,
45
+ tarball_sha256: v.tarballSha256, tree_sha256: v.treeSha256, tarball_bytes: v.bytes, channel: 'general',
46
+ };
47
+ const request = async (method, urlPath, opts = {}) => {
48
+ calls.push({ method, urlPath, json: opts.json });
49
+ if (urlPath.endsWith('/acquire')) return over.acquire || { ok: true, status: 200, data: { ...describe, ...(over.describe || {}) } };
50
+ if (urlPath.endsWith('/tarball')) return over.download || { ok: true, status: 200, buf: over.buf || tgz };
51
+ if (urlPath.endsWith('/acquired')) return over.acquired || { ok: true, status: 200, data: { ok: true } };
52
+ throw new Error(`unexpected ${method} ${urlPath}`);
53
+ };
54
+ return { calls, request };
55
+ }
56
+
57
+ const dest = () => fs.mkdtempSync(path.join(os.tmpdir(), 'mod-install-dest-'));
58
+ const quiet = () => { const out = []; return { out, log: (m) => out.push(m), errlog: (m) => out.push(m) }; };
59
+
60
+ // ---- cmdInstall ----
61
+
62
+ test('installs a free module: verified, placed, recorded, and NOT enabled', async () => {
63
+ const p = await packed();
64
+ const store = fakeStore(p);
65
+ const modulesDir = dest();
66
+ const q = quiet();
67
+ const code = await cmdInstall(['weather'], { ...q, modulesDir, request: store.request, core: '1.19.0' });
68
+ assert.equal(code, 0, q.out.join('\n'));
69
+ const placed = path.join(modulesDir, 'weather');
70
+ assert.equal(JSON.parse(fs.readFileSync(path.join(placed, 'module.json'), 'utf8')).version, '1.2.0');
71
+ assert.equal(fs.readFileSync(path.join(placed, 'routes', 'weather.js'), 'utf8'), 'module.exports = () => null;\n');
72
+ assert.ok(!fs.existsSync(path.join(placed, '.bongos-module.json')), 'the store manifest is not placed as a module file');
73
+ assert.deepEqual(fs.readdirSync(modulesDir), ['weather'], 'no staging dir left behind, nothing else written');
74
+ assert.deepEqual(store.calls.map((c) => c.method + ' ' + c.urlPath), [
75
+ 'POST /store/modules/weather/acquire',
76
+ 'GET /store/modules/weather/versions/1.2.0/tarball',
77
+ 'POST /store/modules/weather/acquired',
78
+ ]);
79
+ assert.deepEqual(store.calls[2].json, { version: '1.2.0' }, 'records the exact version placed');
80
+ assert.match(q.out.join('\n'), /not switched on/);
81
+ });
82
+
83
+ test('--version asks the store for that exact version; a loose one is refused before any call', async () => {
84
+ const p = await packed();
85
+ const store = fakeStore(p);
86
+ const q = quiet();
87
+ await cmdInstall(['weather', '--version', '1.2.0'], { ...q, modulesDir: dest(), request: store.request, core: '1.19.0' });
88
+ assert.deepEqual(store.calls[0].json, { version: '1.2.0' });
89
+ const store2 = fakeStore(p);
90
+ assert.equal(await cmdInstall(['weather', '--version', '^1.2.0'], { ...quiet(), modulesDir: dest(), request: store2.request }), 2);
91
+ assert.equal(store2.calls.length, 0);
92
+ });
93
+
94
+ test('a module already on disk is refused, and the store is never asked', async () => {
95
+ const p = await packed();
96
+ const store = fakeStore(p);
97
+ const modulesDir = dest();
98
+ fs.mkdirSync(path.join(modulesDir, 'weather'));
99
+ assert.equal(await cmdInstall(['weather'], { ...quiet(), modulesDir, request: store.request }), 1);
100
+ assert.equal(store.calls.length, 0);
101
+ });
102
+
103
+ test('a tampered download fails verification: nothing placed, nothing recorded', async () => {
104
+ const p = await packed();
105
+ const entries = readTar(p.tgz);
106
+ const route = entries.find((e) => e.name.endsWith('routes/weather.js'));
107
+ route.buf = Buffer.from('require("child_process").execSync("evil");\n');
108
+ const store = fakeStore(p, { buf: buildTar(entries) });
109
+ const modulesDir = dest();
110
+ const q = quiet();
111
+ assert.equal(await cmdInstall(['weather'], { ...q, modulesDir, request: store.request, core: '1.19.0' }), 1);
112
+ assert.match(q.out.join('\n'), /failed verification \(hash_mismatch\)/);
113
+ assert.deepEqual(fs.readdirSync(modulesDir), []);
114
+ assert.ok(!store.calls.some((c) => c.urlPath.endsWith('/acquired')));
115
+ });
116
+
117
+ test('a download that verifies but is not the version the store described is refused', async () => {
118
+ const p = await packed();
119
+ const other = await packed({ version: '1.3.0' });
120
+ const store = fakeStore(p, { buf: other.tgz });
121
+ const modulesDir = dest();
122
+ assert.equal(await cmdInstall(['weather'], { ...quiet(), modulesDir, request: store.request, core: '1.19.0' }), 1);
123
+ assert.deepEqual(fs.readdirSync(modulesDir), []);
124
+ });
125
+
126
+ test('a priced module the caller does not hold is refused with the store\'s reason', async () => {
127
+ const p = await packed();
128
+ const store = fakeStore(p, { acquire: { ok: false, status: 402, data: { error: { code: 'payment_required', message: 'buying is not available yet' } } } });
129
+ const modulesDir = dest();
130
+ const q = quiet();
131
+ assert.equal(await cmdInstall(['weather'], { ...q, modulesDir, request: store.request }), 1);
132
+ assert.match(q.out.join('\n'), /HTTP 402\): buying is not available yet/);
133
+ assert.equal(store.calls.length, 1, 'no download is attempted');
134
+ assert.deepEqual(fs.readdirSync(modulesDir), []);
135
+ });
136
+
137
+ test('a module this core cannot mount is refused before download', async () => {
138
+ const p = await packed({ coreVersion: '^2.0.0' });
139
+ const store = fakeStore(p);
140
+ assert.equal(await cmdInstall(['weather'], { ...quiet(), modulesDir: dest(), request: store.request, core: '1.19.0' }), 1);
141
+ assert.equal(store.calls.length, 1);
142
+ });
143
+
144
+ test('a failed record after placing keeps the files and says so', async () => {
145
+ const p = await packed();
146
+ const store = fakeStore(p, { acquired: { ok: false, status: 500, data: { error: { message: 'db down' } } } });
147
+ const modulesDir = dest();
148
+ const q = quiet();
149
+ assert.equal(await cmdInstall(['weather'], { ...q, modulesDir, request: store.request, core: '1.19.0' }), 1);
150
+ assert.ok(fs.existsSync(path.join(modulesDir, 'weather', 'module.json')));
151
+ assert.match(q.out.join('\n'), /installed, but the store did not record the version \(db down\)/);
152
+ });
153
+
154
+ // ---- verifyModuleArtifact includeFiles ----
155
+
156
+ test('includeFiles returns the module files from the checked parse, manifest excluded', async () => {
157
+ const { tgz } = await packed();
158
+ const v = await verifyModuleArtifact(tgz, { key: 'weather', includeFiles: true });
159
+ assert.deepEqual(v.files.map((f) => f.path).sort(), ['module.json', 'routes/weather.js']);
160
+ const plain = await verifyModuleArtifact(tgz, { key: 'weather' });
161
+ assert.equal(plain.files, undefined, 'publish keeps its old shape');
162
+ });
163
+
164
+ // ---- the store's read side ----
165
+
166
+ test('an unpriced module is free (owner decision 2026-09-29); a zero in either currency is free', () => {
167
+ assert.equal(isFreePrice(null), true);
168
+ assert.equal(isFreePrice({ price_cents: 0, price_credits: null }), true);
169
+ assert.equal(isFreePrice({ price_cents: 500, price_credits: 0 }), true);
170
+ assert.equal(isFreePrice({ price_cents: 500, price_credits: null }), false);
171
+ assert.equal(isFreePrice({ price_cents: null, price_credits: 20 }), false);
172
+ });
173
+
174
+ function fakeDb({ mod, versions = [], price = null }) {
175
+ return {
176
+ async query(sql) {
177
+ if (sql.includes('FROM store_modules')) return { rows: mod ? [mod] : [] };
178
+ if (sql.includes('FROM store_module_versions')) return { rows: versions };
179
+ if (sql.includes('FROM store_module_prices')) return { rows: price ? [price] : [] };
180
+ throw new Error(`unhandled: ${sql}`);
181
+ },
182
+ };
183
+ }
184
+ const row = (version) => ({ id: 1, module_key: 'weather', version, core_version: '^1.0.0', tarball_sha256: 'a'.repeat(64), tree_sha256: 'b'.repeat(64), tarball_bytes: 10, artifact_path: `weather/weather-${version}.tgz` });
185
+
186
+ test('resolveAcquirable picks the newest version in semver order, not text order', async () => {
187
+ const r = await resolveAcquirable('weather', { db: fakeDb({ mod: { module_key: 'weather', status: 'listed' }, versions: [row('1.9.0'), row('1.10.0'), row('1.2.0')] }) });
188
+ assert.equal(r.ok, true);
189
+ assert.equal(r.version.version, '1.10.0');
190
+ assert.equal(r.free, true);
191
+ });
192
+
193
+ test('resolveAcquirable refuses an unknown module, a delisted one, a missing version, and a module with none', async () => {
194
+ assert.equal((await resolveAcquirable('weather', { db: fakeDb({}) })).code, 'module_not_found');
195
+ assert.equal((await resolveAcquirable('weather', { db: fakeDb({ mod: { status: 'delisted' }, versions: [row('1.0.0')] }) })).code, 'module_delisted');
196
+ assert.equal((await resolveAcquirable('weather', { version: '2.0.0', db: fakeDb({ mod: { status: 'listed' }, versions: [row('1.0.0')] }) })).code, 'version_not_found');
197
+ assert.equal((await resolveAcquirable('weather', { db: fakeDb({ mod: { status: 'listed' } }) })).code, 'no_versions');
198
+ const priced = await resolveAcquirable('weather', { db: fakeDb({ mod: { status: 'listed' }, versions: [row('1.0.0')], price: { price_cents: 900, price_credits: null } }) });
199
+ assert.equal(priced.free, false);
200
+ });
201
+
202
+ test('versionArtifactFile resolves inside the store dir and refuses a row naming another path', () => {
203
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mod-store-'));
204
+ const f = versionArtifactFile(row('1.0.0'), { dir });
205
+ assert.equal(relativeArtifactPath(f, { dir }), 'weather/weather-1.0.0.tgz');
206
+ assert.throws(() => versionArtifactFile({ ...row('1.0.0'), artifact_path: '../../etc/passwd' }, { dir }), /unexpected artifact path/);
207
+ });
208
+
209
+ // ---- the routes ----
210
+
211
+ test('the install routes are own-scoped and the download checks the entitlement before reading the file', () => {
212
+ const src = fs.readFileSync(path.join(ROOT, 'src/bongos/routes/modules.js'), 'utf8');
213
+ for (const decl of [
214
+ "router.post('/store/modules/:key/acquire', auth.requireBuilder,",
215
+ "router.get('/store/modules/:key/versions/:version/tarball', auth.requireBuilder,",
216
+ "router.post('/store/modules/:key/acquired', auth.requireBuilder,",
217
+ ]) assert.ok(src.includes(decl), decl);
218
+ const start = src.indexOf("router.post('/store/modules/:key/acquire'");
219
+ const block = src.slice(start, src.indexOf('return router;', start));
220
+ assert.doesNotMatch(block, /holderRef: (?!String\(req\.builder\.id\))/, 'the holder is always the caller');
221
+ const dl = block.slice(block.indexOf('/tarball'));
222
+ assert.ok(dl.indexOf('checkEntitlement') < dl.indexOf('readFile'), 'no bytes are read for a caller who does not hold it');
223
+ assert.match(block, /if \(!r\.free\)[\s\S]*?payment_required[\s\S]*?status: 402/, 'a priced module is not granted for free');
224
+ });