// tests/main/update-policy-redteam-r3-5.test.ts // 원격 정책의 안전 필드(killSwitch, stagingPercentage)가 형식이 틀리면 가장 허용적인 값이 // 아니라 가장 보수적인 값으로 읽히는지 확인한다. // // 회귀: `"killSwitch": "true"` 가 false 로, `"stagingPercentage": "5"` / 5.5 가 100% 로 // 조용히 바뀌어 운영자 의도와 반대로 전체 배포되던 문제. import { describe, it, expect } from 'vitest' import { DEFAULT_UPDATE_POLICY, evaluateUpdateOffer, parseUpdatePolicy, } from '../../src/main/update-policy' const BASE = { schemaVersion: 1, defaultChannel: 'latest', channels: { latest: { allowPrerelease: false }, beta: { allowPrerelease: true }, alpha: { allowPrerelease: true }, }, minimumSupportedVersion: '1.0.0', forceInstallBelow: null, fullInstallOnMajorChange: true, fullInstallVersionGap: 3, stagingPercentage: 100, killSwitch: false, } describe('parseUpdatePolicy — 안전 필드 fail-closed', () => { it.each(['true', 'false', 1, 0, null, {}])('killSwitch=%j 는 켜진 것으로 본다', (value) => { expect(parseUpdatePolicy({ ...BASE, killSwitch: value }).killSwitch).toBe(true) }) it.each([5.5, '5', null, Number.NaN, '100'])('stagingPercentage=%j 는 0% 로 본다', (value) => { expect(parseUpdatePolicy({ ...BASE, stagingPercentage: value }).stagingPercentage).toBe(0) }) it('형식이 틀린 킬 스위치는 업데이트 제안을 막는다', () => { const policy = parseUpdatePolicy({ ...BASE, killSwitch: 'true' }) const offer = evaluateUpdateOffer({ policy, channel: 'latest', currentVersion: '1.8.0', targetVersion: '1.9.0', skippedVersion: null, deviceId: 'device-a', }) expect(offer).toMatchObject({ action: 'ignore', reason: 'kill-switch' }) }) it('형식이 틀린 staging 은 필수 업데이트가 아니면 아무에게도 노출하지 않는다', () => { const policy = parseUpdatePolicy({ ...BASE, stagingPercentage: '5' }) for (const deviceId of ['a', 'b', 'c', 'd', 'e', 'f']) { const offer = evaluateUpdateOffer({ policy, channel: 'latest', currentVersion: '1.8.0', targetVersion: '1.9.0', skippedVersion: null, deviceId, }) expect(offer).toMatchObject({ action: 'ignore', reason: 'rollout' }) } }) it('필드가 아예 없으면 기존처럼 기본값을 쓴다', () => { const { killSwitch: _k, stagingPercentage: _s, ...rest } = BASE const parsed = parseUpdatePolicy(rest) expect(parsed.killSwitch).toBe(DEFAULT_UPDATE_POLICY.killSwitch) expect(parsed.stagingPercentage).toBe(DEFAULT_UPDATE_POLICY.stagingPercentage) }) it('올바른 값은 그대로(범위 밖 정수는 기존처럼 잘라서) 읽는다', () => { expect(parseUpdatePolicy({ ...BASE, stagingPercentage: 5, killSwitch: false })).toMatchObject({ stagingPercentage: 5, killSwitch: false, }) expect(parseUpdatePolicy({ ...BASE, stagingPercentage: 250 }).stagingPercentage).toBe(100) expect(parseUpdatePolicy({ ...BASE, stagingPercentage: -3 }).stagingPercentage).toBe(0) }) })