mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
fix(#91): make fidelity evidence states reproducible
This commit is contained in:
@@ -88,7 +88,7 @@ Do not create the file or use `approved` until the owner has reviewed the packet
|
|||||||
npm run release:fidelity:verify
|
npm run release:fidelity:verify
|
||||||
```
|
```
|
||||||
|
|
||||||
The verifier checks that the packet exists and the sign-off exists in `HEAD`, names the current candidate commit, and has the required owner fields. It intentionally cannot assess visual fidelity or create approval.
|
The verifier checks that the packet exists, the sign-off exists in `HEAD`, and its full candidate SHA names a commit that is an ancestor of the sign-off commit. It also requires the owner, date, and approved decision fields. It intentionally cannot assess visual fidelity or create approval.
|
||||||
|
|
||||||
For a release candidate, `npm run release:fidelity:gate` runs production visual regression first, then capture, packet generation, and owner-evidence verification in that order. It will remain red until the owner has committed the sign-off.
|
For a release candidate, `npm run release:fidelity:gate` runs production visual regression first, then capture, packet generation, and owner-evidence verification in that order. It will remain red until the owner has committed the sign-off.
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,17 @@ try {
|
|||||||
for (const file of required) await readFile(join(evidence, file))
|
for (const file of required) await readFile(join(evidence, file))
|
||||||
git('cat-file', '-e', `HEAD:${signoff}`)
|
git('cat-file', '-e', `HEAD:${signoff}`)
|
||||||
const text = await readFile(signoff, 'utf8')
|
const text = await readFile(signoff, 'utf8')
|
||||||
const commit = git('rev-parse', 'HEAD')
|
const candidate = text.match(/^- Candidate commit: ([0-9a-f]{40})$/mi)?.[1]
|
||||||
|
if (!candidate) throw new Error(`${signoff} must contain a full candidate commit SHA`)
|
||||||
|
git('cat-file', '-e', `${candidate}^{commit}`)
|
||||||
|
const signoffCommit = git('log', '-1', '--format=%H', 'HEAD', '--', signoff)
|
||||||
|
try {
|
||||||
|
git('merge-base', '--is-ancestor', candidate, signoffCommit)
|
||||||
|
} catch {
|
||||||
|
throw new Error(`${signoff} must be committed after candidate ${candidate}`)
|
||||||
|
}
|
||||||
const fields = [
|
const fields = [
|
||||||
['Version', version],
|
['Version', version],
|
||||||
['Candidate commit', commit],
|
|
||||||
['Reviewed by', '.+'],
|
['Reviewed by', '.+'],
|
||||||
['Reviewed on', '\\d{4}-\\d{2}-\\d{2}'],
|
['Reviewed on', '\\d{4}-\\d{2}-\\d{2}'],
|
||||||
['Decision', 'approved'],
|
['Decision', 'approved'],
|
||||||
@@ -34,7 +41,7 @@ try {
|
|||||||
throw new Error(`${signoff} must contain "- ${name}: ${value}"`)
|
throw new Error(`${signoff} must contain "- ${name}: ${value}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(`Release-fidelity evidence and committed owner approval verified for ${version}.`)
|
console.log(`Release-fidelity evidence and committed owner approval verified for ${version} at ${candidate}.`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Release-fidelity gate blocked: ${error.message}`)
|
console.error(`Release-fidelity gate blocked: ${error.message}`)
|
||||||
process.exitCode = 1
|
process.exitCode = 1
|
||||||
|
|||||||
38
scripts/release-fidelity/verify.test.mjs
Normal file
38
scripts/release-fidelity/verify.test.mjs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { execFileSync } from 'node:child_process'
|
||||||
|
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join, resolve } from 'node:path'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
const verifier = resolve('scripts/release-fidelity/verify.mjs')
|
||||||
|
|
||||||
|
function git(directory, ...args) {
|
||||||
|
return execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
test('accepts a committed sign-off for an ancestor candidate', async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), 'boxbox-fidelity-verify-'))
|
||||||
|
git(root, 'init')
|
||||||
|
git(root, 'config', 'user.email', 'test@example.com')
|
||||||
|
git(root, 'config', 'user.name', 'Test')
|
||||||
|
await writeFile(join(root, 'candidate.txt'), 'candidate')
|
||||||
|
git(root, 'add', 'candidate.txt')
|
||||||
|
git(root, 'commit', '-m', 'candidate')
|
||||||
|
const candidate = git(root, 'rev-parse', 'HEAD')
|
||||||
|
|
||||||
|
await mkdir(join(root, 'docs/release/owner-reviews'), { recursive: true })
|
||||||
|
await writeFile(join(root, 'docs/release/owner-reviews/v-test.md'), `- Version: v-test\n- Candidate commit: ${candidate}\n- Reviewed by: Owner\n- Reviewed on: 2026-07-30\n- Decision: approved\n`)
|
||||||
|
git(root, 'add', 'docs/release/owner-reviews/v-test.md')
|
||||||
|
git(root, 'commit', '-m', 'owner sign-off')
|
||||||
|
|
||||||
|
const evidence = join(root, 'evidence')
|
||||||
|
await mkdir(evidence)
|
||||||
|
await writeFile(join(evidence, 'index.html'), '')
|
||||||
|
await writeFile(join(evidence, 'summary.md'), '')
|
||||||
|
const output = execFileSync(process.execPath, [verifier, '--version', 'v-test', '--evidence', evidence], {
|
||||||
|
cwd: root,
|
||||||
|
encoding: 'utf8',
|
||||||
|
})
|
||||||
|
assert.match(output, new RegExp(candidate))
|
||||||
|
})
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
import { mkdir } from 'node:fs/promises'
|
import { mkdir } from 'node:fs/promises'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { expect, test, type Page } from '@playwright/test'
|
import { expect, test, type Page } from '@playwright/test'
|
||||||
import {
|
import { waitForScreenshotReady } from '../visual/helpers'
|
||||||
gotoLiveEmptyReady,
|
|
||||||
waitForScreenshotReady,
|
|
||||||
} from '../visual/helpers'
|
|
||||||
|
|
||||||
const version = process.env.RELEASE_FIDELITY_VERSION ?? 'v0.4.0'
|
const version = process.env.RELEASE_FIDELITY_VERSION ?? 'v0.4.0'
|
||||||
const output = process.env.RELEASE_FIDELITY_OUTPUT ?? 'release-fidelity'
|
const output = process.env.RELEASE_FIDELITY_OUTPUT ?? 'release-fidelity'
|
||||||
|
const activeLiveState = {
|
||||||
|
is_live: true,
|
||||||
|
data: {
|
||||||
|
Drivers: {
|
||||||
|
'1': { RacingNumber: '1', Position: 1, Interval: '' },
|
||||||
|
'44': { RacingNumber: '44', Position: 2, Interval: '+2.314' },
|
||||||
|
},
|
||||||
|
DriverInfo: {
|
||||||
|
'1': { RacingNumber: '1', Tla: 'VER', TeamColour: '3671C6' },
|
||||||
|
'44': { RacingNumber: '44', Tla: 'HAM', TeamColour: 'E8002D' },
|
||||||
|
},
|
||||||
|
Tyres: {},
|
||||||
|
RCMessages: [],
|
||||||
|
Weather: {},
|
||||||
|
Session: { MeetingName: 'Monaco', SessionName: 'Race', SessionType: 'Race' },
|
||||||
|
TrackStatus: '1',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
async function capture(page: Page, name: string, project: string): Promise<void> {
|
async function capture(page: Page, name: string, project: string): Promise<void> {
|
||||||
const directory = join(output, version, 'candidate', project)
|
const directory = join(output, version, 'candidate', project)
|
||||||
@@ -15,6 +30,12 @@ async function capture(page: Page, name: string, project: string): Promise<void>
|
|||||||
await page.screenshot({ fullPage: true, path: join(directory, `${name}.png`) })
|
await page.screenshot({ fullPage: true, path: join(directory, `${name}.png`) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function useSeededCalendar(page: Page): Promise<void> {
|
||||||
|
await page.route(/\/api\/v1\/meetings\?year=2025&source=openf1$/, (route) =>
|
||||||
|
route.fulfill({ contentType: 'application/json', body: '[]' }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function gotoCommandCenterFidelityReady(page: Page): Promise<void> {
|
async function gotoCommandCenterFidelityReady(page: Page): Promise<void> {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await expect(page.getByTestId('command-center')).toBeVisible()
|
await expect(page.getByTestId('command-center')).toBeVisible()
|
||||||
@@ -27,14 +48,25 @@ async function gotoCommandCenterFidelityReady(page: Page): Promise<void> {
|
|||||||
test.describe('release-fidelity candidate captures', () => {
|
test.describe('release-fidelity candidate captures', () => {
|
||||||
test('approved screen set', async ({ page }, testInfo) => {
|
test('approved screen set', async ({ page }, testInfo) => {
|
||||||
const project = testInfo.project.name
|
const project = testInfo.project.name
|
||||||
|
await useSeededCalendar(page)
|
||||||
|
|
||||||
if (project === 'desktop') {
|
if (project === 'desktop') {
|
||||||
|
await page.clock.install({ time: new Date('2025-06-01T12:00:00Z') })
|
||||||
await gotoCommandCenterFidelityReady(page)
|
await gotoCommandCenterFidelityReady(page)
|
||||||
|
await expect(page.getByTestId('hero-last-race')).toBeVisible()
|
||||||
await capture(page, 'weekend-between-races', project)
|
await capture(page, 'weekend-between-races', project)
|
||||||
await gotoLiveEmptyReady(page)
|
|
||||||
|
await page.route('**/api/v1/live/state', (route) =>
|
||||||
|
route.fulfill({ contentType: 'application/json', body: JSON.stringify(activeLiveState) }),
|
||||||
|
)
|
||||||
|
await page.clock.setFixedTime(new Date('2025-05-25T14:00:00Z'))
|
||||||
|
await gotoCommandCenterFidelityReady(page)
|
||||||
|
await expect(page.getByTestId('hero-live-timing')).toBeVisible()
|
||||||
await capture(page, 'weekend-live', project)
|
await capture(page, 'weekend-live', project)
|
||||||
} else {
|
} else {
|
||||||
|
await page.clock.install({ time: new Date('2025-05-25T12:00:00Z') })
|
||||||
await gotoCommandCenterFidelityReady(page)
|
await gotoCommandCenterFidelityReady(page)
|
||||||
|
await expect(page.getByTestId('hero-countdown')).toContainText('Next')
|
||||||
await capture(page, 'weekend-between-sessions-mobile', project)
|
await capture(page, 'weekend-between-sessions-mobile', project)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user