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:
@@ -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 }))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user