openpencil/tools/release/release-packages/tests/native/artifacts.test.ts
Danila Poyarkov 1aa10a4fed
build(tools): group tools by role and gate them like the rest of the repo (#791)
Tools live under tools/<role>/<domain> (checks, generate, release, ci, dev), every tool is a workspace named @open-pencil/<domain>-tools, a shared tools/tsconfig.json backs the new check:tools gate that fixed 55 latent type errors, test:tools runs through bun --filter, the placement check is its own checks/test-homes package, and every tool resolves the repository through resolveWorkspaceRoot. Bun, Node, and mdast types live in a tools-root workspace so they never reach the app program.
2026-09-30 05:00:31 +04:00

84 lines
2.7 KiB
TypeScript

import { expect, test } from 'bun:test'
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { resolveReportedAssets } from '#release/native/artifacts'
import { desktopAssets } from '#release/native/catalog'
const expected = desktopAssets('aarch64-apple-darwin', '0.15.0')
async function withArtifacts(run: (root: string, reported: string[]) => Promise<void>) {
const root = await mkdtemp(join(tmpdir(), 'release-artifacts-'))
try {
const reported: string[] = []
for (const asset of expected) {
for (const name of asset.signed ? [asset.source, `${asset.source}.sig`] : [asset.source]) {
const path = join(root, name)
await writeFile(path, 'fixture')
reported.push(path)
}
}
await run(root, reported)
} finally {
await rm(root, { recursive: true, force: true })
}
}
test('uses reported paths and retains the stable macOS archive name', () =>
withArtifacts(async (root, reported) => {
const app = join(root, 'OpenPencil.app')
await mkdir(app)
await writeFile(join(root, 'unrelated-file.dmg'), 'not reported')
const assets = await resolveReportedAssets([...reported, app], expected, root)
expect(assets.map((asset) => asset.name).sort()).toEqual([
'OpenPencil_0.15.0_aarch64.dmg',
'OpenPencil_aarch64.app.tar.gz',
'OpenPencil_aarch64.app.tar.gz.sig'
])
}))
test('does not discover an existing signature missing from the action output', () =>
withArtifacts(async (root, reported) => {
await expect(
resolveReportedAssets(
reported.filter((path) => !path.endsWith('.sig')),
expected,
root
)
).rejects.toThrow('Missing reported artifacts')
}))
test('rejects duplicate reports', () =>
withArtifacts(async (root, reported) => {
await expect(resolveReportedAssets([...reported, ...reported], expected, root)).rejects.toThrow(
'Duplicate reported artifact'
)
}))
test('rejects additional reported files', () =>
withArtifacts(async (root, reported) => {
const extra = join(root, 'unexpected.exe')
await writeFile(extra, 'fixture')
await expect(resolveReportedAssets([...reported, extra], expected, root)).rejects.toThrow(
'Unexpected reported artifact'
)
}))
test('rejects symlink escapes', () =>
withArtifacts(async (root, reported) => {
const source = join(root, 'source')
await mkdir(source)
await symlink(reported[0] ?? '', join(source, 'outside.dmg'))
await expect(
resolveReportedAssets([join(source, 'outside.dmg')], expected, source)
).rejects.toThrow('escapes source checkout')
}))