|
25 | 25 | // order, with the same namespace, dependencies, and the same item names |
26 | 26 | // and counts per collection. |
27 | 27 | // |
| 28 | +// [#29871] A second describe pins the bundle's "enabled or not" for a |
| 29 | +// DISABLED package: a runtime-authored package (`POST /packages`, an object |
| 30 | +// drafted into it and published) is disabled before the export; the bundle |
| 31 | +// and its per-package export still carry the object, and a fresh runtime that |
| 32 | +// installs the entry serves it (`GET /meta/object/<name>` 200), where before |
| 33 | +// the entry was an empty package and the object came back 404. |
| 34 | +// |
28 | 35 | // Boots fixture stacks of its own (the install-local plugin mounted on both), |
29 | 36 | // so it stays out of `SHARED_SHOWCASE`. Every credential below is an |
30 | 37 | // obviously-fake placeholder, and every assertion on one is its absence. |
@@ -228,3 +235,90 @@ describe('dogfood: the whole-deployment export round-trips onto a fresh runtime, |
228 | 235 | expect((pair?.dashboards ?? []).map((d: any) => [d.name, d.label, d.overrides?.package])).toEqual([[DASHBOARD, EDITED, BASE]]); |
229 | 236 | }); |
230 | 237 | }); |
| 238 | + |
| 239 | +const OFF = 'com.dogfood.exportoff'; |
| 240 | +const OFF_OBJECT = 'exportoff_note'; |
| 241 | + |
| 242 | +describe('dogfood: a disabled package exports its items, and a fresh runtime that installs the entry serves them', () => { |
| 243 | + const dirs: string[] = []; |
| 244 | + const answers: Record<string, Answer> = {}; |
| 245 | + let offEntry: any; |
| 246 | + let previousHome: string | undefined; |
| 247 | + |
| 248 | + beforeAll(async () => { |
| 249 | + // Each runtime gets its own home: a disable is persisted there |
| 250 | + // (`package-state/`) and replayed at boot, so a shared home would carry |
| 251 | + // the source's disable onto the target, which no fresh runtime has. |
| 252 | + previousHome = process.env.OS_HOME; |
| 253 | + const sourceDir = mkdtempSync(join(tmpdir(), 'dogfood-export-disabled-source-')); |
| 254 | + dirs.push(sourceDir); |
| 255 | + process.env.OS_HOME = join(sourceDir, 'home'); |
| 256 | + const source = await bootShowcase({ |
| 257 | + extraPlugins: [new MarketplaceInstallLocalPlugin({ controlPlaneUrl: 'off', storageDir: sourceDir })], |
| 258 | + }); |
| 259 | + try { |
| 260 | + const token = await source.signIn(); |
| 261 | + answers.created = await call(source, token, 'POST', '/packages', { |
| 262 | + manifest: { id: OFF, name: 'Export disabled', version: '0.1.0', type: 'app', namespace: 'exportoff' }, |
| 263 | + }); |
| 264 | + answers.saved = await call(source, token, 'PUT', `/meta/object/${OFF_OBJECT}?mode=draft&package=${OFF}`, { |
| 265 | + name: OFF_OBJECT, label: 'Note', sharingModel: 'private', fields: { body: { type: 'text', label: 'Body' } }, |
| 266 | + }); |
| 267 | + answers.published = await call(source, token, 'POST', `/packages/${OFF}/publish-drafts`, {}); |
| 268 | + answers.enabledExport = await call(source, token, 'GET', `/packages/${OFF}/export`); |
| 269 | + answers.disabled = await call(source, token, 'PATCH', `/packages/${OFF}/disable`); |
| 270 | + answers.servedAfterDisable = await call(source, token, 'GET', `/meta/object/${OFF_OBJECT}`); |
| 271 | + answers.disabledExport = await call(source, token, 'GET', `/packages/${OFF}/export`); |
| 272 | + answers.bundle = await call(source, token, 'GET', '/packages/export'); |
| 273 | + offEntry = (answers.bundle.body?.data?.packages ?? []).find((p: any) => p?.id === OFF); |
| 274 | + } finally { |
| 275 | + await source.stop(); |
| 276 | + } |
| 277 | + |
| 278 | + const targetDir = mkdtempSync(join(tmpdir(), 'dogfood-export-disabled-target-')); |
| 279 | + dirs.push(targetDir); |
| 280 | + process.env.OS_HOME = join(targetDir, 'home'); |
| 281 | + const target = await bootShowcase({ |
| 282 | + extraPlugins: [new MarketplaceInstallLocalPlugin({ controlPlaneUrl: 'off', storageDir: targetDir })], |
| 283 | + }); |
| 284 | + try { |
| 285 | + const token = await target.signIn(); |
| 286 | + answers.installed = await call(target, token, 'POST', ROUTE, { manifest: offEntry }); |
| 287 | + answers.targetServed = await call(target, token, 'GET', `/meta/object/${OFF_OBJECT}`); |
| 288 | + } finally { |
| 289 | + await target.stop(); |
| 290 | + } |
| 291 | + }, 900_000); |
| 292 | + |
| 293 | + afterAll(() => { |
| 294 | + if (previousHome === undefined) delete process.env.OS_HOME; |
| 295 | + else process.env.OS_HOME = previousHome; |
| 296 | + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); |
| 297 | + }); |
| 298 | + |
| 299 | + const objectsOf = (manifest: any): string[] => (manifest?.objects ?? []).map((o: any) => o?.name); |
| 300 | + |
| 301 | + it('source: the package is created, its object published, and the disable lands (preconditions)', () => { |
| 302 | + for (const key of ['created', 'saved', 'published', 'disabled'] as const) { |
| 303 | + expect(answers[key]?.status, `${key}: ${JSON.stringify(answers[key]?.body)}`).toBeLessThan(300); |
| 304 | + } |
| 305 | + expect(answers.disabled.body?.data?.enabled).toBe(false); |
| 306 | + // CONTROL: enabled, the export carries the object; disabled, the served read hides it. |
| 307 | + expect(objectsOf(answers.enabledExport.body?.data)).toEqual([OFF_OBJECT]); |
| 308 | + expect(answers.servedAfterDisable.status).toBe(404); |
| 309 | + }); |
| 310 | + |
| 311 | + it('source: the disabled package\'s export and its bundle entry still carry its object', () => { |
| 312 | + expect(answers.disabledExport.status, JSON.stringify(answers.disabledExport.body)).toBe(200); |
| 313 | + expect(answers.disabledExport.body?.data).toEqual(answers.enabledExport.body?.data); |
| 314 | + expect(answers.bundle.status, JSON.stringify(answers.bundle.body)).toBe(200); |
| 315 | + expect(PackageExportBundleSchema.safeParse(answers.bundle.body?.data).success).toBe(true); |
| 316 | + expect(objectsOf(offEntry), JSON.stringify(offEntry)).toEqual([OFF_OBJECT]); |
| 317 | + }); |
| 318 | + |
| 319 | + it('target: the entry installs, and the object is served', () => { |
| 320 | + expect(answers.installed.status, JSON.stringify(answers.installed.body)).toBe(200); |
| 321 | + expect(answers.installed.body?.data?.manifestId).toBe(OFF); |
| 322 | + expect(answers.targetServed.status, JSON.stringify(answers.targetServed.body)).toBe(200); |
| 323 | + }); |
| 324 | +}); |
0 commit comments