cangku/backend/test/desktopPresenceService.test.js

209 lines
7.1 KiB
JavaScript

const assert = require('node:assert/strict');
const test = require('node:test');
const {
OFFLINE_AFTER_SECONDS,
aggregateDesktopPresence,
ensureDesktopPresenceSchema,
isPresenceOnline,
listOnlineDesktopUsers,
markDesktopOffline,
normalizeHeartbeatPayload,
recordDesktopHeartbeat,
resetDesktopPresenceStateForTests,
} = require('../src/services/desktopPresenceService');
const CLIENT_ID_A = '11111111-1111-4111-8111-111111111111';
const CLIENT_ID_B = '22222222-2222-4222-8222-222222222222';
test('desktop presence stays online at 120 seconds and expires immediately after the boundary', () => {
const now = new Date('2026-08-18T04:00:00.000Z');
const exactBoundary = new Date(now.getTime() - OFFLINE_AFTER_SECONDS * 1000);
const overBoundary = new Date(exactBoundary.getTime() - 1);
assert.equal(isPresenceOnline({ lastSeenAt: exactBoundary }, now), true);
assert.equal(isPresenceOnline({ lastSeenAt: overBoundary }, now), false);
assert.equal(isPresenceOnline({ lastSeenAt: now, offlineAt: now }, now), false);
});
test('desktop presence aggregates multiple online devices without overwriting the user', () => {
const now = new Date('2026-08-18T04:00:00.000Z');
const result = aggregateDesktopPresence([
{
userId: 7,
clientId: CLIENT_ID_A,
clientVersion: '0.0.10',
deviceName: 'OPS-01',
name: '香茗',
departmentName: '运营中心',
onlineSinceAt: new Date('2026-08-18T03:30:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:59:55.000Z'),
},
{
userId: 7,
clientId: CLIENT_ID_B,
clientVersion: '0.0.11',
deviceName: 'OPS-02',
name: '香茗',
departmentName: '运营中心',
onlineSinceAt: new Date('2026-08-18T03:50:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:59:58.000Z'),
},
], now);
assert.equal(result.onlineUserCount, 1);
assert.equal(result.onlineDeviceCount, 2);
assert.equal(result.users[0].activeDeviceCount, 2);
assert.deepEqual(result.users[0].clientVersions, ['0.0.11', '0.0.10']);
assert.equal(result.users[0].onlineDurationSeconds, 1800);
});
test('desktop presence retains stale and explicitly offline devices as history', () => {
const now = new Date('2026-08-18T04:00:00.000Z');
const result = aggregateDesktopPresence([
{
userId: 7,
clientId: CLIENT_ID_A,
name: '香茗',
onlineSinceAt: new Date('2026-08-18T03:50:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:59:55.000Z'),
},
{
userId: 7,
clientId: CLIENT_ID_B,
name: '香茗',
onlineSinceAt: new Date('2026-08-18T02:00:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:00:00.000Z'),
},
{
userId: 8,
clientId: '33333333-3333-4333-8333-333333333333',
name: '胖大海',
onlineSinceAt: new Date('2026-08-18T03:10:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:20:00.000Z'),
offlineAt: new Date('2026-08-18T03:20:01.000Z'),
},
], now);
assert.equal(result.knownUserCount, 2);
assert.equal(result.knownDeviceCount, 3);
assert.equal(result.onlineUserCount, 1);
assert.equal(result.onlineDeviceCount, 1);
assert.equal(result.offlineUserCount, 1);
assert.equal(result.offlineDeviceCount, 2);
assert.equal(result.users[0].name, '香茗');
assert.equal(result.users[0].devices[0].isOnline, true);
assert.equal(result.users[0].devices[1].isOnline, false);
assert.equal(result.users[0].devices[1].sessionDurationSeconds, 3600);
assert.equal(result.users[1].devices[0].isOnline, false);
});
test('desktop presence listing reads historical rows instead of filtering them in SQL', async () => {
resetDesktopPresenceStateForTests();
const queries = [];
const db = {
async query(sql) {
queries.push(sql);
return [{
userId: 7,
clientId: CLIENT_ID_A,
name: '香茗',
onlineSinceAt: new Date('2026-08-18T02:00:00.000Z'),
lastSeenAt: new Date('2026-08-18T03:00:00.000Z'),
}];
},
};
const result = await listOnlineDesktopUsers({
db,
now: new Date('2026-08-18T04:00:00.000Z'),
});
assert.equal(result.knownDeviceCount, 1);
assert.equal(result.onlineDeviceCount, 0);
assert.equal(result.offlineDeviceCount, 1);
assert.doesNotMatch(queries[0], /WHERE p\.offline_at|p\.last_seen_at >=/);
resetDesktopPresenceStateForTests();
});
test('heartbeat payload requires a UUID and caps untrusted metadata', () => {
assert.throws(() => normalizeHeartbeatPayload({ clientId: 'not-a-uuid' }), /设备标识无效/);
const payload = normalizeHeartbeatPayload({
clientId: CLIENT_ID_A,
clientName: 'x'.repeat(150),
});
assert.equal(payload.clientName.length, 100);
});
test('an existing presence table does not require CREATE permission at startup', async () => {
resetDesktopPresenceStateForTests();
const queries = [];
const db = {
async query(sql) {
queries.push(sql);
if (sql.includes('information_schema.tables')) return [{ present: 1 }];
throw new Error('CREATE must not run when the table already exists');
},
};
assert.equal(await ensureDesktopPresenceSchema(db), true);
assert.equal(queries.length, 1);
assert.match(queries[0], /information_schema\.tables/);
resetDesktopPresenceStateForTests();
});
test('heartbeat and explicit logout are scoped by authenticated user and client', async () => {
resetDesktopPresenceStateForTests();
const queries = [];
const db = {
async query(sql, params) {
queries.push({ sql, params });
return [];
},
};
const now = new Date('2026-08-18T04:00:00.000Z');
await recordDesktopHeartbeat({
userId: '7',
payload: { clientId: CLIENT_ID_A, clientVersion: '0.0.10' },
ipAddress: '127.0.0.1',
now,
}, { db });
await markDesktopOffline({ userId: '7', clientId: CLIENT_ID_A, now }, { db });
assert.equal(queries.length, 2);
assert.equal(queries[0].params[0], '7');
assert.equal(queries[0].params[1], CLIENT_ID_A);
assert.deepEqual(queries[1].params.slice(-2), ['7', CLIENT_ID_A]);
resetDesktopPresenceStateForTests();
});
test('in-memory presence remains available when the system database cannot create the table', async () => {
resetDesktopPresenceStateForTests();
const db = {
async query() {
const error = new Error('table is unavailable');
error.code = 'ER_NO_SUCH_TABLE';
throw error;
},
};
const now = new Date('2026-08-18T04:00:00.000Z');
await recordDesktopHeartbeat({
userId: '7',
identity: { name: '香茗', username: 'xiangming', departmentName: '运营中心' },
payload: { clientId: CLIENT_ID_A, clientVersion: '0.0.10', deviceName: 'OPS-01' },
now,
}, { db });
const online = await require('../src/services/desktopPresenceService').listOnlineDesktopUsers({ db, now });
assert.equal(online.onlineUserCount, 1);
assert.equal(online.users[0].name, '香茗');
assert.equal(online.users[0].devices[0].deviceName, 'OPS-01');
await markDesktopOffline({ userId: '7', clientId: CLIENT_ID_A, now }, { db });
const offline = await require('../src/services/desktopPresenceService').listOnlineDesktopUsers({ db, now });
assert.equal(offline.onlineUserCount, 0);
resetDesktopPresenceStateForTests();
});