@things-factory/auth-base 10.1.278 → 10.1.280

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 (284) hide show
  1. package/package.json +24 -7
  2. package/CHANGELOG.md +0 -55
  3. package/dist-client/actions/auth.js.map +0 -1
  4. package/dist-client/apply-domain-theme.js.map +0 -1
  5. package/dist-client/auth.js.map +0 -1
  6. package/dist-client/bootstrap.js.map +0 -1
  7. package/dist-client/directive/privileged.js.map +0 -1
  8. package/dist-client/domain-required.js.map +0 -1
  9. package/dist-client/index.js.map +0 -1
  10. package/dist-client/profiled.js.map +0 -1
  11. package/dist-client/reducers/auth.js.map +0 -1
  12. package/dist-client/tsconfig.tsbuildinfo +0 -1
  13. package/dist-client/verify-webauthn.js.map +0 -1
  14. package/dist-server/ai-entry/access-assistant.js.map +0 -1
  15. package/dist-server/ai-entry/access-entry-targets.js.map +0 -1
  16. package/dist-server/ai-entry/entry-target-shape.js.map +0 -1
  17. package/dist-server/constants/error-code.js.map +0 -1
  18. package/dist-server/constants/error-message.js.map +0 -1
  19. package/dist-server/constants/max-age.js.map +0 -1
  20. package/dist-server/controllers/access-request.js.map +0 -1
  21. package/dist-server/controllers/account-mail.js.map +0 -1
  22. package/dist-server/controllers/auth.js.map +0 -1
  23. package/dist-server/controllers/change-pwd.js.map +0 -1
  24. package/dist-server/controllers/confirm-identity.js.map +0 -1
  25. package/dist-server/controllers/delete-user.js.map +0 -1
  26. package/dist-server/controllers/passkey.js.map +0 -1
  27. package/dist-server/controllers/password-setup.js.map +0 -1
  28. package/dist-server/controllers/profile.js.map +0 -1
  29. package/dist-server/controllers/reset-password.js.map +0 -1
  30. package/dist-server/controllers/signin.js.map +0 -1
  31. package/dist-server/controllers/signup.js.map +0 -1
  32. package/dist-server/controllers/unlock-user.js.map +0 -1
  33. package/dist-server/controllers/utils/find-verification-token.js.map +0 -1
  34. package/dist-server/controllers/utils/make-verification-token.js.map +0 -1
  35. package/dist-server/controllers/utils/password-rule.js.map +0 -1
  36. package/dist-server/controllers/utils/save-verification-token.js.map +0 -1
  37. package/dist-server/controllers/verification.js.map +0 -1
  38. package/dist-server/errors/auth-error.js.map +0 -1
  39. package/dist-server/errors/index.js.map +0 -1
  40. package/dist-server/errors/user-domain-not-match-error.js.map +0 -1
  41. package/dist-server/index.js.map +0 -1
  42. package/dist-server/middlewares/audit-scope-middleware.js.map +0 -1
  43. package/dist-server/middlewares/authenticate-401-middleware.js.map +0 -1
  44. package/dist-server/middlewares/bypass-signin-middleware.js.map +0 -1
  45. package/dist-server/middlewares/domain-authenticate-middleware.js.map +0 -1
  46. package/dist-server/middlewares/graphql-authenticate-middleware.js.map +0 -1
  47. package/dist-server/middlewares/index.js.map +0 -1
  48. package/dist-server/middlewares/jwt-authenticate-middleware.js.map +0 -1
  49. package/dist-server/middlewares/signin-middleware.js.map +0 -1
  50. package/dist-server/middlewares/verify-recaptcha-middleware.js.map +0 -1
  51. package/dist-server/migrations/1548206416130-SeedUser.js.map +0 -1
  52. package/dist-server/migrations/1566805283882-SeedPrivilege.js.map +0 -1
  53. package/dist-server/migrations/1745000000000-MigrateDomainOwnersToTable.js.map +0 -1
  54. package/dist-server/migrations/index.js.map +0 -1
  55. package/dist-server/role-tasks.js.map +0 -1
  56. package/dist-server/router/auth-checkin-router.js.map +0 -1
  57. package/dist-server/router/auth-private-process-router.js.map +0 -1
  58. package/dist-server/router/auth-public-process-router.js.map +0 -1
  59. package/dist-server/router/auth-signin-router.js.map +0 -1
  60. package/dist-server/router/auth-signup-router.js.map +0 -1
  61. package/dist-server/router/index.js.map +0 -1
  62. package/dist-server/router/oauth2/index.js.map +0 -1
  63. package/dist-server/router/oauth2/oauth2-authorize-router.js.map +0 -1
  64. package/dist-server/router/oauth2/oauth2-router.js.map +0 -1
  65. package/dist-server/router/oauth2/oauth2-server.js.map +0 -1
  66. package/dist-server/router/oauth2/passport-oauth2-client-password.js.map +0 -1
  67. package/dist-server/router/oauth2/passport-refresh-token.js.map +0 -1
  68. package/dist-server/router/path-base-domain-router.js.map +0 -1
  69. package/dist-server/router/site-root-router.js.map +0 -1
  70. package/dist-server/router/webauthn-router.js.map +0 -1
  71. package/dist-server/routes.js.map +0 -1
  72. package/dist-server/service/access-request/access-request-mutation.js.map +0 -1
  73. package/dist-server/service/access-request/access-request-query.js.map +0 -1
  74. package/dist-server/service/access-request/access-request-types.js.map +0 -1
  75. package/dist-server/service/access-request/access-request.js.map +0 -1
  76. package/dist-server/service/access-request/index.js.map +0 -1
  77. package/dist-server/service/app-binding/app-binding-mutation.js.map +0 -1
  78. package/dist-server/service/app-binding/app-binding-query.js.map +0 -1
  79. package/dist-server/service/app-binding/app-binding-types.js.map +0 -1
  80. package/dist-server/service/app-binding/app-binding.js.map +0 -1
  81. package/dist-server/service/app-binding/index.js.map +0 -1
  82. package/dist-server/service/appliance/appliance-mutation.js.map +0 -1
  83. package/dist-server/service/appliance/appliance-query.js.map +0 -1
  84. package/dist-server/service/appliance/appliance-types.js.map +0 -1
  85. package/dist-server/service/appliance/appliance.js.map +0 -1
  86. package/dist-server/service/appliance/index.js.map +0 -1
  87. package/dist-server/service/application/application-mutation.js.map +0 -1
  88. package/dist-server/service/application/application-query.js.map +0 -1
  89. package/dist-server/service/application/application-types.js.map +0 -1
  90. package/dist-server/service/application/application.js.map +0 -1
  91. package/dist-server/service/application/index.js.map +0 -1
  92. package/dist-server/service/audit/audit-action.js.map +0 -1
  93. package/dist-server/service/audit/audit-kinds.js.map +0 -1
  94. package/dist-server/service/audit/audit-query.js.map +0 -1
  95. package/dist-server/service/audit/audit-recorder.js.map +0 -1
  96. package/dist-server/service/audit/audit-request.js.map +0 -1
  97. package/dist-server/service/audit/audit-retention.js.map +0 -1
  98. package/dist-server/service/audit/audit-subscriber.js.map +0 -1
  99. package/dist-server/service/audit/audit-target.js.map +0 -1
  100. package/dist-server/service/audit/audit-types.js.map +0 -1
  101. package/dist-server/service/audit/audit-work.js.map +0 -1
  102. package/dist-server/service/audit/index.js.map +0 -1
  103. package/dist-server/service/auth-provider/auth-provider-mutation.js.map +0 -1
  104. package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +0 -1
  105. package/dist-server/service/auth-provider/auth-provider-query.js.map +0 -1
  106. package/dist-server/service/auth-provider/auth-provider-type.js.map +0 -1
  107. package/dist-server/service/auth-provider/auth-provider.js.map +0 -1
  108. package/dist-server/service/auth-provider/index.js.map +0 -1
  109. package/dist-server/service/domain-generator/domain-generator-mutation.js.map +0 -1
  110. package/dist-server/service/domain-generator/domain-generator-types.js.map +0 -1
  111. package/dist-server/service/domain-generator/index.js.map +0 -1
  112. package/dist-server/service/domain-link/domain-link-mutation.js.map +0 -1
  113. package/dist-server/service/domain-link/domain-link-query.js.map +0 -1
  114. package/dist-server/service/domain-link/domain-link-types.js.map +0 -1
  115. package/dist-server/service/domain-link/domain-link.js.map +0 -1
  116. package/dist-server/service/domain-link/index.js.map +0 -1
  117. package/dist-server/service/domain-owner/domain-owner-mutation.js.map +0 -1
  118. package/dist-server/service/domain-owner/domain-owner-query.js.map +0 -1
  119. package/dist-server/service/domain-owner/domain-owner.js.map +0 -1
  120. package/dist-server/service/domain-owner/index.js.map +0 -1
  121. package/dist-server/service/granted-role/granted-role-mutation.js.map +0 -1
  122. package/dist-server/service/granted-role/granted-role-query.js.map +0 -1
  123. package/dist-server/service/granted-role/granted-role.js.map +0 -1
  124. package/dist-server/service/granted-role/index.js.map +0 -1
  125. package/dist-server/service/identity-confirmation/identity-confirmation.js.map +0 -1
  126. package/dist-server/service/index.js.map +0 -1
  127. package/dist-server/service/open-fields.js.map +0 -1
  128. package/dist-server/service/partner/index.js.map +0 -1
  129. package/dist-server/service/partner/partner-mutation.js.map +0 -1
  130. package/dist-server/service/partner/partner-query.js.map +0 -1
  131. package/dist-server/service/partner/partner-types.js.map +0 -1
  132. package/dist-server/service/partner/partner.js.map +0 -1
  133. package/dist-server/service/password-history/index.js.map +0 -1
  134. package/dist-server/service/password-history/password-history.js.map +0 -1
  135. package/dist-server/service/privilege/index.js.map +0 -1
  136. package/dist-server/service/privilege/open-field-rule.js.map +0 -1
  137. package/dist-server/service/privilege/privilege-axis.js.map +0 -1
  138. package/dist-server/service/privilege/privilege-deprecation.js.map +0 -1
  139. package/dist-server/service/privilege/privilege-directive.js.map +0 -1
  140. package/dist-server/service/privilege/privilege-mutation.js.map +0 -1
  141. package/dist-server/service/privilege/privilege-query.js.map +0 -1
  142. package/dist-server/service/privilege/privilege-rejection.js.map +0 -1
  143. package/dist-server/service/privilege/privilege-sync.js.map +0 -1
  144. package/dist-server/service/privilege/privilege-types.js.map +0 -1
  145. package/dist-server/service/privilege/privilege.js.map +0 -1
  146. package/dist-server/service/role/index.js.map +0 -1
  147. package/dist-server/service/role/role-mutation.js.map +0 -1
  148. package/dist-server/service/role/role-privilege-query.js.map +0 -1
  149. package/dist-server/service/role/role-query.js.map +0 -1
  150. package/dist-server/service/role/role-types.js.map +0 -1
  151. package/dist-server/service/role/role.js.map +0 -1
  152. package/dist-server/service/role-template/index.js.map +0 -1
  153. package/dist-server/service/role-template/role-task.js.map +0 -1
  154. package/dist-server/service/role-template/role-template-gap-resolver.js.map +0 -1
  155. package/dist-server/service/role-template/role-template-mutation.js.map +0 -1
  156. package/dist-server/service/role-template/role-template-query.js.map +0 -1
  157. package/dist-server/service/role-template/role-template-types.js.map +0 -1
  158. package/dist-server/service/role-template/role-template.js.map +0 -1
  159. package/dist-server/service/user/domain-query.js.map +0 -1
  160. package/dist-server/service/user/index.js.map +0 -1
  161. package/dist-server/service/user/user-mutation.js.map +0 -1
  162. package/dist-server/service/user/user-query.js.map +0 -1
  163. package/dist-server/service/user/user-types.js.map +0 -1
  164. package/dist-server/service/user/user.js.map +0 -1
  165. package/dist-server/service/users-auth-providers/index.js.map +0 -1
  166. package/dist-server/service/users-auth-providers/users-auth-providers.js.map +0 -1
  167. package/dist-server/service/verification-token/index.js.map +0 -1
  168. package/dist-server/service/verification-token/verification-token.js.map +0 -1
  169. package/dist-server/service/web-auth-credential/index.js.map +0 -1
  170. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +0 -1
  171. package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +0 -1
  172. package/dist-server/service/web-auth-credential/web-auth-credential.js.map +0 -1
  173. package/dist-server/tsconfig.tsbuildinfo +0 -1
  174. package/dist-server/types.js.map +0 -1
  175. package/dist-server/utils/accepts.js.map +0 -1
  176. package/dist-server/utils/access-request-review.js.map +0 -1
  177. package/dist-server/utils/access-token-cookie.js.map +0 -1
  178. package/dist-server/utils/account-lock-rule.js.map +0 -1
  179. package/dist-server/utils/assignable-roles.js.map +0 -1
  180. package/dist-server/utils/build-auth-url.js.map +0 -1
  181. package/dist-server/utils/can-user-checkin-domain.js.map +0 -1
  182. package/dist-server/utils/check-permission.js.map +0 -1
  183. package/dist-server/utils/check-user-belongs-domain.js.map +0 -1
  184. package/dist-server/utils/check-user-has-role.js.map +0 -1
  185. package/dist-server/utils/checkin-domain-facts.js.map +0 -1
  186. package/dist-server/utils/checkin-domain-rule.js.map +0 -1
  187. package/dist-server/utils/credential-at-rest.js.map +0 -1
  188. package/dist-server/utils/credential-features.js.map +0 -1
  189. package/dist-server/utils/credential-serial-rule.js.map +0 -1
  190. package/dist-server/utils/domain-ownership.js.map +0 -1
  191. package/dist-server/utils/encrypt-state.js.map +0 -1
  192. package/dist-server/utils/get-aes-256-key.js.map +0 -1
  193. package/dist-server/utils/get-domain-users.js.map +0 -1
  194. package/dist-server/utils/get-secret.js.map +0 -1
  195. package/dist-server/utils/get-user-domains.js.map +0 -1
  196. package/dist-server/utils/lock-duration.js.map +0 -1
  197. package/dist-server/utils/password-reset-rule.js.map +0 -1
  198. package/dist-server/utils/profile-user.js.map +0 -1
  199. package/dist-server/utils/refuse-role-name.js.map +0 -1
  200. package/dist-server/utils/role-name-standing.js.map +0 -1
  201. package/dist-server/utils/same-origin-redirect.js.map +0 -1
  202. package/dist-server/utils/verification-token-rule.js.map +0 -1
  203. package/spec/unit/checkin-domain.spec.ts +0 -307
  204. package/spec/unit/checkin-privilege.spec.ts +0 -205
  205. package/spec/unit/domain-owner-entity.spec.ts +0 -179
  206. package/spec/unit/domain-owner-granted.spec.ts +0 -215
  207. package/spec/unit/domain-owner-migration.spec.ts +0 -236
  208. package/spec/unit/domain-owner-mutation.spec.ts +0 -337
  209. package/spec/unit/domain-ownership.spec.ts +0 -188
  210. package/tests/access-assistant-db.test.ts +0 -155
  211. package/tests/access-entry-targets-db.test.ts +0 -176
  212. package/tests/access-request-db.test.ts +0 -394
  213. package/tests/access-request-pg.test.ts +0 -83
  214. package/tests/access-request-review.test.ts +0 -193
  215. package/tests/account-lock-db.test.ts +0 -216
  216. package/tests/account-lock-rule.test.ts +0 -141
  217. package/tests/account-mail.test.ts +0 -69
  218. package/tests/activate-page-case.test.ts +0 -53
  219. package/tests/add-user-gates.test.ts +0 -34
  220. package/tests/app-binding-db.test.ts +0 -210
  221. package/tests/app-binding-delete-db.test.ts +0 -188
  222. package/tests/appliance-credential-state-db.test.ts +0 -207
  223. package/tests/appliance-delete-db.test.ts +0 -119
  224. package/tests/appliance-revoke-db.test.ts +0 -294
  225. package/tests/appliance-schema.test.ts +0 -129
  226. package/tests/application-delete-db.test.ts +0 -190
  227. package/tests/application-token-payload-db.test.ts +0 -185
  228. package/tests/assignable-roles-db.test.ts +0 -219
  229. package/tests/audit-db.test.ts +0 -410
  230. package/tests/audit-work.test.ts +0 -58
  231. package/tests/auth-base-role-tasks.test.ts +0 -58
  232. package/tests/checkin-domain-rule.test.ts +0 -128
  233. package/tests/checkin-privilege-seam-db.test.ts +0 -158
  234. package/tests/compiled.ts +0 -41
  235. package/tests/confirm-identity-db.test.ts +0 -85
  236. package/tests/credential-at-rest-db.test.ts +0 -246
  237. package/tests/credential-features-off-db.test.ts +0 -189
  238. package/tests/credential-serial-rule.test.ts +0 -84
  239. package/tests/declare-privileges.test.ts +0 -29
  240. package/tests/domain-assignees-db.test.ts +0 -95
  241. package/tests/domain-inheritance-sentinel.test.ts +0 -93
  242. package/tests/domain-required.test.ts +0 -127
  243. package/tests/has-privilege-db.test.ts +0 -198
  244. package/tests/identity-confirmation.test.ts +0 -146
  245. package/tests/list-inheritance-db.test.ts +0 -127
  246. package/tests/lock-recovery.test.ts +0 -206
  247. package/tests/mail-request-answers-db.test.ts +0 -142
  248. package/tests/open-doors.test.ts +0 -121
  249. package/tests/open-fields-enforcement.test.ts +0 -215
  250. package/tests/open-fields-framework.test.ts +0 -212
  251. package/tests/open-redirect-db.test.ts +0 -143
  252. package/tests/passkey-authenticator.ts +0 -104
  253. package/tests/passkey-db.test.ts +0 -295
  254. package/tests/passkey-signin-route-db.test.ts +0 -139
  255. package/tests/password-hashing.test.ts +0 -131
  256. package/tests/password-persistence.test.ts +0 -147
  257. package/tests/password-reset-rule-db.test.ts +0 -159
  258. package/tests/password-setup-db.test.ts +0 -215
  259. package/tests/permission-gate.test.ts +0 -228
  260. package/tests/picture-reducer.test.ts +0 -25
  261. package/tests/privilege-axis.test.ts +0 -157
  262. package/tests/privilege-deprecation.test.ts +0 -65
  263. package/tests/privilege-directive.test.ts +0 -332
  264. package/tests/privilege-rejection.test.ts +0 -104
  265. package/tests/privilege-sync-db.test.ts +0 -58
  266. package/tests/profile-user.test.ts +0 -24
  267. package/tests/refusal-cause.test.ts +0 -80
  268. package/tests/refusals-have-korean.test.ts +0 -24
  269. package/tests/request-holds.test.ts +0 -53
  270. package/tests/role-inheritance-db.test.ts +0 -286
  271. package/tests/role-mutation-db.test.ts +0 -293
  272. package/tests/role-name-lookup-sentinel.test.ts +0 -79
  273. package/tests/role-name-standing.test.ts +0 -83
  274. package/tests/role-privileges-db.test.ts +0 -252
  275. package/tests/role-task.test.ts +0 -168
  276. package/tests/role-template-gap-db.test.ts +0 -268
  277. package/tests/role-template-seed-db.test.ts +0 -180
  278. package/tests/role-template.test.ts +0 -162
  279. package/tests/role-users-gate.test.ts +0 -22
  280. package/tests/signin-policy.test.ts +0 -200
  281. package/tests/signup-duplicate-db.test.ts +0 -68
  282. package/tests/token-issuance.test.ts +0 -244
  283. package/tests/user-roles-field-db.test.ts +0 -134
  284. package/tests/verification-token-db.test.ts +0 -285
@@ -1,332 +0,0 @@
1
- /**
2
- * `@privilege` 지시자 — **이음매** 시험.
3
- *
4
- * `permission-gate.test.ts` 는 판정 규칙을 본다. 그런데 규칙이 옳아도 **부르는 자리**가 틀리면
5
- * 화면은 열린다. 그 자리가 이 지시자다: 스키마의 모든 필드를 훑어 게이트를 감싸고, 거절을
6
- * `extensions` 와 함께 던진다. 지시자 안에 인라인으로 있던 문장 만들기는 이미 떼어냈는데
7
- * (`privilege-rejection.ts`), **감싸는 행위 자체**에는 시험이 없었다.
8
- *
9
- * 그래서 여기서는 실제로 스키마를 짓고 쿼리를 태운다. type-graphql 리졸버는 jest 에서 들 수
10
- * 없으므로(tests/compiled.ts) SDL 로 같은 모양을 만든다 — 지시자가 보는 것은 필드 설정과
11
- * `context.state` 뿐이므로 판정 경로는 동일하다.
12
- */
13
-
14
- import { graphql } from 'graphql'
15
- import { makeExecutableSchema } from '@graphql-tools/schema'
16
-
17
- import { loadCompiled } from './compiled'
18
-
19
- const { privilegeDirectiveTypeDefs, privilegeDirectiveResolver, privilegeOfField } = loadCompiled(
20
- 'service/privilege/privilege-directive'
21
- )
22
- const { User } = loadCompiled('service/user/user')
23
-
24
- const DOMAIN = { id: 'domain-1', subdomain: 'plant-a' }
25
- const USER = { id: 'user-1', domains: [DOMAIN] }
26
-
27
- /**
28
- * 지시자가 붙은 필드와 붙지 않은 필드를 나란히 둔다 — 이 대비가 「기본값이 열려 있다」를
29
- * 눈으로 보여 준다.
30
- */
31
- const typeDefs = `
32
- type Query {
33
- guardedBoard: String @privilege(category: "board", privilege: "mutation")
34
- ownershipOnly: String @privilege(domainOwnerGranted: true)
35
- undeclared: String
36
- }
37
- `
38
-
39
- function buildSchema() {
40
- const schema = makeExecutableSchema({
41
- typeDefs: [privilegeDirectiveTypeDefs, typeDefs],
42
- resolvers: {
43
- Query: {
44
- guardedBoard: () => 'board',
45
- ownershipOnly: () => 'ownership',
46
- undeclared: () => 'undeclared'
47
- }
48
- }
49
- })
50
-
51
- return privilegeDirectiveResolver(schema)
52
- }
53
-
54
- function run(schema: any, field: string, state: any = {}) {
55
- return graphql({
56
- schema,
57
- source: `{ ${field} }`,
58
- contextValue: { state: { domain: DOMAIN, user: USER, ...state } }
59
- })
60
- }
61
-
62
- function withOwnership({ owner = false, superUser = false } = {}) {
63
- ;(process as any).domainOwnerGranted = jest.fn().mockResolvedValue(owner)
64
- ;(process as any).superUserGranted = jest.fn().mockResolvedValue(superUser)
65
- }
66
-
67
- beforeEach(() => {
68
- withOwnership()
69
- User.hasPrivilege = jest.fn().mockResolvedValue(false)
70
- })
71
-
72
- afterEach(() => {
73
- delete (process as any).domainOwnerGranted
74
- delete (process as any).superUserGranted
75
- })
76
-
77
- describe('선언하지 않은 필드', () => {
78
- it('아무 권한 없이 열린다 — 지시자를 잊으면 게이트가 없다', async () => {
79
- const result = await run(buildSchema(), 'undeclared')
80
-
81
- expect(result.errors).toBeUndefined()
82
- expect(result.data).toEqual({ undeclared: 'undeclared' })
83
- })
84
-
85
- it('사용자가 없어도 열린다 — 게이트가 아예 감싸지지 않았다', async () => {
86
- const result = await run(buildSchema(), 'undeclared', { user: undefined })
87
-
88
- expect(result.errors).toBeUndefined()
89
- expect(result.data).toEqual({ undeclared: 'undeclared' })
90
- })
91
- })
92
-
93
- describe('선언한 필드', () => {
94
- it('역할이 있으면 값을 준다', async () => {
95
- User.hasPrivilege = jest.fn().mockResolvedValue(true)
96
-
97
- const result = await run(buildSchema(), 'guardedBoard')
98
-
99
- expect(result.errors).toBeUndefined()
100
- expect(result.data).toEqual({ guardedBoard: 'board' })
101
- })
102
-
103
- it('역할이 없으면 값을 주지 않고 거절한다', async () => {
104
- const result = await run(buildSchema(), 'guardedBoard')
105
-
106
- expect(result.data).toEqual({ guardedBoard: null })
107
- expect(result.errors).toHaveLength(1)
108
- })
109
-
110
- it('거절에 무엇이 필요했는지를 구조로 싣는다 — 화면이 문장을 파싱하지 않는다', async () => {
111
- const result = await run(buildSchema(), 'guardedBoard')
112
- const { code, requires } = result.errors![0].extensions as any
113
-
114
- expect(code).toBe('FORBIDDEN')
115
- expect(requires).toEqual({ category: 'board', privilege: 'mutation' })
116
- })
117
-
118
- it('요청의 언어로 거절한다', async () => {
119
- const schema = buildSchema()
120
- const result = await graphql({
121
- schema,
122
- source: '{ guardedBoard }',
123
- contextValue: {
124
- state: { domain: DOMAIN, user: USER },
125
- t: (key: string, params: any) => `${params.x} 권한이 필요합니다.`
126
- }
127
- })
128
-
129
- expect(result.errors![0].message).toBe('board:mutation 권한이 필요합니다.')
130
- })
131
- })
132
-
133
- describe('소유권만 선언한 필드', () => {
134
- it('소유자에게 열린다', async () => {
135
- withOwnership({ owner: true })
136
-
137
- const result = await run(buildSchema(), 'ownershipOnly')
138
-
139
- expect(result.errors).toBeUndefined()
140
- expect(result.data).toEqual({ ownershipOnly: 'ownership' })
141
- })
142
-
143
- it('소유자가 아니면 거절하고, 필요한 것을 `ownership` 으로 말한다', async () => {
144
- const result = await run(buildSchema(), 'ownershipOnly')
145
-
146
- expect(result.errors).toHaveLength(1)
147
- expect((result.errors![0].extensions as any).requires).toBe('ownership')
148
- })
149
- })
150
-
151
- describe('안전하지 않은 IP 가 이음매를 지나 게이트까지 간다', () => {
152
- it('금지 목록이 실려 판정을 바꾼다', async () => {
153
- User.hasPrivilege = jest.fn().mockResolvedValue(true)
154
-
155
- const allowed = await run(buildSchema(), 'guardedBoard', { unsafeIP: true })
156
- expect(allowed.errors).toBeUndefined()
157
-
158
- const prohibited = await run(buildSchema(), 'guardedBoard', {
159
- unsafeIP: true,
160
- prohibitedPrivileges: [{ category: 'board', privilege: 'mutation' }]
161
- })
162
- expect(prohibited.errors).toHaveLength(1)
163
- })
164
- })
165
-
166
- describe('스키마를 지으면서 남기는 것', () => {
167
- it('선언한 (category, privilege) 를 전역 레지스트리에 등록한다', () => {
168
- /* 부팅 때 없는 권한 행을 만드는 근거다 — 마이그레이션도 수동 생성도 필요 없다. */
169
- buildSchema()
170
-
171
- expect((process as any).PRIVILEGES['board mutation']).toEqual(['board', 'mutation'])
172
- })
173
-
174
- /*
175
- * What each field asks for, by name — so another entrance to the same door (an AI tool) reads
176
- * the door's own declaration instead of copying it (operato-twin ADR-0071 / ADR-0072).
177
- */
178
- it('records what a declared field asks for, all four parts', () => {
179
- buildSchema()
180
-
181
- expect(privilegeOfField('Query', 'guardedBoard')).toEqual({
182
- known: true,
183
- gate: { category: 'board', privilege: 'mutation', owner: false, super: false }
184
- })
185
- })
186
-
187
- it('records an ownership-only field too — it names no pair but it is still a gate', () => {
188
- buildSchema()
189
-
190
- expect(privilegeOfField('Query', 'ownershipOnly')).toEqual({
191
- known: true,
192
- gate: { category: undefined, privilege: undefined, owner: true, super: false }
193
- })
194
- })
195
-
196
- it('says a root field with no directive is known and open', () => {
197
- buildSchema()
198
-
199
- expect(privilegeOfField('Query', 'undeclared')).toEqual({ known: true, gate: null })
200
- })
201
-
202
- it('does not call a field it has never seen open — a typo is not an open door', () => {
203
- buildSchema()
204
-
205
- expect(privilegeOfField('Query', 'guardedBord')).toEqual({ known: false })
206
- })
207
-
208
- it('필드 설명에 필요한 권한을 적어 둔다', async () => {
209
- const schema = buildSchema()
210
- const description = (schema.getQueryType() as any).getFields().guardedBoard.description
211
-
212
- expect(description).toContain('board:mutation')
213
- })
214
- })
215
-
216
- /*
217
- * ── A declaration that names nothing stops the boot ───────────────────────────
218
- * Schema assembly is the only moment this can be caught. After it, an empty declaration is
219
- * indistinguishable from a real one at the call site: the directive is present, the wrapper
220
- * runs, and the answer comes back from `checkPermission` with nothing to look up.
221
- *
222
- * `@privilege()` was the worst of these — it answered by the caller's address, refusing from a
223
- * safe one and passing everyone from an unsafe one, so whoever tried it learned the opposite
224
- * lesson depending on where they sat (architect, 2026-09-15).
225
- *
226
- * [확인함] 2026-09-15 · six repositories · zero declarations of either shape, so refusing them
227
- * breaks nothing that exists. The throw is for the one someone writes next.
228
- */
229
- describe('a declaration that names nothing', () => {
230
- function buildField(declaration: string) {
231
- const schema = makeExecutableSchema({
232
- typeDefs: [privilegeDirectiveTypeDefs, `type Query { field: String ${declaration} }`],
233
- resolvers: { Query: { field: () => 'value' } }
234
- })
235
-
236
- return () => privilegeDirectiveResolver(schema)
237
- }
238
-
239
- it('refuses an empty declaration, and names the field', () => {
240
- expect(buildField('@privilege')).toThrow(/Query\.field declares nothing/)
241
- })
242
-
243
- it('tells the writer what the two real shapes are', () => {
244
- expect(buildField('@privilege')).toThrow(/domainOwnerGranted/)
245
- expect(buildField('@privilege')).toThrow(/carries no directive at all/)
246
- })
247
-
248
- it('refuses a category with no privilege', () => {
249
- expect(buildField('@privilege(category: "board")')).toThrow(/a category with no privilege/)
250
- })
251
-
252
- it('refuses a privilege with no category', () => {
253
- expect(buildField('@privilege(privilege: "mutation")')).toThrow(/a privilege with no category/)
254
- })
255
-
256
- /*
257
- * ── An axis this installation does not declare ──────────────────────────────
258
- * A typo used to be the quietest defect in the file. `privilege: "mutaiton"` registered a
259
- * pair in `process['PRIVILEGES']` that no role would ever hold — the field was shut to
260
- * everyone, permanently, and nothing said so. The framework does not own the word list
261
- * (dssp declares eight of its own), so the check asks the installation.
262
- */
263
- it('refuses an axis outside the declared vocabulary', () => {
264
- expect(buildField('@privilege(category: "board", privilege: "mutaiton")')).toThrow(
265
- /uses axis "mutaiton", which this installation does not declare/
266
- )
267
- })
268
-
269
- it('tells the reader what is declared and where to declare more', () => {
270
- const build = buildField('@privilege(category: "board", privilege: "approve")')
271
-
272
- expect(build).toThrow(/Declared axes: mutation, query/)
273
- /* The hook a module would reach for runs after the schema is built, so it must say so. */
274
- expect(build).toThrow(/top level of the module's server\/index\.ts/)
275
- expect(build).toThrow(/too late/)
276
- })
277
-
278
- it('accepts an axis once the installation declares it', () => {
279
- const { declareAxes } = loadCompiled('service/privilege/privilege-axis')
280
-
281
- expect(buildField('@privilege(category: "kpi", privilege: "read")')).toThrow()
282
-
283
- declareAxes(['query', 'mutation', 'read'])
284
-
285
- expect(buildField('@privilege(category: "kpi", privilege: "read")')).not.toThrow()
286
- })
287
-
288
- it('still builds the two shapes that do name something', () => {
289
- expect(buildField('@privilege(category: "board", privilege: "mutation")')).not.toThrow()
290
- expect(buildField('@privilege(domainOwnerGranted: true)')).not.toThrow()
291
- expect(buildField('@privilege(superUserGranted: true)')).not.toThrow()
292
- })
293
- })
294
-
295
- describe('거절이 이유를 싣는다 (ADR-0103 결정 1)', () => {
296
- it('★ 역할이 없으면 because: role — 화면이 「권한 요청」을 띄운다', async () => {
297
- const result = await run(buildSchema(), 'guardedBoard')
298
- expect((result.errors![0].extensions as any).because).toBe('role')
299
- })
300
-
301
- it('★ 라이선스가 막으면 역할이 있어도 because: license — 요청할 것이 아니다', async () => {
302
- User.hasPrivilege = jest.fn().mockResolvedValue(true)
303
- const result = await run(buildSchema(), 'guardedBoard', {
304
- prohibitedPrivileges: [{ category: 'board', privilege: 'mutation', by: 'license' }]
305
- })
306
- expect((result.errors![0].extensions as any).because).toBe('license')
307
- })
308
-
309
- it('★ 보호 주소 밖에서 소유자 전용이면 because: address', async () => {
310
- withOwnership({ owner: true })
311
- const result = await run(buildSchema(), 'ownershipOnly', { unsafeIP: true })
312
- expect((result.errors![0].extensions as any).because).toBe('address')
313
- })
314
- })
315
-
316
- describe('거절이 무엇이 막혔는지도 싣는다 — 변경인가 조회인가 (질문지 Q9 · 4)', () => {
317
- it('★ 조회가 막히면 operation: query', async () => {
318
- const result = await run(buildSchema(), 'guardedBoard')
319
- expect((result.errors![0].extensions as any).operation).toBe('query')
320
- })
321
-
322
- it('★ 변경이 막히면 operation: mutation — 사람이 누른 일이다', async () => {
323
- const schema = privilegeDirectiveResolver(
324
- makeExecutableSchema({
325
- typeDefs: [privilegeDirectiveTypeDefs, 'type Query { ok: String } type Mutation { saveBoard: String @privilege(category: "board", privilege: "mutation") }'],
326
- resolvers: { Query: { ok: () => 'ok' }, Mutation: { saveBoard: () => 'saved' } }
327
- })
328
- )
329
- const result = await graphql({ schema, source: 'mutation { saveBoard }', contextValue: { state: { domain: DOMAIN, user: USER } } })
330
- expect((result.errors![0].extensions as any)).toMatchObject({ because: 'role', operation: 'mutation' })
331
- })
332
- })
@@ -1,104 +0,0 @@
1
- /**
2
- * 권한 거절 문장 테스트.
3
- *
4
- * 이 문장은 프레임워크 apollo 링크를 타고 **사용자 얼굴까지** 간다(자동으로 닫히지 않는 오류 토스트).
5
- * 예전에는 영어 한 문장이었고, 나머지 서버 오류가 모두 `context.t` 로 옮겨진 뒤에도 여기만 남아 있었다.
6
- *
7
- * 위험한 것은 게이트 판정이 아니라 문장 만들기다:
8
- * ① 번역기가 없는 컨텍스트(구독·내부 호출)에서 죽거나 빈 문장을 주면 사용자는 이유를 못 본다.
9
- * ② i18next 는 키를 못 찾으면 **키를 그대로 돌려준다** — 그걸 쓰면 `error.x privilege required` 가 보인다.
10
- * ③ 소비처가 문장 형태에 의존하면 번역하는 순간 깨진다 — 그래서 `extensions` 에 구조로 싣는다.
11
- */
12
-
13
- import { privilegeRejection } from '../server/service/privilege/privilege-rejection.js'
14
-
15
- /** 키를 그대로 돌려주는 i18next 기본 거동(번역 누락). */
16
- const missing = (key: string) => key
17
-
18
- describe('privilege rejection', () => {
19
- it('요청의 언어로 말한다', () => {
20
- const context = { t: (key: string, params: any) => `${params.x} 권한이 필요합니다.` }
21
-
22
- expect(privilegeRejection(context, 'twin', 'mutation').message).toBe('twin:mutation 권한이 필요합니다.')
23
- })
24
-
25
- it('번역기가 없어도 이유를 말한다 — 빈 문장이나 예외로 끝나지 않는다', () => {
26
- for (const context of [undefined, null, {}, { t: 'not a function' }]) {
27
- const error = privilegeRejection(context, 'twin', 'mutation')
28
-
29
- expect(error.message).toBe('unauthorized! twin:mutation privilege required')
30
- }
31
- })
32
-
33
- it('번역이 없는 언어에서 키가 새어 나가지 않는다', () => {
34
- /* i18next 기본 거동을 그대로 쓰면 사용자가 `error.x privilege required` 를 읽는다. */
35
- const error = privilegeRejection({ t: missing }, 'twin', 'mutation')
36
-
37
- expect(error.message).toBe('unauthorized! twin:mutation privilege required')
38
- expect(error.message).not.toContain('error.')
39
- })
40
-
41
- it('번역기가 던져도 거절은 그대로 전달된다 — 오류의 원인이 번역기로 바뀌지 않게', () => {
42
- const context = {
43
- t: () => {
44
- throw new Error('i18next not initialized')
45
- }
46
- }
47
-
48
- expect(privilegeRejection(context, 'twin', 'mutation').message).toBe(
49
- 'unauthorized! twin:mutation privilege required'
50
- )
51
- })
52
-
53
- it('소유권만 요구하는 게이트는 권한 이름을 지어내지 않는다', () => {
54
- /* `@privilege(domainOwnerGranted: true)` 처럼 category·privilege 가 없는 게이트가 실제로 있다. */
55
- const error = privilegeRejection({ t: missing })
56
-
57
- expect(error.message).toBe('unauthorized! domain or system ownership required')
58
- expect(error.extensions.requires).toBe('ownership')
59
- })
60
-
61
- it('무엇이 필요했는지 구조로 싣는다 — 화면이 문장을 다시 파싱하지 않게', () => {
62
- const error = privilegeRejection({ t: () => '번역된 문장' }, 'twin', 'mutation')
63
-
64
- expect(error.extensions.code).toBe('FORBIDDEN')
65
- expect(error.extensions.requires).toEqual({ category: 'twin', privilege: 'mutation' })
66
- })
67
-
68
- it('한쪽만 주어진 게이트는 소유권 요구로 본다 — `undefined:mutation` 같은 문장을 만들지 않는다', () => {
69
- expect(privilegeRejection({}, 'twin', undefined).extensions.requires).toBe('ownership')
70
- expect(privilegeRejection({}, undefined, 'mutation').extensions.requires).toBe('ownership')
71
- })
72
- })
73
-
74
- describe('privilege rejection carries why (ADR-0103 decision 1)', () => {
75
- it('★ the cause goes in extensions.because - a screen offers a request only for role', () => {
76
- expect(privilegeRejection(undefined, 'twin', 'mutation', 'license').extensions).toEqual({
77
- code: 'FORBIDDEN',
78
- requires: { category: 'twin', privilege: 'mutation' },
79
- because: 'license'
80
- })
81
- })
82
-
83
- it('a caller that does not say keeps the old meaning - named is role, unnamed is ownership', () => {
84
- expect(privilegeRejection(undefined, 'twin', 'mutation').extensions.because).toBe('role')
85
- expect(privilegeRejection(undefined).extensions.because).toBe('ownership')
86
- })
87
-
88
- it('★ each cause has its own sentence - an address refusal does not say a privilege is missing', () => {
89
- const address = privilegeRejection(undefined, 'twin', 'mutation', 'address').message
90
- const license = privilegeRejection(undefined, 'twin', 'mutation', 'license').message
91
- const role = privilegeRejection(undefined, 'twin', 'mutation', 'role').message
92
- expect(new Set([address, license, role]).size).toBe(3)
93
- expect(address).toContain('address')
94
- expect(license).toContain('licensed')
95
- })
96
-
97
- it('every sentence key is in all six languages', () => {
98
- const keys = ['error.x not allowed from this address', 'error.x not licensed', 'error.x refused for this request', 'error.ownership not allowed from this address']
99
- for (const lng of ['en', 'ko', 'ja', 'zh', 'ms', 'es']) {
100
- const dict = require(`../translations/${lng}.json`)
101
- for (const key of keys) expect([lng, key, typeof dict[key]]).toEqual([lng, key, 'string'])
102
- }
103
- })
104
- })
@@ -1,58 +0,0 @@
1
- /**
2
- * The privilege sync is boot-time writing, and boot-time writing takes its turn (shell `inBootTurn`).
3
- *
4
- * On sqlite every transaction shares one connection. Another module's boot transaction (work-shift's
5
- * baseline) that began while the sync's insert was open failed with "cannot start a transaction within a
6
- * transaction", and its rollback took the insert with it — so the same privilege was missing again on
7
- * the next boot, and the collision repeated every boot (`contact/query`, operato-plant, 2026-10-10).
8
- */
9
- import { inBootTurn } from '@things-factory/shell'
10
-
11
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
12
- import { loadCompiled } from './compiled'
13
-
14
- const { synchronizePrivileges } = loadCompiled('service/privilege/privilege-sync')
15
- const { Privilege } = loadCompiled('service/privilege/privilege')
16
-
17
- const pause = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
18
-
19
- beforeAll(async () => {
20
- await openAuthDatabase()
21
- })
22
-
23
- beforeEach(async () => {
24
- await resetAuthDatabase()
25
- })
26
-
27
- afterAll(async () => {
28
- await closeAuthDatabase()
29
- })
30
-
31
- test('adds what is missing and leaves what is there', async () => {
32
- await getRepository(Privilege).save({ category: 'contact', name: 'mutation' })
33
-
34
- expect(await synchronizePrivileges([['contact', 'mutation'], ['contact', 'query']])).toEqual(['contact/query'])
35
- expect(await getRepository(Privilege).countBy({ category: 'contact' })).toBe(2)
36
- })
37
-
38
- test('another boot job that fails does not take the sync with it', async () => {
39
- const dataSource = await openAuthDatabase()
40
-
41
- /*
42
- * A boot job of another module, in its turn: it holds a transaction for a while and then fails. On
43
- * sqlite TypeORM shares one query runner, so a save that starts meanwhile rides inside that
44
- * transaction — and goes when it rolls back.
45
- */
46
- const other = inBootTurn(() =>
47
- dataSource.transaction(async tx => {
48
- await tx.getRepository(Domain).save({ name: 'boot-job', subdomain: 'boot-job' })
49
- await pause(30)
50
- throw new Error('the other boot job failed')
51
- })
52
- ).catch(() => undefined)
53
- const sync = synchronizePrivileges([['contact', 'query']])
54
-
55
- await Promise.all([other, sync])
56
- expect(await getRepository(Domain).countBy({ subdomain: 'boot-job' })).toBe(0)
57
- expect(await getRepository(Privilege).countBy({ category: 'contact', name: 'query' })).toBe(1)
58
- })
@@ -1,24 +0,0 @@
1
- import { profileUser } from '../server/utils/profile-user.js'
2
-
3
- /**
4
- * The profile carries the person's own id: the client names whose unsaved drafts this browser keeps by it
5
- * (ADR-0190). It was left out in 19ed1646178, and from then on no draft had an owner and none was written.
6
- */
7
- describe('profileUser', () => {
8
- it('carries the id with what the client shows and the grants', () => {
9
- const user = { id: 'u-1', username: 'kim', email: 'kim@example.com', locale: 'ko', name: '김', userType: 'user', password: 'hash', salt: 'salt' }
10
- const profile = profileUser(user, { owner: false, super: false, unsafeIP: false, privileges: [] })
11
- expect(profile).toEqual({
12
- id: 'u-1',
13
- username: 'kim',
14
- email: 'kim@example.com',
15
- locale: 'ko',
16
- name: '김',
17
- userType: 'user',
18
- owner: false,
19
- super: false,
20
- unsafeIP: false,
21
- privileges: []
22
- })
23
- })
24
- })
@@ -1,80 +0,0 @@
1
- /**
2
- * Why the gate said no (ADR-0103 decision 1).
3
- *
4
- * The four refusals used to arrive as one sentence. Only one of them is opened by a role, so a screen
5
- * that offers "request access" for all four sends the admin a request they cannot answer - the person is
6
- * given the role and is refused again. `judgePermission` says which it was; `checkPermission` stays a boolean.
7
- */
8
-
9
- import { loadCompiled } from './compiled'
10
-
11
- const { judgePermission, checkPermission } = loadCompiled('utils/check-permission')
12
- const { User } = loadCompiled('service/user/user')
13
-
14
- const DOMAIN = { id: 'domain-1' } as any
15
- const USER = { id: 'user-1' } as any
16
- const NAMED = { category: 'warehouse', privilege: 'mutation' }
17
- const OWNED = { ...NAMED, owner: true }
18
-
19
- function given({ role = false, owner = false } = {}) {
20
- User.hasPrivilege = jest.fn().mockResolvedValue(role)
21
- ;(process as any).domainOwnerGranted = jest.fn().mockResolvedValue(owner)
22
- ;(process as any).superUserGranted = jest.fn().mockResolvedValue(false)
23
- }
24
-
25
- afterEach(() => {
26
- delete (process as any).domainOwnerGranted
27
- delete (process as any).superUserGranted
28
- })
29
-
30
- describe('a refusal says which of the four it was', () => {
31
- it('★ no role holds it: role', async () => {
32
- given()
33
- expect(await judgePermission(NAMED, USER, DOMAIN)).toEqual({ permitted: false, because: 'role' })
34
- expect(await judgePermission(NAMED, USER, DOMAIN, true)).toEqual({ permitted: false, because: 'role' })
35
- })
36
-
37
- it('★ withheld by the address list: address - even with the role', async () => {
38
- given({ role: true })
39
- const prohibited = [{ ...NAMED, by: 'address' }]
40
- expect(await judgePermission(NAMED, USER, DOMAIN, true, prohibited)).toEqual({ permitted: false, because: 'address' })
41
- })
42
-
43
- it('★ withheld by the license: license - even with the role', async () => {
44
- given({ role: true })
45
- const prohibited = [{ ...NAMED, by: 'license' }]
46
- expect(await judgePermission(NAMED, USER, DOMAIN, false, prohibited)).toEqual({ permitted: false, because: 'license' })
47
- })
48
-
49
- it('★ a prohibition that does not say who made it: unknown - the cause is not made up', async () => {
50
- given({ role: true })
51
- expect(await judgePermission(NAMED, USER, DOMAIN, false, [NAMED])).toEqual({ permitted: false, because: 'unknown' })
52
- })
53
-
54
- it('★ only owners may: ownership', async () => {
55
- given()
56
- expect(await judgePermission({ owner: true }, USER, DOMAIN)).toEqual({ permitted: false, because: 'ownership' })
57
- })
58
-
59
- it('★ ownership asked from an unsafe address: address', async () => {
60
- given({ owner: true })
61
- expect(await judgePermission({ owner: true }, USER, DOMAIN, true)).toEqual({ permitted: false, because: 'address' })
62
- })
63
-
64
- it('★ an owner without the role, from an unsafe address: address - from a safe one they pass', async () => {
65
- given({ owner: true })
66
- expect(await judgePermission(OWNED, USER, DOMAIN, true)).toEqual({ permitted: false, because: 'address' })
67
- expect(await judgePermission(OWNED, USER, DOMAIN, false)).toEqual({ permitted: true, by: 'domain-owner' })
68
- })
69
-
70
- it('an unsafe address whose ownership cannot be asked still refuses as role - it refused before', async () => {
71
- User.hasPrivilege = jest.fn().mockResolvedValue(false)
72
- expect(await judgePermission(OWNED, USER, DOMAIN, true)).toEqual({ permitted: false, because: 'role' })
73
- })
74
-
75
- it('checkPermission answers the same, as a boolean', async () => {
76
- given({ role: true })
77
- expect(await checkPermission(NAMED, USER, DOMAIN)).toBe(true)
78
- expect(await checkPermission(NAMED, USER, DOMAIN, false, [{ ...NAMED, by: 'license' }])).toBe(false)
79
- })
80
- })
@@ -1,24 +0,0 @@
1
- /*
2
- * Every refusal this package throws has its Korean sentence - a missing one is read in the server's English fallback
3
- * (inline help audit 2026-10-30: passkey-not-found, and the check-in's `error.domain not specified` printed as its key).
4
- */
5
- import { readFileSync, readdirSync } from 'fs'
6
- import { join } from 'path'
7
-
8
- const ROOT = join(__dirname, '..')
9
- const ko: Record<string, string> = JSON.parse(readFileSync(join(ROOT, 'translations', 'ko.json'), 'utf8'))
10
-
11
- const sources = (dir: string): string[] =>
12
- readdirSync(dir, { withFileTypes: true }).flatMap(entry => (entry.isDirectory() ? sources(join(dir, entry.name)) : /\.ts$/.test(entry.name) && !/\.test\.ts$/.test(entry.name) ? [join(dir, entry.name)] : []))
13
- const text = sources(join(ROOT, 'server')).map(file => readFileSync(file, 'utf8')).join('\n')
14
-
15
- test('★ every Refusal code has error.<code> in Korean', () => {
16
- const codes = [...new Set([...text.matchAll(/new Refusal\(\s*'([a-z0-9-]+)'/g)].map(match => match[1]))]
17
- expect(codes.length).toBeGreaterThanOrEqual(10)
18
- expect(codes.filter(code => !(`error.${code}` in ko))).toEqual([])
19
- })
20
-
21
- test('★ the check-in refusal has its Korean sentence', () => {
22
- expect(text).toMatch(/t\('error\.domain not specified'/)
23
- expect(ko['error.domain not specified']).toBeTruthy()
24
- })