@kya-os/mcp-i 0.1.0-alpha.3.9 → 1.2.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 (328) hide show
  1. package/README.md +350 -213
  2. package/dist/149.js +1 -0
  3. package/dist/189.js +1 -0
  4. package/dist/261.js +1 -0
  5. package/dist/28.js +1 -0
  6. package/dist/295.js +1 -0
  7. package/dist/460.js +1 -0
  8. package/dist/570.js +1 -0
  9. package/dist/634.js +1 -0
  10. package/dist/647.js +1 -0
  11. package/dist/67.js +1 -0
  12. package/dist/739.js +1 -0
  13. package/dist/742.js +1 -0
  14. package/dist/904.js +1 -0
  15. package/dist/938.js +1 -0
  16. package/dist/auth/api-key.d.ts +16 -0
  17. package/dist/auth/api-key.js +82 -0
  18. package/dist/auth/jwt.d.ts +43 -0
  19. package/dist/auth/jwt.js +51 -0
  20. package/dist/auth/oauth/factory.d.ts +12 -0
  21. package/dist/auth/oauth/factory.js +36 -0
  22. package/dist/auth/oauth/index.d.ts +5 -0
  23. package/dist/auth/oauth/index.js +27 -0
  24. package/dist/auth/oauth/providers/proxy-provider.d.ts +13 -0
  25. package/dist/auth/oauth/providers/proxy-provider.js +159 -0
  26. package/dist/auth/oauth/router.d.ts +4 -0
  27. package/dist/auth/oauth/router.js +294 -0
  28. package/dist/auth/oauth/storage/memory-storage.d.ts +12 -0
  29. package/dist/auth/oauth/storage/memory-storage.js +40 -0
  30. package/dist/auth/oauth/types.d.ts +112 -0
  31. package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.d.ts +4 -0
  32. package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.js +176 -0
  33. package/dist/cache/__tests__/concurrency.test.d.ts +5 -0
  34. package/dist/cache/__tests__/concurrency.test.js +300 -0
  35. package/dist/cache/__tests__/dynamodb-nonce-cache.test.d.ts +4 -0
  36. package/dist/cache/__tests__/dynamodb-nonce-cache.test.js +176 -0
  37. package/dist/cache/__tests__/memory-nonce-cache.test.d.ts +4 -0
  38. package/dist/cache/__tests__/memory-nonce-cache.test.js +132 -0
  39. package/dist/cache/__tests__/nonce-cache-factory-simple.test.d.ts +4 -0
  40. package/dist/cache/__tests__/nonce-cache-factory-simple.test.js +133 -0
  41. package/dist/cache/__tests__/nonce-cache-factory.test.d.ts +4 -0
  42. package/dist/cache/__tests__/nonce-cache-factory.test.js +252 -0
  43. package/dist/cache/__tests__/redis-nonce-cache.test.d.ts +4 -0
  44. package/dist/cache/__tests__/redis-nonce-cache.test.js +95 -0
  45. package/dist/cache/cloudflare-kv-nonce-cache.d.ts +14 -0
  46. package/dist/cache/cloudflare-kv-nonce-cache.js +93 -0
  47. package/dist/cache/dynamodb-nonce-cache.d.ts +15 -0
  48. package/dist/cache/dynamodb-nonce-cache.js +92 -0
  49. package/dist/cache/index.d.ts +16 -0
  50. package/dist/cache/index.js +32 -0
  51. package/dist/cache/memory-nonce-cache.d.ts +44 -0
  52. package/dist/cache/memory-nonce-cache.js +105 -0
  53. package/dist/cache/nonce-cache-factory.d.ts +20 -0
  54. package/dist/cache/nonce-cache-factory.js +208 -0
  55. package/dist/cache/redis-nonce-cache.d.ts +14 -0
  56. package/dist/cache/redis-nonce-cache.js +53 -0
  57. package/dist/compiler/compiler-context.d.ts +23 -0
  58. package/dist/compiler/compiler-context.js +24 -0
  59. package/dist/compiler/config/constants.d.ts +41 -0
  60. package/dist/compiler/config/constants.js +45 -0
  61. package/dist/compiler/config/index.d.ts +252 -0
  62. package/dist/compiler/config/index.js +15 -0
  63. package/dist/compiler/config/injection.d.ts +26 -0
  64. package/dist/compiler/config/injection.js +58 -0
  65. package/dist/compiler/config/schemas/experimental/index.d.ts +91 -0
  66. package/dist/compiler/config/schemas/experimental/index.js +16 -0
  67. package/dist/compiler/config/schemas/experimental/oauth.d.ts +74 -0
  68. package/dist/compiler/config/schemas/experimental/oauth.js +25 -0
  69. package/dist/compiler/config/schemas/index.d.ts +6 -0
  70. package/dist/compiler/config/schemas/index.js +17 -0
  71. package/dist/compiler/config/schemas/paths.d.ts +9 -0
  72. package/dist/compiler/config/schemas/paths.js +12 -0
  73. package/dist/compiler/config/schemas/transport/http.d.ts +82 -0
  74. package/dist/compiler/config/schemas/transport/http.js +33 -0
  75. package/dist/compiler/config/schemas/transport/stdio.d.ts +9 -0
  76. package/dist/compiler/config/schemas/transport/stdio.js +15 -0
  77. package/dist/compiler/config/schemas/webpack.d.ts +3 -0
  78. package/dist/compiler/config/schemas/webpack.js +15 -0
  79. package/dist/compiler/config/types.d.ts +1 -0
  80. package/dist/compiler/config/types.js +2 -0
  81. package/dist/compiler/config/utils.d.ts +20 -0
  82. package/dist/compiler/config/utils.js +36 -0
  83. package/dist/compiler/generate-env-code.d.ts +1 -0
  84. package/dist/compiler/generate-env-code.js +8 -0
  85. package/dist/compiler/generate-import-code.d.ts +1 -0
  86. package/dist/compiler/generate-import-code.js +24 -0
  87. package/dist/compiler/get-webpack-config/get-entries.d.ts +3 -0
  88. package/dist/compiler/get-webpack-config/get-entries.js +29 -0
  89. package/dist/compiler/get-webpack-config/get-externals.d.ts +7 -0
  90. package/dist/compiler/get-webpack-config/get-externals.js +88 -0
  91. package/dist/compiler/get-webpack-config/get-injected-variables.d.ts +8 -0
  92. package/dist/compiler/get-webpack-config/get-injected-variables.js +25 -0
  93. package/dist/compiler/get-webpack-config/index.d.ts +4 -0
  94. package/dist/compiler/get-webpack-config/index.js +101 -0
  95. package/dist/compiler/get-webpack-config/plugins.d.ts +8 -0
  96. package/dist/compiler/get-webpack-config/plugins.js +132 -0
  97. package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.d.ts +9 -0
  98. package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.js +40 -0
  99. package/dist/compiler/index.d.ts +6 -0
  100. package/dist/compiler/index.js +194 -0
  101. package/dist/compiler/on-first-build.d.ts +3 -0
  102. package/dist/compiler/on-first-build.js +58 -0
  103. package/dist/compiler/parse-xmcp-config.d.ts +9 -0
  104. package/dist/compiler/parse-xmcp-config.js +155 -0
  105. package/dist/compiler/start-http-server.d.ts +1 -0
  106. package/dist/compiler/start-http-server.js +34 -0
  107. package/dist/index.d.ts +12 -0
  108. package/dist/index.js +38 -0
  109. package/dist/index.js.LICENSE.txt +49 -0
  110. package/dist/runtime/__tests__/audit.test.d.ts +4 -0
  111. package/dist/runtime/__tests__/audit.test.js +328 -0
  112. package/dist/runtime/__tests__/identity.test.d.ts +4 -0
  113. package/dist/runtime/__tests__/identity.test.js +164 -0
  114. package/dist/runtime/__tests__/mcpi-runtime.test.d.ts +4 -0
  115. package/dist/runtime/__tests__/mcpi-runtime.test.js +372 -0
  116. package/dist/runtime/__tests__/proof.test.d.ts +4 -0
  117. package/dist/runtime/__tests__/proof.test.js +302 -0
  118. package/dist/runtime/__tests__/session.test.d.ts +4 -0
  119. package/dist/runtime/__tests__/session.test.js +254 -0
  120. package/dist/runtime/__tests__/well-known.test.d.ts +4 -0
  121. package/dist/runtime/__tests__/well-known.test.js +312 -0
  122. package/dist/runtime/adapter-express.js +2 -0
  123. package/dist/runtime/adapter-express.js.LICENSE.txt +252 -0
  124. package/dist/runtime/adapter-nextjs.js +2 -0
  125. package/dist/runtime/adapter-nextjs.js.LICENSE.txt +53 -0
  126. package/dist/runtime/adapters/express/index.d.ts +2 -0
  127. package/dist/runtime/adapters/express/index.js +48 -0
  128. package/dist/runtime/adapters/nextjs/index.d.ts +8 -0
  129. package/dist/runtime/adapters/nextjs/index.js +18 -0
  130. package/dist/runtime/audit.d.ts +93 -0
  131. package/dist/runtime/audit.js +212 -0
  132. package/dist/runtime/debug.d.ts +118 -0
  133. package/dist/runtime/debug.js +612 -0
  134. package/dist/runtime/delegation-hooks.d.ts +85 -0
  135. package/dist/runtime/delegation-hooks.js +116 -0
  136. package/dist/runtime/demo.d.ts +71 -0
  137. package/dist/runtime/demo.js +135 -0
  138. package/dist/runtime/headers.d.ts +1 -0
  139. package/dist/runtime/headers.js +9 -0
  140. package/dist/runtime/http.js +2 -0
  141. package/dist/runtime/http.js.LICENSE.txt +252 -0
  142. package/dist/runtime/identity.d.ts +105 -0
  143. package/dist/runtime/identity.js +232 -0
  144. package/dist/runtime/index.d.ts +16 -0
  145. package/dist/runtime/index.js +56 -0
  146. package/dist/runtime/mcpi-runtime.d.ts +164 -0
  147. package/dist/runtime/mcpi-runtime.js +352 -0
  148. package/dist/runtime/proof.d.ts +87 -0
  149. package/dist/runtime/proof.js +223 -0
  150. package/dist/runtime/session.d.ts +88 -0
  151. package/dist/runtime/session.js +216 -0
  152. package/dist/runtime/stdio.js +2 -0
  153. package/dist/runtime/stdio.js.LICENSE.txt +1 -0
  154. package/dist/runtime/templates/home.d.ts +2 -0
  155. package/dist/runtime/templates/home.js +50 -0
  156. package/dist/runtime/transports/http/base-streamable-http.d.ts +25 -0
  157. package/dist/runtime/transports/http/base-streamable-http.js +16 -0
  158. package/dist/runtime/transports/http/http-context.d.ts +9 -0
  159. package/dist/runtime/transports/http/http-context.js +8 -0
  160. package/dist/runtime/transports/http/index.js +55 -0
  161. package/dist/runtime/transports/http/setup-cors.d.ts +4 -0
  162. package/dist/runtime/transports/http/setup-cors.js +24 -0
  163. package/dist/runtime/transports/http/stateless-streamable-http.d.ts +39 -0
  164. package/dist/runtime/transports/http/stateless-streamable-http.js +331 -0
  165. package/dist/runtime/transports/stdio/index.d.ts +1 -0
  166. package/dist/runtime/transports/stdio/index.js +51 -0
  167. package/dist/runtime/utils/server.d.ts +42 -0
  168. package/dist/runtime/utils/server.js +39 -0
  169. package/dist/runtime/utils/tools.d.ts +8 -0
  170. package/dist/runtime/utils/tools.js +115 -0
  171. package/dist/runtime/verifier-middleware.d.ts +76 -0
  172. package/dist/runtime/verifier-middleware.js +322 -0
  173. package/dist/runtime/well-known.d.ts +151 -0
  174. package/dist/runtime/well-known.js +258 -0
  175. package/dist/storage/config.d.ts +28 -0
  176. package/dist/storage/config.js +79 -0
  177. package/dist/storage/delegation.d.ts +59 -0
  178. package/dist/storage/delegation.js +130 -0
  179. package/dist/storage/merkle-verifier.d.ts +84 -0
  180. package/dist/storage/merkle-verifier.js +261 -0
  181. package/dist/test/__tests__/nonce-cache-integration.test.d.ts +1 -0
  182. package/dist/test/__tests__/nonce-cache-integration.test.js +116 -0
  183. package/dist/test/__tests__/nonce-cache.test.d.ts +1 -0
  184. package/dist/test/__tests__/nonce-cache.test.js +122 -0
  185. package/dist/test/__tests__/runtime-integration.test.d.ts +4 -0
  186. package/dist/test/__tests__/runtime-integration.test.js +192 -0
  187. package/dist/test/__tests__/test-infrastructure.test.d.ts +4 -0
  188. package/dist/test/__tests__/test-infrastructure.test.js +178 -0
  189. package/dist/test/deterministic-keys.d.ts +31 -0
  190. package/dist/test/deterministic-keys.js +108 -0
  191. package/dist/test/examples/test-usage-example.d.ts +140 -0
  192. package/dist/test/examples/test-usage-example.js +175 -0
  193. package/dist/test/index.d.ts +11 -0
  194. package/dist/test/index.js +27 -0
  195. package/dist/test/local-verification.d.ts +28 -0
  196. package/dist/test/local-verification.js +342 -0
  197. package/dist/test/mock-identity-provider.d.ts +96 -0
  198. package/dist/test/mock-identity-provider.js +243 -0
  199. package/dist/test/runtime-integration.d.ts +63 -0
  200. package/dist/test/runtime-integration.js +140 -0
  201. package/dist/test/test-environment.d.ts +26 -0
  202. package/dist/test/test-environment.js +50 -0
  203. package/dist/types/declarations.d.ts +1 -0
  204. package/dist/types/declarations.js +6 -0
  205. package/dist/types/middleware.d.ts +2 -0
  206. package/dist/types/middleware.js +2 -0
  207. package/dist/types/tool.d.ts +80 -0
  208. package/dist/types/tool.js +2 -0
  209. package/dist/utils/cli-icons.d.ts +3 -0
  210. package/dist/utils/cli-icons.js +7 -0
  211. package/dist/utils/constants.d.ts +6 -0
  212. package/dist/utils/constants.js +13 -0
  213. package/dist/utils/context.d.ts +33 -0
  214. package/dist/utils/context.js +58 -0
  215. package/dist/utils/file-watcher.d.ts +19 -0
  216. package/dist/utils/file-watcher.js +49 -0
  217. package/dist/utils/fs-utils.d.ts +2 -0
  218. package/dist/utils/fs-utils.js +22 -0
  219. package/dist/utils/path-validation.d.ts +3 -0
  220. package/dist/utils/path-validation.js +56 -0
  221. package/dist/utils/spawn-process.d.ts +9 -0
  222. package/dist/utils/spawn-process.js +50 -0
  223. package/dist/utils/subscribable.d.ts +12 -0
  224. package/dist/utils/subscribable.js +44 -0
  225. package/package.json +91 -77
  226. package/dist/cjs/auto.js +0 -16
  227. package/dist/cjs/cli-mode.d.ts +0 -16
  228. package/dist/cjs/cli-mode.js +0 -32
  229. package/dist/cjs/crypto.d.ts +0 -16
  230. package/dist/cjs/crypto.js +0 -212
  231. package/dist/cjs/dev-helper.d.ts +0 -3
  232. package/dist/cjs/dev-helper.js +0 -46
  233. package/dist/cjs/encrypted-storage.d.ts +0 -11
  234. package/dist/cjs/encrypted-storage.js +0 -73
  235. package/dist/cjs/index.d.ts +0 -56
  236. package/dist/cjs/index.js +0 -727
  237. package/dist/cjs/logger.d.ts +0 -32
  238. package/dist/cjs/logger.js +0 -85
  239. package/dist/cjs/nextjs.d.ts +0 -10
  240. package/dist/cjs/nextjs.js +0 -83
  241. package/dist/cjs/platform-info.d.ts +0 -36
  242. package/dist/cjs/platform-info.js +0 -274
  243. package/dist/cjs/polling.d.ts +0 -13
  244. package/dist/cjs/polling.js +0 -52
  245. package/dist/cjs/registry/index.d.ts +0 -12
  246. package/dist/cjs/registry/index.js +0 -56
  247. package/dist/cjs/registry/knowthat.d.ts +0 -17
  248. package/dist/cjs/registry/knowthat.js +0 -173
  249. package/dist/cjs/rotation.d.ts +0 -35
  250. package/dist/cjs/rotation.js +0 -102
  251. package/dist/cjs/storage.d.ts +0 -41
  252. package/dist/cjs/storage.js +0 -163
  253. package/dist/cjs/transport.d.ts +0 -35
  254. package/dist/cjs/transport.js +0 -300
  255. package/dist/cjs/types.d.ts +0 -206
  256. package/dist/cjs/vercel-adapter.d.ts +0 -8
  257. package/dist/cjs/vercel-adapter.js +0 -67
  258. package/dist/esm/auto.d.ts +0 -13
  259. package/dist/esm/auto.d.ts.map +0 -1
  260. package/dist/esm/auto.js +0 -30
  261. package/dist/esm/auto.js.map +0 -1
  262. package/dist/esm/cli-mode.d.ts +0 -52
  263. package/dist/esm/cli-mode.d.ts.map +0 -1
  264. package/dist/esm/cli-mode.js +0 -59
  265. package/dist/esm/cli-mode.js.map +0 -1
  266. package/dist/esm/crypto.d.ts +0 -51
  267. package/dist/esm/crypto.d.ts.map +0 -1
  268. package/dist/esm/crypto.js +0 -230
  269. package/dist/esm/crypto.js.map +0 -1
  270. package/dist/esm/dev-helper.d.ts +0 -15
  271. package/dist/esm/dev-helper.d.ts.map +0 -1
  272. package/dist/esm/dev-helper.js +0 -63
  273. package/dist/esm/dev-helper.js.map +0 -1
  274. package/dist/esm/encrypted-storage.d.ts +0 -19
  275. package/dist/esm/encrypted-storage.d.ts.map +0 -1
  276. package/dist/esm/encrypted-storage.js +0 -48
  277. package/dist/esm/encrypted-storage.js.map +0 -1
  278. package/dist/esm/index.d.ts +0 -129
  279. package/dist/esm/index.d.ts.map +0 -1
  280. package/dist/esm/index.js +0 -853
  281. package/dist/esm/index.js.map +0 -1
  282. package/dist/esm/logger.d.ts +0 -46
  283. package/dist/esm/logger.d.ts.map +0 -1
  284. package/dist/esm/logger.js +0 -100
  285. package/dist/esm/logger.js.map +0 -1
  286. package/dist/esm/nextjs.d.ts +0 -22
  287. package/dist/esm/nextjs.d.ts.map +0 -1
  288. package/dist/esm/nextjs.js +0 -83
  289. package/dist/esm/nextjs.js.map +0 -1
  290. package/dist/esm/package.json +0 -1
  291. package/dist/esm/platform-info.d.ts +0 -74
  292. package/dist/esm/platform-info.d.ts.map +0 -1
  293. package/dist/esm/platform-info.js +0 -293
  294. package/dist/esm/platform-info.js.map +0 -1
  295. package/dist/esm/polling.d.ts +0 -29
  296. package/dist/esm/polling.d.ts.map +0 -1
  297. package/dist/esm/polling.js +0 -76
  298. package/dist/esm/polling.js.map +0 -1
  299. package/dist/esm/registry/index.d.ts +0 -43
  300. package/dist/esm/registry/index.d.ts.map +0 -1
  301. package/dist/esm/registry/index.js +0 -89
  302. package/dist/esm/registry/index.js.map +0 -1
  303. package/dist/esm/registry/knowthat.d.ts +0 -44
  304. package/dist/esm/registry/knowthat.d.ts.map +0 -1
  305. package/dist/esm/registry/knowthat.js +0 -220
  306. package/dist/esm/registry/knowthat.js.map +0 -1
  307. package/dist/esm/rotation.d.ts +0 -57
  308. package/dist/esm/rotation.d.ts.map +0 -1
  309. package/dist/esm/rotation.js +0 -133
  310. package/dist/esm/rotation.js.map +0 -1
  311. package/dist/esm/storage.d.ts +0 -65
  312. package/dist/esm/storage.d.ts.map +0 -1
  313. package/dist/esm/storage.js +0 -160
  314. package/dist/esm/storage.js.map +0 -1
  315. package/dist/esm/transport.d.ts +0 -52
  316. package/dist/esm/transport.d.ts.map +0 -1
  317. package/dist/esm/transport.js +0 -340
  318. package/dist/esm/transport.js.map +0 -1
  319. package/dist/esm/types.d.ts +0 -293
  320. package/dist/esm/types.d.ts.map +0 -1
  321. package/dist/esm/types.js +0 -5
  322. package/dist/esm/types.js.map +0 -1
  323. package/dist/esm/vercel-adapter.d.ts +0 -26
  324. package/dist/esm/vercel-adapter.d.ts.map +0 -1
  325. package/dist/esm/vercel-adapter.js +0 -80
  326. package/dist/esm/vercel-adapter.js.map +0 -1
  327. /package/dist/{cjs → auth/oauth}/types.js +0 -0
  328. /package/dist/{cjs/auto.d.ts → runtime/transports/http/index.d.ts} +0 -0
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for Cloudflare KV Nonce Cache
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const cloudflare_kv_nonce_cache_js_1 = require("../cloudflare-kv-nonce-cache.js");
8
+ // Mock Cloudflare KV namespace
9
+ const mockKV = {
10
+ get: vitest_1.vi.fn(),
11
+ getWithMetadata: vitest_1.vi.fn(),
12
+ put: vitest_1.vi.fn(),
13
+ delete: vitest_1.vi.fn(),
14
+ };
15
+ (0, vitest_1.describe)("CloudflareKVNonceCache", () => {
16
+ let cache;
17
+ (0, vitest_1.beforeEach)(() => {
18
+ vitest_1.vi.clearAllMocks();
19
+ cache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV, "test:");
20
+ });
21
+ (0, vitest_1.describe)("Basic Operations", () => {
22
+ (0, vitest_1.it)("should add and check nonce existence", async () => {
23
+ const nonce = "test-nonce-123";
24
+ // Mock KV responses
25
+ mockKV.get.mockResolvedValue(null); // doesn't exist initially
26
+ mockKV.put.mockResolvedValue(undefined); // successful put
27
+ // Initially should not exist
28
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
29
+ (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("test:test-nonce-123");
30
+ // Mock getWithMetadata for add operation
31
+ mockKV.getWithMetadata.mockResolvedValue({ value: null, metadata: null });
32
+ // Add nonce
33
+ await cache.add(nonce, 60);
34
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalledWith("test:test-nonce-123", vitest_1.expect.stringContaining('"nonce":"test-nonce-123"'), { expirationTtl: 60 });
35
+ // Mock that it now exists and is valid
36
+ const futureTime = Date.now() + 50000;
37
+ mockKV.get.mockResolvedValue(JSON.stringify({
38
+ nonce,
39
+ expiresAt: futureTime,
40
+ createdAt: Date.now(),
41
+ }));
42
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(true);
43
+ });
44
+ (0, vitest_1.it)("should handle expired nonces correctly", async () => {
45
+ const nonce = "expired-nonce";
46
+ // Mock expired nonce data
47
+ const pastTime = Date.now() - 10000;
48
+ mockKV.get.mockResolvedValue(JSON.stringify({
49
+ nonce,
50
+ expiresAt: pastTime,
51
+ createdAt: Date.now() - 20000,
52
+ }));
53
+ mockKV.delete.mockResolvedValue(undefined);
54
+ // Should return false and clean up expired entry
55
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
56
+ (0, vitest_1.expect)(mockKV.delete).toHaveBeenCalledWith("test:expired-nonce");
57
+ });
58
+ (0, vitest_1.it)("should handle corrupted data gracefully", async () => {
59
+ const nonce = "corrupted-nonce";
60
+ // Mock corrupted JSON data
61
+ mockKV.get.mockResolvedValue("invalid-json");
62
+ mockKV.delete.mockResolvedValue(undefined);
63
+ // Should return false and clean up corrupted entry
64
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
65
+ (0, vitest_1.expect)(mockKV.delete).toHaveBeenCalledWith("test:corrupted-nonce");
66
+ });
67
+ });
68
+ (0, vitest_1.describe)("Atomic Operations with getWithMetadata", () => {
69
+ (0, vitest_1.it)("should use getWithMetadata for better atomicity when available", async () => {
70
+ const nonce = "atomic-test-nonce";
71
+ // Mock getWithMetadata returning no existing value
72
+ mockKV.getWithMetadata.mockResolvedValue({ value: null, metadata: null });
73
+ mockKV.put.mockResolvedValue(undefined);
74
+ await cache.add(nonce, 60);
75
+ (0, vitest_1.expect)(mockKV.getWithMetadata).toHaveBeenCalledWith("test:atomic-test-nonce");
76
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
77
+ });
78
+ (0, vitest_1.it)("should prevent duplicate addition with getWithMetadata", async () => {
79
+ const nonce = "duplicate-nonce";
80
+ // Mock existing valid nonce
81
+ const futureTime = Date.now() + 50000;
82
+ mockKV.getWithMetadata.mockResolvedValue({
83
+ value: JSON.stringify({
84
+ nonce,
85
+ expiresAt: futureTime,
86
+ createdAt: Date.now(),
87
+ }),
88
+ metadata: null,
89
+ });
90
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Nonce duplicate-nonce already exists - potential replay attack");
91
+ });
92
+ (0, vitest_1.it)("should allow overwrite of expired nonce with getWithMetadata", async () => {
93
+ const nonce = "expired-overwrite-nonce";
94
+ // Mock existing expired nonce
95
+ const pastTime = Date.now() - 10000;
96
+ mockKV.getWithMetadata.mockResolvedValue({
97
+ value: JSON.stringify({
98
+ nonce,
99
+ expiresAt: pastTime,
100
+ createdAt: Date.now() - 20000,
101
+ }),
102
+ metadata: null,
103
+ });
104
+ mockKV.put.mockResolvedValue(undefined);
105
+ // Should succeed in overwriting expired nonce
106
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).resolves.not.toThrow();
107
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
108
+ });
109
+ });
110
+ (0, vitest_1.describe)("Fallback to Basic Operations", () => {
111
+ (0, vitest_1.it)("should fall back to basic operations when getWithMetadata fails", async () => {
112
+ const nonce = "fallback-nonce";
113
+ // Mock getWithMetadata throwing error
114
+ const getWithMetadataError = new Error("getWithMetadata is not available");
115
+ mockKV.getWithMetadata.mockRejectedValue(getWithMetadataError);
116
+ // Mock basic operations
117
+ mockKV.get.mockResolvedValue(null);
118
+ mockKV.put.mockResolvedValue(undefined);
119
+ await cache.add(nonce, 60);
120
+ // Should have fallen back to basic has() check
121
+ (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("test:fallback-nonce");
122
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
123
+ });
124
+ (0, vitest_1.it)("should prevent duplicate addition in fallback mode", async () => {
125
+ const nonce = "fallback-duplicate-nonce";
126
+ // Mock getWithMetadata not available
127
+ const getWithMetadataError = new Error("getWithMetadata is not available");
128
+ mockKV.getWithMetadata.mockRejectedValue(getWithMetadataError);
129
+ // Mock existing nonce in basic mode
130
+ const futureTime = Date.now() + 50000;
131
+ mockKV.get.mockResolvedValue(JSON.stringify({
132
+ nonce,
133
+ expiresAt: futureTime,
134
+ createdAt: Date.now(),
135
+ }));
136
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Nonce fallback-duplicate-nonce already exists - potential replay attack");
137
+ });
138
+ });
139
+ (0, vitest_1.describe)("Error Handling", () => {
140
+ (0, vitest_1.it)("should propagate unexpected errors", async () => {
141
+ const nonce = "error-nonce";
142
+ const unexpectedError = new Error("Network error");
143
+ mockKV.getWithMetadata.mockRejectedValue(unexpectedError);
144
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Network error");
145
+ });
146
+ (0, vitest_1.it)("should handle KV operation failures gracefully", async () => {
147
+ const nonce = "kv-error-nonce";
148
+ mockKV.get.mockRejectedValue(new Error("KV service unavailable"));
149
+ // has() should handle KV errors gracefully
150
+ await (0, vitest_1.expect)(cache.has(nonce)).rejects.toThrow("KV service unavailable");
151
+ });
152
+ });
153
+ (0, vitest_1.describe)("Cleanup", () => {
154
+ (0, vitest_1.it)("should be a no-op since KV handles expiry", async () => {
155
+ // cleanup() should not call any KV methods
156
+ await cache.cleanup();
157
+ (0, vitest_1.expect)(mockKV.get).not.toHaveBeenCalled();
158
+ (0, vitest_1.expect)(mockKV.put).not.toHaveBeenCalled();
159
+ (0, vitest_1.expect)(mockKV.delete).not.toHaveBeenCalled();
160
+ });
161
+ });
162
+ (0, vitest_1.describe)("Key Prefix", () => {
163
+ (0, vitest_1.it)("should use custom key prefix", async () => {
164
+ const customCache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV, "custom:");
165
+ mockKV.get.mockResolvedValue(null);
166
+ await customCache.has("test-nonce");
167
+ (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("custom:test-nonce");
168
+ });
169
+ (0, vitest_1.it)("should use default key prefix", async () => {
170
+ const defaultCache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV);
171
+ mockKV.get.mockResolvedValue(null);
172
+ await defaultCache.has("test-nonce");
173
+ (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("nonce:test-nonce");
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Concurrency tests for nonce cache implementations
3
+ * Tests multi-instance replay prevention and atomic operations
4
+ */
5
+ export {};
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ /**
3
+ * Concurrency tests for nonce cache implementations
4
+ * Tests multi-instance replay prevention and atomic operations
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const vitest_1 = require("vitest");
8
+ const memory_nonce_cache_js_1 = require("../memory-nonce-cache.js");
9
+ const redis_nonce_cache_js_1 = require("../redis-nonce-cache.js");
10
+ const dynamodb_nonce_cache_js_1 = require("../dynamodb-nonce-cache.js");
11
+ const cloudflare_kv_nonce_cache_js_1 = require("../cloudflare-kv-nonce-cache.js");
12
+ // Mock implementations for external services
13
+ const mockRedis = {
14
+ exists: vitest_1.vi.fn(),
15
+ set: vitest_1.vi.fn(),
16
+ };
17
+ const mockDynamoDB = {
18
+ getItem: vitest_1.vi.fn(),
19
+ putItem: vitest_1.vi.fn(),
20
+ };
21
+ const mockKV = {
22
+ get: vitest_1.vi.fn(),
23
+ getWithMetadata: vitest_1.vi.fn(),
24
+ put: vitest_1.vi.fn(),
25
+ delete: vitest_1.vi.fn(),
26
+ };
27
+ (0, vitest_1.describe)("Nonce Cache Concurrency Tests", () => {
28
+ let memoryCache;
29
+ let redisCache;
30
+ let dynamoCache;
31
+ let kvCache;
32
+ (0, vitest_1.beforeEach)(() => {
33
+ vitest_1.vi.clearAllMocks();
34
+ memoryCache = new memory_nonce_cache_js_1.MemoryNonceCache(100);
35
+ redisCache = new redis_nonce_cache_js_1.RedisNonceCache(mockRedis, "test:");
36
+ dynamoCache = new dynamodb_nonce_cache_js_1.DynamoNonceCache(mockDynamoDB, "test-table");
37
+ kvCache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV, "test:");
38
+ });
39
+ (0, vitest_1.afterEach)(() => {
40
+ memoryCache.destroy();
41
+ });
42
+ (0, vitest_1.describe)("Memory Cache Concurrency", () => {
43
+ (0, vitest_1.it)("should prevent concurrent duplicate nonce addition", async () => {
44
+ const nonce = "concurrent-memory-nonce";
45
+ const ttl = 60;
46
+ // Simulate concurrent add operations
47
+ const promises = [
48
+ memoryCache.add(nonce, ttl),
49
+ memoryCache.add(nonce, ttl),
50
+ memoryCache.add(nonce, ttl),
51
+ ];
52
+ const results = await Promise.allSettled(promises);
53
+ // Exactly one should succeed, others should fail
54
+ const successful = results.filter((r) => r.status === "fulfilled");
55
+ const failed = results.filter((r) => r.status === "rejected");
56
+ (0, vitest_1.expect)(successful).toHaveLength(1);
57
+ (0, vitest_1.expect)(failed).toHaveLength(2);
58
+ // All failures should be due to duplicate nonce
59
+ failed.forEach((result) => {
60
+ if (result.status === "rejected") {
61
+ (0, vitest_1.expect)(result.reason.message).toContain("already exists");
62
+ }
63
+ });
64
+ // Nonce should exist after successful addition
65
+ (0, vitest_1.expect)(await memoryCache.has(nonce)).toBe(true);
66
+ });
67
+ (0, vitest_1.it)("should handle rapid sequential operations", async () => {
68
+ const baseNonce = "rapid-memory-nonce";
69
+ const operations = [];
70
+ // Create 100 rapid sequential operations
71
+ for (let i = 0; i < 100; i++) {
72
+ operations.push(memoryCache.add(`${baseNonce}-${i}`, 60));
73
+ }
74
+ // All should succeed since they're different nonces
75
+ const results = await Promise.allSettled(operations);
76
+ const successful = results.filter((r) => r.status === "fulfilled");
77
+ (0, vitest_1.expect)(successful).toHaveLength(100);
78
+ // All nonces should exist
79
+ for (let i = 0; i < 100; i++) {
80
+ (0, vitest_1.expect)(await memoryCache.has(`${baseNonce}-${i}`)).toBe(true);
81
+ }
82
+ });
83
+ });
84
+ (0, vitest_1.describe)("Redis Cache Atomicity", () => {
85
+ (0, vitest_1.it)("should use atomic SET NX EX for add operations", async () => {
86
+ const nonce = "atomic-redis-nonce";
87
+ const ttl = 300;
88
+ // Mock successful atomic operation
89
+ mockRedis.set.mockResolvedValue("OK");
90
+ await redisCache.add(nonce, ttl);
91
+ // Verify atomic command was used
92
+ (0, vitest_1.expect)(mockRedis.set).toHaveBeenCalledWith("test:atomic-redis-nonce", "1", "EX", ttl, "NX");
93
+ });
94
+ (0, vitest_1.it)("should handle concurrent add attempts atomically", async () => {
95
+ const nonce = "concurrent-redis-nonce";
96
+ // First call succeeds, subsequent calls fail
97
+ mockRedis.set.mockResolvedValueOnce("OK").mockResolvedValue(null);
98
+ const promises = [
99
+ redisCache.add(nonce, 60),
100
+ redisCache.add(nonce, 60),
101
+ redisCache.add(nonce, 60),
102
+ ];
103
+ const results = await Promise.allSettled(promises);
104
+ // First should succeed, others should fail
105
+ (0, vitest_1.expect)(results[0].status).toBe("fulfilled");
106
+ (0, vitest_1.expect)(results[1].status).toBe("rejected");
107
+ (0, vitest_1.expect)(results[2].status).toBe("rejected");
108
+ // Verify all calls used atomic operation
109
+ (0, vitest_1.expect)(mockRedis.set).toHaveBeenCalledTimes(3);
110
+ mockRedis.set.mock.calls.forEach((call) => {
111
+ (0, vitest_1.expect)(call).toEqual([
112
+ vitest_1.expect.stringContaining(nonce),
113
+ "1",
114
+ "EX",
115
+ 60,
116
+ "NX",
117
+ ]);
118
+ });
119
+ });
120
+ });
121
+ (0, vitest_1.describe)("DynamoDB Cache Atomicity", () => {
122
+ (0, vitest_1.it)("should use conditional writes for atomicity", async () => {
123
+ const nonce = "atomic-dynamo-nonce";
124
+ const ttl = 300;
125
+ // Mock successful conditional write
126
+ mockDynamoDB.putItem.mockReturnValue({
127
+ promise: () => Promise.resolve({}),
128
+ });
129
+ await dynamoCache.add(nonce, ttl);
130
+ // Verify conditional expression was used
131
+ (0, vitest_1.expect)(mockDynamoDB.putItem).toHaveBeenCalledWith({
132
+ TableName: "test-table",
133
+ Item: {
134
+ nonce: { S: nonce },
135
+ expiresAt: { N: vitest_1.expect.any(String) },
136
+ createdAt: { N: vitest_1.expect.any(String) },
137
+ },
138
+ ConditionExpression: "attribute_not_exists(nonce)",
139
+ });
140
+ });
141
+ (0, vitest_1.it)("should handle concurrent add attempts with conditional writes", async () => {
142
+ const nonce = "concurrent-dynamo-nonce";
143
+ // First call succeeds
144
+ mockDynamoDB.putItem.mockReturnValueOnce({
145
+ promise: () => Promise.resolve({}),
146
+ });
147
+ // Subsequent calls fail with conditional check exception
148
+ const conditionalError = new Error("ConditionalCheckFailedException");
149
+ conditionalError.code = "ConditionalCheckFailedException";
150
+ mockDynamoDB.putItem.mockReturnValue({
151
+ promise: () => Promise.reject(conditionalError),
152
+ });
153
+ const promises = [
154
+ dynamoCache.add(nonce, 60),
155
+ dynamoCache.add(nonce, 60),
156
+ dynamoCache.add(nonce, 60),
157
+ ];
158
+ const results = await Promise.allSettled(promises);
159
+ // First should succeed, others should fail
160
+ (0, vitest_1.expect)(results[0].status).toBe("fulfilled");
161
+ (0, vitest_1.expect)(results[1].status).toBe("rejected");
162
+ (0, vitest_1.expect)(results[2].status).toBe("rejected");
163
+ // All failures should be due to conditional check
164
+ results.slice(1).forEach((result) => {
165
+ if (result.status === "rejected") {
166
+ (0, vitest_1.expect)(result.reason.message).toContain("already exists");
167
+ }
168
+ });
169
+ });
170
+ });
171
+ (0, vitest_1.describe)("Cloudflare KV Cache Best-Effort Atomicity", () => {
172
+ (0, vitest_1.it)("should attempt atomicity with getWithMetadata", async () => {
173
+ const nonce = "atomic-kv-nonce";
174
+ // Mock no existing value
175
+ mockKV.getWithMetadata.mockResolvedValue({ value: null, metadata: null });
176
+ mockKV.put.mockResolvedValue(undefined);
177
+ await kvCache.add(nonce, 60);
178
+ (0, vitest_1.expect)(mockKV.getWithMetadata).toHaveBeenCalledWith("test:atomic-kv-nonce");
179
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
180
+ });
181
+ (0, vitest_1.it)("should detect existing nonces with getWithMetadata", async () => {
182
+ const nonce = "existing-kv-nonce";
183
+ // Mock existing valid nonce
184
+ const futureTime = Date.now() + 50000;
185
+ mockKV.getWithMetadata.mockResolvedValue({
186
+ value: JSON.stringify({
187
+ nonce,
188
+ expiresAt: futureTime,
189
+ createdAt: Date.now(),
190
+ }),
191
+ metadata: null,
192
+ });
193
+ await (0, vitest_1.expect)(kvCache.add(nonce, 60)).rejects.toThrow("already exists - potential replay attack");
194
+ });
195
+ (0, vitest_1.it)("should fall back to basic operations when getWithMetadata unavailable", async () => {
196
+ const nonce = "fallback-kv-nonce";
197
+ // Mock getWithMetadata failure
198
+ mockKV.getWithMetadata.mockRejectedValue(new Error("getWithMetadata is not available"));
199
+ // Mock basic operations
200
+ mockKV.get.mockResolvedValue(null);
201
+ mockKV.put.mockResolvedValue(undefined);
202
+ await kvCache.add(nonce, 60);
203
+ // Should fall back to basic has() check
204
+ (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("test:fallback-kv-nonce");
205
+ (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
206
+ });
207
+ });
208
+ (0, vitest_1.describe)("Cross-Implementation Consistency", () => {
209
+ const testCases = [
210
+ { name: "Memory", cache: () => memoryCache },
211
+ {
212
+ name: "Redis",
213
+ cache: () => redisCache,
214
+ setup: () => {
215
+ mockRedis.exists.mockResolvedValue(0);
216
+ mockRedis.set.mockResolvedValue("OK");
217
+ },
218
+ },
219
+ {
220
+ name: "DynamoDB",
221
+ cache: () => dynamoCache,
222
+ setup: () => {
223
+ mockDynamoDB.getItem.mockReturnValue({
224
+ promise: () => Promise.resolve({}),
225
+ });
226
+ mockDynamoDB.putItem.mockReturnValue({
227
+ promise: () => Promise.resolve({}),
228
+ });
229
+ },
230
+ },
231
+ {
232
+ name: "Cloudflare KV",
233
+ cache: () => kvCache,
234
+ setup: () => {
235
+ mockKV.get.mockResolvedValue(null);
236
+ mockKV.getWithMetadata.mockResolvedValue({
237
+ value: null,
238
+ metadata: null,
239
+ });
240
+ mockKV.put.mockResolvedValue(undefined);
241
+ },
242
+ },
243
+ ];
244
+ testCases.forEach(({ name, cache, setup }) => {
245
+ (0, vitest_1.it)(`${name} should implement consistent interface`, async () => {
246
+ if (setup)
247
+ setup();
248
+ const cacheInstance = cache();
249
+ const nonce = `interface-test-${name.toLowerCase()}-nonce`;
250
+ // Test interface methods exist and work
251
+ (0, vitest_1.expect)(typeof cacheInstance.has).toBe("function");
252
+ (0, vitest_1.expect)(typeof cacheInstance.add).toBe("function");
253
+ (0, vitest_1.expect)(typeof cacheInstance.cleanup).toBe("function");
254
+ // Test basic flow
255
+ if (name === "Memory") {
256
+ // Only test actual functionality for memory cache
257
+ (0, vitest_1.expect)(await cacheInstance.has(nonce)).toBe(false);
258
+ await cacheInstance.add(nonce, 60);
259
+ (0, vitest_1.expect)(await cacheInstance.has(nonce)).toBe(true);
260
+ }
261
+ // cleanup should not throw
262
+ await (0, vitest_1.expect)(cacheInstance.cleanup()).resolves.not.toThrow();
263
+ });
264
+ });
265
+ });
266
+ (0, vitest_1.describe)("Performance and Stress Testing", () => {
267
+ (0, vitest_1.it)("should handle high-frequency operations on memory cache", async () => {
268
+ const startTime = Date.now();
269
+ const operations = [];
270
+ // Create 1000 rapid operations
271
+ for (let i = 0; i < 1000; i++) {
272
+ operations.push(memoryCache.add(`stress-nonce-${i}`, 60));
273
+ }
274
+ await Promise.all(operations);
275
+ const endTime = Date.now();
276
+ // Should complete within reasonable time (adjust threshold as needed)
277
+ (0, vitest_1.expect)(endTime - startTime).toBeLessThan(1000); // 1 second
278
+ // All nonces should exist
279
+ for (let i = 0; i < 1000; i++) {
280
+ (0, vitest_1.expect)(await memoryCache.has(`stress-nonce-${i}`)).toBe(true);
281
+ }
282
+ });
283
+ (0, vitest_1.it)("should handle mixed read/write operations on memory cache", async () => {
284
+ const nonce = "mixed-ops-nonce";
285
+ // Add initial nonce
286
+ await memoryCache.add(nonce, 60);
287
+ // Create mixed operations
288
+ const operations = [];
289
+ for (let i = 0; i < 100; i++) {
290
+ operations.push(memoryCache.has(nonce));
291
+ operations.push(memoryCache.add(`mixed-${i}`, 60));
292
+ }
293
+ const results = await Promise.allSettled(operations);
294
+ // All has() operations should succeed
295
+ // All add() operations should succeed (different nonces)
296
+ const successful = results.filter((r) => r.status === "fulfilled");
297
+ (0, vitest_1.expect)(successful.length).toBeGreaterThan(150); // Most should succeed
298
+ });
299
+ });
300
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for DynamoDB Nonce Cache
3
+ */
4
+ export {};
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for DynamoDB Nonce Cache
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const dynamodb_nonce_cache_js_1 = require("../dynamodb-nonce-cache.js");
8
+ // Mock DynamoDB client
9
+ const mockDynamoDB = {
10
+ getItem: vitest_1.vi.fn(),
11
+ putItem: vitest_1.vi.fn(),
12
+ };
13
+ // Helper to create mock DynamoDB responses
14
+ const createMockGetItemResponse = (exists, expired = false) => {
15
+ if (!exists) {
16
+ return { promise: () => Promise.resolve({}) };
17
+ }
18
+ const expiresAt = expired
19
+ ? Math.floor(Date.now() / 1000) - 100 // Expired 100 seconds ago
20
+ : Math.floor(Date.now() / 1000) + 300; // Expires in 300 seconds
21
+ return {
22
+ promise: () => Promise.resolve({
23
+ Item: {
24
+ nonce: { S: "test-nonce" },
25
+ expiresAt: { N: expiresAt.toString() },
26
+ },
27
+ }),
28
+ };
29
+ };
30
+ const createMockPutItemResponse = (success = true) => {
31
+ if (success) {
32
+ return { promise: () => Promise.resolve({}) };
33
+ }
34
+ else {
35
+ const error = new Error("ConditionalCheckFailedException");
36
+ error.code = "ConditionalCheckFailedException";
37
+ return { promise: () => Promise.reject(error) };
38
+ }
39
+ };
40
+ (0, vitest_1.describe)("DynamoNonceCache", () => {
41
+ let cache;
42
+ (0, vitest_1.beforeEach)(() => {
43
+ vitest_1.vi.clearAllMocks();
44
+ cache = new dynamodb_nonce_cache_js_1.DynamoNonceCache(mockDynamoDB, "test-nonce-table");
45
+ });
46
+ (0, vitest_1.describe)("Basic Operations", () => {
47
+ (0, vitest_1.it)("should add and check nonce existence", async () => {
48
+ const nonce = "test-nonce-123";
49
+ // Mock DynamoDB responses
50
+ mockDynamoDB.getItem.mockReturnValue(createMockGetItemResponse(false));
51
+ mockDynamoDB.putItem.mockReturnValue(createMockPutItemResponse(true));
52
+ // Initially should not exist
53
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
54
+ (0, vitest_1.expect)(mockDynamoDB.getItem).toHaveBeenCalledWith({
55
+ TableName: "test-nonce-table",
56
+ Key: { nonce: { S: nonce } },
57
+ ConsistentRead: true,
58
+ });
59
+ // Add nonce
60
+ await cache.add(nonce, 60);
61
+ (0, vitest_1.expect)(mockDynamoDB.putItem).toHaveBeenCalledWith({
62
+ TableName: "test-nonce-table",
63
+ Item: {
64
+ nonce: { S: nonce },
65
+ expiresAt: { N: vitest_1.expect.any(String) },
66
+ createdAt: { N: vitest_1.expect.any(String) },
67
+ },
68
+ ConditionExpression: "attribute_not_exists(nonce)",
69
+ });
70
+ // Mock that it now exists
71
+ mockDynamoDB.getItem.mockReturnValue(createMockGetItemResponse(true));
72
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(true);
73
+ });
74
+ (0, vitest_1.it)("should prevent duplicate nonce addition", async () => {
75
+ const nonce = "duplicate-nonce";
76
+ // Mock DynamoDB conditional check failure
77
+ mockDynamoDB.putItem.mockReturnValue(createMockPutItemResponse(false));
78
+ // Adding duplicate nonce should throw
79
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Nonce duplicate-nonce already exists - potential replay attack");
80
+ });
81
+ (0, vitest_1.it)("should handle expired nonces correctly", async () => {
82
+ const nonce = "expired-nonce";
83
+ // Mock expired nonce
84
+ mockDynamoDB.getItem.mockReturnValue(createMockGetItemResponse(true, true));
85
+ // Should return false for expired nonce
86
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
87
+ });
88
+ });
89
+ (0, vitest_1.describe)("Error Handling", () => {
90
+ (0, vitest_1.it)("should handle ResourceNotFoundException", async () => {
91
+ const nonce = "missing-table-nonce";
92
+ const error = new Error("Table not found");
93
+ error.code = "ResourceNotFoundException";
94
+ mockDynamoDB.getItem.mockReturnValue({
95
+ promise: () => Promise.reject(error),
96
+ });
97
+ // Should return false for missing table
98
+ (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
99
+ });
100
+ (0, vitest_1.it)("should handle ValidationException on add", async () => {
101
+ const nonce = "validation-error-nonce";
102
+ const error = new Error("Invalid request");
103
+ error.code = "ValidationException";
104
+ mockDynamoDB.putItem.mockReturnValue({
105
+ promise: () => Promise.reject(error),
106
+ });
107
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Invalid DynamoDB operation");
108
+ });
109
+ (0, vitest_1.it)("should handle ProvisionedThroughputExceededException", async () => {
110
+ const nonce = "throughput-error-nonce";
111
+ const error = new Error("Throughput exceeded");
112
+ error.code = "ProvisionedThroughputExceededException";
113
+ mockDynamoDB.putItem.mockReturnValue({
114
+ promise: () => Promise.reject(error),
115
+ });
116
+ await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("DynamoDB throughput exceeded");
117
+ });
118
+ (0, vitest_1.it)("should propagate unknown errors with context", async () => {
119
+ const nonce = "unknown-error-nonce";
120
+ const error = new Error("Unknown error");
121
+ error.code = "UnknownException";
122
+ mockDynamoDB.getItem.mockReturnValue({
123
+ promise: () => Promise.reject(error),
124
+ });
125
+ await (0, vitest_1.expect)(cache.has(nonce)).rejects.toThrow("Failed to check nonce existence");
126
+ });
127
+ });
128
+ (0, vitest_1.describe)("Atomic Operations", () => {
129
+ (0, vitest_1.it)("should use conditional write for atomicity", async () => {
130
+ const nonce = "atomic-test-nonce";
131
+ const ttl = 300;
132
+ mockDynamoDB.putItem.mockReturnValue(createMockPutItemResponse(true));
133
+ await cache.add(nonce, ttl);
134
+ // Verify conditional expression was used
135
+ (0, vitest_1.expect)(mockDynamoDB.putItem).toHaveBeenCalledWith({
136
+ TableName: "test-nonce-table",
137
+ Item: {
138
+ nonce: { S: nonce },
139
+ expiresAt: { N: vitest_1.expect.any(String) },
140
+ createdAt: { N: vitest_1.expect.any(String) },
141
+ },
142
+ ConditionExpression: "attribute_not_exists(nonce)",
143
+ });
144
+ });
145
+ (0, vitest_1.it)("should use consistent reads for has() operations", async () => {
146
+ const nonce = "consistent-read-nonce";
147
+ mockDynamoDB.getItem.mockReturnValue(createMockGetItemResponse(false));
148
+ await cache.has(nonce);
149
+ (0, vitest_1.expect)(mockDynamoDB.getItem).toHaveBeenCalledWith({
150
+ TableName: "test-nonce-table",
151
+ Key: { nonce: { S: nonce } },
152
+ ConsistentRead: true,
153
+ });
154
+ });
155
+ });
156
+ (0, vitest_1.describe)("Cleanup", () => {
157
+ (0, vitest_1.it)("should be a no-op since DynamoDB handles TTL", async () => {
158
+ // cleanup() should not call any DynamoDB methods
159
+ await cache.cleanup();
160
+ (0, vitest_1.expect)(mockDynamoDB.getItem).not.toHaveBeenCalled();
161
+ (0, vitest_1.expect)(mockDynamoDB.putItem).not.toHaveBeenCalled();
162
+ });
163
+ });
164
+ (0, vitest_1.describe)("Custom Configuration", () => {
165
+ (0, vitest_1.it)("should use custom attribute names", () => {
166
+ const customCache = new dynamodb_nonce_cache_js_1.DynamoNonceCache(mockDynamoDB, "custom-table", "customKey", "customTTL");
167
+ mockDynamoDB.getItem.mockReturnValue(createMockGetItemResponse(false));
168
+ customCache.has("test-nonce");
169
+ (0, vitest_1.expect)(mockDynamoDB.getItem).toHaveBeenCalledWith({
170
+ TableName: "custom-table",
171
+ Key: { customKey: { S: "test-nonce" } },
172
+ ConsistentRead: true,
173
+ });
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for Memory Nonce Cache
3
+ */
4
+ export {};