import test from 'node:test'; import assert from 'node:assert/strict'; import vm from 'node:vm'; import { buildProductAnalyticsContext, injectMindSpaceAnalytics, pseudonymizeAnalyticsId, resolveAnalyticsIdentity, resolveAnalyticsPlan, resolveAnalyticsOwnerSegment, resolveAnalyticsOwnerLabel, resolveMindSpaceAnalyticsConfig, resolveProductAnalyticsConfig, sendMindSpaceAnalyticsEvent, } from './mindspace-analytics.mjs'; test('product analytics uses a separate website and the existing pseudonymization secret', () => { const base = resolveMindSpaceAnalyticsConfig({ MEMIND_ANALYTICS_ENABLED: 'true', MEMIND_ANALYTICS_WEBSITE_ID: 'generated-pages', MEMIND_ANALYTICS_ID_SECRET: 'local-secret', }); const config = resolveProductAnalyticsConfig({ MEMIND_PRODUCT_ANALYTICS_ENABLED: 'true', MEMIND_PRODUCT_ANALYTICS_WEBSITE_ID: 'product-shell', }, base); assert.equal(config.enabled, true); assert.equal(config.websiteId, 'product-shell'); assert.notEqual(config.websiteId, base.websiteId); const context = buildProductAnalyticsContext({ config, user: { id: 'user-123', displayName: '张三', role: 'user', planType: 'pro' }, }); assert.deepEqual(context.identity, { distinctId: pseudonymizeAnalyticsId('user-123', 'local-secret'), username: '张三', ownerSegment: 'plan:pro', planType: 'pro', channel: 'h5', surface: 'product', identityMode: 'pseudonymous', }); }); test('product analytics context stays public-safe before login', () => { const context = buildProductAnalyticsContext({ config: { enabled: true, websiteId: 'product-shell', idSecret: 'local-secret', scriptPath: '/analytics/script.js', hostPath: '/analytics', }, }); assert.equal(context.enabled, true); assert.equal(context.websiteId, 'product-shell'); assert.equal(context.identity, null); assert.equal('idSecret' in context, false); }); 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('approved raw identity mode preserves the stable Memind user id', () => { const config = { identityMode: 'raw', idSecret: 'unused' }; assert.equal(resolveAnalyticsIdentity('user-123', config), 'user-123'); assert.equal(resolveAnalyticsIdentity(' user-123\n', config), 'user-123'); }); 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'); assert.equal(resolveAnalyticsPlan({ role: 'user', planType: 'pro' }), 'pro'); assert.equal(resolveAnalyticsPlan({ role: 'user' }), '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 = 'Demo

Demo

'; const out = injectMindSpaceAnalytics(html, { ownerId: 'user-123', ownerLabel: '张三', pageId: 'page-1', publicationId: 'pub-1', planType: 'pro', generatedAt: '2026-07-20T01:02:03.000Z', config: { enabled: true, websiteId: 'local-website', idSecret: 'secret', scriptPath: '/analytics/script.js', hostPath: '/analytics', domains: '127.0.0.1,localhost', identityMode: 'raw', }, }); assert.match(out, /src="\/analytics\/script\.js"/); assert.match(out, /data-host-url="\/analytics"/); assert.match(out, /data-auto-track="false"/); assert.match(out, /window\.umami\.identify\(d\.owner_id,\{username:d\.username,memind_page_url:location\.href,owner_segment:d\.owner_segment,plan_type:d\.plan_type,channel:d\.channel,surface:d\.surface,identity_mode:d\.identity_mode\}\)/); assert.match(out, /function pageview\(\).*window\.umami\.track\(\)/); assert.ok(out.indexOf('identify();pageview();') > 0); assert.doesNotMatch(out, /t\('page_view'\)/); assert.match(out, /page_id/); assert.match(out, /owner_segment/); assert.doesNotMatch(out, /owner_label/); assert.match(out, /"username":"张三"/); assert.match(out, /page_click/); assert.match(out, /target_path/); assert.doesNotMatch(out, /target_url/); assert.match(out, /page_form_submit/); assert.match(out, /page_scroll_/); assert.match(out, /page_engaged_10s/); assert.match(out, /"owner_id":"user-123"/); assert.match(out, /"generated_at":"2026-07-20T01:02:03.000Z"/); assert.equal(injectMindSpaceAnalytics(out, { ownerId: 'user-123', config: { enabled: true, websiteId: 'local-website', idSecret: 'secret' } }), out); }); test('identifies the pseudonymous owner before sending a standard page view', () => { const out = injectMindSpaceAnalytics('', { ownerId: 'user-123', ownerSegment: 'plan:pro', ownerLabel: '张三', channel: 'h5', config: { enabled: true, websiteId: 'local-website', idSecret: 'secret', scriptPath: '/analytics/script.js', hostPath: '/analytics', }, }); const inlineScript = out.match(/