Achieve 100% coverage on analytics.js

- Add test for navigator.language falsy branch
- Add test for retry getOrCreateVisitor failing on second attempt
- Mark unreachable defensive guards (encrypt/decrypt/send with null encKey)
  with c8 ignore since they cannot be triggered via public API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-01 20:17:21 +02:00
parent 8b18617360
commit b1ec125bb9
2 changed files with 53 additions and 3 deletions

View File

@@ -12,7 +12,7 @@ async function importKey(b64) {
}
async function encrypt(data) {
if (!encKey) return null
/* c8 ignore next */ if (!encKey) return null
const json = new globalThis.TextEncoder().encode(JSON.stringify(data))
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, encKey, json)
@@ -24,7 +24,7 @@ async function encrypt(data) {
}
async function decrypt(b64) {
if (!encKey) return null
/* c8 ignore next */ if (!encKey) return null
const raw = Uint8Array.from(globalThis.atob(b64), c => c.codePointAt(0))
const iv = raw.slice(0, 12)
const data = raw.slice(12)
@@ -43,7 +43,7 @@ function clearSession() {
async function send(data, expectResponse = false) {
const d = await encrypt(data)
if (!d) return null
/* c8 ignore next */ if (!d) return null
try {
if (!expectResponse && navigator.sendBeacon) {
navigator.sendBeacon(ENDPOINT, JSON.stringify({ d }))

View File

@@ -293,6 +293,56 @@ describe('analytics.js', () => {
expect(beaconMock).not.toHaveBeenCalled()
})
it('covers navigator.language being undefined', async () => {
const key = await realImportKey(TEST_KEY_B64)
const visitorResp = await realEncrypt({ uid: 'vl', h: 'hashl' }, key)
document.body.dataset.k = TEST_KEY_B64
document.body.dataset.e = '/t'
const origLang = navigator.language
Object.defineProperty(navigator, 'language', { value: '', configurable: true })
fetchMock.mockResolvedValue({
ok: true, status: 200, json: async () => ({ d: visitorResp }),
})
const { initAnalytics } = await loadModule()
await initAnalytics()
Object.defineProperty(navigator, 'language', { value: origLang, configurable: true })
expect(fetchMock).toHaveBeenCalledTimes(1)
})
it('retry getOrCreateVisitor fails on second attempt after 403', async () => {
const key = await realImportKey(TEST_KEY_B64)
const visitorResp = await realEncrypt({ uid: 'v6', h: 'hash6' }, key)
document.body.dataset.k = TEST_KEY_B64
document.body.dataset.e = '/t'
navigator.sendBeacon = undefined
let callCount = 0
fetchMock.mockImplementation(async () => {
callCount++
// 1st: visitor creation succeeds
if (callCount === 1) return { ok: true, status: 200, json: async () => ({ d: visitorResp }) }
// 2nd: trackPageView gets 403 -> clears session
if (callCount === 2) return { ok: false, status: 403 }
// 3rd: second getOrCreateVisitor also fails (204 = no body)
return { ok: true, status: 204 }
})
const { initAnalytics } = await loadModule()
await initAnalytics()
// After 403, retry creates visitor (call 3) but gets 204 -> null visitor -> early return
expect(callCount).toBe(3)
expect(sessionStorage.getItem('_u')).toBeNull()
})
it('setAuth sends when session and key exist', async () => {
const key = await realImportKey(TEST_KEY_B64)
const visitorResp = await realEncrypt({ uid: 'v5', h: 'hash5' }, key)