aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/tests/OC/session-heartbeat.spec.ts
blob: 61b82d928877c7a2c789715cd7b205808fe80abc (plain)
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()
	})
})