test(core): migrate session heartbeat tests

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-06-04 18:55:21 +02:00
parent 688ca81a6e
commit c3e66e22c7
No known key found for this signature in database
GPG key ID: 45FAE7268762B400
2 changed files with 123 additions and 87 deletions

View file

@ -119,93 +119,6 @@ describe('Core base tests', function() {
})).toEqual('number=123');
});
});
describe('Session heartbeat', function() {
var clock,
oldConfig,
counter;
beforeEach(function() {
clock = sinon.useFakeTimers();
oldConfig = OC.config;
counter = 0;
fakeServer.autoRespond = true;
fakeServer.autoRespondAfter = 0;
fakeServer.respondWith(/\/csrftoken/, function(xhr) {
counter++;
xhr.respond(200, {'Content-Type': 'application/json'}, '{"token": "pgBEsb3MzTb1ZPd2mfDZbQ6/0j3OrXHMEZrghHcOkg8=:3khw5PSa+wKQVo4f26exFD3nplud9ECjJ8/Y5zk5/k4="}');
});
$(document).off('ajaxComplete'); // ignore previously registered heartbeats
});
afterEach(function() {
clock.restore();
/* jshint camelcase: false */
OC.config = oldConfig;
$(document).off('ajaxError');
$(document).off('ajaxComplete');
});
it('sends heartbeat half the session lifetime when heartbeat enabled', function() {
/* jshint camelcase: false */
OC.config = {
session_keepalive: true,
session_lifetime: 300
};
window.initCore();
expect(counter).toEqual(0);
// less than half, still nothing
clock.tick(100 * 1000);
expect(counter).toEqual(0);
// reach past half (160), one call
clock.tick(55 * 1000);
expect(counter).toEqual(1);
// almost there to the next, still one
clock.tick(140 * 1000);
expect(counter).toEqual(1);
// past it, second call
clock.tick(20 * 1000);
expect(counter).toEqual(2);
});
it('does not send heartbeat when heartbeat disabled', function() {
/* jshint camelcase: false */
OC.config = {
session_keepalive: false,
session_lifetime: 300
};
window.initCore();
expect(counter).toEqual(0);
clock.tick(1000000);
// still nothing
expect(counter).toEqual(0);
});
it('limits the heartbeat between one minute and one day', function() {
/* jshint camelcase: false */
var setIntervalStub = sinon.stub(window, 'setInterval');
OC.config = {
session_keepalive: true,
session_lifetime: 5
};
window.initCore();
expect(setIntervalStub.getCall(0).args[1]).toEqual(60 * 1000);
setIntervalStub.reset();
OC.config = {
session_keepalive: true,
session_lifetime: 48 * 3600
};
window.initCore();
expect(setIntervalStub.getCall(0).args[1]).toEqual(24 * 3600 * 1000);
setIntervalStub.restore();
});
});
describe('Parse query string', function() {
it('Parses query string from full URL', function() {
var query = OC.parseQueryString('http://localhost/stuff.php?q=a&b=x');

View file

@ -0,0 +1,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()
})
})