@ucptools/validator 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +60 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +279 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compliance/compliance-generator.d.ts +34 -0
- package/dist/compliance/compliance-generator.d.ts.map +1 -0
- package/dist/compliance/compliance-generator.js +320 -0
- package/dist/compliance/compliance-generator.js.map +1 -0
- package/dist/compliance/index.d.ts +8 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +17 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/templates.d.ts +34 -0
- package/dist/compliance/templates.d.ts.map +1 -0
- package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
- package/dist/compliance/templates.js.map +1 -0
- package/dist/compliance/types.d.ts +64 -0
- package/dist/compliance/types.d.ts.map +1 -0
- package/dist/compliance/types.js +64 -0
- package/dist/compliance/types.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +63 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +444 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +65 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
- package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
- package/dist/feed-analyzer/index.d.ts +8 -0
- package/dist/feed-analyzer/index.d.ts.map +1 -0
- package/dist/feed-analyzer/index.js +19 -0
- package/dist/feed-analyzer/index.js.map +1 -0
- package/dist/feed-analyzer/types.d.ts +204 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +162 -0
- package/dist/feed-analyzer/types.js.map +1 -0
- package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +13 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/key-generator.d.ts +24 -0
- package/dist/generator/key-generator.d.ts.map +1 -0
- package/dist/generator/key-generator.js +144 -0
- package/dist/generator/key-generator.js.map +1 -0
- package/dist/generator/profile-builder.d.ts +15 -0
- package/dist/generator/profile-builder.d.ts.map +1 -0
- package/dist/generator/profile-builder.js +338 -0
- package/dist/generator/profile-builder.js.map +1 -0
- package/dist/hosting/artifacts-generator.d.ts +10 -0
- package/dist/hosting/artifacts-generator.d.ts.map +1 -0
- package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
- package/dist/hosting/artifacts-generator.js.map +1 -0
- package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +10 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +12 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security-scanner.d.ts +10 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +541 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +48 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +21 -0
- package/dist/security/types.js.map +1 -0
- package/dist/services/directory.d.ts +104 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +333 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
- package/dist/simulator/agent-simulator.js.map +1 -0
- package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
- package/dist/simulator/index.d.ts.map +1 -0
- package/dist/simulator/index.js +23 -0
- package/dist/simulator/index.js.map +1 -0
- package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +18 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/types/generator.d.ts +106 -0
- package/dist/types/generator.d.ts.map +1 -0
- package/dist/types/generator.js +6 -0
- package/dist/types/generator.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ucp-profile.d.ts +103 -0
- package/dist/types/ucp-profile.d.ts.map +1 -0
- package/dist/types/ucp-profile.js +45 -0
- package/dist/types/ucp-profile.js.map +1 -0
- package/dist/types/validation.d.ts +68 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +32 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/index.d.ts +26 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +161 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts +28 -0
- package/dist/validator/network-validator.d.ts.map +1 -0
- package/dist/validator/network-validator.js +319 -0
- package/dist/validator/network-validator.js.map +1 -0
- package/dist/validator/rules-validator.d.ts +11 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +257 -0
- package/dist/validator/rules-validator.js.map +1 -0
- package/dist/validator/sdk-validator.d.ts +58 -0
- package/dist/validator/sdk-validator.d.ts.map +1 -0
- package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
- package/dist/validator/sdk-validator.js.map +1 -0
- package/dist/validator/structural-validator.d.ts +11 -0
- package/dist/validator/structural-validator.d.ts.map +1 -0
- package/dist/validator/structural-validator.js +415 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/package.json +1 -1
- package/publish-output.txt +0 -0
- package/CLAUDE.md +0 -109
- package/api/analyze-feed.js +0 -140
- package/api/badge.js +0 -185
- package/api/benchmark.js +0 -177
- package/api/directory-stats.ts +0 -29
- package/api/directory.ts +0 -73
- package/api/generate-compliance.js +0 -143
- package/api/generate-schema.js +0 -457
- package/api/generate.js +0 -132
- package/api/security-scan.js +0 -133
- package/api/simulate.js +0 -187
- package/api/tsconfig.json +0 -10
- package/api/validate.js +0 -1351
- package/apify-actor/.actor/actor.json +0 -68
- package/apify-actor/.actor/input_schema.json +0 -32
- package/apify-actor/APIFY-STORE-LISTING.md +0 -412
- package/apify-actor/Dockerfile +0 -8
- package/apify-actor/README.md +0 -166
- package/apify-actor/main.ts +0 -111
- package/apify-actor/package.json +0 -17
- package/apify-actor/src/main.js +0 -199
- package/docs/BRAND-IDENTITY.md +0 -238
- package/docs/BRAND-STYLE-GUIDE.md +0 -356
- package/drizzle/0000_black_king_cobra.sql +0 -39
- package/drizzle/meta/0000_snapshot.json +0 -309
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/public/.well-known/ucp +0 -25
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +0 -321
- package/public/directory.html +0 -701
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +0 -743
- package/public/guides/fastucp.html +0 -838
- package/public/guides/magento.html +0 -779
- package/public/guides/shopify.html +0 -726
- package/public/guides/squarespace.html +0 -749
- package/public/guides/wix.html +0 -747
- package/public/guides/woocommerce.html +0 -733
- package/public/index.html +0 -3835
- package/public/learn.html +0 -396
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +0 -6
- package/public/site.webmanifest +0 -31
- package/public/sitemap.xml +0 -69
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +0 -410
- package/scripts/generate-favicons.js +0 -44
- package/scripts/generate-ico.js +0 -23
- package/scripts/generate-og-image.js +0 -45
- package/scripts/reset-db.ts +0 -77
- package/scripts/seed-db.ts +0 -71
- package/scripts/setup-benchmark-db.js +0 -70
- package/src/api/server.ts +0 -266
- package/src/cli/index.ts +0 -302
- package/src/compliance/compliance-generator.ts +0 -452
- package/src/compliance/index.ts +0 -28
- package/src/compliance/types.ts +0 -170
- package/src/db/index.ts +0 -28
- package/src/db/schema.ts +0 -84
- package/src/feed-analyzer/index.ts +0 -34
- package/src/feed-analyzer/types.ts +0 -354
- package/src/generator/key-generator.ts +0 -124
- package/src/generator/profile-builder.ts +0 -402
- package/src/index.ts +0 -105
- package/src/security/security-scanner.ts +0 -604
- package/src/security/types.ts +0 -55
- package/src/services/directory.ts +0 -434
- package/src/types/generator.ts +0 -140
- package/src/types/ucp-profile.ts +0 -140
- package/src/types/validation.ts +0 -89
- package/src/validator/index.ts +0 -194
- package/src/validator/network-validator.ts +0 -417
- package/src/validator/rules-validator.ts +0 -297
- package/src/validator/structural-validator.ts +0 -476
- package/tests/fixtures/non-compliant-profile.json +0 -25
- package/tests/fixtures/official-sample-profile.json +0 -75
- package/tests/integration/benchmark.test.ts +0 -207
- package/tests/integration/database.test.ts +0 -163
- package/tests/integration/directory-api.test.ts +0 -268
- package/tests/integration/simulate-api.test.ts +0 -230
- package/tests/integration/validate-api.test.ts +0 -269
- package/tests/setup.ts +0 -15
- package/tests/unit/agent-simulator.test.ts +0 -575
- package/tests/unit/compliance-generator.test.ts +0 -374
- package/tests/unit/directory-service.test.ts +0 -272
- package/tests/unit/feed-analyzer.test.ts +0 -517
- package/tests/unit/lint-suggestions.test.ts +0 -423
- package/tests/unit/official-samples.test.ts +0 -211
- package/tests/unit/pdf-report.test.ts +0 -390
- package/tests/unit/sdk-validator.test.ts +0 -531
- package/tests/unit/security-scanner.test.ts +0 -410
- package/tests/unit/validation.test.ts +0 -390
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for GDPR/Privacy Compliance Generator
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
generateComplianceDocuments,
|
|
8
|
-
getAvailableRegions,
|
|
9
|
-
getLawfulBasisOptions,
|
|
10
|
-
getAiPlatformOptions,
|
|
11
|
-
AI_PLATFORM_PROCESSORS,
|
|
12
|
-
REGION_NAMES,
|
|
13
|
-
LAWFUL_BASIS_DESCRIPTIONS,
|
|
14
|
-
} from '../../src/compliance/index.js';
|
|
15
|
-
import type { ComplianceGeneratorInput } from '../../src/compliance/index.js';
|
|
16
|
-
|
|
17
|
-
describe('Compliance Generator', () => {
|
|
18
|
-
describe('generateComplianceDocuments', () => {
|
|
19
|
-
const validInput: ComplianceGeneratorInput = {
|
|
20
|
-
companyName: 'Test Company',
|
|
21
|
-
companyEmail: 'privacy@test.com',
|
|
22
|
-
regions: ['eu'],
|
|
23
|
-
platforms: ['openai'],
|
|
24
|
-
lawfulBasis: 'contract',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
it('should generate complete compliance documents', () => {
|
|
28
|
-
const result = generateComplianceDocuments(validInput);
|
|
29
|
-
|
|
30
|
-
expect(result).toBeDefined();
|
|
31
|
-
expect(result.privacyAddendum).toBeDefined();
|
|
32
|
-
expect(result.snippets).toBeDefined();
|
|
33
|
-
expect(result.embedHtml).toBeDefined();
|
|
34
|
-
expect(result.plainText).toBeDefined();
|
|
35
|
-
expect(result.generatedAt).toBeDefined();
|
|
36
|
-
expect(result.lawfulBasis).toBe('contract');
|
|
37
|
-
expect(result.regions).toContain('eu');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should include company name in generated content', () => {
|
|
41
|
-
const result = generateComplianceDocuments(validInput);
|
|
42
|
-
|
|
43
|
-
expect(result.snippets.aiCommerceSection).toContain('Test Company');
|
|
44
|
-
expect(result.plainText).toContain('Test Company');
|
|
45
|
-
expect(result.embedHtml).toContain('Test Company');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should include selected AI platforms', () => {
|
|
49
|
-
const input: ComplianceGeneratorInput = {
|
|
50
|
-
...validInput,
|
|
51
|
-
platforms: ['openai', 'google'],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const result = generateComplianceDocuments(input);
|
|
55
|
-
|
|
56
|
-
expect(result.snippets.processorDisclosures).toContain('OpenAI');
|
|
57
|
-
expect(result.snippets.processorDisclosures).toContain('Google');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should include GDPR-specific content for EU region', () => {
|
|
61
|
-
const result = generateComplianceDocuments(validInput);
|
|
62
|
-
|
|
63
|
-
expect(result.snippets.aiCommerceSection).toContain('GDPR');
|
|
64
|
-
expect(result.snippets.dataSubjectRights).toContain('Article');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should include CCPA content for California region', () => {
|
|
68
|
-
const input: ComplianceGeneratorInput = {
|
|
69
|
-
...validInput,
|
|
70
|
-
regions: ['california'],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const result = generateComplianceDocuments(input);
|
|
74
|
-
|
|
75
|
-
expect(result.snippets.aiCommerceSection).toContain('California');
|
|
76
|
-
expect(result.snippets.aiCommerceSection).toContain('CCPA');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should include UK GDPR content for UK region', () => {
|
|
80
|
-
const input: ComplianceGeneratorInput = {
|
|
81
|
-
...validInput,
|
|
82
|
-
regions: ['uk'],
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const result = generateComplianceDocuments(input);
|
|
86
|
-
|
|
87
|
-
expect(result.snippets.aiCommerceSection).toContain('United Kingdom');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should include marketing consent when requested', () => {
|
|
91
|
-
const input: ComplianceGeneratorInput = {
|
|
92
|
-
...validInput,
|
|
93
|
-
includeMarketingConsent: true,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const result = generateComplianceDocuments(input);
|
|
97
|
-
|
|
98
|
-
expect(result.snippets.marketingOptIn).toBeDefined();
|
|
99
|
-
expect(result.snippets.marketingOptIn).toContain('marketing');
|
|
100
|
-
expect(result.snippets.consentLanguage).toContain('marketing');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should not include marketing consent when not requested', () => {
|
|
104
|
-
const input: ComplianceGeneratorInput = {
|
|
105
|
-
...validInput,
|
|
106
|
-
includeMarketingConsent: false,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const result = generateComplianceDocuments(input);
|
|
110
|
-
|
|
111
|
-
expect(result.snippets.marketingOptIn).toBeUndefined();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should include data retention section when requested', () => {
|
|
115
|
-
const input: ComplianceGeneratorInput = {
|
|
116
|
-
...validInput,
|
|
117
|
-
includeDataRetention: true,
|
|
118
|
-
retentionPeriodYears: 5,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const result = generateComplianceDocuments(input);
|
|
122
|
-
|
|
123
|
-
const retentionSection = result.privacyAddendum.sections.find(s => s.id === 'retention');
|
|
124
|
-
expect(retentionSection).toBeDefined();
|
|
125
|
-
expect(retentionSection?.content).toContain('5 years');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should use default retention period of 7 years', () => {
|
|
129
|
-
const input: ComplianceGeneratorInput = {
|
|
130
|
-
...validInput,
|
|
131
|
-
includeDataRetention: true,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const result = generateComplianceDocuments(input);
|
|
135
|
-
|
|
136
|
-
const retentionSection = result.privacyAddendum.sections.find(s => s.id === 'retention');
|
|
137
|
-
expect(retentionSection?.content).toContain('7 years');
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should include correct lawful basis information', () => {
|
|
141
|
-
const bases: Array<ComplianceGeneratorInput['lawfulBasis']> = ['contract', 'consent', 'legitimate', 'legal'];
|
|
142
|
-
|
|
143
|
-
for (const basis of bases) {
|
|
144
|
-
const input: ComplianceGeneratorInput = {
|
|
145
|
-
...validInput,
|
|
146
|
-
lawfulBasis: basis,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const result = generateComplianceDocuments(input);
|
|
150
|
-
const basisInfo = LAWFUL_BASIS_DESCRIPTIONS[basis];
|
|
151
|
-
|
|
152
|
-
expect(result.snippets.aiCommerceSection).toContain(basisInfo.title);
|
|
153
|
-
expect(result.lawfulBasis).toBe(basis);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should include DPO email when provided', () => {
|
|
158
|
-
const input: ComplianceGeneratorInput = {
|
|
159
|
-
...validInput,
|
|
160
|
-
dpoEmail: 'dpo@test.com',
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const result = generateComplianceDocuments(input);
|
|
164
|
-
|
|
165
|
-
expect(result.snippets.dataSubjectRights).toContain('dpo@test.com');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should include additional custom processors', () => {
|
|
169
|
-
const input: ComplianceGeneratorInput = {
|
|
170
|
-
...validInput,
|
|
171
|
-
additionalProcessors: [
|
|
172
|
-
{
|
|
173
|
-
name: 'Custom Processor Inc',
|
|
174
|
-
purpose: 'Custom processing',
|
|
175
|
-
country: 'Germany',
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const result = generateComplianceDocuments(input);
|
|
181
|
-
|
|
182
|
-
expect(result.snippets.processorDisclosures).toContain('Custom Processor Inc');
|
|
183
|
-
expect(result.snippets.processorDisclosures).toContain('Germany');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should generate valid HTML embed code', () => {
|
|
187
|
-
const result = generateComplianceDocuments(validInput);
|
|
188
|
-
|
|
189
|
-
expect(result.embedHtml).toContain('<div class="ucp-privacy-addendum">');
|
|
190
|
-
expect(result.embedHtml).toContain('</div>');
|
|
191
|
-
expect(result.embedHtml).toContain('<style>');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('should include multiple regions when selected', () => {
|
|
195
|
-
const input: ComplianceGeneratorInput = {
|
|
196
|
-
...validInput,
|
|
197
|
-
regions: ['eu', 'uk', 'california'],
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const result = generateComplianceDocuments(input);
|
|
201
|
-
|
|
202
|
-
expect(result.regions).toHaveLength(3);
|
|
203
|
-
expect(result.snippets.aiCommerceSection).toContain('GDPR');
|
|
204
|
-
expect(result.snippets.aiCommerceSection).toContain('California');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should throw error for missing company name', () => {
|
|
208
|
-
const input: ComplianceGeneratorInput = {
|
|
209
|
-
...validInput,
|
|
210
|
-
companyName: '',
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
expect(() => generateComplianceDocuments(input)).toThrow('Company name is required');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should throw error for empty regions', () => {
|
|
217
|
-
const input: ComplianceGeneratorInput = {
|
|
218
|
-
...validInput,
|
|
219
|
-
regions: [],
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
expect(() => generateComplianceDocuments(input)).toThrow('At least one region must be selected');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('should throw error for empty platforms', () => {
|
|
226
|
-
const input: ComplianceGeneratorInput = {
|
|
227
|
-
...validInput,
|
|
228
|
-
platforms: [],
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
expect(() => generateComplianceDocuments(input)).toThrow('At least one AI platform must be selected');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should include consent language for checkout', () => {
|
|
235
|
-
const result = generateComplianceDocuments(validInput);
|
|
236
|
-
|
|
237
|
-
expect(result.snippets.consentLanguage).toContain('checkout');
|
|
238
|
-
expect(result.snippets.consentLanguage).toContain('AI shopping assistant');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('should include tracking notice', () => {
|
|
242
|
-
const result = generateComplianceDocuments(validInput);
|
|
243
|
-
|
|
244
|
-
expect(result.snippets.trackingNotice).toBeDefined();
|
|
245
|
-
expect(result.snippets.trackingNotice).toContain('cookie');
|
|
246
|
-
expect(result.snippets.trackingNotice).toContain('AI agent');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('should include disclaimer in generated documents', () => {
|
|
250
|
-
const result = generateComplianceDocuments(validInput);
|
|
251
|
-
|
|
252
|
-
expect(result.privacyAddendum.disclaimer).toContain('NOT constitute legal advice');
|
|
253
|
-
expect(result.plainText).toContain('DISCLAIMER');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should include all required sections in privacy addendum', () => {
|
|
257
|
-
const result = generateComplianceDocuments(validInput);
|
|
258
|
-
|
|
259
|
-
const sectionIds = result.privacyAddendum.sections.map(s => s.id);
|
|
260
|
-
expect(sectionIds).toContain('ai-commerce');
|
|
261
|
-
expect(sectionIds).toContain('processors');
|
|
262
|
-
expect(sectionIds).toContain('rights');
|
|
263
|
-
expect(sectionIds).toContain('tracking');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('should handle Microsoft Copilot platform', () => {
|
|
267
|
-
const input: ComplianceGeneratorInput = {
|
|
268
|
-
...validInput,
|
|
269
|
-
platforms: ['microsoft'],
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const result = generateComplianceDocuments(input);
|
|
273
|
-
|
|
274
|
-
expect(result.snippets.processorDisclosures).toContain('Microsoft');
|
|
275
|
-
expect(result.snippets.processorDisclosures).toContain('Copilot');
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('getAvailableRegions', () => {
|
|
280
|
-
it('should return all available regions', () => {
|
|
281
|
-
const regions = getAvailableRegions();
|
|
282
|
-
|
|
283
|
-
expect(regions).toHaveLength(4);
|
|
284
|
-
expect(regions.map(r => r.id)).toContain('eu');
|
|
285
|
-
expect(regions.map(r => r.id)).toContain('uk');
|
|
286
|
-
expect(regions.map(r => r.id)).toContain('california');
|
|
287
|
-
expect(regions.map(r => r.id)).toContain('global');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should include display names for regions', () => {
|
|
291
|
-
const regions = getAvailableRegions();
|
|
292
|
-
|
|
293
|
-
const euRegion = regions.find(r => r.id === 'eu');
|
|
294
|
-
expect(euRegion?.name).toContain('GDPR');
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
describe('getLawfulBasisOptions', () => {
|
|
299
|
-
it('should return all lawful basis options', () => {
|
|
300
|
-
const options = getLawfulBasisOptions();
|
|
301
|
-
|
|
302
|
-
expect(options).toHaveLength(4);
|
|
303
|
-
expect(options.map(o => o.id)).toContain('contract');
|
|
304
|
-
expect(options.map(o => o.id)).toContain('consent');
|
|
305
|
-
expect(options.map(o => o.id)).toContain('legitimate');
|
|
306
|
-
expect(options.map(o => o.id)).toContain('legal');
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('should include descriptions and GDPR articles', () => {
|
|
310
|
-
const options = getLawfulBasisOptions();
|
|
311
|
-
|
|
312
|
-
const contractOption = options.find(o => o.id === 'contract');
|
|
313
|
-
expect(contractOption?.title).toBeDefined();
|
|
314
|
-
expect(contractOption?.description).toBeDefined();
|
|
315
|
-
expect(contractOption?.gdprArticle).toContain('Article');
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
describe('getAiPlatformOptions', () => {
|
|
320
|
-
it('should return all AI platform options', () => {
|
|
321
|
-
const options = getAiPlatformOptions();
|
|
322
|
-
|
|
323
|
-
expect(options.length).toBeGreaterThanOrEqual(3);
|
|
324
|
-
expect(options.map(o => o.id)).toContain('openai');
|
|
325
|
-
expect(options.map(o => o.id)).toContain('google');
|
|
326
|
-
expect(options.map(o => o.id)).toContain('microsoft');
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should include platform names and descriptions', () => {
|
|
330
|
-
const options = getAiPlatformOptions();
|
|
331
|
-
|
|
332
|
-
const openaiOption = options.find(o => o.id === 'openai');
|
|
333
|
-
expect(openaiOption?.name).toContain('OpenAI');
|
|
334
|
-
expect(openaiOption?.description).toBeDefined();
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('AI_PLATFORM_PROCESSORS', () => {
|
|
339
|
-
it('should have processor info for all main platforms', () => {
|
|
340
|
-
expect(AI_PLATFORM_PROCESSORS.openai).toBeDefined();
|
|
341
|
-
expect(AI_PLATFORM_PROCESSORS.google).toBeDefined();
|
|
342
|
-
expect(AI_PLATFORM_PROCESSORS.microsoft).toBeDefined();
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('should include privacy policy URLs', () => {
|
|
346
|
-
expect(AI_PLATFORM_PROCESSORS.openai.privacyPolicyUrl).toContain('openai.com');
|
|
347
|
-
expect(AI_PLATFORM_PROCESSORS.google.privacyPolicyUrl).toContain('google.com');
|
|
348
|
-
expect(AI_PLATFORM_PROCESSORS.microsoft.privacyPolicyUrl).toContain('microsoft.com');
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe('REGION_NAMES', () => {
|
|
353
|
-
it('should have display names for all regions', () => {
|
|
354
|
-
expect(REGION_NAMES.eu).toContain('European Union');
|
|
355
|
-
expect(REGION_NAMES.uk).toContain('United Kingdom');
|
|
356
|
-
expect(REGION_NAMES.california).toContain('California');
|
|
357
|
-
expect(REGION_NAMES.global).toContain('Global');
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
describe('LAWFUL_BASIS_DESCRIPTIONS', () => {
|
|
362
|
-
it('should have descriptions for all lawful bases', () => {
|
|
363
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.contract.title).toBeDefined();
|
|
364
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.consent.title).toBeDefined();
|
|
365
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.legitimate.title).toBeDefined();
|
|
366
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.legal.title).toBeDefined();
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it('should include GDPR article references', () => {
|
|
370
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.contract.gdprArticle).toContain('6(1)(b)');
|
|
371
|
-
expect(LAWFUL_BASIS_DESCRIPTIONS.consent.gdprArticle).toContain('6(1)(a)');
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
});
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests for Directory Service
|
|
3
|
-
* Tests Issue #1: UCP Merchant Directory
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
|
|
8
|
-
// Mock the database module before importing the service
|
|
9
|
-
vi.mock('../../src/db/index.js', () => {
|
|
10
|
-
const mockMerchants: any[] = [];
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
getDb: vi.fn(() => ({
|
|
14
|
-
select: vi.fn(() => ({
|
|
15
|
-
from: vi.fn(() => ({
|
|
16
|
-
where: vi.fn(() => ({
|
|
17
|
-
orderBy: vi.fn(() => ({
|
|
18
|
-
limit: vi.fn(() => ({
|
|
19
|
-
offset: vi.fn(() => Promise.resolve(mockMerchants)),
|
|
20
|
-
})),
|
|
21
|
-
})),
|
|
22
|
-
groupBy: vi.fn(() => ({
|
|
23
|
-
orderBy: vi.fn(() => Promise.resolve([])),
|
|
24
|
-
})),
|
|
25
|
-
limit: vi.fn(() => Promise.resolve([])),
|
|
26
|
-
})),
|
|
27
|
-
})),
|
|
28
|
-
})),
|
|
29
|
-
insert: vi.fn(() => ({
|
|
30
|
-
values: vi.fn(() => ({
|
|
31
|
-
returning: vi.fn(() => Promise.resolve([{ id: 'test-id', domain: 'test.com' }])),
|
|
32
|
-
})),
|
|
33
|
-
})),
|
|
34
|
-
update: vi.fn(() => ({
|
|
35
|
-
set: vi.fn(() => ({
|
|
36
|
-
where: vi.fn(() => Promise.resolve()),
|
|
37
|
-
})),
|
|
38
|
-
})),
|
|
39
|
-
})),
|
|
40
|
-
merchants: {
|
|
41
|
-
id: 'id',
|
|
42
|
-
domain: 'domain',
|
|
43
|
-
displayName: 'display_name',
|
|
44
|
-
isPublic: 'is_public',
|
|
45
|
-
category: 'category',
|
|
46
|
-
countryCode: 'country_code',
|
|
47
|
-
ucpScore: 'ucp_score',
|
|
48
|
-
ucpGrade: 'ucp_grade',
|
|
49
|
-
isVerified: 'is_verified',
|
|
50
|
-
createdAt: 'created_at',
|
|
51
|
-
},
|
|
52
|
-
benchmarkStats: {},
|
|
53
|
-
benchmarkSummary: {},
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Import after mocking
|
|
58
|
-
import * as directoryService from '../../src/services/directory.js';
|
|
59
|
-
|
|
60
|
-
describe('Directory Service', () => {
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
vi.clearAllMocks();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
afterEach(() => {
|
|
66
|
-
vi.restoreAllMocks();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('validateDomain', () => {
|
|
70
|
-
it('should return invalid for non-existent domain', async () => {
|
|
71
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('ENOTFOUND'));
|
|
72
|
-
|
|
73
|
-
const result = await directoryService.validateDomain('nonexistent-domain-xyz.com');
|
|
74
|
-
|
|
75
|
-
expect(result.valid).toBe(false);
|
|
76
|
-
expect(result.error).toBeDefined();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should return invalid for HTTP error responses', async () => {
|
|
80
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
81
|
-
ok: false,
|
|
82
|
-
status: 404,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const result = await directoryService.validateDomain('example.com');
|
|
86
|
-
|
|
87
|
-
expect(result.valid).toBe(false);
|
|
88
|
-
expect(result.error).toContain('No UCP profile found');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should return invalid for malformed UCP profile', async () => {
|
|
92
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
93
|
-
ok: true,
|
|
94
|
-
text: () => Promise.resolve(JSON.stringify({ invalid: 'structure' })),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const result = await directoryService.validateDomain('example.com');
|
|
98
|
-
|
|
99
|
-
expect(result.valid).toBe(false);
|
|
100
|
-
expect(result.error).toBe('Invalid UCP profile structure');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should return valid with correct score for valid UCP profile', async () => {
|
|
104
|
-
const validProfile = {
|
|
105
|
-
ucp: {
|
|
106
|
-
version: '2024-01-01',
|
|
107
|
-
services: {
|
|
108
|
-
shopping: {
|
|
109
|
-
rest: { endpoint: 'https://api.example.com' },
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
capabilities: [
|
|
113
|
-
{ name: 'dev.ucp.shopping.checkout' },
|
|
114
|
-
{ name: 'dev.ucp.shopping.cart' },
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
signing_keys: [{ kid: 'key1' }],
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
121
|
-
ok: true,
|
|
122
|
-
text: () => Promise.resolve(JSON.stringify(validProfile)),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = await directoryService.validateDomain('example.com');
|
|
126
|
-
|
|
127
|
-
expect(result.valid).toBe(true);
|
|
128
|
-
expect(result.score).toBeGreaterThanOrEqual(50);
|
|
129
|
-
expect(result.grade).toBeDefined();
|
|
130
|
-
expect(result.transports).toContain('REST');
|
|
131
|
-
expect(result.ucpVersion).toBe('2024-01-01');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should calculate correct score based on capabilities', async () => {
|
|
135
|
-
// Base profile: 50 points
|
|
136
|
-
// checkout: +20, cart: +10, order: +10, signing_keys: +10
|
|
137
|
-
const fullProfile = {
|
|
138
|
-
ucp: {
|
|
139
|
-
version: '2024-01-01',
|
|
140
|
-
services: { shopping: { rest: {} } },
|
|
141
|
-
capabilities: [
|
|
142
|
-
{ name: 'dev.ucp.shopping.checkout' },
|
|
143
|
-
{ name: 'dev.ucp.shopping.cart' },
|
|
144
|
-
{ name: 'dev.ucp.shopping.order' },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
signing_keys: [{ kid: 'key1' }],
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
151
|
-
ok: true,
|
|
152
|
-
text: () => Promise.resolve(JSON.stringify(fullProfile)),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const result = await directoryService.validateDomain('example.com');
|
|
156
|
-
|
|
157
|
-
// 50 + 20 + 10 + 10 + 10 = 100
|
|
158
|
-
expect(result.score).toBe(100);
|
|
159
|
-
expect(result.grade).toBe('A');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should detect multiple transport types', async () => {
|
|
163
|
-
const multiTransportProfile = {
|
|
164
|
-
ucp: {
|
|
165
|
-
version: '2024-01-01',
|
|
166
|
-
services: {
|
|
167
|
-
shopping: {
|
|
168
|
-
rest: { endpoint: 'https://api.example.com' },
|
|
169
|
-
mcp: { server: 'mcp://example.com' },
|
|
170
|
-
},
|
|
171
|
-
catalog: {
|
|
172
|
-
a2a: { url: 'https://a2a.example.com' },
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
capabilities: [],
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
180
|
-
ok: true,
|
|
181
|
-
text: () => Promise.resolve(JSON.stringify(multiTransportProfile)),
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const result = await directoryService.validateDomain('example.com');
|
|
185
|
-
|
|
186
|
-
expect(result.valid).toBe(true);
|
|
187
|
-
expect(result.transports).toContain('REST');
|
|
188
|
-
expect(result.transports).toContain('MCP');
|
|
189
|
-
expect(result.transports).toContain('A2A');
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe('listMerchants', () => {
|
|
194
|
-
it('should apply default pagination', async () => {
|
|
195
|
-
const result = await directoryService.listMerchants({});
|
|
196
|
-
|
|
197
|
-
expect(result.pagination.page).toBe(1);
|
|
198
|
-
expect(result.pagination.limit).toBe(20);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should enforce maximum limit of 100', async () => {
|
|
202
|
-
const result = await directoryService.listMerchants({ limit: 500 });
|
|
203
|
-
|
|
204
|
-
expect(result.pagination.limit).toBe(100);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should enforce minimum page of 1', async () => {
|
|
208
|
-
const result = await directoryService.listMerchants({ page: -5 });
|
|
209
|
-
|
|
210
|
-
expect(result.pagination.page).toBe(1);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('submitMerchant', () => {
|
|
215
|
-
it('should clean domain properly', async () => {
|
|
216
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
217
|
-
ok: true,
|
|
218
|
-
text: () => Promise.resolve(JSON.stringify({
|
|
219
|
-
ucp: { version: '2024-01-01', services: {}, capabilities: [] },
|
|
220
|
-
})),
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// The function should strip protocol and trailing slash
|
|
224
|
-
await directoryService.submitMerchant({
|
|
225
|
-
domain: 'https://EXAMPLE.COM/',
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
229
|
-
'https://example.com/.well-known/ucp',
|
|
230
|
-
expect.any(Object)
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('Grade Calculation', () => {
|
|
237
|
-
it('should assign correct grades based on score', async () => {
|
|
238
|
-
const testCases = [
|
|
239
|
-
{ score: 95, expectedGrade: 'A' },
|
|
240
|
-
{ score: 90, expectedGrade: 'A' },
|
|
241
|
-
{ score: 85, expectedGrade: 'B' },
|
|
242
|
-
{ score: 80, expectedGrade: 'B' },
|
|
243
|
-
{ score: 75, expectedGrade: 'C' },
|
|
244
|
-
{ score: 70, expectedGrade: 'C' },
|
|
245
|
-
{ score: 65, expectedGrade: 'D' },
|
|
246
|
-
{ score: 60, expectedGrade: 'D' },
|
|
247
|
-
{ score: 55, expectedGrade: 'F' },
|
|
248
|
-
{ score: 0, expectedGrade: 'F' },
|
|
249
|
-
];
|
|
250
|
-
|
|
251
|
-
for (const { score, expectedGrade } of testCases) {
|
|
252
|
-
const profile = {
|
|
253
|
-
ucp: {
|
|
254
|
-
version: '2024-01-01',
|
|
255
|
-
services: { s: { rest: {} } },
|
|
256
|
-
capabilities: score >= 70 ? [{ name: 'dev.ucp.shopping.checkout' }] : [],
|
|
257
|
-
},
|
|
258
|
-
signing_keys: score >= 60 ? [{}] : undefined,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
262
|
-
ok: true,
|
|
263
|
-
text: () => Promise.resolve(JSON.stringify(profile)),
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const result = await directoryService.validateDomain('test.com');
|
|
267
|
-
|
|
268
|
-
// Verify grade assignment logic works (scores may vary based on capabilities)
|
|
269
|
-
expect(['A', 'B', 'C', 'D', 'F']).toContain(result.grade);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
});
|