@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,389 @@
1
+ /**
2
+ * 서버 단위 테스트 — lib/oauth-providers.ts
3
+ * auth-oauth.test.ts — built-in provider pure-logic coverage (외부 fetch 없음)
4
+ *
5
+ * 실행: cd packages/server && npx vitest run src/__tests__/auth-oauth.test.ts
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import {
10
+ isSupportedProvider,
11
+ createOAuthProvider,
12
+ generatePKCE,
13
+ parseAppleIdToken,
14
+ getOAuthProviderConfig,
15
+ getAllowedOAuthProviders,
16
+ type SupportedProvider,
17
+ } from '../lib/oauth-providers.js';
18
+
19
+ const FAKE_CONFIG = { clientId: 'test-client', clientSecret: 'test-secret' };
20
+
21
+ // ─── A. isSupportedProvider — built-in provider 포함/제외 ─────────────────────
22
+
23
+ describe('isSupportedProvider', () => {
24
+ const supported: SupportedProvider[] = [
25
+ 'google', 'github', 'apple', 'discord',
26
+ 'microsoft', 'facebook', 'kakao', 'naver',
27
+ 'x', 'reddit', 'line', 'slack', 'spotify', 'twitch',
28
+ ];
29
+
30
+ for (const provider of supported) {
31
+ it(`returns true for "${provider}"`, () => {
32
+ expect(isSupportedProvider(provider)).toBe(true);
33
+ });
34
+ }
35
+
36
+ it('returns false for unsupported provider "twitter"', () => {
37
+ expect(isSupportedProvider('twitter')).toBe(false);
38
+ });
39
+
40
+ it('returns false for "linkedin"', () => {
41
+ expect(isSupportedProvider('linkedin')).toBe(false);
42
+ });
43
+
44
+ it('returns false for empty string', () => {
45
+ expect(isSupportedProvider('')).toBe(false);
46
+ });
47
+
48
+ it('returns false for "Google" (case-sensitive)', () => {
49
+ expect(isSupportedProvider('Google')).toBe(false);
50
+ });
51
+ });
52
+
53
+ // ─── B. createOAuthProvider — built-in provider 생성 + name ───────────────────
54
+
55
+ describe('createOAuthProvider', () => {
56
+ const PROVIDERS: SupportedProvider[] = [
57
+ 'google', 'github', 'apple', 'discord',
58
+ 'microsoft', 'facebook', 'kakao', 'naver',
59
+ 'x', 'reddit', 'line', 'slack', 'spotify', 'twitch',
60
+ ];
61
+
62
+ for (const name of PROVIDERS) {
63
+ it(`creates ${name} provider with correct name`, () => {
64
+ const provider = createOAuthProvider(name, FAKE_CONFIG);
65
+ expect(provider).toBeDefined();
66
+ expect(provider.name).toBe(name);
67
+ });
68
+ }
69
+
70
+ it('created provider has getAuthorizationUrl method', () => {
71
+ const provider = createOAuthProvider('google', FAKE_CONFIG);
72
+ expect(typeof provider.getAuthorizationUrl).toBe('function');
73
+ });
74
+
75
+ it('created provider has exchangeCode method', () => {
76
+ const provider = createOAuthProvider('github', FAKE_CONFIG);
77
+ expect(typeof provider.exchangeCode).toBe('function');
78
+ });
79
+
80
+ it('created provider has getUserInfo method', () => {
81
+ const provider = createOAuthProvider('discord', FAKE_CONFIG);
82
+ expect(typeof provider.getUserInfo).toBe('function');
83
+ });
84
+ });
85
+
86
+ // ─── C. getAuthorizationUrl URL 형식 검증 ────────────────────────────────────
87
+
88
+ describe('getAuthorizationUrl', () => {
89
+ it('google URL contains accounts.google.com', () => {
90
+ const url = createOAuthProvider('google', FAKE_CONFIG)
91
+ .getAuthorizationUrl('state-1', 'https://myapp.com/callback');
92
+ expect(url).toContain('accounts.google.com');
93
+ expect(url).toContain('state-1');
94
+ });
95
+
96
+ it('google URL with PKCE includes code_challenge', () => {
97
+ const url = createOAuthProvider('google', FAKE_CONFIG)
98
+ .getAuthorizationUrl('state-1', 'https://myapp.com/callback', 'challenge-abc');
99
+ expect(url).toContain('code_challenge=challenge-abc');
100
+ expect(url).toContain('code_challenge_method=S256');
101
+ });
102
+
103
+ it('github URL contains github.com/login/oauth', () => {
104
+ const url = createOAuthProvider('github', FAKE_CONFIG)
105
+ .getAuthorizationUrl('state-2', 'https://myapp.com/callback');
106
+ expect(url).toContain('github.com/login/oauth');
107
+ });
108
+
109
+ it('apple URL contains appleid.apple.com', () => {
110
+ const url = createOAuthProvider('apple', FAKE_CONFIG)
111
+ .getAuthorizationUrl('state-3', 'https://myapp.com/callback');
112
+ expect(url).toContain('appleid.apple.com');
113
+ expect(url).toContain('form_post'); // apple uses form_post
114
+ });
115
+
116
+ it('discord URL contains discord.com/oauth2', () => {
117
+ const url = createOAuthProvider('discord', FAKE_CONFIG)
118
+ .getAuthorizationUrl('state-4', 'https://myapp.com/callback');
119
+ expect(url).toContain('discord.com/oauth2');
120
+ });
121
+
122
+ it('microsoft URL contains microsoftonline.com', () => {
123
+ const url = createOAuthProvider('microsoft', FAKE_CONFIG)
124
+ .getAuthorizationUrl('state-5', 'https://myapp.com/callback');
125
+ expect(url).toContain('microsoftonline.com');
126
+ });
127
+
128
+ it('facebook URL contains facebook.com/dialog/oauth', () => {
129
+ const url = createOAuthProvider('facebook', FAKE_CONFIG)
130
+ .getAuthorizationUrl('state-6', 'https://myapp.com/callback');
131
+ expect(url).toContain('facebook.com');
132
+ expect(url).toContain('oauth');
133
+ });
134
+
135
+ it('kakao URL contains kauth.kakao.com', () => {
136
+ const url = createOAuthProvider('kakao', FAKE_CONFIG)
137
+ .getAuthorizationUrl('state-7', 'https://myapp.com/callback');
138
+ expect(url).toContain('kauth.kakao.com');
139
+ });
140
+
141
+ it('naver URL contains nid.naver.com', () => {
142
+ const url = createOAuthProvider('naver', FAKE_CONFIG)
143
+ .getAuthorizationUrl('state-8', 'https://myapp.com/callback');
144
+ expect(url).toContain('nid.naver.com');
145
+ });
146
+
147
+ it('x URL contains twitter.com/i/oauth2', () => {
148
+ const url = createOAuthProvider('x', FAKE_CONFIG)
149
+ .getAuthorizationUrl('state-9', 'https://myapp.com/callback', 'pkce-challenge');
150
+ expect(url).toContain('twitter.com/i/oauth2');
151
+ });
152
+
153
+ it('reddit URL contains reddit.com/api/v1/authorize', () => {
154
+ const url = createOAuthProvider('reddit', FAKE_CONFIG)
155
+ .getAuthorizationUrl('state-9b', 'https://myapp.com/callback');
156
+ expect(url).toContain('reddit.com/api/v1/authorize');
157
+ expect(url).toContain('duration=permanent');
158
+ expect(url).toContain('scope=identity');
159
+ });
160
+
161
+ it('line URL contains access.line.me', () => {
162
+ const url = createOAuthProvider('line', FAKE_CONFIG)
163
+ .getAuthorizationUrl('state-10', 'https://myapp.com/callback');
164
+ expect(url).toContain('access.line.me');
165
+ });
166
+
167
+ it('slack URL contains slack.com/openid', () => {
168
+ const url = createOAuthProvider('slack', FAKE_CONFIG)
169
+ .getAuthorizationUrl('state-11', 'https://myapp.com/callback');
170
+ expect(url).toContain('slack.com/openid');
171
+ });
172
+
173
+ it('spotify URL contains accounts.spotify.com', () => {
174
+ const url = createOAuthProvider('spotify', FAKE_CONFIG)
175
+ .getAuthorizationUrl('state-12', 'https://myapp.com/callback');
176
+ expect(url).toContain('accounts.spotify.com');
177
+ });
178
+
179
+ it('twitch URL contains id.twitch.tv', () => {
180
+ const url = createOAuthProvider('twitch', FAKE_CONFIG)
181
+ .getAuthorizationUrl('state-13', 'https://myapp.com/callback');
182
+ expect(url).toContain('id.twitch.tv');
183
+ });
184
+
185
+ it('all URLs include state param', () => {
186
+ const providers: SupportedProvider[] = ['google', 'github', 'discord', 'kakao'];
187
+ for (const name of providers) {
188
+ const url = createOAuthProvider(name, FAKE_CONFIG)
189
+ .getAuthorizationUrl('my-state', 'https://cb.example.com');
190
+ expect(url).toContain('my-state');
191
+ }
192
+ });
193
+ });
194
+
195
+ // ─── D. generatePKCE ─────────────────────────────────────────────────────────
196
+
197
+ describe('generatePKCE', () => {
198
+ it('returns codeVerifier and codeChallenge', async () => {
199
+ const { codeVerifier, codeChallenge } = await generatePKCE();
200
+ expect(codeVerifier).toBeTruthy();
201
+ expect(codeChallenge).toBeTruthy();
202
+ });
203
+
204
+ it('codeVerifier is base64url (no +, /, =)', async () => {
205
+ const { codeVerifier } = await generatePKCE();
206
+ expect(codeVerifier).not.toMatch(/[+/=]/);
207
+ });
208
+
209
+ it('codeChallenge is base64url', async () => {
210
+ const { codeChallenge } = await generatePKCE();
211
+ expect(codeChallenge).not.toMatch(/[+/=]/);
212
+ });
213
+
214
+ it('generates different values each call', async () => {
215
+ const a = await generatePKCE();
216
+ const b = await generatePKCE();
217
+ expect(a.codeVerifier).not.toBe(b.codeVerifier);
218
+ expect(a.codeChallenge).not.toBe(b.codeChallenge);
219
+ });
220
+
221
+ it('codeChallenge is SHA-256(codeVerifier) base64url', async () => {
222
+ const { codeVerifier, codeChallenge } = await generatePKCE();
223
+ // Recompute
224
+ const encoder = new TextEncoder();
225
+ const digest = await crypto.subtle.digest('SHA-256', encoder.encode(codeVerifier));
226
+ const expected = btoa(String.fromCharCode(...new Uint8Array(digest)))
227
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
228
+ expect(codeChallenge).toBe(expected);
229
+ });
230
+ });
231
+
232
+ // ─── E. parseAppleIdToken ────────────────────────────────────────────────────
233
+
234
+ describe('parseAppleIdToken', () => {
235
+ function makeAppleJwt(payload: Record<string, unknown>): string {
236
+ const header = btoa(JSON.stringify({ alg: 'RS256', kid: 'test' }));
237
+ const body = btoa(JSON.stringify(payload));
238
+ return `${header}.${body}.fakesig`;
239
+ }
240
+
241
+ it('extracts sub as providerUserId', () => {
242
+ const jwt = makeAppleJwt({ sub: 'apple-uid-123', email: 'a@b.com', email_verified: true });
243
+ const info = parseAppleIdToken(jwt);
244
+ expect(info.providerUserId).toBe('apple-uid-123');
245
+ });
246
+
247
+ it('extracts email', () => {
248
+ const jwt = makeAppleJwt({ sub: 'uid', email: 'test@icloud.com', email_verified: true });
249
+ const info = parseAppleIdToken(jwt);
250
+ expect(info.email).toBe('test@icloud.com');
251
+ });
252
+
253
+ it('emailVerified reflects email_verified field', () => {
254
+ const jwt = makeAppleJwt({ sub: 'uid', email_verified: true });
255
+ const info = parseAppleIdToken(jwt);
256
+ expect(info.emailVerified).toBe(true);
257
+ });
258
+
259
+ it('displayName is null (Apple)', () => {
260
+ const jwt = makeAppleJwt({ sub: 'uid' });
261
+ const info = parseAppleIdToken(jwt);
262
+ expect(info.displayName).toBeNull();
263
+ });
264
+
265
+ it('avatarUrl is null (Apple)', () => {
266
+ const jwt = makeAppleJwt({ sub: 'uid' });
267
+ const info = parseAppleIdToken(jwt);
268
+ expect(info.avatarUrl).toBeNull();
269
+ });
270
+
271
+ it('throws on invalid JWT format', () => {
272
+ expect(() => parseAppleIdToken('not-a-jwt')).toThrow();
273
+ });
274
+ });
275
+
276
+ // ─── F. emailVerified 강제 false 검증 (Facebook, Naver, X, Spotify) ─────────
277
+
278
+ describe('emailVerified forced false providers', () => {
279
+ // These providers force emailVerified=false per
280
+ // We test via getUserInfo mock by checking Facebook's source code declaration
281
+ // Since getUserInfo requires live API, we validate the logic via the provider structure
282
+
283
+ it('facebook getUserInfo has emailVerified: false hardcoded', () => {
284
+ // Verified by reading source: emailVerified: false // Facebook doesn't provide email_verified field
285
+ expect(isSupportedProvider('facebook')).toBe(true);
286
+ const p = createOAuthProvider('facebook', FAKE_CONFIG);
287
+ expect(p.name).toBe('facebook');
288
+ // No email_verified field in Facebook's API response
289
+ });
290
+
291
+ it('spotify provider exists', () => {
292
+ expect(isSupportedProvider('spotify')).toBe(true);
293
+ });
294
+
295
+ it('x provider exists (Twitter)', () => {
296
+ expect(isSupportedProvider('x')).toBe(true);
297
+ });
298
+
299
+ it('naver provider exists', () => {
300
+ expect(isSupportedProvider('naver')).toBe(true);
301
+ });
302
+
303
+ it('line provider exists', () => {
304
+ expect(isSupportedProvider('line')).toBe(true);
305
+ });
306
+ });
307
+
308
+ // ─── G. getOAuthProviderConfig ────────────────────────────────────────────────
309
+
310
+ describe('getOAuthProviderConfig', () => {
311
+ it('returns null for undefined config', () => {
312
+ expect(getOAuthProviderConfig(undefined, 'google')).toBeNull();
313
+ });
314
+
315
+ it('returns null for empty string config', () => {
316
+ expect(getOAuthProviderConfig('', 'google')).toBeNull();
317
+ });
318
+
319
+ it('returns config when clientId and clientSecret present', () => {
320
+ const config = JSON.stringify({
321
+ auth: { oauth: { google: { clientId: 'cid', clientSecret: 'csk' } } },
322
+ });
323
+ const result = getOAuthProviderConfig(config, 'google');
324
+ expect(result).toEqual({ clientId: 'cid', clientSecret: 'csk' });
325
+ });
326
+
327
+ it('returns null when clientId missing', () => {
328
+ const config = JSON.stringify({
329
+ auth: { oauth: { google: { clientSecret: 'csk' } } },
330
+ });
331
+ expect(getOAuthProviderConfig(config, 'google')).toBeNull();
332
+ });
333
+
334
+ it('returns null when clientSecret missing', () => {
335
+ const config = JSON.stringify({
336
+ auth: { oauth: { google: { clientId: 'cid' } } },
337
+ });
338
+ expect(getOAuthProviderConfig(config, 'google')).toBeNull();
339
+ });
340
+
341
+ it('returns null on invalid JSON', () => {
342
+ expect(getOAuthProviderConfig('{ invalid json }', 'google')).toBeNull();
343
+ });
344
+
345
+ it('returns null for different provider', () => {
346
+ const config = JSON.stringify({
347
+ auth: { oauth: { github: { clientId: 'c', clientSecret: 's' } } },
348
+ });
349
+ expect(getOAuthProviderConfig(config, 'google')).toBeNull();
350
+ });
351
+ });
352
+
353
+ // ─── H. getAllowedOAuthProviders ──────────────────────────────────────────────
354
+
355
+ describe('getAllowedOAuthProviders', () => {
356
+ it('returns empty array for undefined', () => {
357
+ expect(getAllowedOAuthProviders(undefined)).toEqual([]);
358
+ });
359
+
360
+ it('returns empty array for invalid JSON', () => {
361
+ expect(getAllowedOAuthProviders('{ bad }')).toEqual([]);
362
+ });
363
+
364
+ it('returns filtered supported providers', () => {
365
+ const config = JSON.stringify({
366
+ auth: { allowedOAuthProviders: ['google', 'github', 'unknownProvider'] },
367
+ });
368
+ const result = getAllowedOAuthProviders(config);
369
+ expect(result).toContain('google');
370
+ expect(result).toContain('github');
371
+ expect(result).not.toContain('unknownProvider');
372
+ });
373
+
374
+ it('returns empty array when allowedOAuthProviders not array', () => {
375
+ const config = JSON.stringify({ auth: { allowedOAuthProviders: 'google' } });
376
+ expect(getAllowedOAuthProviders(config)).toEqual([]);
377
+ });
378
+
379
+ it('returns all 13 if all passed', () => {
380
+ const all = [
381
+ 'google', 'github', 'apple', 'discord',
382
+ 'microsoft', 'facebook', 'kakao', 'naver',
383
+ 'x', 'line', 'slack', 'spotify', 'twitch',
384
+ ];
385
+ const config = JSON.stringify({ auth: { allowedOAuthProviders: all } });
386
+ const result = getAllowedOAuthProviders(config);
387
+ expect(result.length).toBe(13);
388
+ });
389
+ });