1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
const requestToken = vi.hoisted(() => ({
fetchRequestToken: vi.fn<() => Promise<string>>(),
setRequestToken: vi.fn<(token: string) => void>(),
}))
vi.mock('../../OC/requesttoken.ts', () => requestToken)
const initialState = vi.hoisted(() => ({ loadState: vi.fn() }))
vi.mock('@nextcloud/initial-state', () => initialState)
describe('Session heartbeat', () => {
beforeAll(() => {
vi.useFakeTimers()
})
beforeEach(() => {
vi.clearAllTimers()
vi.resetModules()
vi.resetAllMocks()
})
it('sends heartbeat half the session lifetime when heartbeat enabled', async () => {
initialState.loadState.mockImplementationOnce(() => ({
session_keepalive: true,
session_lifetime: 300,
}))
const { initSessionHeartBeat } = await import('../../session-heartbeat.ts')
initSessionHeartBeat()
// initial state loaded
expect(initialState.loadState).toBeCalledWith('core', 'config', {})
// less than half, still nothing
await vi.advanceTimersByTimeAsync(100 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
// reach past half, one call
await vi.advanceTimersByTimeAsync(60 * 1000)
expect(requestToken.fetchRequestToken).toBeCalledTimes(1)
// almost there to the next, still one
await vi.advanceTimersByTimeAsync(135 * 1000)
expect(requestToken.fetchRequestToken).toBeCalledTimes(1)
// past it, second call
await vi.advanceTimersByTimeAsync(5 * 1000)
expect(requestToken.fetchRequestToken).toBeCalledTimes(2)
})
it('does not send heartbeat when heartbeat disabled', async () => {
initialState.loadState.mockImplementationOnce(() => ({
session_keepalive: false,
session_lifetime: 300,
}))
const { initSessionHeartBeat } = await import('../../session-heartbeat.ts')
initSessionHeartBeat()
// initial state loaded
expect(initialState.loadState).toBeCalledWith('core', 'config', {})
// less than half, still nothing
await vi.advanceTimersByTimeAsync(100 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
// more than one, still nothing
await vi.advanceTimersByTimeAsync(300 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
})
it('limit heartbeat to at least one minute', async () => {
initialState.loadState.mockImplementationOnce(() => ({
session_keepalive: true,
session_lifetime: 55,
}))
const { initSessionHeartBeat } = await import('../../session-heartbeat.ts')
initSessionHeartBeat()
// initial state loaded
expect(initialState.loadState).toBeCalledWith('core', 'config', {})
// 30 / 55 seconds
await vi.advanceTimersByTimeAsync(30 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
// 59 / 55 seconds should not be called except it does not limit
await vi.advanceTimersByTimeAsync(29 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
// now one minute has passed
await vi.advanceTimersByTimeAsync(1000)
expect(requestToken.fetchRequestToken).toHaveBeenCalledOnce()
})
it('limit heartbeat to at least one minute', async () => {
initialState.loadState.mockImplementationOnce(() => ({
session_keepalive: true,
session_lifetime: 50 * 60 * 60,
}))
const { initSessionHeartBeat } = await import('../../session-heartbeat.ts')
initSessionHeartBeat()
// initial state loaded
expect(initialState.loadState).toBeCalledWith('core', 'config', {})
// 23 hours
await vi.advanceTimersByTimeAsync(23 * 60 * 60 * 1000)
expect(requestToken.fetchRequestToken).not.toBeCalled()
// one day - it should be called now
await vi.advanceTimersByTimeAsync(60 * 60 * 1000)
expect(requestToken.fetchRequestToken).toHaveBeenCalledOnce()
})
})
|