Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
167 lines
5.7 KiB
TypeScript
167 lines
5.7 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||
import fs from 'fs';
|
||
import os from 'os';
|
||
import path from 'path';
|
||
import {
|
||
computeAgreementsVersion,
|
||
EULA_ACCEPT_ENV,
|
||
isConsentRequired,
|
||
loadAgreements,
|
||
parseAgreementMeta,
|
||
PRIVACY_ACCEPT_ENV,
|
||
recordConsent,
|
||
loadConsentRecord,
|
||
resolveEnvironmentConsent,
|
||
} from '@/webui/consent';
|
||
|
||
describe('computeAgreementsVersion', () => {
|
||
it('is deterministic for identical input', () => {
|
||
const docs = [
|
||
{ id: 'eula', text: 'A' },
|
||
{ id: 'privacy', text: 'B' },
|
||
];
|
||
expect(computeAgreementsVersion(docs)).toBe(computeAgreementsVersion(docs));
|
||
});
|
||
|
||
it('changes when any agreement TEXT changes (forces re-consent)', () => {
|
||
const before = computeAgreementsVersion([
|
||
{ id: 'eula', text: 'terms v1' },
|
||
{ id: 'privacy', text: 'privacy v1' },
|
||
]);
|
||
const after = computeAgreementsVersion([
|
||
{ id: 'eula', text: 'terms v2' }, // edited
|
||
{ id: 'privacy', text: 'privacy v1' },
|
||
]);
|
||
expect(after).not.toBe(before);
|
||
});
|
||
|
||
it('is independent of app version — same text => same version', () => {
|
||
// The function takes only doc id+text, never an app version, so an app
|
||
// upgrade with unchanged docs yields an identical version (stable consent).
|
||
const v1 = computeAgreementsVersion([{ id: 'eula', text: 'same' }]);
|
||
const v2 = computeAgreementsVersion([{ id: 'eula', text: 'same' }]);
|
||
expect(v1).toBe(v2);
|
||
});
|
||
|
||
it('is not fooled by id/text boundary shifting', () => {
|
||
const a = computeAgreementsVersion([{ id: 'eula', text: 'x' }]);
|
||
const b = computeAgreementsVersion([{ id: 'eu', text: 'lax' }]);
|
||
expect(a).not.toBe(b);
|
||
});
|
||
});
|
||
|
||
describe('parseAgreementMeta', () => {
|
||
it('pulls title / declared version / effective date from a bilingual doc', () => {
|
||
const text = [
|
||
'# SnowLuma 最终用户许可协议(EULA)/ End-User License Agreement',
|
||
'',
|
||
'- **生效日期 / Effective date:** 2026-06-19',
|
||
'- **版本 / Version:** 1.0',
|
||
'',
|
||
'正文…',
|
||
].join('\n');
|
||
const meta = parseAgreementMeta(text);
|
||
expect(meta.title).toBe('SnowLuma 最终用户许可协议(EULA)/ End-User License Agreement');
|
||
expect(meta.declaredVersion).toBe('1.0');
|
||
expect(meta.effectiveDate).toBe('2026-06-19');
|
||
});
|
||
|
||
it('degrades gracefully on empty text', () => {
|
||
expect(parseAgreementMeta('')).toEqual({
|
||
title: '',
|
||
declaredVersion: '',
|
||
effectiveDate: '',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('environment consent', () => {
|
||
it('requires explicit acceptance of both EULA and PRIVACY', () => {
|
||
expect(resolveEnvironmentConsent({})).toEqual({
|
||
eulaAccepted: false,
|
||
privacyAccepted: false,
|
||
accepted: false,
|
||
});
|
||
expect(resolveEnvironmentConsent({ [EULA_ACCEPT_ENV]: '1' })).toEqual({
|
||
eulaAccepted: true,
|
||
privacyAccepted: false,
|
||
accepted: false,
|
||
});
|
||
});
|
||
|
||
it('accepts trimmed, case-insensitive true values', () => {
|
||
expect(
|
||
resolveEnvironmentConsent({
|
||
[EULA_ACCEPT_ENV]: ' true ',
|
||
[PRIVACY_ACCEPT_ENV]: 'TRUE',
|
||
}).accepted,
|
||
).toBe(true);
|
||
});
|
||
|
||
it('fails fast on an invalid configured value', () => {
|
||
expect(() =>
|
||
resolveEnvironmentConsent({
|
||
[EULA_ACCEPT_ENV]: 'yes',
|
||
[PRIVACY_ACCEPT_ENV]: '1',
|
||
}),
|
||
).toThrow(`${EULA_ACCEPT_ENV} must be one of: 1, true, 0, false`);
|
||
});
|
||
});
|
||
|
||
describe('consent persistence', () => {
|
||
let tmp: string;
|
||
let cwd: string;
|
||
|
||
beforeEach(() => {
|
||
cwd = process.cwd();
|
||
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'snowluma-consent-'));
|
||
process.chdir(tmp);
|
||
});
|
||
|
||
afterEach(() => {
|
||
process.chdir(cwd);
|
||
fs.rmSync(tmp, { recursive: true, force: true });
|
||
});
|
||
|
||
it('records and reads back the accepted version', () => {
|
||
expect(loadConsentRecord()).toBeNull();
|
||
const rec = recordConsent('abc123');
|
||
expect(rec.version).toBe('abc123');
|
||
expect(Date.parse(rec.acceptedAt)).not.toBeNaN();
|
||
|
||
const loaded = loadConsentRecord();
|
||
expect(loaded?.version).toBe('abc123');
|
||
expect(fs.existsSync(path.join('config', 'consent.json'))).toBe(true);
|
||
});
|
||
|
||
it('staleness check: stored version != current => re-consent needed', () => {
|
||
recordConsent('old-version');
|
||
const stored = loadConsentRecord();
|
||
// mirrors isConsentRequired()'s comparison against the current version
|
||
expect(stored?.version !== 'new-version').toBe(true);
|
||
expect(stored?.version !== 'old-version').toBe(false);
|
||
});
|
||
|
||
it('environment consent unlocks without writing a permanent consent record', () => {
|
||
expect(
|
||
isConsentRequired({
|
||
[EULA_ACCEPT_ENV]: '1',
|
||
[PRIVACY_ACCEPT_ENV]: 'true',
|
||
}),
|
||
).toBe(false);
|
||
expect(loadConsentRecord()).toBeNull();
|
||
expect(isConsentRequired({ [EULA_ACCEPT_ENV]: '1' })).toBe(true);
|
||
});
|
||
|
||
it('stored consent still covers the current agreement version', () => {
|
||
recordConsent(loadAgreements().version);
|
||
expect(isConsentRequired({})).toBe(false);
|
||
});
|
||
|
||
it('ignores a malformed consent.json (treated as no consent)', () => {
|
||
fs.mkdirSync('config', { recursive: true });
|
||
fs.writeFileSync(path.join('config', 'consent.json'), '{ not json');
|
||
expect(loadConsentRecord()).toBeNull();
|
||
});
|
||
});
|