@edge-base/server 0.1.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 (309) hide show
  1. package/admin-build/.gitkeep +0 -0
  2. package/admin-build/_app/env.js +1 -0
  3. package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
  4. package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
  5. package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
  6. package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
  7. package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
  8. package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
  9. package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
  10. package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
  11. package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
  12. package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
  13. package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
  14. package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
  15. package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
  16. package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
  17. package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
  18. package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
  19. package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
  20. package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
  21. package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
  22. package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
  23. package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
  24. package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
  25. package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
  26. package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
  27. package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
  28. package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
  29. package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
  30. package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
  31. package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
  32. package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
  33. package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
  34. package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
  35. package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
  36. package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
  37. package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
  38. package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
  39. package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
  40. package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
  41. package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
  42. package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
  43. package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
  44. package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
  45. package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
  46. package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
  47. package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
  48. package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
  49. package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
  50. package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
  51. package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
  52. package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
  53. package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
  54. package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
  55. package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
  56. package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
  57. package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
  58. package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
  59. package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
  60. package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
  61. package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
  62. package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
  63. package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
  64. package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
  65. package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
  66. package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
  67. package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
  68. package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
  69. package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
  70. package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
  71. package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
  72. package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
  73. package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
  74. package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
  75. package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
  76. package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
  77. package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
  78. package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
  79. package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
  80. package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
  81. package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
  82. package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
  83. package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
  84. package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
  85. package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
  86. package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
  87. package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
  88. package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
  89. package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
  90. package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
  91. package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
  92. package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
  93. package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
  94. package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
  95. package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
  96. package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
  97. package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
  98. package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
  99. package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
  100. package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
  101. package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
  102. package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
  103. package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
  104. package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
  105. package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
  106. package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
  107. package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
  108. package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
  109. package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
  110. package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
  111. package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
  112. package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
  113. package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
  114. package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
  115. package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
  116. package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
  117. package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
  118. package/admin-build/_app/version.json +1 -0
  119. package/admin-build/favicon.svg +26 -0
  120. package/admin-build/index.html +45 -0
  121. package/openapi.json +19543 -0
  122. package/package.json +66 -0
  123. package/src/__tests__/admin-assets.test.ts +55 -0
  124. package/src/__tests__/admin-data-routes.test.ts +488 -0
  125. package/src/__tests__/admin-db-target.test.ts +103 -0
  126. package/src/__tests__/admin-routing.test.ts +31 -0
  127. package/src/__tests__/admin-user-management.test.ts +311 -0
  128. package/src/__tests__/analytics-query.test.ts +75 -0
  129. package/src/__tests__/auth-d1.test.ts +749 -0
  130. package/src/__tests__/auth-db-adapter.test.ts +73 -0
  131. package/src/__tests__/auth-jwt.test.ts +440 -0
  132. package/src/__tests__/auth-oauth.test.ts +389 -0
  133. package/src/__tests__/auth-password.test.ts +367 -0
  134. package/src/__tests__/auth-redirect.test.ts +87 -0
  135. package/src/__tests__/backup-restore.test.ts +711 -0
  136. package/src/__tests__/broadcast.test.ts +128 -0
  137. package/src/__tests__/cli.test.ts +178 -0
  138. package/src/__tests__/cloudflare-realtime.test.ts +113 -0
  139. package/src/__tests__/config.test.ts +469 -0
  140. package/src/__tests__/cors.test.ts +154 -0
  141. package/src/__tests__/cron.test.ts +302 -0
  142. package/src/__tests__/d1-handler.test.ts +402 -0
  143. package/src/__tests__/d1-sql.test.ts +120 -0
  144. package/src/__tests__/database-live-config.test.ts +42 -0
  145. package/src/__tests__/database-live-emitter.test.ts +56 -0
  146. package/src/__tests__/database-live-filters.test.ts +63 -0
  147. package/src/__tests__/database-live-route.test.ts +113 -0
  148. package/src/__tests__/db-sql.test.ts +163 -0
  149. package/src/__tests__/do-lifecycle.test.ts +263 -0
  150. package/src/__tests__/do-router.test.ts +729 -0
  151. package/src/__tests__/email-provider.test.ts +128 -0
  152. package/src/__tests__/email-templates.test.ts +528 -0
  153. package/src/__tests__/error-format.test.ts +250 -0
  154. package/src/__tests__/field-ops.test.ts +242 -0
  155. package/src/__tests__/functions-context.test.ts +334 -0
  156. package/src/__tests__/functions-d1-proxy.test.ts +229 -0
  157. package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
  158. package/src/__tests__/functions-route.test.ts +139 -0
  159. package/src/__tests__/internal-request.test.ts +77 -0
  160. package/src/__tests__/log-writer.test.ts +44 -0
  161. package/src/__tests__/logger.test.ts +58 -0
  162. package/src/__tests__/meta-admin-proxy.test.ts +48 -0
  163. package/src/__tests__/meta-export-coverage.test.ts +191 -0
  164. package/src/__tests__/meta-route-registration.test.ts +47 -0
  165. package/src/__tests__/namespace-dump.test.ts +28 -0
  166. package/src/__tests__/oauth-providers.test.ts +337 -0
  167. package/src/__tests__/openapi-coverage.test.ts +144 -0
  168. package/src/__tests__/pagination.test.ts +59 -0
  169. package/src/__tests__/password-policy.test.ts +191 -0
  170. package/src/__tests__/plugin-migrations.test.ts +379 -0
  171. package/src/__tests__/postgres-batch-compat.test.ts +133 -0
  172. package/src/__tests__/postgres-dialect.test.ts +328 -0
  173. package/src/__tests__/postgres-executor.test.ts +79 -0
  174. package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
  175. package/src/__tests__/postgres-schema-init.test.ts +105 -0
  176. package/src/__tests__/postgres-table-utils.test.ts +107 -0
  177. package/src/__tests__/presence.test.ts +199 -0
  178. package/src/__tests__/provider.test.ts +550 -0
  179. package/src/__tests__/public-user-profile.test.ts +339 -0
  180. package/src/__tests__/push-handlers.test.ts +179 -0
  181. package/src/__tests__/push-provider.test.ts +80 -0
  182. package/src/__tests__/push-token.test.ts +418 -0
  183. package/src/__tests__/query.test.ts +771 -0
  184. package/src/__tests__/rate-limit.test.ts +260 -0
  185. package/src/__tests__/room-access-policy.test.ts +101 -0
  186. package/src/__tests__/room-handler-context.test.ts +130 -0
  187. package/src/__tests__/room-monitoring.test.ts +138 -0
  188. package/src/__tests__/room-runtime-routing.test.ts +222 -0
  189. package/src/__tests__/room.test.ts +254 -0
  190. package/src/__tests__/route-parser.test.ts +490 -0
  191. package/src/__tests__/rules.test.ts +234 -0
  192. package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
  193. package/src/__tests__/scheduled.test.ts +80 -0
  194. package/src/__tests__/schema.test.ts +1273 -0
  195. package/src/__tests__/security-hardening.test.ts +312 -0
  196. package/src/__tests__/server.unit.test.ts +333 -0
  197. package/src/__tests__/service-key-db-proxy.test.ts +650 -0
  198. package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
  199. package/src/__tests__/service-key.test.ts +757 -0
  200. package/src/__tests__/smoke-skip-report.test.ts +72 -0
  201. package/src/__tests__/sms-provider.test.ts +39 -0
  202. package/src/__tests__/sql-route.test.ts +218 -0
  203. package/src/__tests__/storage-hook-context.test.ts +115 -0
  204. package/src/__tests__/totp.test.ts +200 -0
  205. package/src/__tests__/uuid.test.ts +144 -0
  206. package/src/__tests__/validation.test.ts +773 -0
  207. package/src/__tests__/websocket-pending.test.ts +163 -0
  208. package/src/_functions-registry.ts +51 -0
  209. package/src/bench-entry.ts +9 -0
  210. package/src/cloudflare-test.d.ts +1 -0
  211. package/src/durable-objects/auth-do.ts +49 -0
  212. package/src/durable-objects/database-do.ts +2240 -0
  213. package/src/durable-objects/database-live-do.ts +949 -0
  214. package/src/durable-objects/logs-do.ts +1200 -0
  215. package/src/durable-objects/room-runtime-base.ts +1604 -0
  216. package/src/durable-objects/rooms-do.ts +2191 -0
  217. package/src/generated-config.ts +6 -0
  218. package/src/index.ts +382 -0
  219. package/src/lib/admin-assets.ts +54 -0
  220. package/src/lib/admin-db-target.ts +301 -0
  221. package/src/lib/admin-routing.ts +35 -0
  222. package/src/lib/admin-user-management.ts +464 -0
  223. package/src/lib/analytics-adapter.ts +103 -0
  224. package/src/lib/analytics-query.ts +579 -0
  225. package/src/lib/auth-d1-service.ts +1193 -0
  226. package/src/lib/auth-d1.ts +1056 -0
  227. package/src/lib/auth-db-adapter.ts +289 -0
  228. package/src/lib/auth-redirect.ts +116 -0
  229. package/src/lib/cidr.ts +115 -0
  230. package/src/lib/client-ip.ts +51 -0
  231. package/src/lib/cloudflare-realtime.ts +251 -0
  232. package/src/lib/control-db.ts +36 -0
  233. package/src/lib/cron.ts +163 -0
  234. package/src/lib/d1-handler.ts +1425 -0
  235. package/src/lib/d1-schema-init.ts +255 -0
  236. package/src/lib/d1-sql.ts +33 -0
  237. package/src/lib/database-live-config.ts +24 -0
  238. package/src/lib/database-live-emitter.ts +111 -0
  239. package/src/lib/db-sql.ts +66 -0
  240. package/src/lib/do-retry.ts +36 -0
  241. package/src/lib/do-router.ts +270 -0
  242. package/src/lib/do-sql.ts +73 -0
  243. package/src/lib/email-provider.ts +379 -0
  244. package/src/lib/email-templates.ts +285 -0
  245. package/src/lib/email-translations.ts +422 -0
  246. package/src/lib/errors.ts +151 -0
  247. package/src/lib/functions.ts +2091 -0
  248. package/src/lib/hono.ts +56 -0
  249. package/src/lib/internal-request.ts +56 -0
  250. package/src/lib/jwt.ts +354 -0
  251. package/src/lib/log-writer.ts +272 -0
  252. package/src/lib/namespace-dump.ts +125 -0
  253. package/src/lib/oauth-providers.ts +1225 -0
  254. package/src/lib/op-parser.ts +99 -0
  255. package/src/lib/openapi.ts +146 -0
  256. package/src/lib/pagination.ts +19 -0
  257. package/src/lib/password-policy.ts +102 -0
  258. package/src/lib/password.ts +145 -0
  259. package/src/lib/plugin-migrations.ts +612 -0
  260. package/src/lib/postgres-executor.ts +203 -0
  261. package/src/lib/postgres-handler.ts +1102 -0
  262. package/src/lib/postgres-schema-init.ts +341 -0
  263. package/src/lib/postgres-table-utils.ts +87 -0
  264. package/src/lib/public-user-profile.ts +187 -0
  265. package/src/lib/push-provider.ts +409 -0
  266. package/src/lib/push-token.ts +294 -0
  267. package/src/lib/query-engine.ts +768 -0
  268. package/src/lib/room-monitoring.ts +97 -0
  269. package/src/lib/room-runtime.ts +14 -0
  270. package/src/lib/route-parser.ts +434 -0
  271. package/src/lib/schema.ts +538 -0
  272. package/src/lib/schemas.ts +152 -0
  273. package/src/lib/service-key.ts +419 -0
  274. package/src/lib/sms-provider.ts +230 -0
  275. package/src/lib/startup-config.ts +99 -0
  276. package/src/lib/totp.ts +242 -0
  277. package/src/lib/uuid.ts +87 -0
  278. package/src/lib/validation.ts +205 -0
  279. package/src/lib/version.ts +2 -0
  280. package/src/lib/websocket-pending.ts +40 -0
  281. package/src/middleware/auth.ts +169 -0
  282. package/src/middleware/captcha-verify.ts +217 -0
  283. package/src/middleware/cors.ts +159 -0
  284. package/src/middleware/error-handler.ts +54 -0
  285. package/src/middleware/internal-guard.ts +26 -0
  286. package/src/middleware/logger.ts +126 -0
  287. package/src/middleware/rate-limit.ts +283 -0
  288. package/src/middleware/rules.ts +475 -0
  289. package/src/routes/admin-auth.ts +447 -0
  290. package/src/routes/admin.ts +3501 -0
  291. package/src/routes/analytics-api.ts +290 -0
  292. package/src/routes/auth.ts +4222 -0
  293. package/src/routes/backup.ts +1466 -0
  294. package/src/routes/config.ts +53 -0
  295. package/src/routes/d1.ts +109 -0
  296. package/src/routes/database-live.ts +281 -0
  297. package/src/routes/functions.ts +155 -0
  298. package/src/routes/health.ts +32 -0
  299. package/src/routes/kv.ts +167 -0
  300. package/src/routes/oauth.ts +1055 -0
  301. package/src/routes/push.ts +1465 -0
  302. package/src/routes/room.ts +639 -0
  303. package/src/routes/schema-endpoint.ts +76 -0
  304. package/src/routes/sql.ts +176 -0
  305. package/src/routes/storage.ts +1674 -0
  306. package/src/routes/tables.ts +699 -0
  307. package/src/routes/users.ts +21 -0
  308. package/src/routes/vectorize.ts +372 -0
  309. package/src/types.ts +99 -0
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ interface SmokeSkipEntry {
6
+ method: string;
7
+ path: string;
8
+ operationId: string;
9
+ reasonCode: string;
10
+ reasonDescription: string;
11
+ }
12
+
13
+ interface SmokeSkipReport {
14
+ totalRoutes: number;
15
+ skippedRouteCount: number;
16
+ summaryByReason: Record<string, number>;
17
+ skippedRoutes: SmokeSkipEntry[];
18
+ }
19
+
20
+ const REPORT_PATH = resolve(
21
+ new URL('../../test/integration/generated/smoke-skip-report.json', import.meta.url).pathname,
22
+ );
23
+
24
+ function readReport(): SmokeSkipReport {
25
+ return JSON.parse(readFileSync(REPORT_PATH, 'utf-8')) as SmokeSkipReport;
26
+ }
27
+
28
+ describe('smoke skip report', () => {
29
+ it('tracks the current skip budget by reason', () => {
30
+ const report = readReport();
31
+
32
+ expect({
33
+ totalRoutes: report.totalRoutes,
34
+ skippedRouteCount: report.skippedRouteCount,
35
+ summaryByReason: report.summaryByReason,
36
+ }).toMatchInlineSnapshot(`
37
+ {
38
+ "skippedRouteCount": 0,
39
+ "summaryByReason": {},
40
+ "totalRoutes": 192,
41
+ }
42
+ `);
43
+ });
44
+
45
+ it('keeps skipped routes sorted and unique', () => {
46
+ const report = readReport();
47
+ const serialize = (entry: SmokeSkipEntry) => (
48
+ `${entry.reasonCode}:${entry.path}:${entry.method}:${entry.operationId}`
49
+ );
50
+ const sortedEntries = [...report.skippedRoutes].sort((a, b) => (
51
+ a.reasonCode.localeCompare(b.reasonCode)
52
+ || a.path.localeCompare(b.path)
53
+ || a.method.localeCompare(b.method)
54
+ || a.operationId.localeCompare(b.operationId)
55
+ ));
56
+
57
+ expect(report.skippedRoutes).toEqual(sortedEntries);
58
+ expect(new Set(report.skippedRoutes.map(serialize)).size).toBe(report.skippedRoutes.length);
59
+ });
60
+
61
+ it('requires every skipped route to explain itself', () => {
62
+ const report = readReport();
63
+
64
+ for (const entry of report.skippedRoutes) {
65
+ expect(entry.reasonCode.trim().length, `${entry.operationId} is missing reasonCode.`).toBeGreaterThan(0);
66
+ expect(
67
+ entry.reasonDescription.trim().length,
68
+ `${entry.operationId} is missing reasonDescription.`,
69
+ ).toBeGreaterThan(0);
70
+ }
71
+ });
72
+ });
@@ -0,0 +1,39 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { createSmsProvider } from '../lib/sms-provider.js';
3
+
4
+ const originalFetch = globalThis.fetch;
5
+
6
+ afterEach(() => {
7
+ globalThis.fetch = originalFetch;
8
+ vi.restoreAllMocks();
9
+ });
10
+
11
+ describe('createSmsProvider', () => {
12
+ it('returns a mock sms provider when EDGEBASE_SMS_API_URL is set', async () => {
13
+ const fetchMock = vi.fn().mockResolvedValue(
14
+ new Response(JSON.stringify({ sid: 'mock-sid-1' }), {
15
+ status: 200,
16
+ headers: { 'content-type': 'application/json' },
17
+ }),
18
+ );
19
+ vi.stubGlobal('fetch', fetchMock);
20
+
21
+ const provider = createSmsProvider(undefined, {
22
+ EDGEBASE_SMS_API_URL: 'https://mock.example/sms',
23
+ });
24
+ expect(provider).not.toBeNull();
25
+
26
+ const result = await provider!.send({
27
+ to: '+821012341234',
28
+ body: 'Your code is: 123456',
29
+ });
30
+
31
+ expect(result).toEqual({ success: true, messageId: 'mock-sid-1' });
32
+ expect(fetchMock).toHaveBeenCalledWith(
33
+ 'https://mock.example/sms/send',
34
+ expect.objectContaining({
35
+ method: 'POST',
36
+ }),
37
+ );
38
+ });
39
+ });
@@ -0,0 +1,218 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { OpenAPIHono, type HonoEnv } from '../lib/hono.js';
3
+ import { sqlRoute } from '../routes/sql.js';
4
+ import { setConfig } from '../lib/do-router.js';
5
+ import { defineConfig } from '@edge-base/shared';
6
+ import type { Env } from '../types.js';
7
+ import { executeDoSql } from '../lib/do-sql.js';
8
+
9
+ function createApp() {
10
+ const app = new OpenAPIHono<HonoEnv>();
11
+ app.route('/api/sql', sqlRoute);
12
+ return app;
13
+ }
14
+
15
+ describe('sql route', () => {
16
+ afterEach(() => {
17
+ setConfig({});
18
+ });
19
+
20
+ it('rejects unconfigured shared namespace instead of treating it as implicit', async () => {
21
+ setConfig(defineConfig({
22
+ databases: {
23
+ app: {
24
+ tables: {
25
+ posts: { schema: { title: { type: 'string' } } },
26
+ },
27
+ },
28
+ },
29
+ }));
30
+
31
+ const app = createApp();
32
+ const response = await app.request('/api/sql', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ namespace: 'shared', sql: 'SELECT 1' }),
36
+ }, {} as Env);
37
+
38
+ expect(response.status).toBe(404);
39
+ await expect(response.json()).resolves.toMatchObject({
40
+ code: 404,
41
+ message: "Namespace 'shared' not found in config",
42
+ });
43
+ });
44
+
45
+ it('retries dynamic DO SQL after create handshake and forwards the DO name', async () => {
46
+ setConfig(defineConfig({
47
+ databases: {
48
+ workspace: {
49
+ instance: true,
50
+ tables: {
51
+ members: { schema: { userId: { type: 'string' } } },
52
+ },
53
+ },
54
+ },
55
+ serviceKeys: {
56
+ keys: [
57
+ {
58
+ kid: 'root',
59
+ tier: 'root',
60
+ scopes: ['*'],
61
+ secretSource: 'inline',
62
+ inlineSecret: 'sk-root',
63
+ },
64
+ ],
65
+ },
66
+ }));
67
+
68
+ const stub = {
69
+ fetch: vi.fn()
70
+ .mockResolvedValueOnce(
71
+ new Response(JSON.stringify({ needsCreate: true, namespace: 'workspace', id: 'ws-1' }), {
72
+ status: 201,
73
+ headers: { 'Content-Type': 'application/json' },
74
+ }),
75
+ )
76
+ .mockResolvedValueOnce(
77
+ new Response(JSON.stringify({ rows: [{ total: 1 }] }), {
78
+ status: 200,
79
+ headers: { 'Content-Type': 'application/json' },
80
+ }),
81
+ ),
82
+ };
83
+ const env = {
84
+ DATABASE: {
85
+ idFromName: vi.fn().mockReturnValue('do-id'),
86
+ get: vi.fn().mockReturnValue(stub),
87
+ },
88
+ } as unknown as Env;
89
+
90
+ const app = createApp();
91
+ const response = await app.request('/api/sql', {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ 'X-EdgeBase-Service-Key': 'sk-root',
96
+ },
97
+ body: JSON.stringify({
98
+ namespace: 'workspace',
99
+ id: 'ws-1',
100
+ sql: 'SELECT COUNT(*) AS total FROM members',
101
+ params: [],
102
+ }),
103
+ }, env);
104
+
105
+ expect(response.status).toBe(200);
106
+ await expect(response.json()).resolves.toMatchObject({
107
+ rows: [{ total: 1 }],
108
+ items: [{ total: 1 }],
109
+ results: [{ total: 1 }],
110
+ });
111
+ expect(stub.fetch).toHaveBeenCalledTimes(2);
112
+ const firstRequest = stub.fetch.mock.calls[0]?.[0] as Request;
113
+ const secondRequest = stub.fetch.mock.calls[1]?.[0] as Request;
114
+ expect(firstRequest.headers.get('X-DO-Name')).toBe('workspace:ws-1');
115
+ expect(firstRequest.headers.get('X-DO-Create-Authorized')).toBeNull();
116
+ await expect(firstRequest.json()).resolves.toEqual({
117
+ query: 'SELECT COUNT(*) AS total FROM members',
118
+ params: [],
119
+ });
120
+ expect(secondRequest.headers.get('X-DO-Name')).toBe('workspace:ws-1');
121
+ expect(secondRequest.headers.get('X-DO-Create-Authorized')).toBe('1');
122
+ });
123
+
124
+ it('uses D1 run() for non-SELECT SQL so schema mutations actually execute', async () => {
125
+ setConfig(defineConfig({
126
+ databases: {
127
+ shared: {
128
+ tables: {
129
+ posts: { schema: { title: { type: 'string' } } },
130
+ },
131
+ },
132
+ },
133
+ serviceKeys: {
134
+ keys: [
135
+ {
136
+ kid: 'root',
137
+ tier: 'root',
138
+ scopes: ['*'],
139
+ secretSource: 'inline',
140
+ inlineSecret: 'sk-root',
141
+ },
142
+ ],
143
+ },
144
+ }));
145
+
146
+ const stmt: {
147
+ bind: ReturnType<typeof vi.fn>;
148
+ all: ReturnType<typeof vi.fn>;
149
+ run: ReturnType<typeof vi.fn>;
150
+ } = {
151
+ bind: vi.fn(() => stmt),
152
+ all: vi.fn().mockResolvedValue({ results: [] }),
153
+ run: vi.fn().mockResolvedValue({ meta: { changes: 0 } }),
154
+ };
155
+ const env = {
156
+ DB_D1_SHARED: {
157
+ prepare: vi.fn(() => stmt),
158
+ },
159
+ } as unknown as Env;
160
+
161
+ const app = createApp();
162
+ const response = await app.request('/api/sql', {
163
+ method: 'POST',
164
+ headers: {
165
+ 'Content-Type': 'application/json',
166
+ 'X-EdgeBase-Service-Key': 'sk-root',
167
+ },
168
+ body: JSON.stringify({
169
+ namespace: 'shared',
170
+ sql: 'ALTER TABLE "posts" RENAME TO "articles"',
171
+ }),
172
+ }, env);
173
+
174
+ expect(response.status).toBe(200);
175
+ await expect(response.json()).resolves.toMatchObject({
176
+ rows: [],
177
+ rowCount: 0,
178
+ });
179
+ expect((env as unknown as { DB_D1_SHARED: { prepare: ReturnType<typeof vi.fn> } }).DB_D1_SHARED.prepare).toHaveBeenCalledWith(
180
+ 'ALTER TABLE "posts" RENAME TO "articles"',
181
+ );
182
+ expect(stmt.run).toHaveBeenCalledTimes(1);
183
+ expect(stmt.all).not.toHaveBeenCalled();
184
+ });
185
+
186
+ it('executeDoSql retries the create handshake before returning rows', async () => {
187
+ const stub = {
188
+ fetch: vi.fn()
189
+ .mockResolvedValueOnce(
190
+ new Response(JSON.stringify({ needsCreate: true, namespace: 'workspace', id: 'ws-2' }), {
191
+ status: 201,
192
+ headers: { 'Content-Type': 'application/json' },
193
+ }),
194
+ )
195
+ .mockResolvedValueOnce(
196
+ new Response(JSON.stringify({ rows: [{ total: 2 }] }), {
197
+ status: 200,
198
+ headers: { 'Content-Type': 'application/json' },
199
+ }),
200
+ ),
201
+ };
202
+
203
+ const rows = await executeDoSql({
204
+ databaseNamespace: {
205
+ idFromName: vi.fn().mockReturnValue('do-id'),
206
+ get: vi.fn().mockReturnValue(stub),
207
+ } as unknown as DurableObjectNamespace,
208
+ namespace: 'workspace',
209
+ id: 'ws-2',
210
+ query: 'SELECT COUNT(*) AS total FROM members',
211
+ params: [],
212
+ internal: true,
213
+ });
214
+
215
+ expect(rows).toEqual([{ total: 2 }]);
216
+ expect(stub.fetch).toHaveBeenCalledTimes(2);
217
+ });
218
+ });
@@ -0,0 +1,115 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { defineConfig } from '@edge-base/shared';
3
+ import { setConfig } from '../lib/do-router.js';
4
+ import { OpenAPIHono, type HonoEnv } from '../lib/hono.js';
5
+ import { storageRoute } from '../routes/storage.js';
6
+ import type { Env } from '../types.js';
7
+
8
+ function createApp() {
9
+ const app = new OpenAPIHono<HonoEnv>();
10
+ app.route('/api/storage', storageRoute);
11
+ return app;
12
+ }
13
+
14
+ function createEnv(): Env {
15
+ return {
16
+ STORAGE: {
17
+ put: vi.fn().mockResolvedValue({
18
+ key: 'avatars/notify/upload.txt',
19
+ size: 5,
20
+ etag: 'etag-1',
21
+ uploaded: new Date('2025-01-01T00:00:00.000Z'),
22
+ httpMetadata: { contentType: 'text/plain' },
23
+ customMetadata: {},
24
+ }),
25
+ },
26
+ } as unknown as Env;
27
+ }
28
+
29
+ describe('Storage hook context', () => {
30
+ afterEach(() => {
31
+ setConfig({});
32
+ vi.unstubAllGlobals();
33
+ vi.clearAllMocks();
34
+ });
35
+
36
+ it('routes push self-calls through the request origin using the root service key', async () => {
37
+ setConfig(defineConfig({
38
+ release: true,
39
+ serviceKeys: {
40
+ keys: [
41
+ {
42
+ kid: 'storage-write',
43
+ tier: 'scoped',
44
+ scopes: ['storage:bucket:avatars:write'],
45
+ secretSource: 'inline',
46
+ inlineSecret: 'sk-storage-write',
47
+ },
48
+ {
49
+ kid: 'root',
50
+ tier: 'root',
51
+ scopes: ['*'],
52
+ secretSource: 'inline',
53
+ inlineSecret: 'sk-root',
54
+ },
55
+ ],
56
+ },
57
+ storage: {
58
+ buckets: {
59
+ avatars: {
60
+ access: {
61
+ write: () => true,
62
+ },
63
+ handlers: {
64
+ hooks: {
65
+ beforeUpload: async (_auth, file, ctx) => {
66
+ await ctx.push.send('user-123', { body: `Uploaded ${file.key}` });
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ }));
74
+
75
+ const fetchMock = vi.fn().mockResolvedValue(new Response(
76
+ JSON.stringify({ sent: 1, failed: 0, removed: 0 }),
77
+ {
78
+ status: 200,
79
+ headers: { 'Content-Type': 'application/json' },
80
+ },
81
+ ));
82
+ vi.stubGlobal('fetch', fetchMock);
83
+
84
+ const app = createApp();
85
+ const form = new FormData();
86
+ form.append('file', new Blob(['hello'], { type: 'text/plain' }), 'hello.txt');
87
+ form.append('key', 'notify/upload.txt');
88
+ const executionCtx = {
89
+ waitUntil: vi.fn(),
90
+ passThroughOnException: vi.fn(),
91
+ } as unknown as ExecutionContext;
92
+
93
+ const response = await app.fetch(new Request('https://storage.example.test/api/storage/avatars/upload', {
94
+ method: 'POST',
95
+ headers: { 'X-EdgeBase-Service-Key': 'sk-root' },
96
+ body: form,
97
+ }), createEnv(), executionCtx);
98
+
99
+ expect(response.status).toBe(201);
100
+ expect(fetchMock).toHaveBeenCalledWith(
101
+ 'https://storage.example.test/api/push/send',
102
+ expect.objectContaining({
103
+ method: 'POST',
104
+ headers: expect.objectContaining({
105
+ 'Content-Type': 'application/json',
106
+ 'X-EdgeBase-Service-Key': 'sk-root',
107
+ }),
108
+ body: JSON.stringify({
109
+ userId: 'user-123',
110
+ payload: { body: 'Uploaded notify/upload.txt' },
111
+ }),
112
+ }),
113
+ );
114
+ });
115
+ });
@@ -0,0 +1,200 @@
1
+ /**
2
+ * 서버 단위 테스트 — lib/totp.ts
3
+ *
4
+ * 실행: cd packages/server && npx vitest run src/__tests__/totp.test.ts
5
+ *
6
+ * 테스트 대상:
7
+ * generateTOTPSecret / generateTOTPUri / verifyTOTP
8
+ * generateRecoveryCodes / encryptSecret / decryptSecret
9
+ */
10
+
11
+ import { describe, it, expect } from 'vitest';
12
+ import {
13
+ generateTOTPSecret,
14
+ generateTOTPUri,
15
+ verifyTOTP,
16
+ generateRecoveryCodes,
17
+ encryptSecret,
18
+ decryptSecret,
19
+ } from '../lib/totp.js';
20
+
21
+ // ─── A. generateTOTPSecret ──────────────────────────────────────────────────
22
+
23
+ describe('generateTOTPSecret', () => {
24
+ it('returns a base32 string', () => {
25
+ const secret = generateTOTPSecret();
26
+ expect(secret).toMatch(/^[A-Z2-7]+$/);
27
+ });
28
+
29
+ it('returns 32 characters (20 bytes → 32 base32 chars)', () => {
30
+ const secret = generateTOTPSecret();
31
+ expect(secret.length).toBe(32);
32
+ });
33
+
34
+ it('different calls produce different secrets', () => {
35
+ const s1 = generateTOTPSecret();
36
+ const s2 = generateTOTPSecret();
37
+ expect(s1).not.toBe(s2);
38
+ });
39
+ });
40
+
41
+ // ─── B. generateTOTPUri ─────────────────────────────────────────────────────
42
+
43
+ describe('generateTOTPUri', () => {
44
+ it('returns otpauth:// URI', () => {
45
+ const uri = generateTOTPUri('JBSWY3DPEHPK3PXP', 'user@example.com', 'MyApp');
46
+ expect(uri).toMatch(/^otpauth:\/\/totp\//);
47
+ });
48
+
49
+ it('includes secret parameter', () => {
50
+ const uri = generateTOTPUri('MYSECRET', 'u@e.com', 'App');
51
+ expect(uri).toContain('secret=MYSECRET');
52
+ });
53
+
54
+ it('includes issuer parameter', () => {
55
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'MyApp');
56
+ expect(uri).toContain('issuer=MyApp');
57
+ });
58
+
59
+ it('includes algorithm=SHA1', () => {
60
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'App');
61
+ expect(uri).toContain('algorithm=SHA1');
62
+ });
63
+
64
+ it('includes digits=6', () => {
65
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'App');
66
+ expect(uri).toContain('digits=6');
67
+ });
68
+
69
+ it('includes period=30', () => {
70
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'App');
71
+ expect(uri).toContain('period=30');
72
+ });
73
+
74
+ it('encodes special characters in issuer', () => {
75
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'My App & Co');
76
+ expect(uri).toContain('My%20App%20%26%20Co');
77
+ });
78
+
79
+ it('encodes email in label', () => {
80
+ const uri = generateTOTPUri('SECRET', 'user@example.com', 'App');
81
+ expect(uri).toContain('user%40example.com');
82
+ });
83
+
84
+ it('label format is issuer:email', () => {
85
+ const uri = generateTOTPUri('SECRET', 'u@e.com', 'MyApp');
86
+ expect(uri).toContain('MyApp:u%40e.com');
87
+ });
88
+ });
89
+
90
+ // ─── C. verifyTOTP ──────────────────────────────────────────────────────────
91
+
92
+ describe('verifyTOTP', () => {
93
+ it('empty code → false', async () => {
94
+ expect(await verifyTOTP('JBSWY3DPEHPK3PXP', '')).toBe(false);
95
+ });
96
+
97
+ it('wrong length code (5 digits) → false', async () => {
98
+ expect(await verifyTOTP('JBSWY3DPEHPK3PXP', '12345')).toBe(false);
99
+ });
100
+
101
+ it('wrong length code (7 digits) → false', async () => {
102
+ expect(await verifyTOTP('JBSWY3DPEHPK3PXP', '1234567')).toBe(false);
103
+ });
104
+
105
+ it('random 6-digit code with random secret → false (extremely likely)', async () => {
106
+ const secret = generateTOTPSecret();
107
+ // A random code should almost never match
108
+ expect(await verifyTOTP(secret, '999999')).toBe(false);
109
+ });
110
+
111
+ it('window parameter controls step range', async () => {
112
+ // With window=0, only the current step is checked
113
+ const secret = generateTOTPSecret();
114
+ const result = await verifyTOTP(secret, '000000', 0);
115
+ expect(typeof result).toBe('boolean');
116
+ });
117
+ });
118
+
119
+ // ─── D. generateRecoveryCodes ───────────────────────────────────────────────
120
+
121
+ describe('generateRecoveryCodes', () => {
122
+ it('default: 8 codes', () => {
123
+ const codes = generateRecoveryCodes();
124
+ expect(codes.length).toBe(8);
125
+ });
126
+
127
+ it('custom count', () => {
128
+ const codes = generateRecoveryCodes(5);
129
+ expect(codes.length).toBe(5);
130
+ });
131
+
132
+ it('each code is 8 characters', () => {
133
+ const codes = generateRecoveryCodes();
134
+ for (const code of codes) {
135
+ expect(code.length).toBe(8);
136
+ }
137
+ });
138
+
139
+ it('codes use unambiguous charset (no 0/o/1/l/i)', () => {
140
+ const codes = generateRecoveryCodes(20);
141
+ const combined = codes.join('');
142
+ expect(combined).not.toMatch(/[01ilo]/);
143
+ });
144
+
145
+ it('codes only contain allowed characters', () => {
146
+ const allowed = /^[abcdefghjkmnpqrstuvwxyz23456789]+$/;
147
+ const codes = generateRecoveryCodes(10);
148
+ for (const code of codes) {
149
+ expect(code).toMatch(allowed);
150
+ }
151
+ });
152
+
153
+ it('zero count → empty array', () => {
154
+ const codes = generateRecoveryCodes(0);
155
+ expect(codes).toEqual([]);
156
+ });
157
+ });
158
+
159
+ // ─── E. encryptSecret / decryptSecret ───────────────────────────────────────
160
+
161
+ describe('encryptSecret / decryptSecret', () => {
162
+ it('round-trip: encrypt then decrypt returns original', async () => {
163
+ const original = 'JBSWY3DPEHPK3PXP';
164
+ const encrypted = await encryptSecret(original, 'my-key-material');
165
+ const decrypted = await decryptSecret(encrypted, 'my-key-material');
166
+ expect(decrypted).toBe(original);
167
+ });
168
+
169
+ it('encrypted output is base64', async () => {
170
+ const encrypted = await encryptSecret('test-secret', 'key');
171
+ // Base64 characters only
172
+ expect(encrypted).toMatch(/^[A-Za-z0-9+/=]+$/);
173
+ });
174
+
175
+ it('different keys produce different ciphertexts', async () => {
176
+ const e1 = await encryptSecret('same', 'key1');
177
+ const e2 = await encryptSecret('same', 'key2');
178
+ expect(e1).not.toBe(e2);
179
+ });
180
+
181
+ it('same key + same plaintext → different ciphertexts (random IV)', async () => {
182
+ const e1 = await encryptSecret('same', 'same-key');
183
+ const e2 = await encryptSecret('same', 'same-key');
184
+ expect(e1).not.toBe(e2);
185
+ });
186
+
187
+ it('wrong key → throws (AES-GCM decryption failure)', async () => {
188
+ const encrypted = await encryptSecret('secret', 'correct-key');
189
+ await expect(
190
+ decryptSecret(encrypted, 'wrong-key'),
191
+ ).rejects.toThrow();
192
+ });
193
+
194
+ it('unicode secret round-trip', async () => {
195
+ const original = '한글시크릿🔑';
196
+ const encrypted = await encryptSecret(original, 'key');
197
+ const decrypted = await decryptSecret(encrypted, 'key');
198
+ expect(decrypted).toBe(original);
199
+ });
200
+ });