@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.
Files changed (236) hide show
  1. package/.claude/settings.local.json +60 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/dist/cli/index.d.ts +6 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +279 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/compliance/compliance-generator.d.ts +34 -0
  9. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  10. package/dist/compliance/compliance-generator.js +320 -0
  11. package/dist/compliance/compliance-generator.js.map +1 -0
  12. package/dist/compliance/index.d.ts +8 -0
  13. package/dist/compliance/index.d.ts.map +1 -0
  14. package/dist/compliance/index.js +17 -0
  15. package/dist/compliance/index.js.map +1 -0
  16. package/dist/compliance/templates.d.ts +34 -0
  17. package/dist/compliance/templates.d.ts.map +1 -0
  18. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  19. package/dist/compliance/templates.js.map +1 -0
  20. package/dist/compliance/types.d.ts +64 -0
  21. package/dist/compliance/types.d.ts.map +1 -0
  22. package/dist/compliance/types.js +64 -0
  23. package/dist/compliance/types.js.map +1 -0
  24. package/dist/db/index.d.ts +11 -0
  25. package/dist/db/index.d.ts.map +1 -0
  26. package/dist/db/index.js +63 -0
  27. package/dist/db/index.js.map +1 -0
  28. package/dist/db/schema.d.ts +444 -0
  29. package/dist/db/schema.d.ts.map +1 -0
  30. package/dist/db/schema.js +65 -0
  31. package/dist/db/schema.js.map +1 -0
  32. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  33. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  34. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
  35. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  36. package/dist/feed-analyzer/index.d.ts +8 -0
  37. package/dist/feed-analyzer/index.d.ts.map +1 -0
  38. package/dist/feed-analyzer/index.js +19 -0
  39. package/dist/feed-analyzer/index.js.map +1 -0
  40. package/dist/feed-analyzer/types.d.ts +204 -0
  41. package/dist/feed-analyzer/types.d.ts.map +1 -0
  42. package/dist/feed-analyzer/types.js +162 -0
  43. package/dist/feed-analyzer/types.js.map +1 -0
  44. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +13 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/key-generator.d.ts +24 -0
  49. package/dist/generator/key-generator.d.ts.map +1 -0
  50. package/dist/generator/key-generator.js +144 -0
  51. package/dist/generator/key-generator.js.map +1 -0
  52. package/dist/generator/profile-builder.d.ts +15 -0
  53. package/dist/generator/profile-builder.d.ts.map +1 -0
  54. package/dist/generator/profile-builder.js +338 -0
  55. package/dist/generator/profile-builder.js.map +1 -0
  56. package/dist/hosting/artifacts-generator.d.ts +10 -0
  57. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  58. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  59. package/dist/hosting/artifacts-generator.js.map +1 -0
  60. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  61. package/dist/hosting/index.d.ts.map +1 -0
  62. package/dist/hosting/index.js +10 -0
  63. package/dist/hosting/index.js.map +1 -0
  64. package/dist/index.d.ts +18 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +78 -0
  67. package/dist/index.js.map +1 -0
  68. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  69. package/dist/security/index.d.ts.map +1 -0
  70. package/dist/security/index.js +12 -0
  71. package/dist/security/index.js.map +1 -0
  72. package/dist/security/security-scanner.d.ts +10 -0
  73. package/dist/security/security-scanner.d.ts.map +1 -0
  74. package/dist/security/security-scanner.js +541 -0
  75. package/dist/security/security-scanner.js.map +1 -0
  76. package/dist/security/types.d.ts +48 -0
  77. package/dist/security/types.d.ts.map +1 -0
  78. package/dist/security/types.js +21 -0
  79. package/dist/security/types.js.map +1 -0
  80. package/dist/services/directory.d.ts +104 -0
  81. package/dist/services/directory.d.ts.map +1 -0
  82. package/dist/services/directory.js +333 -0
  83. package/dist/services/directory.js.map +1 -0
  84. package/dist/simulator/agent-simulator.d.ts +69 -0
  85. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  86. package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
  87. package/dist/simulator/agent-simulator.js.map +1 -0
  88. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  89. package/dist/simulator/index.d.ts.map +1 -0
  90. package/dist/simulator/index.js +23 -0
  91. package/dist/simulator/index.js.map +1 -0
  92. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
  93. package/dist/simulator/types.d.ts.map +1 -0
  94. package/dist/simulator/types.js +18 -0
  95. package/dist/simulator/types.js.map +1 -0
  96. package/dist/types/generator.d.ts +106 -0
  97. package/dist/types/generator.d.ts.map +1 -0
  98. package/dist/types/generator.js +6 -0
  99. package/dist/types/generator.js.map +1 -0
  100. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +23 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/ucp-profile.d.ts +103 -0
  105. package/dist/types/ucp-profile.d.ts.map +1 -0
  106. package/dist/types/ucp-profile.js +45 -0
  107. package/dist/types/ucp-profile.js.map +1 -0
  108. package/dist/types/validation.d.ts +68 -0
  109. package/dist/types/validation.d.ts.map +1 -0
  110. package/dist/types/validation.js +32 -0
  111. package/dist/types/validation.js.map +1 -0
  112. package/dist/validator/index.d.ts +26 -0
  113. package/dist/validator/index.d.ts.map +1 -0
  114. package/dist/validator/index.js +161 -0
  115. package/dist/validator/index.js.map +1 -0
  116. package/dist/validator/network-validator.d.ts +28 -0
  117. package/dist/validator/network-validator.d.ts.map +1 -0
  118. package/dist/validator/network-validator.js +319 -0
  119. package/dist/validator/network-validator.js.map +1 -0
  120. package/dist/validator/rules-validator.d.ts +11 -0
  121. package/dist/validator/rules-validator.d.ts.map +1 -0
  122. package/dist/validator/rules-validator.js +257 -0
  123. package/dist/validator/rules-validator.js.map +1 -0
  124. package/dist/validator/sdk-validator.d.ts +58 -0
  125. package/dist/validator/sdk-validator.d.ts.map +1 -0
  126. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  127. package/dist/validator/sdk-validator.js.map +1 -0
  128. package/dist/validator/structural-validator.d.ts +11 -0
  129. package/dist/validator/structural-validator.d.ts.map +1 -0
  130. package/dist/validator/structural-validator.js +415 -0
  131. package/dist/validator/structural-validator.js.map +1 -0
  132. package/package.json +1 -1
  133. package/publish-output.txt +0 -0
  134. package/CLAUDE.md +0 -109
  135. package/api/analyze-feed.js +0 -140
  136. package/api/badge.js +0 -185
  137. package/api/benchmark.js +0 -177
  138. package/api/directory-stats.ts +0 -29
  139. package/api/directory.ts +0 -73
  140. package/api/generate-compliance.js +0 -143
  141. package/api/generate-schema.js +0 -457
  142. package/api/generate.js +0 -132
  143. package/api/security-scan.js +0 -133
  144. package/api/simulate.js +0 -187
  145. package/api/tsconfig.json +0 -10
  146. package/api/validate.js +0 -1351
  147. package/apify-actor/.actor/actor.json +0 -68
  148. package/apify-actor/.actor/input_schema.json +0 -32
  149. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  150. package/apify-actor/Dockerfile +0 -8
  151. package/apify-actor/README.md +0 -166
  152. package/apify-actor/main.ts +0 -111
  153. package/apify-actor/package.json +0 -17
  154. package/apify-actor/src/main.js +0 -199
  155. package/docs/BRAND-IDENTITY.md +0 -238
  156. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  157. package/drizzle/0000_black_king_cobra.sql +0 -39
  158. package/drizzle/meta/0000_snapshot.json +0 -309
  159. package/drizzle/meta/_journal.json +0 -13
  160. package/drizzle.config.ts +0 -10
  161. package/public/.well-known/ucp +0 -25
  162. package/public/android-chrome-192x192.png +0 -0
  163. package/public/android-chrome-512x512.png +0 -0
  164. package/public/apple-touch-icon.png +0 -0
  165. package/public/brand.css +0 -321
  166. package/public/directory.html +0 -701
  167. package/public/favicon-16x16.png +0 -0
  168. package/public/favicon-32x32.png +0 -0
  169. package/public/favicon.ico +0 -0
  170. package/public/guides/bigcommerce.html +0 -743
  171. package/public/guides/fastucp.html +0 -838
  172. package/public/guides/magento.html +0 -779
  173. package/public/guides/shopify.html +0 -726
  174. package/public/guides/squarespace.html +0 -749
  175. package/public/guides/wix.html +0 -747
  176. package/public/guides/woocommerce.html +0 -733
  177. package/public/index.html +0 -3835
  178. package/public/learn.html +0 -396
  179. package/public/logo.jpeg +0 -0
  180. package/public/og-image-icon.png +0 -0
  181. package/public/og-image.png +0 -0
  182. package/public/robots.txt +0 -6
  183. package/public/site.webmanifest +0 -31
  184. package/public/sitemap.xml +0 -69
  185. package/public/social/linkedin-banner-1128x191.png +0 -0
  186. package/public/social/temp.PNG +0 -0
  187. package/public/social/x-header-1500x500.png +0 -0
  188. package/public/verify.html +0 -410
  189. package/scripts/generate-favicons.js +0 -44
  190. package/scripts/generate-ico.js +0 -23
  191. package/scripts/generate-og-image.js +0 -45
  192. package/scripts/reset-db.ts +0 -77
  193. package/scripts/seed-db.ts +0 -71
  194. package/scripts/setup-benchmark-db.js +0 -70
  195. package/src/api/server.ts +0 -266
  196. package/src/cli/index.ts +0 -302
  197. package/src/compliance/compliance-generator.ts +0 -452
  198. package/src/compliance/index.ts +0 -28
  199. package/src/compliance/types.ts +0 -170
  200. package/src/db/index.ts +0 -28
  201. package/src/db/schema.ts +0 -84
  202. package/src/feed-analyzer/index.ts +0 -34
  203. package/src/feed-analyzer/types.ts +0 -354
  204. package/src/generator/key-generator.ts +0 -124
  205. package/src/generator/profile-builder.ts +0 -402
  206. package/src/index.ts +0 -105
  207. package/src/security/security-scanner.ts +0 -604
  208. package/src/security/types.ts +0 -55
  209. package/src/services/directory.ts +0 -434
  210. package/src/types/generator.ts +0 -140
  211. package/src/types/ucp-profile.ts +0 -140
  212. package/src/types/validation.ts +0 -89
  213. package/src/validator/index.ts +0 -194
  214. package/src/validator/network-validator.ts +0 -417
  215. package/src/validator/rules-validator.ts +0 -297
  216. package/src/validator/structural-validator.ts +0 -476
  217. package/tests/fixtures/non-compliant-profile.json +0 -25
  218. package/tests/fixtures/official-sample-profile.json +0 -75
  219. package/tests/integration/benchmark.test.ts +0 -207
  220. package/tests/integration/database.test.ts +0 -163
  221. package/tests/integration/directory-api.test.ts +0 -268
  222. package/tests/integration/simulate-api.test.ts +0 -230
  223. package/tests/integration/validate-api.test.ts +0 -269
  224. package/tests/setup.ts +0 -15
  225. package/tests/unit/agent-simulator.test.ts +0 -575
  226. package/tests/unit/compliance-generator.test.ts +0 -374
  227. package/tests/unit/directory-service.test.ts +0 -272
  228. package/tests/unit/feed-analyzer.test.ts +0 -517
  229. package/tests/unit/lint-suggestions.test.ts +0 -423
  230. package/tests/unit/official-samples.test.ts +0 -211
  231. package/tests/unit/pdf-report.test.ts +0 -390
  232. package/tests/unit/sdk-validator.test.ts +0 -531
  233. package/tests/unit/security-scanner.test.ts +0 -410
  234. package/tests/unit/validation.test.ts +0 -390
  235. package/vercel.json +0 -34
  236. package/vitest.config.ts +0 -22
@@ -1,410 +0,0 @@
1
- /**
2
- * Tests for Security Posture Scanner
3
- */
4
-
5
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
- import { scanEndpointSecurity } from '../../src/security/security-scanner.js';
7
- import { SecurityCheckIds } from '../../src/security/types.js';
8
-
9
- // Mock fetch globally
10
- const mockFetch = vi.fn();
11
- global.fetch = mockFetch;
12
-
13
- describe('Security Scanner', () => {
14
- beforeEach(() => {
15
- mockFetch.mockReset();
16
- });
17
-
18
- afterEach(() => {
19
- vi.restoreAllMocks();
20
- });
21
-
22
- describe('scanEndpointSecurity', () => {
23
- it('should return a complete security scan result', async () => {
24
- // Mock HTTP check (redirect to HTTPS)
25
- mockFetch.mockResolvedValueOnce({
26
- ok: false,
27
- status: 301,
28
- headers: new Headers({ 'location': 'https://example.com/.well-known/ucp' }),
29
- });
30
-
31
- // Mock HTTPS endpoint check
32
- mockFetch.mockResolvedValueOnce({
33
- ok: true,
34
- status: 200,
35
- headers: new Headers({
36
- 'content-type': 'application/json',
37
- 'strict-transport-security': 'max-age=31536000',
38
- 'x-content-type-options': 'nosniff',
39
- 'access-control-allow-origin': '*',
40
- }),
41
- text: async () => JSON.stringify({ ucp: { version: '2026-01-11' } }),
42
- });
43
-
44
- const result = await scanEndpointSecurity('example.com');
45
-
46
- expect(result.domain).toBe('example.com');
47
- expect(result.endpoint).toBe('https://example.com/.well-known/ucp');
48
- expect(result.scanned_at).toBeTruthy();
49
- expect(result.score).toBeGreaterThanOrEqual(0);
50
- expect(result.score).toBeLessThanOrEqual(100);
51
- expect(result.grade).toMatch(/^[A-F]$/);
52
- expect(result.checks).toBeInstanceOf(Array);
53
- expect(result.checks.length).toBeGreaterThan(0);
54
- expect(result.summary).toHaveProperty('passed');
55
- expect(result.summary).toHaveProperty('failed');
56
- expect(result.summary).toHaveProperty('warnings');
57
- expect(result.summary).toHaveProperty('skipped');
58
- });
59
-
60
- it('should detect HTTPS enforcement via redirect', async () => {
61
- // HTTP redirects to HTTPS
62
- mockFetch.mockResolvedValueOnce({
63
- ok: false,
64
- status: 301,
65
- headers: new Headers({ 'location': 'https://example.com/.well-known/ucp' }),
66
- });
67
-
68
- // HTTPS endpoint
69
- mockFetch.mockResolvedValueOnce({
70
- ok: true,
71
- status: 200,
72
- headers: new Headers({ 'content-type': 'application/json' }),
73
- text: async () => '{}',
74
- });
75
-
76
- const result = await scanEndpointSecurity('example.com');
77
-
78
- const httpsCheck = result.checks.find(c => c.id === SecurityCheckIds.HTTPS_REQUIRED);
79
- expect(httpsCheck).toBeDefined();
80
- expect(httpsCheck?.status).toBe('pass');
81
- });
82
-
83
- it('should fail HTTPS check if HTTP serves content', async () => {
84
- // HTTP serves content without redirect
85
- mockFetch.mockResolvedValueOnce({
86
- ok: true,
87
- status: 200,
88
- headers: new Headers({ 'content-type': 'application/json' }),
89
- text: async () => '{}',
90
- });
91
-
92
- // HTTPS endpoint
93
- mockFetch.mockResolvedValueOnce({
94
- ok: true,
95
- status: 200,
96
- headers: new Headers({ 'content-type': 'application/json' }),
97
- text: async () => '{}',
98
- });
99
-
100
- const result = await scanEndpointSecurity('example.com');
101
-
102
- const httpsCheck = result.checks.find(c => c.id === SecurityCheckIds.HTTPS_REQUIRED);
103
- expect(httpsCheck).toBeDefined();
104
- expect(httpsCheck?.status).toBe('fail');
105
- });
106
-
107
- it('should detect private IP addresses', async () => {
108
- // HTTP check fails
109
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
110
-
111
- const result = await scanEndpointSecurity('192.168.1.1');
112
-
113
- const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
114
- expect(privateIpCheck).toBeDefined();
115
- expect(privateIpCheck?.status).toBe('fail');
116
- expect(privateIpCheck?.severity).toBe('high');
117
- });
118
-
119
- it('should pass private IP check for public domains', async () => {
120
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
121
-
122
- const result = await scanEndpointSecurity('example.com');
123
-
124
- const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
125
- expect(privateIpCheck).toBeDefined();
126
- expect(privateIpCheck?.status).toBe('pass');
127
- });
128
-
129
- it('should check security headers', async () => {
130
- // HTTP fails
131
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
132
-
133
- // HTTPS with good security headers
134
- mockFetch.mockResolvedValueOnce({
135
- ok: true,
136
- status: 200,
137
- headers: new Headers({
138
- 'content-type': 'application/json',
139
- 'strict-transport-security': 'max-age=31536000; includeSubDomains',
140
- 'x-content-type-options': 'nosniff',
141
- 'x-frame-options': 'DENY',
142
- }),
143
- text: async () => '{}',
144
- });
145
-
146
- const result = await scanEndpointSecurity('example.com');
147
-
148
- const headersCheck = result.checks.find(c => c.id === SecurityCheckIds.SECURITY_HEADERS);
149
- expect(headersCheck).toBeDefined();
150
- expect(headersCheck?.status).toBe('pass');
151
- expect(headersCheck?.details).toContain('HSTS');
152
- });
153
-
154
- it('should warn on missing security headers', async () => {
155
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
156
-
157
- // HTTPS with no security headers
158
- mockFetch.mockResolvedValueOnce({
159
- ok: true,
160
- status: 200,
161
- headers: new Headers({
162
- 'content-type': 'application/json',
163
- }),
164
- text: async () => '{}',
165
- });
166
-
167
- const result = await scanEndpointSecurity('example.com');
168
-
169
- const headersCheck = result.checks.find(c => c.id === SecurityCheckIds.SECURITY_HEADERS);
170
- expect(headersCheck).toBeDefined();
171
- expect(headersCheck?.status).toBe('fail');
172
- });
173
-
174
- it('should detect CORS configuration', async () => {
175
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
176
-
177
- mockFetch.mockResolvedValueOnce({
178
- ok: true,
179
- status: 200,
180
- headers: new Headers({
181
- 'content-type': 'application/json',
182
- 'access-control-allow-origin': 'https://trusted.com',
183
- 'access-control-allow-methods': 'GET, POST',
184
- }),
185
- text: async () => '{}',
186
- });
187
-
188
- const result = await scanEndpointSecurity('example.com');
189
-
190
- const corsCheck = result.checks.find(c => c.id === SecurityCheckIds.CORS_CONFIG);
191
- expect(corsCheck).toBeDefined();
192
- expect(corsCheck?.status).toBe('pass');
193
- });
194
-
195
- it('should warn on wildcard CORS', async () => {
196
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
197
-
198
- mockFetch.mockResolvedValueOnce({
199
- ok: true,
200
- status: 200,
201
- headers: new Headers({
202
- 'content-type': 'application/json',
203
- 'access-control-allow-origin': '*',
204
- }),
205
- text: async () => '{}',
206
- });
207
-
208
- const result = await scanEndpointSecurity('example.com');
209
-
210
- const corsCheck = result.checks.find(c => c.id === SecurityCheckIds.CORS_CONFIG);
211
- expect(corsCheck).toBeDefined();
212
- expect(corsCheck?.status).toBe('warn');
213
- expect(corsCheck?.details).toContain('all origins');
214
- });
215
-
216
- it('should detect rate limiting headers', async () => {
217
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
218
-
219
- mockFetch.mockResolvedValueOnce({
220
- ok: true,
221
- status: 200,
222
- headers: new Headers({
223
- 'content-type': 'application/json',
224
- 'x-ratelimit-limit': '100',
225
- 'x-ratelimit-remaining': '99',
226
- }),
227
- text: async () => '{}',
228
- });
229
-
230
- const result = await scanEndpointSecurity('example.com');
231
-
232
- const rateLimitCheck = result.checks.find(c => c.id === SecurityCheckIds.RATE_LIMITING);
233
- expect(rateLimitCheck).toBeDefined();
234
- expect(rateLimitCheck?.status).toBe('pass');
235
- });
236
-
237
- it('should warn when no rate limiting detected', async () => {
238
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
239
-
240
- mockFetch.mockResolvedValueOnce({
241
- ok: true,
242
- status: 200,
243
- headers: new Headers({
244
- 'content-type': 'application/json',
245
- }),
246
- text: async () => '{}',
247
- });
248
-
249
- const result = await scanEndpointSecurity('example.com');
250
-
251
- const rateLimitCheck = result.checks.find(c => c.id === SecurityCheckIds.RATE_LIMITING);
252
- expect(rateLimitCheck).toBeDefined();
253
- expect(rateLimitCheck?.status).toBe('warn');
254
- });
255
-
256
- it('should check content-type header', async () => {
257
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
258
-
259
- mockFetch.mockResolvedValueOnce({
260
- ok: true,
261
- status: 200,
262
- headers: new Headers({
263
- 'content-type': 'application/json; charset=utf-8',
264
- }),
265
- text: async () => '{}',
266
- });
267
-
268
- const result = await scanEndpointSecurity('example.com');
269
-
270
- const contentTypeCheck = result.checks.find(c => c.id === SecurityCheckIds.CONTENT_TYPE);
271
- expect(contentTypeCheck).toBeDefined();
272
- expect(contentTypeCheck?.status).toBe('pass');
273
- });
274
-
275
- it('should detect potential error disclosure', async () => {
276
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
277
-
278
- mockFetch.mockResolvedValueOnce({
279
- ok: true,
280
- status: 200,
281
- headers: new Headers({
282
- 'content-type': 'application/json',
283
- }),
284
- text: async () => JSON.stringify({
285
- error: 'Error at /home/user/app/server.js:42:15',
286
- stack: 'at Object.<anonymous> (/home/user/app/server.js:42:15)',
287
- }),
288
- });
289
-
290
- const result = await scanEndpointSecurity('example.com');
291
-
292
- const errorCheck = result.checks.find(c => c.id === SecurityCheckIds.ERROR_DISCLOSURE);
293
- expect(errorCheck).toBeDefined();
294
- expect(errorCheck?.status).toBe('warn');
295
- });
296
-
297
- it('should handle unreachable endpoints gracefully', async () => {
298
- // Both HTTP and HTTPS fail
299
- mockFetch.mockRejectedValue(new Error('Network error'));
300
-
301
- const result = await scanEndpointSecurity('unreachable.example.com');
302
-
303
- expect(result.domain).toBe('unreachable.example.com');
304
- expect(result.checks.length).toBeGreaterThan(0);
305
- // Should have skipped checks for unreachable endpoint
306
- expect(result.summary.skipped).toBeGreaterThan(0);
307
- });
308
-
309
- it('should calculate appropriate grades', async () => {
310
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
311
-
312
- // Good security setup
313
- mockFetch.mockResolvedValueOnce({
314
- ok: true,
315
- status: 200,
316
- headers: new Headers({
317
- 'content-type': 'application/json',
318
- 'strict-transport-security': 'max-age=31536000',
319
- 'x-content-type-options': 'nosniff',
320
- 'access-control-allow-origin': 'https://trusted.com',
321
- 'x-ratelimit-limit': '100',
322
- }),
323
- text: async () => '{}',
324
- });
325
-
326
- const result = await scanEndpointSecurity('secure.example.com');
327
-
328
- expect(result.score).toBeGreaterThanOrEqual(60);
329
- expect(['A', 'B', 'C']).toContain(result.grade);
330
- });
331
-
332
- it('should normalize domain input', async () => {
333
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
334
- mockFetch.mockResolvedValueOnce({
335
- ok: true,
336
- status: 200,
337
- headers: new Headers({ 'content-type': 'application/json' }),
338
- text: async () => '{}',
339
- });
340
-
341
- const result = await scanEndpointSecurity('https://example.com/path/to/something');
342
-
343
- expect(result.domain).toBe('example.com');
344
- expect(result.endpoint).toBe('https://example.com/.well-known/ucp');
345
- });
346
-
347
- it('should detect localhost as private IP', async () => {
348
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
349
-
350
- const result = await scanEndpointSecurity('localhost');
351
-
352
- const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
353
- expect(privateIpCheck?.status).toBe('fail');
354
- });
355
-
356
- it('should detect various private IP ranges', async () => {
357
- const privateIps = ['10.0.0.1', '172.16.0.1', '192.168.0.1', '127.0.0.1'];
358
-
359
- for (const ip of privateIps) {
360
- mockFetch.mockReset();
361
- mockFetch.mockRejectedValue(new Error('connection refused'));
362
-
363
- const result = await scanEndpointSecurity(ip);
364
-
365
- const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
366
- expect(privateIpCheck?.status).toBe('fail');
367
- }
368
- });
369
- });
370
-
371
- describe('Score Calculation', () => {
372
- it('should give higher scores for passing critical checks', async () => {
373
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
374
-
375
- // All good headers
376
- mockFetch.mockResolvedValueOnce({
377
- ok: true,
378
- status: 200,
379
- headers: new Headers({
380
- 'content-type': 'application/json',
381
- 'strict-transport-security': 'max-age=31536000',
382
- 'x-content-type-options': 'nosniff',
383
- 'x-frame-options': 'DENY',
384
- 'access-control-allow-origin': 'https://trusted.com',
385
- 'access-control-allow-methods': 'GET, POST',
386
- 'x-ratelimit-limit': '100',
387
- 'cache-control': 'no-store',
388
- }),
389
- text: async () => '{}',
390
- });
391
-
392
- const goodResult = await scanEndpointSecurity('secure.example.com');
393
-
394
- mockFetch.mockReset();
395
- mockFetch.mockRejectedValueOnce(new Error('connection refused'));
396
-
397
- // Minimal headers
398
- mockFetch.mockResolvedValueOnce({
399
- ok: true,
400
- status: 200,
401
- headers: new Headers({}),
402
- text: async () => '{}',
403
- });
404
-
405
- const badResult = await scanEndpointSecurity('insecure.example.com');
406
-
407
- expect(goodResult.score).toBeGreaterThan(badResult.score);
408
- });
409
- });
410
- });