Files
dsh-context/tests/client/latestVersion.spec.ts
T
bowenliang123 3c7ca98d32 feat: fall back to npmmirror when official npm registry fails
Order registries as official npm then npmmirror; each source failure
(non-ok response, missing/non-string version, network error) advances to
the next source, and only an all-source failure resolves to null so the
plugin info card keeps its baked-in version. TTL cache and module-level
promise reuse are unchanged.
2026-08-26 17:16:09 +08:00

159 lines
5.6 KiB
TypeScript

// Latest-version check (src/client/latestVersion.ts): registry fetch with a
// 1h TTL and the semver comparator. The fetch is stubbed per test — the
// suite never hits the network — and each fetch test re-imports the module
// (vi.resetModules) to reset the module-level cache. Two registries are
// tried in order (official npm, then npmmirror as a fallback).
import assert from 'node:assert/strict'
import { afterEach, describe, test, vi } from 'vitest'
import { isNewerVersion } from '../../src/client/latestVersion'
const HOUR_MS = 60 * 60 * 1000
afterEach(() => {
vi.unstubAllGlobals()
vi.useRealTimers()
vi.resetModules()
})
async function freshModule(): Promise<typeof import('../../src/client/latestVersion')> {
vi.resetModules()
return await import('../../src/client/latestVersion')
}
describe('fetchLatestVersion', () => {
test('an ok response with a string version resolves to it', async () => {
vi.stubGlobal('fetch', async () => ({ ok: true, json: async () => ({ version: '9.9.9' }) }))
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), '9.9.9')
})
test('an ok response without a version resolves to null', async () => {
vi.stubGlobal('fetch', async () => ({ ok: true, json: async () => ({}) }))
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), null)
})
test('an ok response with a non-string version resolves to null', async () => {
vi.stubGlobal('fetch', async () => ({ ok: true, json: async () => ({ version: 9 }) }))
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), null)
})
test('a non-ok response resolves to null', async () => {
vi.stubGlobal('fetch', async () => ({ ok: false, json: async () => ({ version: '9.9.9' }) }))
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), null)
})
test('a rejected fetch resolves to null', async () => {
vi.stubGlobal('fetch', async () => { throw new Error('offline') })
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), null)
})
test('a second call within the TTL does not refetch', async () => {
vi.useFakeTimers()
vi.setSystemTime(1_000_000)
let fetches = 0
vi.stubGlobal('fetch', async () => {
fetches++
return { ok: true, json: async () => ({ version: '1.0.0' }) }
})
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), '1.0.0')
vi.setSystemTime(1_000_000 + HOUR_MS - 1)
assert.equal(await fetchLatestVersion(), '1.0.0')
assert.equal(fetches, 1)
})
test('a call after the TTL expires refetches', async () => {
vi.useFakeTimers()
vi.setSystemTime(1_000_000)
let fetches = 0
vi.stubGlobal('fetch', async () => {
fetches++
return { ok: true, json: async () => ({ version: '1.0.' + fetches }) }
})
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), '1.0.1')
vi.setSystemTime(1_000_000 + HOUR_MS)
assert.equal(await fetchLatestVersion(), '1.0.2')
assert.equal(fetches, 2)
})
test('falls back to npmmirror when the official registry fails', async () => {
const urls: string[] = []
vi.stubGlobal('fetch', async (input: unknown) => {
const url = String(input)
urls.push(url)
if (url.startsWith('https://registry.npmjs.org/')) {
return { ok: false, json: async () => ({}) }
}
return { ok: true, json: async () => ({ version: '2.0.0' }) }
})
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), '2.0.0')
assert.deepEqual(urls, [
'https://registry.npmjs.org/dsh-context/latest',
'https://registry.npmmirror.com/dsh-context/latest',
])
})
test('falls back to npmmirror when the official fetch rejects', async () => {
const urls: string[] = []
vi.stubGlobal('fetch', async (input: unknown) => {
const url = String(input)
urls.push(url)
if (url.startsWith('https://registry.npmjs.org/')) {
throw new Error('offline')
}
return { ok: true, json: async () => ({ version: '3.0.0' }) }
})
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), '3.0.0')
assert.equal(urls.length, 2)
})
test('resolves to null when every source fails', async () => {
const urls: string[] = []
vi.stubGlobal('fetch', async (input: unknown) => {
const url = String(input)
urls.push(url)
return { ok: false, json: async () => ({}) }
})
const { fetchLatestVersion } = await freshModule()
assert.equal(await fetchLatestVersion(), null)
assert.equal(urls.length, 2)
})
})
describe('isNewerVersion', () => {
test('a strictly newer version is newer', () => {
assert.equal(isNewerVersion('1.2.3', '1.2.2'), true)
})
test('equal and lower versions are not newer', () => {
assert.equal(isNewerVersion('1.2.3', '1.2.3'), false)
assert.equal(isNewerVersion('1.2.2', '1.2.3'), false)
})
test('a v prefix is stripped', () => {
assert.equal(isNewerVersion('v1.3.0', '1.2.9'), true)
})
test('a pre-release suffix is ignored', () => {
assert.equal(isNewerVersion('1.3.0-rc.1', '1.3.0'), false)
assert.equal(isNewerVersion('1.3.0-rc.1', '1.2.0'), true)
})
test('missing segments compare as zero', () => {
assert.equal(isNewerVersion('1.2', '1.2.1'), false)
assert.equal(isNewerVersion('1.2.1', '1.2'), true)
})
test('garbage segments parse as zero', () => {
assert.equal(isNewerVersion('x.y', '0.0.0'), false)
})
})