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 = '