Files
orca/config/scripts/ssh-windows-mobile-bundle.test.mjs
T
OrcaWinandOrcaWin 791502fae4 ci: move secondary, scheduled and contract workflows to GitHub-hosted runners (#34568)
* ci: move secondary, scheduled and contract workflows to GitHub-hosted runners

Public-repo standard hosted runners are free under Enterprise. Release, build,
benchmark, e2e and unit workflows stay on Blacksmith. ci-xterm-cache seeds both
cache stores since its PR consumer's pool is undecided.

* test(pi): resolve the smoke bundle's external packages from the repo

#34148 pulled zod into the commit-message planner graph; the bundle in the
OS temp dir could not resolve it on any runner.

* ci(unit): use four workers on the hosted four-core x64 runner

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 16:15:14 -07:00

107 lines
4.5 KiB
JavaScript

import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { runProcessSync } from '@orca/process-host'
import { expect, it } from 'vitest'
import { parse } from 'yaml'
import { compileAggregateExpression } from './pr-aggregate-gate-context.mjs'
import { writeMobileWebBundleFixtureTree } from './mobile-web-bundle-fixture-tree.mjs'
const workflow = parse(readFileSync('.github/workflows/ssh-windows-hosts.yml', 'utf8'))
const producer = workflow.jobs.mobile_web_bundle
const consumer = workflow.jobs.hosts
const upload = producer.steps.find((step) => step.uses?.startsWith('actions/upload-artifact'))
const download = consumer.steps.find((step) => step.id === 'mobile-bundle')
const fallback = consumer.steps.find(
(step) => step.uses === './.github/actions/build-mobile-web-bundle'
)
const verify = consumer.steps.find(
(step) => step.name === 'Verify the mobile bundle before staging it'
)
it('binds the bundle to the same run and source across architectures and retry attempts', () => {
expect(producer['runs-on']).toBe('ubuntu-24.04')
for (const job of [producer, consumer]) {
expect(
job.steps.find((step) => step.uses?.startsWith('actions/checkout')).with.ref
).toBeUndefined()
}
expect(download.with['run-id']).toBeUndefined()
expect(download.with.repository).toBeUndefined()
const name = compileAggregateExpression(upload.with.name, ['github'])
const requested = compileAggregateExpression(download.with.name, ['github'])
for (const sha of ['a'.repeat(40), 'b'.repeat(40)]) {
const first = { github: { sha, run_attempt: 1 } }
const retry = { github: { sha, run_attempt: 2 } }
expect(name(first).coerceString()).toBe(requested(retry).coerceString())
expect(name(first).coerceString()).toContain(sha)
}
expect(upload.with.overwrite).toBe(true)
expect(upload.with.path).toBe(download.with.path)
})
it.each(['success', 'failure', 'cancelled', 'skipped', ''])(
'admits Windows hosts only after a successful producer: %s',
(result) => {
const eligible = compileAggregateExpression(consumer.if, ['github', 'needs'])
const value = {
github: { event_name: 'pull_request', event: { pull_request: { draft: false } } },
needs: { mobile_web_bundle: { result } }
}
expect(consumer.needs).toBe('mobile_web_bundle')
expect(eligible(value).coerceString()).toBe(String(result === 'success'))
expect(eligible(value, true).coerceString()).toBe('false')
}
)
it.each(['success', 'failure', 'skipped', ''])(
'rebuilds locally only when the artifact is unavailable: %s',
(outcome) => {
const eligible = compileAggregateExpression(fallback.if, ['steps'])
expect(eligible({ steps: { 'mobile-bundle': { outcome } } }).coerceString()).toBe(
String(outcome !== 'success')
)
expect(download['continue-on-error']).toBe(true)
expect(fallback['continue-on-error']).toBeUndefined()
expect(verify['continue-on-error']).toBeUndefined()
const template = consumer.steps.findIndex((step) =>
step.run?.includes('build-orcad-template.mjs')
)
expect(consumer.steps.indexOf(download)).toBeLessThan(consumer.steps.indexOf(fallback))
expect(consumer.steps.indexOf(fallback)).toBeLessThan(consumer.steps.indexOf(verify))
expect(consumer.steps.indexOf(verify)).toBeLessThan(template)
}
)
it.skipIf(process.platform === 'win32')(
'executes the consumer verifier without mobile dependencies and rejects corrupt assets',
async () => {
const directory = mkdtempSync(join(tmpdir(), 'ssh-mobile-bundle-'))
try {
mkdirSync(join(directory, 'config', 'scripts'), { recursive: true })
for (const file of [
'config/dark-build-switches.cjs',
'config/scripts/verify-packaged-mobile-web-bundle.cjs'
]) {
copyFileSync(file, join(directory, file))
}
const bundleDir = join(directory, 'out', 'mobile-web')
await writeMobileWebBundleFixtureTree({ outDir: bundleDir })
const execute = () =>
runProcessSync({
program: 'bash',
args: ['-e', '-o', 'pipefail', '-c', verify.run],
cwd: directory
})
expect(execute().code).toBe(0)
const indexPath = join(bundleDir, 'index.html')
writeFileSync(indexPath, 'x'.repeat(readFileSync(indexPath).length))
const corrupted = execute()
expect(corrupted.code).not.toBe(0)
expect(corrupted.stderr).toContain('hashes to')
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
)