Files
memind/mindspace-analytics.test.mjs

89 lines
3.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
injectMindSpaceAnalytics,
pseudonymizeAnalyticsId,
resolveAnalyticsOwnerSegment,
resolveAnalyticsOwnerLabel,
resolveMindSpaceAnalyticsConfig,
sendMindSpaceAnalyticsEvent,
} from './mindspace-analytics.mjs';
test('analytics config is disabled unless explicitly enabled and configured', () => {
assert.equal(resolveMindSpaceAnalyticsConfig({ MEMIND_ANALYTICS_ENABLED: 'true' }).enabled, false);
assert.equal(resolveMindSpaceAnalyticsConfig({
MEMIND_ANALYTICS_ENABLED: 'true',
MEMIND_ANALYTICS_WEBSITE_ID: 'local-website',
}).enabled, false);
const config = resolveMindSpaceAnalyticsConfig({
MEMIND_ANALYTICS_ENABLED: 'true',
MEMIND_ANALYTICS_URL: 'http://127.0.0.1:3200',
MEMIND_ANALYTICS_WEBSITE_ID: 'local-website',
MEMIND_ANALYTICS_ID_SECRET: 'local-secret',
});
assert.equal(config.enabled, true);
assert.equal(config.analyticsUrl, 'http://127.0.0.1:3200');
assert.equal(config.hostPath, '/analytics');
});
test('owner ids are stable pseudonyms and never expose the source id', () => {
const first = pseudonymizeAnalyticsId('user-123', 'secret');
assert.equal(first, pseudonymizeAnalyticsId('user-123', 'secret'));
assert.notEqual(first, 'user-123');
assert.notEqual(first, pseudonymizeAnalyticsId('user-456', 'secret'));
});
test('owner segments come from server-side Memind user profile data', () => {
assert.equal(resolveAnalyticsOwnerSegment({ role: 'admin' }), 'admin');
assert.equal(resolveAnalyticsOwnerSegment({ role: 'user', planType: 'pro' }), 'plan:pro');
assert.equal(resolveAnalyticsOwnerSegment({ role: 'user' }), 'plan:free');
});
test('owner labels are readable but bounded and stripped of control characters', () => {
assert.equal(resolveAnalyticsOwnerLabel({ displayName: '张三\n管理员' }), '张三 管理员');
assert.equal(resolveAnalyticsOwnerLabel({}), '未命名用户');
});
test('injects one local same-origin tracker with page dimensions', () => {
const html = '<!doctype html><html><head><title>Demo</title></head><body><h1>Demo</h1></body></html>';
const out = injectMindSpaceAnalytics(html, {
ownerId: 'user-123',
ownerLabel: '张三',
pageId: 'page-1',
publicationId: 'pub-1',
config: {
enabled: true,
websiteId: 'local-website',
idSecret: 'secret',
scriptPath: '/analytics/script.js',
hostPath: '/analytics',
domains: '127.0.0.1,localhost',
},
});
assert.match(out, /src="\/analytics\/script\.js"/);
assert.match(out, /data-host-url="\/analytics"/);
assert.match(out, /data-auto-track="false"/);
assert.match(out, /page_id/);
assert.match(out, /owner_segment/);
assert.doesNotMatch(out, /owner_label/);
assert.doesNotMatch(out, /张三/);
assert.match(out, /page_click/);
assert.match(out, /page_form_submit/);
assert.match(out, /page_scroll_/);
assert.match(out, /page_engaged_10s/);
assert.doesNotMatch(out, /user-123/);
assert.equal(injectMindSpaceAnalytics(out, { ownerId: 'user-123', config: { enabled: true, websiteId: 'local-website', idSecret: 'secret' } }), out);
});
test('does not alter non-full-html or disabled pages', () => {
const fragment = '<div>hello</div>';
assert.equal(injectMindSpaceAnalytics(fragment, { ownerId: 'u', config: { enabled: true, websiteId: 'w', idSecret: 's' } }), fragment);
const html = '<!doctype html><html><head></head><body></body></html>';
assert.equal(injectMindSpaceAnalytics(html, { ownerId: 'u', config: { enabled: false, websiteId: 'w', idSecret: 's' } }), html);
});
test('analytics event sender is fail-open when analytics is disabled', async () => {
assert.equal(await sendMindSpaceAnalyticsEvent({ eventName: 'page_generated', ownerId: 'u', config: { enabled: false } }), false);
});