107 lines
4.8 KiB
JavaScript
107 lines
4.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
activityJournalEventCopy,
|
|
compactActivityJournalEvents,
|
|
refreshStreakCopy,
|
|
} from '../../.test-dist/src/web/features/activity-journal/activityJournalModel.js';
|
|
|
|
function event(id, occurredAt, type = 'subscription.refreshed', data = {}) {
|
|
return {
|
|
id,
|
|
occurredAt,
|
|
type,
|
|
severity: 'info',
|
|
source: type.startsWith('subscription.') ? 'subscription' : 'connection',
|
|
dedupeKey: null,
|
|
data: type === 'subscription.refreshed'
|
|
? { profileLabel: 'Дом', serverCount: 8, added: 0, removed: 0, ...data }
|
|
: data,
|
|
};
|
|
}
|
|
|
|
test('adjacent unchanged refreshes become one observed success streak', () => {
|
|
const items = compactActivityJournalEvents([
|
|
event('newest', '2026-08-25T15:00:00.000Z', 'subscription.refreshed', { profileLabel: 'Дом' }),
|
|
event('middle', '2026-08-25T13:30:00.000Z', 'subscription.refreshed', { profileLabel: 'Работа' }),
|
|
event('oldest', '2026-08-25T12:00:00.000Z', 'subscription.refreshed', { profileLabel: 'Дом' }),
|
|
]);
|
|
|
|
assert.equal(items.length, 1);
|
|
assert.deepEqual(items[0].refreshStreak, {
|
|
count: 3,
|
|
firstOccurredAt: '2026-08-25T12:00:00.000Z',
|
|
profileLabels: ['Дом', 'Работа'],
|
|
});
|
|
assert.deepEqual(refreshStreakCopy(items[0]), [
|
|
'Подписки обновлялись без ошибок',
|
|
'3 ч всё хорошо · 3 обновления · подписок: 2',
|
|
]);
|
|
});
|
|
|
|
test('another event and a changed server list break refresh streaks', () => {
|
|
const items = compactActivityJournalEvents([
|
|
event('routine-new', '2026-08-25T15:00:00.000Z'),
|
|
event('started', '2026-08-25T14:50:00.000Z', 'connection.started', { profileLabel: 'Дом', serverLabel: 'Амстердам' }),
|
|
event('changed', '2026-08-25T14:45:00.000Z', 'subscription.refreshed', { added: 1 }),
|
|
event('routine-old', '2026-08-25T14:30:00.000Z'),
|
|
]);
|
|
|
|
assert.equal(items.length, 4);
|
|
assert.ok(items.every(({ refreshStreak }) => refreshStreak === null));
|
|
});
|
|
|
|
test('a local calendar-day boundary breaks an otherwise adjacent streak', () => {
|
|
const newer = new Date(2026, 7, 25, 0, 5).toISOString();
|
|
const older = new Date(2026, 7, 24, 23, 55).toISOString();
|
|
const items = compactActivityJournalEvents([
|
|
event('new-day', newer),
|
|
event('old-day', older),
|
|
]);
|
|
|
|
assert.equal(items.length, 2);
|
|
assert.ok(items.every(({ refreshStreak }) => refreshStreak === null));
|
|
});
|
|
|
|
test('one-profile streak names the profile and keeps the latest event identity', () => {
|
|
const items = compactActivityJournalEvents([
|
|
event('newest', '2026-08-25T15:15:00.000Z'),
|
|
event('oldest', '2026-08-25T15:00:00.000Z'),
|
|
]);
|
|
|
|
assert.equal(items[0].event.id, 'newest');
|
|
assert.deepEqual(refreshStreakCopy(items[0]), [
|
|
'Подписки обновлялись без ошибок',
|
|
'15 мин всё хорошо · 2 обновления · Дом',
|
|
]);
|
|
});
|
|
|
|
test('channel health events use role-specific copy without raw reason codes', () => {
|
|
const cases = [
|
|
['failover.primary_unavailable', 'primary', 'probe-failed', ['Основной канал недоступен', 'Проверки канала не пройдены']],
|
|
['failover.reserve_unavailable', 'reserve', 'probe-failed', ['Резервный канал недоступен', 'Проверки канала не пройдены']],
|
|
['failover.primary_recovered', 'primary', 'probe-recovered', ['Основной канал восстановлен', 'Проверки канала снова проходят успешно']],
|
|
['failover.reserve_recovered', 'reserve', 'probe-recovered', ['Резервный канал восстановлен', 'Проверки канала снова проходят успешно']],
|
|
['failover.recovered', 'primary', 'primary-recovered', ['Основной канал восстановлен', 'Проверки канала снова проходят успешно']],
|
|
['failover.recovered', 'reserve', 'reserve-recovered', ['Резервный канал восстановлен', 'Проверки канала снова проходят успешно']],
|
|
];
|
|
|
|
for (const [type, role, reason, copy] of cases) {
|
|
const item = event(`event-${type}-${role}`, '2026-08-27T10:00:00.000Z', type, { role, reason });
|
|
assert.deepEqual(activityJournalEventCopy(item), copy);
|
|
assert.equal(activityJournalEventCopy(item).join(' ').includes(reason), false);
|
|
}
|
|
|
|
const switched = event('switched', '2026-08-27T10:01:00.000Z', 'failover.switched', {
|
|
fromRole: 'primary',
|
|
toRole: 'reserve',
|
|
reason: 'failure-window',
|
|
});
|
|
assert.deepEqual(activityJournalEventCopy(switched), [
|
|
'Новые соединения переключены',
|
|
'primary → reserve',
|
|
]);
|
|
assert.equal(activityJournalEventCopy(switched).join(' ').includes('failure-window'), false);
|
|
});
|