import { describe, expect, test } from 'bun:test'; import { DPKG_ARCHITECTURES, manifestProblems } from './manifest'; import { reportDependencies } from './os-deps'; import type { DiscoveredPlugin, OsDependency } from './manifest'; /** * A package cannot be installed for an architecture the host does not know about, and no package name can * say so — which is why `multiarch` is a field rather than something clever in the package string. * * Minesweeper is the case that produced it: `apt-get install wine` on a 64-bit host succeeds, reports a * clean plugin install, and leaves a 32-bit game dying with `Bad EXE format`. Nothing in that chain * mentions architectures, so it is worth pinning the two properties that keep it working. */ const dep = (over: Partial = {}): OsDependency => ({ binary: 'definitely-not-a-real-binary-xyzzy', reason: 'test', packages: { apt: 'some-package' }, ...over, }); const plugin = (deps: OsDependency[]): DiscoveredPlugin => ({ appName: 'probe', dir: '/tmp/probe', manifest: { osDependencies: deps } }) as unknown as DiscoveredPlugin; describe('multiarch is a closed vocabulary, not a command', () => { test('a valid architecture is accepted', () => { for (const arch of DPKG_ARCHITECTURES) { const problems = manifestProblems('probe', { publisher: 'x', version: '1.0.0', platform: '>=1.0.0', label: 'X', summary: 'x', color: '#000000', permissions: [], osDependencies: [dep({ multiarch: arch })], } as never); expect(problems.filter((p) => p.includes('multiarch'))).toEqual([]); } }); // The whole point of the enum. A manifest that could supply the string could run it as root on every // machine that installs the plugin, which is a far larger grant than "may install wine". test('anything else is refused, including something shell-shaped', () => { for (const bad of ['i486', 'I386', 'i386; rm -rf /', '$(whoami)', '']) { const problems = manifestProblems('probe', { publisher: 'x', version: '1.0.0', platform: '>=1.0.0', label: 'X', summary: 'x', color: '#000000', permissions: [], osDependencies: [dep({ multiarch: bad as never })], } as never); expect(problems.some((p) => p.includes('multiarch'))).toBe(true); } }); test('a package name may carry a known architecture suffix, and only a known one', () => { const ok = manifestProblems('probe', { publisher: 'x', version: '1.0.0', platform: '>=1.0.0', label: 'X', summary: 'x', color: '#000000', permissions: [], osDependencies: [dep({ packages: { apt: 'wine32:i386' } })], } as never); expect(ok.filter((p) => p.includes('packages'))).toEqual([]); // The list form, which is what wine actually needs. const list = manifestProblems('probe', { publisher: 'x', version: '1.0.0', platform: '>=1.0.0', label: 'X', summary: 'x', color: '#000000', permissions: [], osDependencies: [dep({ packages: { apt: ['wine', 'wine32:i386'] } })], } as never); expect(list.filter((p) => p.includes('packages'))).toEqual([]); const bad = manifestProblems('probe', { publisher: 'x', version: '1.0.0', platform: '>=1.0.0', label: 'X', summary: 'x', color: '#000000', permissions: [], osDependencies: [dep({ packages: { apt: 'wine32:sparc' } })], } as never); expect(bad.some((p) => p.includes('packages'))).toBe(true); }); }); describe('the report tells the owner what will CHANGE', () => { test('a missing dependency surfaces its architecture', () => { const report = reportDependencies(plugin([dep({ multiarch: 'i386' })])); expect(report.missing.length).toBe(1); // Reported on every host. Whether it is ACTED on is apt-only — see `enableArchitectures` — but the // owner is told what the plugin asks for regardless of what this machine will do about it. expect(report.architectures).toEqual(['i386']); }); // A binary already on PATH had its architecture enabled at some point in the past. Re-reporting it would // tell the owner the machine is about to change when it is not. test('a dependency that is already present does not', () => { const report = reportDependencies(plugin([dep({ binary: 'sh', multiarch: 'i386' })])); expect(report.missing.length).toBe(0); expect(report.architectures).toEqual([]); }); test('two dependencies wanting the same architecture report it once', () => { const report = reportDependencies( plugin([dep({ binary: 'nope-a-xyzzy', multiarch: 'i386' }), dep({ binary: 'nope-b-xyzzy', multiarch: 'i386' })]), ); expect(report.architectures).toEqual(['i386']); }); });