Anrufer- und Webhook-Verarbeitung verbessern

This commit is contained in:
Sven committed 2026-11-02 15:58:53 +01:00
1 parent 7b3c187985
commit 9e71fe3a11
18 files changed
+1283 -111

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
// @vitest-environment jsdom
import { act, StrictMode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { findCallerByNumber, type StoredAddress } from '../addresses'
import type { CallEvent } from '../swyx/protocol'
import { useCallerLookup } from './useCallerLookup'
vi.mock('../addresses', () => ({ findCallerByNumber: vi.fn() }))
const ringing: CallEvent = { line: 1, callId: 42, event: 'incoming', peerNumber: '+493012345' }
const customer: StoredAddress = { number: ringing.peerNumber, name: 'Kunde', role: 'customer', jobIds: [4711] }
function deferred() {
let resolve!: (value: StoredAddress | null) => void
let reject!: (reason: Error) => void
const promise = new Promise<StoredAddress | null>((yes, no) => { resolve = yes; reject = no })
return { promise, resolve, reject }
}
describe('useCallerLookup', () => {
let root: Root
let element: HTMLDivElement
let current: ReturnType<typeof useCallerLookup>
function Probe({ call }: { call: CallEvent | null }) {
current = useCallerLookup(call)
return <div>{current.caller?.name ?? current.caller?.number ?? ''}</div>
}
async function render(call: CallEvent | null, strict = false) {
await act(async () => {
root.render(strict ? <StrictMode><Probe call={call} /></StrictMode> : <Probe call={call} />)
})
}
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
vi.mocked(findCallerByNumber).mockReset()
element = document.createElement('div')
document.body.append(element)
root = createRoot(element)
})
afterEach(async () => {
await act(async () => root.unmount())
element.remove()
vi.unstubAllGlobals()
})
it('shows the lookup result even when another snapshot arrives before it', async () => {
const request = deferred()
vi.mocked(findCallerByNumber).mockReturnValue(request.promise)
await render(ringing)
await render({ ...ringing, stateText: 'klingelt' })
await render({ ...ringing })
expect(findCallerByNumber).toHaveBeenCalledTimes(1)
await act(async () => request.resolve(customer))
expect(element.textContent).toBe('Kunde')
expect(current.caller?.jobIds).toEqual([4711])
})
it('ignores the previous lookup when the line is reused, even for the same number', async () => {
const previous = deferred(), next = deferred()
vi.mocked(findCallerByNumber).mockReturnValueOnce(previous.promise).mockReturnValueOnce(next.promise)
await render(ringing)
await render({ ...ringing, callId: 43 })
await act(async () => previous.resolve(customer))
expect(current.caller).toBeNull()
await act(async () => next.resolve({ ...customer, name: 'Aktueller Kunde' }))
expect(element.textContent).toBe('Aktueller Kunde')
expect(findCallerByNumber).toHaveBeenCalledTimes(2)
})
it('does not open a late popup after ringing ends', async () => {
const request = deferred()
vi.mocked(findCallerByNumber).mockReturnValue(request.promise)
await render(ringing)
await render(null)
await act(async () => request.resolve(customer))
expect(current.caller).toBeNull()
})
it('keeps loaded customer details after ringing ends', async () => {
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
await render(ringing)
await render(null)
expect(current.caller).toEqual(customer)
})
it('keeps a dismissed popup closed until the next call', async () => {
const legacyCall = { ...ringing, callId: undefined }
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
await render(legacyCall)
await act(async () => current.dismissCaller())
await render({ ...legacyCall })
expect(current.caller).toBeNull()
expect(findCallerByNumber).toHaveBeenCalledTimes(1)
// Auch ältere Tray-Versionen ohne callId dürfen denselben Anschluss erneut melden.
await render(null)
await render(legacyCall)
expect(current.caller).toEqual(customer)
expect(findCallerByNumber).toHaveBeenCalledTimes(2)
})
it('falls back to the phone number on a lookup failure', async () => {
vi.mocked(findCallerByNumber).mockRejectedValue(new Error('Backend unavailable'))
await render(ringing)
expect(current.caller).toEqual({ number: ringing.peerNumber })
})
it('still loads the caller when React StrictMode restarts the effect', async () => {
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
await render(ringing, true)
expect(element.textContent).toBe('Kunde')
})
})