@ucptools/validator 1.0.0 → 1.1.0

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.
Files changed (330) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.d.ts +6 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +375 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/mock-server.d.ts +20 -0
  22. package/dist/cli/mock-server.d.ts.map +1 -0
  23. package/dist/cli/mock-server.js +261 -0
  24. package/dist/cli/mock-server.js.map +1 -0
  25. package/dist/compliance/compliance-generator.d.ts +34 -0
  26. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  27. package/dist/compliance/compliance-generator.js +320 -0
  28. package/dist/compliance/compliance-generator.js.map +1 -0
  29. package/dist/compliance/index.d.ts +8 -0
  30. package/dist/compliance/index.d.ts.map +1 -0
  31. package/dist/compliance/index.js +17 -0
  32. package/dist/compliance/index.js.map +1 -0
  33. package/dist/compliance/templates.d.ts +34 -0
  34. package/dist/compliance/templates.d.ts.map +1 -0
  35. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  36. package/dist/compliance/templates.js.map +1 -0
  37. package/dist/compliance/types.d.ts +64 -0
  38. package/dist/compliance/types.d.ts.map +1 -0
  39. package/dist/compliance/types.js +64 -0
  40. package/dist/compliance/types.js.map +1 -0
  41. package/dist/db/index.d.ts +17 -0
  42. package/dist/db/index.d.ts.map +1 -0
  43. package/dist/db/index.js +80 -0
  44. package/dist/db/index.js.map +1 -0
  45. package/dist/db/schema.d.ts +3886 -0
  46. package/dist/db/schema.d.ts.map +1 -0
  47. package/dist/db/schema.js +425 -0
  48. package/dist/db/schema.js.map +1 -0
  49. package/dist/db/utils.d.ts +252 -0
  50. package/dist/db/utils.d.ts.map +1 -0
  51. package/dist/db/utils.js +295 -0
  52. package/dist/db/utils.js.map +1 -0
  53. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  54. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  55. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
  56. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  57. package/dist/feed-analyzer/index.d.ts +8 -0
  58. package/dist/feed-analyzer/index.d.ts.map +1 -0
  59. package/dist/feed-analyzer/index.js +19 -0
  60. package/dist/feed-analyzer/index.js.map +1 -0
  61. package/dist/feed-analyzer/types.d.ts +285 -0
  62. package/dist/feed-analyzer/types.d.ts.map +1 -0
  63. package/dist/feed-analyzer/types.js +175 -0
  64. package/dist/feed-analyzer/types.js.map +1 -0
  65. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  66. package/dist/generator/index.d.ts.map +1 -0
  67. package/dist/generator/index.js +13 -0
  68. package/dist/generator/index.js.map +1 -0
  69. package/dist/generator/key-generator.d.ts +24 -0
  70. package/dist/generator/key-generator.d.ts.map +1 -0
  71. package/dist/generator/key-generator.js +144 -0
  72. package/dist/generator/key-generator.js.map +1 -0
  73. package/dist/generator/profile-builder.d.ts +15 -0
  74. package/dist/generator/profile-builder.d.ts.map +1 -0
  75. package/dist/generator/profile-builder.js +338 -0
  76. package/dist/generator/profile-builder.js.map +1 -0
  77. package/dist/hosting/artifacts-generator.d.ts +10 -0
  78. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  79. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  80. package/dist/hosting/artifacts-generator.js.map +1 -0
  81. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  82. package/dist/hosting/index.d.ts.map +1 -0
  83. package/dist/hosting/index.js +10 -0
  84. package/dist/hosting/index.js.map +1 -0
  85. package/dist/index.d.ts +18 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +78 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/lib/analytics.d.ts +337 -0
  90. package/dist/lib/analytics.d.ts.map +1 -0
  91. package/dist/lib/analytics.js +188 -0
  92. package/dist/lib/analytics.js.map +1 -0
  93. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  94. package/dist/security/index.d.ts.map +1 -0
  95. package/dist/security/index.js +12 -0
  96. package/dist/security/index.js.map +1 -0
  97. package/dist/security/security-scanner.d.ts +10 -0
  98. package/dist/security/security-scanner.d.ts.map +1 -0
  99. package/dist/security/security-scanner.js +669 -0
  100. package/dist/security/security-scanner.js.map +1 -0
  101. package/dist/security/types.d.ts +80 -0
  102. package/dist/security/types.d.ts.map +1 -0
  103. package/dist/security/types.js +21 -0
  104. package/dist/security/types.js.map +1 -0
  105. package/dist/services/analytics.d.ts +114 -0
  106. package/dist/services/analytics.d.ts.map +1 -0
  107. package/dist/services/analytics.js +862 -0
  108. package/dist/services/analytics.js.map +1 -0
  109. package/dist/services/badge.d.ts +31 -0
  110. package/dist/services/badge.d.ts.map +1 -0
  111. package/dist/services/badge.js +152 -0
  112. package/dist/services/badge.js.map +1 -0
  113. package/dist/services/cron.d.ts +125 -0
  114. package/dist/services/cron.d.ts.map +1 -0
  115. package/dist/services/cron.js +613 -0
  116. package/dist/services/cron.js.map +1 -0
  117. package/dist/services/directory.d.ts +106 -0
  118. package/dist/services/directory.d.ts.map +1 -0
  119. package/dist/services/directory.js +351 -0
  120. package/dist/services/directory.js.map +1 -0
  121. package/dist/services/email.d.ts +112 -0
  122. package/dist/services/email.d.ts.map +1 -0
  123. package/dist/services/email.js +772 -0
  124. package/dist/services/email.js.map +1 -0
  125. package/dist/services/hosted-profiles.d.ts +77 -0
  126. package/dist/services/hosted-profiles.d.ts.map +1 -0
  127. package/dist/services/hosted-profiles.js +433 -0
  128. package/dist/services/hosted-profiles.js.map +1 -0
  129. package/dist/services/latency.d.ts +67 -0
  130. package/dist/services/latency.d.ts.map +1 -0
  131. package/dist/services/latency.js +274 -0
  132. package/dist/services/latency.js.map +1 -0
  133. package/dist/services/manifest-compliance.d.ts +64 -0
  134. package/dist/services/manifest-compliance.d.ts.map +1 -0
  135. package/dist/services/manifest-compliance.js +271 -0
  136. package/dist/services/manifest-compliance.js.map +1 -0
  137. package/dist/services/monitoring-diff.d.ts +31 -0
  138. package/dist/services/monitoring-diff.d.ts.map +1 -0
  139. package/dist/services/monitoring-diff.js +189 -0
  140. package/dist/services/monitoring-diff.js.map +1 -0
  141. package/dist/services/notifications.d.ts +46 -0
  142. package/dist/services/notifications.d.ts.map +1 -0
  143. package/dist/services/notifications.js +88 -0
  144. package/dist/services/notifications.js.map +1 -0
  145. package/dist/services/stripe.d.ts +93 -0
  146. package/dist/services/stripe.d.ts.map +1 -0
  147. package/dist/services/stripe.js +490 -0
  148. package/dist/services/stripe.js.map +1 -0
  149. package/dist/services/validation-history.d.ts +99 -0
  150. package/dist/services/validation-history.d.ts.map +1 -0
  151. package/dist/services/validation-history.js +344 -0
  152. package/dist/services/validation-history.js.map +1 -0
  153. package/dist/services/validation-logging.d.ts +103 -0
  154. package/dist/services/validation-logging.d.ts.map +1 -0
  155. package/dist/services/validation-logging.js +210 -0
  156. package/dist/services/validation-logging.js.map +1 -0
  157. package/dist/services/validation.d.ts +119 -0
  158. package/dist/services/validation.d.ts.map +1 -0
  159. package/dist/services/validation.js +1185 -0
  160. package/dist/services/validation.js.map +1 -0
  161. package/dist/simulator/agent-simulator.d.ts +69 -0
  162. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  163. package/dist/simulator/agent-simulator.js +870 -0
  164. package/dist/simulator/agent-simulator.js.map +1 -0
  165. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  166. package/dist/simulator/index.d.ts.map +1 -0
  167. package/dist/simulator/index.js +23 -0
  168. package/dist/simulator/index.js.map +1 -0
  169. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
  170. package/dist/simulator/types.d.ts.map +1 -0
  171. package/dist/simulator/types.js +18 -0
  172. package/dist/simulator/types.js.map +1 -0
  173. package/dist/types/acp-validation.d.ts +87 -0
  174. package/dist/types/acp-validation.d.ts.map +1 -0
  175. package/dist/types/acp-validation.js +40 -0
  176. package/dist/types/acp-validation.js.map +1 -0
  177. package/dist/types/analytics.d.ts +182 -0
  178. package/dist/types/analytics.d.ts.map +1 -0
  179. package/dist/types/analytics.js +7 -0
  180. package/dist/types/analytics.js.map +1 -0
  181. package/dist/types/generator.d.ts +106 -0
  182. package/dist/types/generator.d.ts.map +1 -0
  183. package/dist/types/generator.js +6 -0
  184. package/dist/types/generator.js.map +1 -0
  185. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  186. package/dist/types/index.d.ts.map +1 -0
  187. package/dist/types/index.js +23 -0
  188. package/dist/types/index.js.map +1 -0
  189. package/dist/types/ucp-profile.d.ts +111 -0
  190. package/dist/types/ucp-profile.d.ts.map +1 -0
  191. package/dist/types/ucp-profile.js +45 -0
  192. package/dist/types/ucp-profile.js.map +1 -0
  193. package/dist/types/validation.d.ts +76 -0
  194. package/dist/types/validation.d.ts.map +1 -0
  195. package/dist/types/validation.js +42 -0
  196. package/dist/types/validation.js.map +1 -0
  197. package/dist/validator/acp/index.d.ts +31 -0
  198. package/dist/validator/acp/index.d.ts.map +1 -0
  199. package/dist/validator/acp/index.js +574 -0
  200. package/dist/validator/acp/index.js.map +1 -0
  201. package/dist/validator/index.d.ts +26 -0
  202. package/dist/validator/index.d.ts.map +1 -0
  203. package/dist/validator/index.js +161 -0
  204. package/dist/validator/index.js.map +1 -0
  205. package/dist/validator/network-validator.d.ts +28 -0
  206. package/dist/validator/network-validator.d.ts.map +1 -0
  207. package/dist/validator/network-validator.js +319 -0
  208. package/dist/validator/network-validator.js.map +1 -0
  209. package/dist/validator/rules-validator.d.ts +19 -0
  210. package/dist/validator/rules-validator.d.ts.map +1 -0
  211. package/dist/validator/rules-validator.js +306 -0
  212. package/dist/validator/rules-validator.js.map +1 -0
  213. package/dist/validator/sdk-validator.d.ts +58 -0
  214. package/dist/validator/sdk-validator.d.ts.map +1 -0
  215. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  216. package/dist/validator/sdk-validator.js.map +1 -0
  217. package/dist/validator/structural-validator.d.ts +11 -0
  218. package/dist/validator/structural-validator.d.ts.map +1 -0
  219. package/dist/validator/structural-validator.js +549 -0
  220. package/dist/validator/structural-validator.js.map +1 -0
  221. package/dist/validator/utils.d.ts +51 -0
  222. package/dist/validator/utils.d.ts.map +1 -0
  223. package/dist/validator/utils.js +132 -0
  224. package/dist/validator/utils.js.map +1 -0
  225. package/package.json +44 -12
  226. package/CLAUDE.md +0 -109
  227. package/api/analyze-feed.js +0 -140
  228. package/api/badge.js +0 -185
  229. package/api/benchmark.js +0 -177
  230. package/api/directory-stats.ts +0 -29
  231. package/api/directory.ts +0 -73
  232. package/api/generate-compliance.js +0 -143
  233. package/api/generate-schema.js +0 -457
  234. package/api/generate.js +0 -132
  235. package/api/security-scan.js +0 -133
  236. package/api/simulate.js +0 -187
  237. package/api/tsconfig.json +0 -10
  238. package/api/validate.js +0 -1351
  239. package/apify-actor/.actor/actor.json +0 -68
  240. package/apify-actor/.actor/input_schema.json +0 -32
  241. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  242. package/apify-actor/Dockerfile +0 -8
  243. package/apify-actor/README.md +0 -166
  244. package/apify-actor/main.ts +0 -111
  245. package/apify-actor/package.json +0 -17
  246. package/apify-actor/src/main.js +0 -199
  247. package/docs/BRAND-IDENTITY.md +0 -238
  248. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  249. package/drizzle/0000_black_king_cobra.sql +0 -39
  250. package/drizzle/meta/0000_snapshot.json +0 -309
  251. package/drizzle/meta/_journal.json +0 -13
  252. package/drizzle.config.ts +0 -10
  253. package/public/.well-known/ucp +0 -25
  254. package/public/android-chrome-192x192.png +0 -0
  255. package/public/android-chrome-512x512.png +0 -0
  256. package/public/apple-touch-icon.png +0 -0
  257. package/public/brand.css +0 -321
  258. package/public/directory.html +0 -701
  259. package/public/favicon-16x16.png +0 -0
  260. package/public/favicon-32x32.png +0 -0
  261. package/public/favicon.ico +0 -0
  262. package/public/guides/bigcommerce.html +0 -743
  263. package/public/guides/fastucp.html +0 -838
  264. package/public/guides/magento.html +0 -779
  265. package/public/guides/shopify.html +0 -726
  266. package/public/guides/squarespace.html +0 -749
  267. package/public/guides/wix.html +0 -747
  268. package/public/guides/woocommerce.html +0 -733
  269. package/public/index.html +0 -3835
  270. package/public/learn.html +0 -396
  271. package/public/logo.jpeg +0 -0
  272. package/public/og-image-icon.png +0 -0
  273. package/public/og-image.png +0 -0
  274. package/public/robots.txt +0 -6
  275. package/public/site.webmanifest +0 -31
  276. package/public/sitemap.xml +0 -69
  277. package/public/social/linkedin-banner-1128x191.png +0 -0
  278. package/public/social/temp.PNG +0 -0
  279. package/public/social/x-header-1500x500.png +0 -0
  280. package/public/verify.html +0 -410
  281. package/scripts/generate-favicons.js +0 -44
  282. package/scripts/generate-ico.js +0 -23
  283. package/scripts/generate-og-image.js +0 -45
  284. package/scripts/reset-db.ts +0 -77
  285. package/scripts/seed-db.ts +0 -71
  286. package/scripts/setup-benchmark-db.js +0 -70
  287. package/src/api/server.ts +0 -266
  288. package/src/cli/index.ts +0 -302
  289. package/src/compliance/compliance-generator.ts +0 -452
  290. package/src/compliance/index.ts +0 -28
  291. package/src/compliance/types.ts +0 -170
  292. package/src/db/index.ts +0 -28
  293. package/src/db/schema.ts +0 -84
  294. package/src/feed-analyzer/index.ts +0 -34
  295. package/src/feed-analyzer/types.ts +0 -354
  296. package/src/generator/key-generator.ts +0 -124
  297. package/src/generator/profile-builder.ts +0 -402
  298. package/src/index.ts +0 -105
  299. package/src/security/security-scanner.ts +0 -604
  300. package/src/security/types.ts +0 -55
  301. package/src/services/directory.ts +0 -434
  302. package/src/simulator/agent-simulator.ts +0 -941
  303. package/src/types/generator.ts +0 -140
  304. package/src/types/ucp-profile.ts +0 -140
  305. package/src/types/validation.ts +0 -89
  306. package/src/validator/index.ts +0 -194
  307. package/src/validator/network-validator.ts +0 -417
  308. package/src/validator/rules-validator.ts +0 -297
  309. package/src/validator/structural-validator.ts +0 -476
  310. package/tests/fixtures/non-compliant-profile.json +0 -25
  311. package/tests/fixtures/official-sample-profile.json +0 -75
  312. package/tests/integration/benchmark.test.ts +0 -207
  313. package/tests/integration/database.test.ts +0 -163
  314. package/tests/integration/directory-api.test.ts +0 -268
  315. package/tests/integration/simulate-api.test.ts +0 -230
  316. package/tests/integration/validate-api.test.ts +0 -269
  317. package/tests/setup.ts +0 -15
  318. package/tests/unit/agent-simulator.test.ts +0 -575
  319. package/tests/unit/compliance-generator.test.ts +0 -374
  320. package/tests/unit/directory-service.test.ts +0 -272
  321. package/tests/unit/feed-analyzer.test.ts +0 -517
  322. package/tests/unit/lint-suggestions.test.ts +0 -423
  323. package/tests/unit/official-samples.test.ts +0 -211
  324. package/tests/unit/pdf-report.test.ts +0 -390
  325. package/tests/unit/sdk-validator.test.ts +0 -531
  326. package/tests/unit/security-scanner.test.ts +0 -410
  327. package/tests/unit/validation.test.ts +0 -390
  328. package/tsconfig.json +0 -20
  329. package/vercel.json +0 -34
  330. package/vitest.config.ts +0 -22
@@ -1,207 +0,0 @@
1
- /**
2
- * Integration Tests for Benchmark Functionality
3
- * Tests Issue #2: Industry Benchmark Comparison
4
- */
5
-
6
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
7
- import { getDb, benchmarkStats, benchmarkSummary } from '../../src/db/index.js';
8
- import { eq, sql } from 'drizzle-orm';
9
-
10
- // Skip tests if DATABASE_URL is not set
11
- const skipIfNoDb = !process.env.DATABASE_URL;
12
-
13
- describe.skipIf(skipIfNoDb)('Benchmark Integration (Issue #2)', () => {
14
- describe('Benchmark Stats Table Structure', () => {
15
- it('should have all required score buckets (0-100 in increments of 10)', async () => {
16
- const db = getDb();
17
- const stats = await db.select().from(benchmarkStats);
18
-
19
- const buckets = stats.map((s) => s.scoreBucket).sort((a, b) => a - b);
20
- const expectedBuckets = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
21
-
22
- expect(buckets).toEqual(expectedBuckets);
23
- });
24
-
25
- it('should have non-negative counts for all buckets', async () => {
26
- const db = getDb();
27
- const stats = await db.select().from(benchmarkStats);
28
-
29
- for (const stat of stats) {
30
- expect(stat.count).toBeGreaterThanOrEqual(0);
31
- }
32
- });
33
- });
34
-
35
- describe('Benchmark Summary Table', () => {
36
- it('should have exactly one summary row', async () => {
37
- const db = getDb();
38
- const summary = await db.select().from(benchmarkSummary);
39
-
40
- expect(summary).toHaveLength(1);
41
- expect(summary[0].id).toBe(1);
42
- });
43
-
44
- it('should have valid totalValidations count', async () => {
45
- const db = getDb();
46
- const summary = await db.select().from(benchmarkSummary);
47
-
48
- expect(summary[0].totalValidations).toBeGreaterThanOrEqual(0);
49
- });
50
-
51
- it('should have valid avgScore', async () => {
52
- const db = getDb();
53
- const summary = await db.select().from(benchmarkSummary);
54
-
55
- if (summary[0].avgScore !== null) {
56
- const avgScore = parseFloat(summary[0].avgScore);
57
- expect(avgScore).toBeGreaterThanOrEqual(0);
58
- expect(avgScore).toBeLessThanOrEqual(100);
59
- }
60
- });
61
-
62
- it('should have updatedAt timestamp', async () => {
63
- const db = getDb();
64
- const summary = await db.select().from(benchmarkSummary);
65
-
66
- expect(summary[0].updatedAt).toBeInstanceOf(Date);
67
- });
68
- });
69
-
70
- describe('Benchmark Recording Logic', () => {
71
- // Save original values to restore after tests
72
- let originalBucketCount: number;
73
- let originalTotalValidations: number;
74
- const testBucket = 70;
75
-
76
- beforeAll(async () => {
77
- const db = getDb();
78
-
79
- // Get original values
80
- const bucketResult = await db
81
- .select()
82
- .from(benchmarkStats)
83
- .where(eq(benchmarkStats.scoreBucket, testBucket));
84
- originalBucketCount = bucketResult[0]?.count || 0;
85
-
86
- const summaryResult = await db.select().from(benchmarkSummary);
87
- originalTotalValidations = summaryResult[0]?.totalValidations || 0;
88
- });
89
-
90
- afterAll(async () => {
91
- // Restore original values
92
- const db = getDb();
93
-
94
- await db
95
- .update(benchmarkStats)
96
- .set({ count: originalBucketCount })
97
- .where(eq(benchmarkStats.scoreBucket, testBucket));
98
-
99
- await db
100
- .update(benchmarkSummary)
101
- .set({ totalValidations: originalTotalValidations })
102
- .where(eq(benchmarkSummary.id, 1));
103
- });
104
-
105
- it('should increment bucket count when score is recorded', async () => {
106
- const db = getDb();
107
-
108
- // Get current count
109
- const beforeResult = await db
110
- .select()
111
- .from(benchmarkStats)
112
- .where(eq(benchmarkStats.scoreBucket, testBucket));
113
- const beforeCount = beforeResult[0]?.count || 0;
114
-
115
- // Increment (simulating what validate.js does)
116
- await db
117
- .update(benchmarkStats)
118
- .set({ count: sql`${benchmarkStats.count} + 1` })
119
- .where(eq(benchmarkStats.scoreBucket, testBucket));
120
-
121
- // Verify increment
122
- const afterResult = await db
123
- .select()
124
- .from(benchmarkStats)
125
- .where(eq(benchmarkStats.scoreBucket, testBucket));
126
-
127
- expect(afterResult[0].count).toBe(beforeCount + 1);
128
- });
129
-
130
- it('should increment total validations in summary', async () => {
131
- const db = getDb();
132
-
133
- // Get current total
134
- const beforeResult = await db.select().from(benchmarkSummary);
135
- const beforeTotal = beforeResult[0]?.totalValidations || 0;
136
-
137
- // Increment
138
- await db
139
- .update(benchmarkSummary)
140
- .set({ totalValidations: sql`${benchmarkSummary.totalValidations} + 1` })
141
- .where(eq(benchmarkSummary.id, 1));
142
-
143
- // Verify
144
- const afterResult = await db.select().from(benchmarkSummary);
145
- expect(afterResult[0].totalValidations).toBe(beforeTotal + 1);
146
- });
147
- });
148
-
149
- describe('Percentile Calculation', () => {
150
- it('should be calculable from bucket data', async () => {
151
- const db = getDb();
152
-
153
- // Get all buckets with cumulative counts
154
- const stats = await db
155
- .select()
156
- .from(benchmarkStats)
157
- .orderBy(benchmarkStats.scoreBucket);
158
-
159
- // Calculate cumulative
160
- let cumulative = 0;
161
- const withCumulative = stats.map((s) => {
162
- cumulative += s.count;
163
- return { ...s, cumulative };
164
- });
165
-
166
- // Total is the last cumulative
167
- const total = cumulative;
168
-
169
- if (total > 0) {
170
- // For a score of 70, calculate percentile
171
- const bucket70 = withCumulative.find((s) => s.scoreBucket === 70);
172
-
173
- if (bucket70) {
174
- const belowCount = withCumulative
175
- .filter((s) => s.scoreBucket < 70)
176
- .reduce((sum, s) => sum + s.count, 0);
177
-
178
- const percentile = Math.round((belowCount / total) * 100);
179
-
180
- expect(percentile).toBeGreaterThanOrEqual(0);
181
- expect(percentile).toBeLessThanOrEqual(100);
182
- }
183
- }
184
- });
185
- });
186
- });
187
-
188
- describe.skipIf(skipIfNoDb)('Validate API Benchmark Response', () => {
189
- it('should include benchmark data in validation response format', () => {
190
- // This tests the expected response structure from api/validate.js
191
- const mockBenchmarkResponse = {
192
- percentile: 75,
193
- comparison: 'Your site scores better than 75% of sites analyzed',
194
- total_sites_analyzed: 100,
195
- average_score: 65,
196
- };
197
-
198
- expect(mockBenchmarkResponse).toHaveProperty('percentile');
199
- expect(mockBenchmarkResponse).toHaveProperty('comparison');
200
- expect(mockBenchmarkResponse).toHaveProperty('total_sites_analyzed');
201
- expect(mockBenchmarkResponse).toHaveProperty('average_score');
202
-
203
- expect(typeof mockBenchmarkResponse.percentile).toBe('number');
204
- expect(mockBenchmarkResponse.percentile).toBeGreaterThanOrEqual(0);
205
- expect(mockBenchmarkResponse.percentile).toBeLessThanOrEqual(100);
206
- });
207
- });
@@ -1,163 +0,0 @@
1
- /**
2
- * Integration Tests for Database
3
- * Tests database connectivity and schema
4
- */
5
-
6
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
7
- import { getDb, merchants, benchmarkStats, benchmarkSummary } from '../../src/db/index.js';
8
- import { eq, sql } from 'drizzle-orm';
9
-
10
- // Skip tests if DATABASE_URL is not set
11
- const skipIfNoDb = !process.env.DATABASE_URL;
12
-
13
- describe.skipIf(skipIfNoDb)('Database Integration', () => {
14
- describe('Connection', () => {
15
- it('should connect to the database successfully', async () => {
16
- const db = getDb();
17
- expect(db).toBeDefined();
18
-
19
- // Simple query to verify connection
20
- const result = await db.execute(sql`SELECT 1 as test`);
21
- expect(result).toBeDefined();
22
- });
23
-
24
- it('should use lazy initialization', () => {
25
- // getDb should return the same instance
26
- const db1 = getDb();
27
- const db2 = getDb();
28
- expect(db1).toBe(db2);
29
- });
30
- });
31
-
32
- describe('Merchants Table', () => {
33
- const testDomain = `test-${Date.now()}.example.com`;
34
-
35
- afterAll(async () => {
36
- // Clean up test data
37
- const db = getDb();
38
- await db.delete(merchants).where(eq(merchants.domain, testDomain));
39
- });
40
-
41
- it('should insert a merchant', async () => {
42
- const db = getDb();
43
-
44
- const result = await db
45
- .insert(merchants)
46
- .values({
47
- domain: testDomain,
48
- displayName: 'Test Merchant',
49
- description: 'A test merchant for integration testing',
50
- category: 'test',
51
- countryCode: 'US',
52
- ucpScore: 75,
53
- ucpGrade: 'C',
54
- transports: 'REST',
55
- isPublic: true,
56
- isVerified: false,
57
- })
58
- .returning();
59
-
60
- expect(result).toHaveLength(1);
61
- expect(result[0].domain).toBe(testDomain);
62
- expect(result[0].id).toBeDefined();
63
- });
64
-
65
- it('should query merchants', async () => {
66
- const db = getDb();
67
-
68
- const result = await db
69
- .select()
70
- .from(merchants)
71
- .where(eq(merchants.domain, testDomain));
72
-
73
- expect(result).toHaveLength(1);
74
- expect(result[0].displayName).toBe('Test Merchant');
75
- expect(result[0].ucpScore).toBe(75);
76
- });
77
-
78
- it('should update a merchant', async () => {
79
- const db = getDb();
80
-
81
- await db
82
- .update(merchants)
83
- .set({ ucpScore: 85, ucpGrade: 'B' })
84
- .where(eq(merchants.domain, testDomain));
85
-
86
- const result = await db
87
- .select()
88
- .from(merchants)
89
- .where(eq(merchants.domain, testDomain));
90
-
91
- expect(result[0].ucpScore).toBe(85);
92
- expect(result[0].ucpGrade).toBe('B');
93
- });
94
-
95
- it('should enforce unique domain constraint', async () => {
96
- const db = getDb();
97
-
98
- await expect(
99
- db.insert(merchants).values({
100
- domain: testDomain,
101
- displayName: 'Duplicate Merchant',
102
- })
103
- ).rejects.toThrow();
104
- });
105
- });
106
-
107
- describe('Benchmark Stats Table (Issue #2)', () => {
108
- it('should have score buckets from 0 to 100', async () => {
109
- const db = getDb();
110
-
111
- const result = await db.select().from(benchmarkStats);
112
-
113
- expect(result.length).toBe(11); // 0, 10, 20, ..., 100
114
-
115
- const buckets = result.map((r) => r.scoreBucket).sort((a, b) => a - b);
116
- expect(buckets).toEqual([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);
117
- });
118
-
119
- it('should have count field for each bucket', async () => {
120
- const db = getDb();
121
-
122
- const result = await db.select().from(benchmarkStats);
123
-
124
- for (const row of result) {
125
- expect(typeof row.count).toBe('number');
126
- expect(row.count).toBeGreaterThanOrEqual(0);
127
- }
128
- });
129
- });
130
-
131
- describe('Benchmark Summary Table (Issue #2)', () => {
132
- it('should have a summary row', async () => {
133
- const db = getDb();
134
-
135
- const result = await db.select().from(benchmarkSummary);
136
-
137
- expect(result.length).toBe(1);
138
- expect(result[0].id).toBe(1);
139
- });
140
-
141
- it('should track total validations', async () => {
142
- const db = getDb();
143
-
144
- const result = await db.select().from(benchmarkSummary);
145
-
146
- expect(typeof result[0].totalValidations).toBe('number');
147
- expect(result[0].totalValidations).toBeGreaterThanOrEqual(0);
148
- });
149
-
150
- it('should track average score', async () => {
151
- const db = getDb();
152
-
153
- const result = await db.select().from(benchmarkSummary);
154
-
155
- // avgScore can be null or a string (decimal type)
156
- if (result[0].avgScore !== null) {
157
- const avgScore = parseFloat(result[0].avgScore);
158
- expect(avgScore).toBeGreaterThanOrEqual(0);
159
- expect(avgScore).toBeLessThanOrEqual(100);
160
- }
161
- });
162
- });
163
- });
@@ -1,268 +0,0 @@
1
- /**
2
- * Integration Tests for Directory API Endpoints
3
- * Tests Issue #1: UCP Merchant Directory
4
- */
5
-
6
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
7
- import { getDb, merchants } from '../../src/db/index.js';
8
- import { eq } from 'drizzle-orm';
9
- import {
10
- listMerchants,
11
- submitMerchant,
12
- getMerchantByDomain,
13
- getDirectoryStats,
14
- revalidateMerchant,
15
- } from '../../src/services/directory.js';
16
-
17
- // Skip tests if DATABASE_URL is not set
18
- const skipIfNoDb = !process.env.DATABASE_URL;
19
-
20
- describe.skipIf(skipIfNoDb)('Directory API Integration', () => {
21
- const testDomains: string[] = [];
22
-
23
- // Helper to create a unique test domain
24
- const createTestDomain = () => {
25
- const domain = `api-test-${Date.now()}-${Math.random().toString(36).slice(2)}.example.com`;
26
- testDomains.push(domain);
27
- return domain;
28
- };
29
-
30
- afterAll(async () => {
31
- // Clean up all test data
32
- const db = getDb();
33
- for (const domain of testDomains) {
34
- await db.delete(merchants).where(eq(merchants.domain, domain));
35
- }
36
- });
37
-
38
- describe('listMerchants', () => {
39
- it('should return empty list when no merchants exist', async () => {
40
- const result = await listMerchants({ search: 'nonexistent-unique-query-xyz' });
41
-
42
- expect(result.merchants).toHaveLength(0);
43
- expect(result.pagination.total).toBe(0);
44
- });
45
-
46
- it('should return correct pagination structure', async () => {
47
- const result = await listMerchants({ page: 1, limit: 10 });
48
-
49
- expect(result.pagination).toHaveProperty('page');
50
- expect(result.pagination).toHaveProperty('limit');
51
- expect(result.pagination).toHaveProperty('total');
52
- expect(result.pagination).toHaveProperty('totalPages');
53
- });
54
-
55
- it('should return filter options', async () => {
56
- const result = await listMerchants({});
57
-
58
- expect(result.filters).toHaveProperty('categories');
59
- expect(result.filters).toHaveProperty('countries');
60
- expect(Array.isArray(result.filters.categories)).toBe(true);
61
- expect(Array.isArray(result.filters.countries)).toBe(true);
62
- });
63
-
64
- it('should filter by category', async () => {
65
- const db = getDb();
66
- const domain1 = createTestDomain();
67
- const domain2 = createTestDomain();
68
-
69
- // Create test merchants with different categories
70
- await db.insert(merchants).values([
71
- { domain: domain1, displayName: 'Fashion Store', category: 'fashion', isPublic: true },
72
- { domain: domain2, displayName: 'Tech Store', category: 'electronics', isPublic: true },
73
- ]);
74
-
75
- const fashionResult = await listMerchants({ category: 'fashion' });
76
- const electronicsResult = await listMerchants({ category: 'electronics' });
77
-
78
- const fashionDomains = fashionResult.merchants.map((m) => m.domain);
79
- const electronicsDomains = electronicsResult.merchants.map((m) => m.domain);
80
-
81
- expect(fashionDomains).toContain(domain1);
82
- expect(fashionDomains).not.toContain(domain2);
83
- expect(electronicsDomains).toContain(domain2);
84
- expect(electronicsDomains).not.toContain(domain1);
85
- });
86
-
87
- it('should search by domain and display name', async () => {
88
- const db = getDb();
89
- const domain = createTestDomain();
90
-
91
- await db.insert(merchants).values({
92
- domain,
93
- displayName: 'Unique Searchable Store XYZ123',
94
- isPublic: true,
95
- });
96
-
97
- // Search by display name
98
- const nameResult = await listMerchants({ search: 'XYZ123' });
99
- expect(nameResult.merchants.some((m) => m.domain === domain)).toBe(true);
100
-
101
- // Search by domain
102
- const domainResult = await listMerchants({ search: domain.split('.')[0] });
103
- expect(domainResult.merchants.some((m) => m.domain === domain)).toBe(true);
104
- });
105
-
106
- it('should only return public merchants', async () => {
107
- const db = getDb();
108
- const publicDomain = createTestDomain();
109
- const privateDomain = createTestDomain();
110
-
111
- await db.insert(merchants).values([
112
- { domain: publicDomain, displayName: 'Public Store', isPublic: true },
113
- { domain: privateDomain, displayName: 'Private Store', isPublic: false },
114
- ]);
115
-
116
- const result = await listMerchants({});
117
- const domains = result.merchants.map((m) => m.domain);
118
-
119
- expect(domains).toContain(publicDomain);
120
- expect(domains).not.toContain(privateDomain);
121
- });
122
-
123
- it('should sort by score descending by default', async () => {
124
- const db = getDb();
125
- const domain1 = createTestDomain();
126
- const domain2 = createTestDomain();
127
- const domain3 = createTestDomain();
128
-
129
- await db.insert(merchants).values([
130
- { domain: domain1, displayName: 'Low Score', ucpScore: 30, isPublic: true },
131
- { domain: domain2, displayName: 'High Score', ucpScore: 90, isPublic: true },
132
- { domain: domain3, displayName: 'Mid Score', ucpScore: 60, isPublic: true },
133
- ]);
134
-
135
- const result = await listMerchants({});
136
-
137
- // Find positions of our test merchants
138
- const positions = {
139
- high: result.merchants.findIndex((m) => m.domain === domain2),
140
- mid: result.merchants.findIndex((m) => m.domain === domain3),
141
- low: result.merchants.findIndex((m) => m.domain === domain1),
142
- };
143
-
144
- // High score should come before mid, mid before low
145
- if (positions.high !== -1 && positions.mid !== -1) {
146
- expect(positions.high).toBeLessThan(positions.mid);
147
- }
148
- if (positions.mid !== -1 && positions.low !== -1) {
149
- expect(positions.mid).toBeLessThan(positions.low);
150
- }
151
- });
152
- });
153
-
154
- describe('getMerchantByDomain', () => {
155
- it('should return null for non-existent domain', async () => {
156
- const result = await getMerchantByDomain('nonexistent-domain-xyz.com');
157
- expect(result).toBeNull();
158
- });
159
-
160
- it('should find merchant by domain', async () => {
161
- const db = getDb();
162
- const domain = createTestDomain();
163
-
164
- await db.insert(merchants).values({
165
- domain,
166
- displayName: 'Test Store',
167
- ucpScore: 75,
168
- });
169
-
170
- const result = await getMerchantByDomain(domain);
171
-
172
- expect(result).not.toBeNull();
173
- expect(result?.domain).toBe(domain);
174
- expect(result?.displayName).toBe('Test Store');
175
- });
176
-
177
- it('should be case-insensitive', async () => {
178
- const db = getDb();
179
- const domain = createTestDomain();
180
-
181
- await db.insert(merchants).values({
182
- domain: domain.toLowerCase(),
183
- displayName: 'Case Test Store',
184
- });
185
-
186
- const result = await getMerchantByDomain(domain.toUpperCase());
187
-
188
- expect(result).not.toBeNull();
189
- });
190
- });
191
-
192
- describe('getDirectoryStats', () => {
193
- it('should return stats structure', async () => {
194
- const stats = await getDirectoryStats();
195
-
196
- expect(stats).toHaveProperty('totalMerchants');
197
- expect(stats).toHaveProperty('verifiedMerchants');
198
- expect(stats).toHaveProperty('avgScore');
199
- expect(stats).toHaveProperty('totalCategories');
200
- expect(stats).toHaveProperty('totalCountries');
201
- expect(stats).toHaveProperty('gradeDistribution');
202
- expect(stats).toHaveProperty('topCategories');
203
- expect(stats).toHaveProperty('recentAdditions');
204
- });
205
-
206
- it('should return valid numeric values', async () => {
207
- const stats = await getDirectoryStats();
208
-
209
- expect(typeof stats.totalMerchants).toBe('number');
210
- expect(stats.totalMerchants).toBeGreaterThanOrEqual(0);
211
-
212
- // verifiedMerchants may be string from SQL aggregate, coerce to number
213
- const verifiedCount = Number(stats.verifiedMerchants);
214
- expect(verifiedCount).toBeGreaterThanOrEqual(0);
215
- expect(verifiedCount).toBeLessThanOrEqual(stats.totalMerchants);
216
-
217
- expect(typeof stats.avgScore).toBe('number');
218
- expect(stats.avgScore).toBeGreaterThanOrEqual(0);
219
- expect(stats.avgScore).toBeLessThanOrEqual(100);
220
- });
221
-
222
- it('should return arrays for distributions', async () => {
223
- const stats = await getDirectoryStats();
224
-
225
- expect(Array.isArray(stats.gradeDistribution)).toBe(true);
226
- expect(Array.isArray(stats.topCategories)).toBe(true);
227
- expect(Array.isArray(stats.recentAdditions)).toBe(true);
228
- });
229
- });
230
-
231
- describe('submitMerchant', () => {
232
- it('should reject domain without UCP profile', async () => {
233
- const result = await submitMerchant({
234
- domain: 'example.com', // example.com doesn't have UCP
235
- });
236
-
237
- expect(result.success).toBe(false);
238
- expect(result.error).toBeDefined();
239
- });
240
-
241
- it('should reject duplicate domain', async () => {
242
- const db = getDb();
243
- const domain = createTestDomain();
244
-
245
- // First, insert directly
246
- await db.insert(merchants).values({
247
- domain,
248
- displayName: 'Original Store',
249
- });
250
-
251
- // Then try to submit via service
252
- const result = await submitMerchant({ domain });
253
-
254
- expect(result.success).toBe(false);
255
- expect(result.error).toContain('already registered');
256
- });
257
-
258
- it('should clean domain before validation', async () => {
259
- // Test that https://, trailing slash, and uppercase are handled
260
- const result = await submitMerchant({
261
- domain: 'HTTPS://EXAMPLE.COM/',
262
- });
263
-
264
- // Should fail for UCP validation, not domain parsing
265
- expect(result.error).not.toContain('parse');
266
- });
267
- });
268
- });