feat(desktop): add client release updater
Desktop Client Build / Resolve Build Metadata (push) Successful in 19s
Frontend CI / Frontend (push) Successful in 3m20s
Backend CI / Backend (push) Successful in 16m48s
Desktop Client Build / Build Desktop Client (push) Successful in 24m46s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 37s

This commit is contained in:
2026-05-25 19:23:49 +08:00
parent 41b4a765ac
commit 5f6e9f11da
46 changed files with 5508 additions and 160 deletions
@@ -0,0 +1,108 @@
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AuthProbeResult } from './auth-types'
const probeAIAccountSession = vi.fn()
const probePublishAccountSession = vi.fn()
const silentRefreshAIAccountSession = vi.fn()
const silentRefreshPublishAccountSession = vi.fn()
const getSessionHandle = vi.fn()
const getPersistedPartition = vi.fn()
const emitRuntimeInvalidated = vi.fn()
vi.mock('electron/main', () => ({
app: {
getPath: () => join(tmpdir(), 'geo-rankly-account-health-test'),
},
}))
vi.mock('./account-binder', () => ({
probeAIAccountSession,
probePublishAccountSession,
silentRefreshAIAccountSession,
silentRefreshPublishAccountSession,
}))
vi.mock('./session-registry', () => ({
getSessionHandle,
getPersistedPartition,
}))
vi.mock('./runtime-events', () => ({
emitRuntimeInvalidated,
}))
const account = {
id: 'account-1',
platform: 'doubao',
platformUid: 'doubao-session:demo',
displayName: '豆包账号',
}
function challenge(reason: AuthProbeResult['reason'] = 'captcha_gate'): AuthProbeResult {
return {
verdict: 'challenge_required',
reason,
profile: null,
sessionFingerprint: null,
}
}
function active(): AuthProbeResult {
return {
verdict: 'active',
reason: 'probe_success',
profile: {
displayName: '豆包账号',
avatarUrl: null,
subjectUid: 'doubao-session:demo',
},
sessionFingerprint: 'fingerprint-live',
}
}
describe('account health challenge recheck', () => {
beforeEach(() => {
vi.resetModules()
vi.clearAllMocks()
getSessionHandle.mockReturnValue({ partition: 'persist:test' })
getPersistedPartition.mockReturnValue('persist:test')
silentRefreshAIAccountSession.mockResolvedValue(true)
silentRefreshPublishAccountSession.mockResolvedValue(true)
})
it('hides a first challenge result when the automatic recheck succeeds', async () => {
probeAIAccountSession.mockResolvedValueOnce(challenge()).mockResolvedValueOnce(active())
const { probeTrackedAccount } = await import('./account-health')
const snapshot = await probeTrackedAccount(account, {
trigger: 'manual',
allowSilentRefresh: true,
force: true,
})
expect(probeAIAccountSession).toHaveBeenCalledTimes(2)
expect(snapshot.authState).toBe('active')
expect(snapshot.authReason).toBe('probe_success')
})
it('shows manual verification only after the automatic recheck still needs it', async () => {
probeAIAccountSession
.mockResolvedValueOnce(challenge('risk_control'))
.mockResolvedValueOnce(challenge('risk_control'))
const { probeTrackedAccount } = await import('./account-health')
const snapshot = await probeTrackedAccount(account, {
trigger: 'manual',
allowSilentRefresh: true,
force: true,
})
expect(probeAIAccountSession).toHaveBeenCalledTimes(2)
expect(snapshot.authState).toBe('challenge_required')
expect(snapshot.authReason).toBe('risk_control')
})
})