Files
hermes-agent/apps/desktop/src/lib/version-status.test.ts
T
Teknium 9442a718da fix(update-check): recover the real behind-count via the GitHub compare API
The honesty half (no fabricated counts) leaves shallow installs permanently
count-less. The compare API knows the full graph regardless of local clone
depth: GET /repos/<o>/<r>/compare/<current>...<target> returns ahead_by —
exactly the behind count the shallow boundary lost.

- hermes_cli/banner.py: _github_compare_behind() (bounded, unauthenticated,
  best-effort); wired into _check_via_rev and the shallow branch of
  _check_via_local_git. ahead_by==0 with differing tips = local-ahead => 0.
- hermes_cli/update_cmd.py: hermes update --check shallow path prints the
  exact count when recoverable, presence-only wording otherwise.
- apps/desktop/electron/update-count.ts: compareApiUrl() +
  parseCompareBehindCount() pure helpers; main.ts fetches the count when
  resolveBehindCount() returns null, and the SSH-official passive path stops
  fabricating behind:1 (uses compare API + updateAvailable flag).
- apps/desktop/src/lib/version-status.ts: updateAvailable now applies to the
  client target too, so a shallow desktop install shows '(update)' instead of
  nothing (or the old frozen '(+1)').

Fixes #84591; CLI siblings of #78253 / #53479 behavior.

E2E: live compare API returned 61/62 for real 61/62-commit gaps and 0 for the
reversed (local-ahead) pair; real shallow-clone fixture (depth-1 clone +
depth-1 fetch, merge-base broken) recovers the exact count with the API and
falls back to the honest sentinel offline.
2026-08-14 13:09:44 -07:00

98 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { en } from '@/i18n/en'
import { resolveVersionStatus } from './version-status'
const copy = en.shell.statusbar
const client = (over: Partial<Parameters<typeof resolveVersionStatus>[0]> = {}) =>
resolveVersionStatus({ applying: false, copy, remote: false, restarting: false, target: 'client', ...over })
const backend = (over: Partial<Parameters<typeof resolveVersionStatus>[0]> = {}) =>
resolveVersionStatus({ applying: false, copy, remote: true, restarting: false, target: 'backend', ...over })
describe('resolveVersionStatus', () => {
it('labels a current local client with its version and sha detail', () => {
const status = client({ sha: 'abc1234', version: '0.4.2' })
expect(status.label).toBe('v0.4.2')
expect(status.detail).toBe('abc1234')
expect(status.hasUpdate).toBe(false)
expect(status.unknown).toBe(false)
})
it('appends the commit diff when the client is behind', () => {
const status = client({ behind: 12, branch: 'main', version: '0.4.2' })
expect(status.label).toBe('v0.4.2 (+12)')
expect(status.hasUpdate).toBe(true)
expect(status.tooltip).toContain('12 commits behind main')
})
// FAIL-BEFORE (#84591 class): a shallow install reports behind:null +
// updateAvailable. The client target ignored updateAvailable entirely, so
// the statusbar showed no update at all — and further back, the fabricated
// behind:1 rendered a frozen "(+1)" while the real distance grew to 61.
it('shows a count-free update hint when the client count is unknown', () => {
const status = client({ behind: 0, updateAvailable: true, version: '0.4.2' })
expect(status.label).toBe(`v0.4.2 (${copy.update})`)
expect(status.label).not.toContain('+1')
expect(status.hasUpdate).toBe(true)
})
it('names the client as one of two versions in remote mode', () => {
expect(client({ remote: true, version: '0.4.2' }).label).toBe('client v0.4.2')
})
it('falls back to the sha, then to unknown, when there is no version', () => {
expect(client({ sha: 'abc1234' }).label).toBe('abc1234')
expect(client({ sha: 'abc1234' }).unknown).toBe(false)
expect(client().label).toBe(copy.unknown)
expect(client().unknown).toBe(true)
})
it('drops the diff and the sha detail while an apply is in flight', () => {
const applying = client({ applying: true, behind: 3, sha: 'abc1234', version: '0.4.2' })
expect(applying.label).toBe('v0.4.2 · update')
expect(applying.detail).toBeUndefined()
expect(applying.hasUpdate).toBe(false)
expect(client({ applying: true, restarting: true, version: '0.4.2' }).label).toBe('v0.4.2 · restart')
})
it('leads the tooltip with the apply message while applying', () => {
expect(client({ applyMessage: 'Pulling…', applying: true, version: '0.4.2' }).tooltip).toBe(
'Pulling… · Hermes Desktop v0.4.2'
)
expect(client({ applying: true, version: '0.4.2' }).tooltip).toBe(
`${copy.updateInProgress} · Hermes Desktop v0.4.2`
)
})
it('labels the backend target distinctly and never claims a client sha', () => {
const status = backend({ sha: 'abc1234', version: '0.4.2' })
expect(status.label).toBe('backend v0.4.2')
expect(status.detail).toBeUndefined()
expect(status.tooltip).toBe('Backend v0.4.2')
})
it('falls back to (update) for a backend that cannot count commits', () => {
const status = backend({ updateAvailable: true, version: '0.4.2' })
expect(status.label).toBe('backend v0.4.2 (update)')
expect(status.hasUpdate).toBe(true)
})
it('prefers the exact commit diff over the generic (update) hint', () => {
expect(backend({ behind: 4, updateAvailable: true, version: '0.4.2' }).label).toBe('backend v0.4.2 (+4)')
})
it('hides a backend row that has no version at all', () => {
expect(backend().unknown).toBe(true)
})
})