Keep private device list requests separate across view generations
This commit is contained in:
@@ -87,3 +87,15 @@ Final follow-up receipts:
|
||||
|
||||
All fixture browser/server processes stopped after verification. No node, helper,
|
||||
firewall, wallet or saved-device configuration was changed by these UI checks.
|
||||
|
||||
A final narrow follow-up disables in-flight deduplication for the private device
|
||||
list. The shared client's dedup key is only method+params; a new view/capability
|
||||
generation could otherwise attach to the preceding generation's unresolved
|
||||
promise. The device suite now passes 18/18, including fresh-list recovery and late
|
||||
old-response rejection (`/tmp/archy-firewall-private-generation-tests-20261008.log`).
|
||||
No broader browser rerun was needed for this request-policy-only change.
|
||||
|
||||
Retry behavior is unchanged: this client's maxRetries value of 1 already means
|
||||
one network attempt, just like 0 after its minimum clamp. A confirmed pre-dispatch
|
||||
CSRF rejection may still refresh credentials and retry. No cross-node dedup fault
|
||||
was claimed: the client's base URL is fixed per instance.
|
||||
|
||||
@@ -36,7 +36,23 @@ describe('Private device management', () => {
|
||||
expect(rpcClient.call).not.toHaveBeenCalled()
|
||||
await w.setProps({ statusKnown: true }); await flushPromises()
|
||||
expect(rpcClient.call).toHaveBeenCalledTimes(1)
|
||||
expect(rpcClient.call).toHaveBeenCalledWith(expect.objectContaining({ method: 'vpn.list-peers' }))
|
||||
expect(rpcClient.call).toHaveBeenCalledWith(expect.objectContaining({ method: 'vpn.list-peers', dedup: false }))
|
||||
w.unmount()
|
||||
})
|
||||
it('starts a fresh non-deduplicated list after capability recovery and ignores the prior generation', async () => {
|
||||
const replies: Array<(value: { peers: typeof peer[] }) => void> = []
|
||||
vi.mocked(rpcClient.call).mockImplementation(async () => new Promise(resolve => { replies.push(resolve) }))
|
||||
const w = view(); await flushPromises()
|
||||
await w.setProps({ managementVerified: false }); await flushPromises()
|
||||
await w.setProps({ managementVerified: true }); await flushPromises()
|
||||
expect(replies).toHaveLength(2)
|
||||
for (const [options] of vi.mocked(rpcClient.call).mock.calls) {
|
||||
expect(options).toEqual(expect.objectContaining({ method: 'vpn.list-peers', dedup: false }))
|
||||
}
|
||||
replies[1]!({ peers: [{ ...peer, name: 'Tablet' }] }); await flushPromises()
|
||||
expect(w.text()).toContain('Tablet')
|
||||
replies[0]!({ peers: [{ ...peer, name: 'Old Phone' }] }); await flushPromises()
|
||||
expect(w.text()).toContain('Tablet'); expect(w.text()).not.toContain('Old Phone')
|
||||
w.unmount()
|
||||
})
|
||||
it('clears private details and rejects an in-flight reveal when capability is lost', async () => {
|
||||
|
||||
@@ -32,7 +32,7 @@ async function refresh() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const result = await rpcClient.call<{ peers: Device[] }>({ method: 'vpn.list-peers', dedup: true, maxRetries: 1 })
|
||||
const result = await rpcClient.call<{ peers: Device[] }>({ method: 'vpn.list-peers', dedup: false, maxRetries: 1 })
|
||||
if (!canReadDevices.value || !active || generation !== request) return
|
||||
if (!Array.isArray(result?.peers) || result.peers.some(p => !p || typeof p.name !== 'string' || typeof p.ip !== 'string' || (p.management_state !== undefined && !['active', 'pending', 'revoking'].includes(p.management_state)))) throw new Error('Invalid device list')
|
||||
devices.value = result.peers.map(p => ({ name: p.name, ip: p.ip, management_state: p.management_state }))
|
||||
|
||||
Reference in New Issue
Block a user