fix(update): re-evaluate policy on every update transition and skip NSIS updater on portable installs
This commit is contained in:
parent
a4acb42573
commit
88f3dbcd69
11 changed files with 1216 additions and 307 deletions
96
apps/desktop/tests/main/update-session.test.ts
Normal file
96
apps/desktop/tests/main/update-session.test.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// tests/main/update-session.test.ts
|
||||
// 업데이트 세션 상태 머신 (순수) — 전이마다 정책을 다시 평가하는지 표로 검증한다.
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { DEFAULT_UPDATE_POLICY, type UpdatePolicy } from '../../src/main/update-policy'
|
||||
import {
|
||||
IDLE_UPDATE_SESSION,
|
||||
nextUpdateAction,
|
||||
reduceUpdateSession,
|
||||
type UpdateGateContext,
|
||||
type UpdateSession,
|
||||
} from '../../src/main/update-session'
|
||||
|
||||
function ctx(overrides: Partial<UpdatePolicy> = {}): UpdateGateContext {
|
||||
return {
|
||||
policy: { ...DEFAULT_UPDATE_POLICY, channels: { ...DEFAULT_UPDATE_POLICY.channels }, ...overrides },
|
||||
channel: 'latest',
|
||||
currentVersion: '1.9.0',
|
||||
deviceId: 'device-1',
|
||||
}
|
||||
}
|
||||
|
||||
const downloading: UpdateSession = { consenting: null, payload: { version: '1.9.1', phase: 'downloading' } }
|
||||
const downloaded: UpdateSession = { consenting: null, payload: { version: '1.9.1', phase: 'downloaded' } }
|
||||
|
||||
describe('reduceUpdateSession', () => {
|
||||
it('동의 대화상자와 설치 파일 슬롯을 따로 추적한다', () => {
|
||||
let s = reduceUpdateSession(downloaded, { type: 'consent-opened', version: '1.9.2' })
|
||||
expect(s).toEqual({ consenting: '1.9.2', payload: { version: '1.9.1', phase: 'downloaded' } })
|
||||
s = reduceUpdateSession(s, { type: 'consent-closed' })
|
||||
s = reduceUpdateSession(s, { type: 'download-started', version: '1.9.2' })
|
||||
expect(s.payload).toEqual({ version: '1.9.2', phase: 'downloading' })
|
||||
s = reduceUpdateSession(s, { type: 'download-finished', version: '1.9.2' })
|
||||
expect(s.payload).toEqual({ version: '1.9.2', phase: 'downloaded' })
|
||||
})
|
||||
|
||||
it('실패는 받는 중인 설치 파일만 비운다 (체크 실패가 받아 둔 파일을 지우지 않는다)', () => {
|
||||
expect(reduceUpdateSession(downloading, { type: 'download-failed' }).payload).toBeNull()
|
||||
expect(reduceUpdateSession(downloaded, { type: 'download-failed' })).toBe(downloaded)
|
||||
expect(reduceUpdateSession(IDLE_UPDATE_SESSION, { type: 'download-failed' })).toBe(IDLE_UPDATE_SESSION)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nextUpdateAction', () => {
|
||||
it.each([
|
||||
// [설명, 세션, 트리거, 정책, 기대 동작]
|
||||
['동의 → 허용', IDLE_UPDATE_SESSION, { type: 'consented', version: '1.9.1', offeredVersion: '1.9.1' }, {}, 'download'],
|
||||
['동의 → 대상 바뀜', IDLE_UPDATE_SESSION, { type: 'consented', version: '1.9.1', offeredVersion: '1.9.2' }, {}, 'drop-consent'],
|
||||
['동의 → 킬 스위치', IDLE_UPDATE_SESSION, { type: 'consented', version: '1.9.1', offeredVersion: '1.9.1' }, { killSwitch: true }, 'drop-consent'],
|
||||
['동의 → rollout 중단', IDLE_UPDATE_SESSION, { type: 'consented', version: '1.9.1', offeredVersion: '1.9.1' }, { stagingPercentage: 0 }, 'drop-consent'],
|
||||
['설치 파일 없음 → 정책 갱신', IDLE_UPDATE_SESSION, { type: 'policy-refreshed' }, { killSwitch: true }, 'none'],
|
||||
['받는 중 → 킬 스위치', downloading, { type: 'policy-refreshed' }, { killSwitch: true }, 'cancel-pending'],
|
||||
['받는 중 → 허용 유지', downloading, { type: 'policy-refreshed' }, {}, 'keep-pending'],
|
||||
['받아 둠 → 킬 스위치', downloaded, { type: 'policy-refreshed' }, { killSwitch: true }, 'cancel-pending'],
|
||||
['받아 둠 → rollout 중단', downloaded, { type: 'policy-refreshed' }, { stagingPercentage: 0 }, 'cancel-pending'],
|
||||
['받아 둠 → 허용 유지', downloaded, { type: 'policy-refreshed' }, {}, 'keep-pending'],
|
||||
['완료 → 재시작 묻기', downloaded, { type: 'download-finished' }, {}, 'ask-restart'],
|
||||
['완료 → 킬 스위치', downloaded, { type: 'download-finished' }, { killSwitch: true }, 'cancel-pending'],
|
||||
['재시작 수락', downloaded, { type: 'restart-answered', answer: 'restart' }, {}, 'install'],
|
||||
['재시작 미룸', downloaded, { type: 'restart-answered', answer: 'later' }, {}, 'keep-pending'],
|
||||
['재시작 미룸 (강제 설치 하한)', downloaded, { type: 'restart-answered', answer: 'later' }, { forceInstallBelow: '1.9.1' }, 'install'],
|
||||
['재시작 미룸 (최소 지원)', downloaded, { type: 'restart-answered', answer: 'later' }, { minimumSupportedVersion: '1.9.1' }, 'install'],
|
||||
['재시작 수락 → 그 사이 킬 스위치', downloaded, { type: 'restart-answered', answer: 'restart' }, { killSwitch: true }, 'cancel-pending'],
|
||||
] as const)('%s', (_label, session, trigger, overrides, expected) => {
|
||||
expect(nextUpdateAction(session, trigger, ctx(overrides)).type).toBe(expected)
|
||||
})
|
||||
|
||||
it('완료 시 필수 여부를 재시작 대화상자에 넘긴다', () => {
|
||||
expect(nextUpdateAction(downloaded, { type: 'download-finished' }, ctx({ forceInstallBelow: '1.9.1' }))).toEqual({
|
||||
type: 'ask-restart',
|
||||
version: '1.9.1',
|
||||
mandatory: true,
|
||||
})
|
||||
expect(nextUpdateAction(downloaded, { type: 'download-finished' }, ctx())).toEqual({
|
||||
type: 'ask-restart',
|
||||
version: '1.9.1',
|
||||
mandatory: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('필수 업데이트는 rollout 중단(stagingPercentage=0)에도 설치 파일을 유지한다', () => {
|
||||
expect(
|
||||
nextUpdateAction(downloaded, { type: 'policy-refreshed' }, ctx({ stagingPercentage: 0, minimumSupportedVersion: '1.9.1' }))
|
||||
.type,
|
||||
).toBe('keep-pending')
|
||||
})
|
||||
|
||||
it('다운로드 결정은 동의 시점의 정책으로 다시 계산한다 (전체/차분)', () => {
|
||||
const action = nextUpdateAction(
|
||||
IDLE_UPDATE_SESSION,
|
||||
{ type: 'consented', version: '2.0.0', offeredVersion: '2.0.0' },
|
||||
ctx(),
|
||||
)
|
||||
expect(action).toMatchObject({ type: 'download', version: '2.0.0', decision: { forceFull: true } })
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue