@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.
- package/package.json +24 -7
- package/CHANGELOG.md +0 -55
- package/dist-client/actions/auth.js.map +0 -1
- package/dist-client/apply-domain-theme.js.map +0 -1
- package/dist-client/auth.js.map +0 -1
- package/dist-client/bootstrap.js.map +0 -1
- package/dist-client/directive/privileged.js.map +0 -1
- package/dist-client/domain-required.js.map +0 -1
- package/dist-client/index.js.map +0 -1
- package/dist-client/profiled.js.map +0 -1
- package/dist-client/reducers/auth.js.map +0 -1
- package/dist-client/tsconfig.tsbuildinfo +0 -1
- package/dist-client/verify-webauthn.js.map +0 -1
- package/dist-server/ai-entry/access-assistant.js.map +0 -1
- package/dist-server/ai-entry/access-entry-targets.js.map +0 -1
- package/dist-server/ai-entry/entry-target-shape.js.map +0 -1
- package/dist-server/constants/error-code.js.map +0 -1
- package/dist-server/constants/error-message.js.map +0 -1
- package/dist-server/constants/max-age.js.map +0 -1
- package/dist-server/controllers/access-request.js.map +0 -1
- package/dist-server/controllers/account-mail.js.map +0 -1
- package/dist-server/controllers/auth.js.map +0 -1
- package/dist-server/controllers/change-pwd.js.map +0 -1
- package/dist-server/controllers/confirm-identity.js.map +0 -1
- package/dist-server/controllers/delete-user.js.map +0 -1
- package/dist-server/controllers/passkey.js.map +0 -1
- package/dist-server/controllers/password-setup.js.map +0 -1
- package/dist-server/controllers/profile.js.map +0 -1
- package/dist-server/controllers/reset-password.js.map +0 -1
- package/dist-server/controllers/signin.js.map +0 -1
- package/dist-server/controllers/signup.js.map +0 -1
- package/dist-server/controllers/unlock-user.js.map +0 -1
- package/dist-server/controllers/utils/find-verification-token.js.map +0 -1
- package/dist-server/controllers/utils/make-verification-token.js.map +0 -1
- package/dist-server/controllers/utils/password-rule.js.map +0 -1
- package/dist-server/controllers/utils/save-verification-token.js.map +0 -1
- package/dist-server/controllers/verification.js.map +0 -1
- package/dist-server/errors/auth-error.js.map +0 -1
- package/dist-server/errors/index.js.map +0 -1
- package/dist-server/errors/user-domain-not-match-error.js.map +0 -1
- package/dist-server/index.js.map +0 -1
- package/dist-server/middlewares/audit-scope-middleware.js.map +0 -1
- package/dist-server/middlewares/authenticate-401-middleware.js.map +0 -1
- package/dist-server/middlewares/bypass-signin-middleware.js.map +0 -1
- package/dist-server/middlewares/domain-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/graphql-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/index.js.map +0 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/signin-middleware.js.map +0 -1
- package/dist-server/middlewares/verify-recaptcha-middleware.js.map +0 -1
- package/dist-server/migrations/1548206416130-SeedUser.js.map +0 -1
- package/dist-server/migrations/1566805283882-SeedPrivilege.js.map +0 -1
- package/dist-server/migrations/1745000000000-MigrateDomainOwnersToTable.js.map +0 -1
- package/dist-server/migrations/index.js.map +0 -1
- package/dist-server/role-tasks.js.map +0 -1
- package/dist-server/router/auth-checkin-router.js.map +0 -1
- package/dist-server/router/auth-private-process-router.js.map +0 -1
- package/dist-server/router/auth-public-process-router.js.map +0 -1
- package/dist-server/router/auth-signin-router.js.map +0 -1
- package/dist-server/router/auth-signup-router.js.map +0 -1
- package/dist-server/router/index.js.map +0 -1
- package/dist-server/router/oauth2/index.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-authorize-router.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-router.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-server.js.map +0 -1
- package/dist-server/router/oauth2/passport-oauth2-client-password.js.map +0 -1
- package/dist-server/router/oauth2/passport-refresh-token.js.map +0 -1
- package/dist-server/router/path-base-domain-router.js.map +0 -1
- package/dist-server/router/site-root-router.js.map +0 -1
- package/dist-server/router/webauthn-router.js.map +0 -1
- package/dist-server/routes.js.map +0 -1
- package/dist-server/service/access-request/access-request-mutation.js.map +0 -1
- package/dist-server/service/access-request/access-request-query.js.map +0 -1
- package/dist-server/service/access-request/access-request-types.js.map +0 -1
- package/dist-server/service/access-request/access-request.js.map +0 -1
- package/dist-server/service/access-request/index.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-mutation.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-query.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-types.js.map +0 -1
- package/dist-server/service/app-binding/app-binding.js.map +0 -1
- package/dist-server/service/app-binding/index.js.map +0 -1
- package/dist-server/service/appliance/appliance-mutation.js.map +0 -1
- package/dist-server/service/appliance/appliance-query.js.map +0 -1
- package/dist-server/service/appliance/appliance-types.js.map +0 -1
- package/dist-server/service/appliance/appliance.js.map +0 -1
- package/dist-server/service/appliance/index.js.map +0 -1
- package/dist-server/service/application/application-mutation.js.map +0 -1
- package/dist-server/service/application/application-query.js.map +0 -1
- package/dist-server/service/application/application-types.js.map +0 -1
- package/dist-server/service/application/application.js.map +0 -1
- package/dist-server/service/application/index.js.map +0 -1
- package/dist-server/service/audit/audit-action.js.map +0 -1
- package/dist-server/service/audit/audit-kinds.js.map +0 -1
- package/dist-server/service/audit/audit-query.js.map +0 -1
- package/dist-server/service/audit/audit-recorder.js.map +0 -1
- package/dist-server/service/audit/audit-request.js.map +0 -1
- package/dist-server/service/audit/audit-retention.js.map +0 -1
- package/dist-server/service/audit/audit-subscriber.js.map +0 -1
- package/dist-server/service/audit/audit-target.js.map +0 -1
- package/dist-server/service/audit/audit-types.js.map +0 -1
- package/dist-server/service/audit/audit-work.js.map +0 -1
- package/dist-server/service/audit/index.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-mutation.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-query.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-type.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider.js.map +0 -1
- package/dist-server/service/auth-provider/index.js.map +0 -1
- package/dist-server/service/domain-generator/domain-generator-mutation.js.map +0 -1
- package/dist-server/service/domain-generator/domain-generator-types.js.map +0 -1
- package/dist-server/service/domain-generator/index.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-mutation.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-query.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-types.js.map +0 -1
- package/dist-server/service/domain-link/domain-link.js.map +0 -1
- package/dist-server/service/domain-link/index.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner-mutation.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner-query.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner.js.map +0 -1
- package/dist-server/service/domain-owner/index.js.map +0 -1
- package/dist-server/service/granted-role/granted-role-mutation.js.map +0 -1
- package/dist-server/service/granted-role/granted-role-query.js.map +0 -1
- package/dist-server/service/granted-role/granted-role.js.map +0 -1
- package/dist-server/service/granted-role/index.js.map +0 -1
- package/dist-server/service/identity-confirmation/identity-confirmation.js.map +0 -1
- package/dist-server/service/index.js.map +0 -1
- package/dist-server/service/open-fields.js.map +0 -1
- package/dist-server/service/partner/index.js.map +0 -1
- package/dist-server/service/partner/partner-mutation.js.map +0 -1
- package/dist-server/service/partner/partner-query.js.map +0 -1
- package/dist-server/service/partner/partner-types.js.map +0 -1
- package/dist-server/service/partner/partner.js.map +0 -1
- package/dist-server/service/password-history/index.js.map +0 -1
- package/dist-server/service/password-history/password-history.js.map +0 -1
- package/dist-server/service/privilege/index.js.map +0 -1
- package/dist-server/service/privilege/open-field-rule.js.map +0 -1
- package/dist-server/service/privilege/privilege-axis.js.map +0 -1
- package/dist-server/service/privilege/privilege-deprecation.js.map +0 -1
- package/dist-server/service/privilege/privilege-directive.js.map +0 -1
- package/dist-server/service/privilege/privilege-mutation.js.map +0 -1
- package/dist-server/service/privilege/privilege-query.js.map +0 -1
- package/dist-server/service/privilege/privilege-rejection.js.map +0 -1
- package/dist-server/service/privilege/privilege-sync.js.map +0 -1
- package/dist-server/service/privilege/privilege-types.js.map +0 -1
- package/dist-server/service/privilege/privilege.js.map +0 -1
- package/dist-server/service/role/index.js.map +0 -1
- package/dist-server/service/role/role-mutation.js.map +0 -1
- package/dist-server/service/role/role-privilege-query.js.map +0 -1
- package/dist-server/service/role/role-query.js.map +0 -1
- package/dist-server/service/role/role-types.js.map +0 -1
- package/dist-server/service/role/role.js.map +0 -1
- package/dist-server/service/role-template/index.js.map +0 -1
- package/dist-server/service/role-template/role-task.js.map +0 -1
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +0 -1
- package/dist-server/service/role-template/role-template-mutation.js.map +0 -1
- package/dist-server/service/role-template/role-template-query.js.map +0 -1
- package/dist-server/service/role-template/role-template-types.js.map +0 -1
- package/dist-server/service/role-template/role-template.js.map +0 -1
- package/dist-server/service/user/domain-query.js.map +0 -1
- package/dist-server/service/user/index.js.map +0 -1
- package/dist-server/service/user/user-mutation.js.map +0 -1
- package/dist-server/service/user/user-query.js.map +0 -1
- package/dist-server/service/user/user-types.js.map +0 -1
- package/dist-server/service/user/user.js.map +0 -1
- package/dist-server/service/users-auth-providers/index.js.map +0 -1
- package/dist-server/service/users-auth-providers/users-auth-providers.js.map +0 -1
- package/dist-server/service/verification-token/index.js.map +0 -1
- package/dist-server/service/verification-token/verification-token.js.map +0 -1
- package/dist-server/service/web-auth-credential/index.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/dist-server/types.js.map +0 -1
- package/dist-server/utils/accepts.js.map +0 -1
- package/dist-server/utils/access-request-review.js.map +0 -1
- package/dist-server/utils/access-token-cookie.js.map +0 -1
- package/dist-server/utils/account-lock-rule.js.map +0 -1
- package/dist-server/utils/assignable-roles.js.map +0 -1
- package/dist-server/utils/build-auth-url.js.map +0 -1
- package/dist-server/utils/can-user-checkin-domain.js.map +0 -1
- package/dist-server/utils/check-permission.js.map +0 -1
- package/dist-server/utils/check-user-belongs-domain.js.map +0 -1
- package/dist-server/utils/check-user-has-role.js.map +0 -1
- package/dist-server/utils/checkin-domain-facts.js.map +0 -1
- package/dist-server/utils/checkin-domain-rule.js.map +0 -1
- package/dist-server/utils/credential-at-rest.js.map +0 -1
- package/dist-server/utils/credential-features.js.map +0 -1
- package/dist-server/utils/credential-serial-rule.js.map +0 -1
- package/dist-server/utils/domain-ownership.js.map +0 -1
- package/dist-server/utils/encrypt-state.js.map +0 -1
- package/dist-server/utils/get-aes-256-key.js.map +0 -1
- package/dist-server/utils/get-domain-users.js.map +0 -1
- package/dist-server/utils/get-secret.js.map +0 -1
- package/dist-server/utils/get-user-domains.js.map +0 -1
- package/dist-server/utils/lock-duration.js.map +0 -1
- package/dist-server/utils/password-reset-rule.js.map +0 -1
- package/dist-server/utils/profile-user.js.map +0 -1
- package/dist-server/utils/refuse-role-name.js.map +0 -1
- package/dist-server/utils/role-name-standing.js.map +0 -1
- package/dist-server/utils/same-origin-redirect.js.map +0 -1
- package/dist-server/utils/verification-token-rule.js.map +0 -1
- package/spec/unit/checkin-domain.spec.ts +0 -307
- package/spec/unit/checkin-privilege.spec.ts +0 -205
- package/spec/unit/domain-owner-entity.spec.ts +0 -179
- package/spec/unit/domain-owner-granted.spec.ts +0 -215
- package/spec/unit/domain-owner-migration.spec.ts +0 -236
- package/spec/unit/domain-owner-mutation.spec.ts +0 -337
- package/spec/unit/domain-ownership.spec.ts +0 -188
- package/tests/access-assistant-db.test.ts +0 -155
- package/tests/access-entry-targets-db.test.ts +0 -176
- package/tests/access-request-db.test.ts +0 -394
- package/tests/access-request-pg.test.ts +0 -83
- package/tests/access-request-review.test.ts +0 -193
- package/tests/account-lock-db.test.ts +0 -216
- package/tests/account-lock-rule.test.ts +0 -141
- package/tests/account-mail.test.ts +0 -69
- package/tests/activate-page-case.test.ts +0 -53
- package/tests/add-user-gates.test.ts +0 -34
- package/tests/app-binding-db.test.ts +0 -210
- package/tests/app-binding-delete-db.test.ts +0 -188
- package/tests/appliance-credential-state-db.test.ts +0 -207
- package/tests/appliance-delete-db.test.ts +0 -119
- package/tests/appliance-revoke-db.test.ts +0 -294
- package/tests/appliance-schema.test.ts +0 -129
- package/tests/application-delete-db.test.ts +0 -190
- package/tests/application-token-payload-db.test.ts +0 -185
- package/tests/assignable-roles-db.test.ts +0 -219
- package/tests/audit-db.test.ts +0 -410
- package/tests/audit-work.test.ts +0 -58
- package/tests/auth-base-role-tasks.test.ts +0 -58
- package/tests/checkin-domain-rule.test.ts +0 -128
- package/tests/checkin-privilege-seam-db.test.ts +0 -158
- package/tests/compiled.ts +0 -41
- package/tests/confirm-identity-db.test.ts +0 -85
- package/tests/credential-at-rest-db.test.ts +0 -246
- package/tests/credential-features-off-db.test.ts +0 -189
- package/tests/credential-serial-rule.test.ts +0 -84
- package/tests/declare-privileges.test.ts +0 -29
- package/tests/domain-assignees-db.test.ts +0 -95
- package/tests/domain-inheritance-sentinel.test.ts +0 -93
- package/tests/domain-required.test.ts +0 -127
- package/tests/has-privilege-db.test.ts +0 -198
- package/tests/identity-confirmation.test.ts +0 -146
- package/tests/list-inheritance-db.test.ts +0 -127
- package/tests/lock-recovery.test.ts +0 -206
- package/tests/mail-request-answers-db.test.ts +0 -142
- package/tests/open-doors.test.ts +0 -121
- package/tests/open-fields-enforcement.test.ts +0 -215
- package/tests/open-fields-framework.test.ts +0 -212
- package/tests/open-redirect-db.test.ts +0 -143
- package/tests/passkey-authenticator.ts +0 -104
- package/tests/passkey-db.test.ts +0 -295
- package/tests/passkey-signin-route-db.test.ts +0 -139
- package/tests/password-hashing.test.ts +0 -131
- package/tests/password-persistence.test.ts +0 -147
- package/tests/password-reset-rule-db.test.ts +0 -159
- package/tests/password-setup-db.test.ts +0 -215
- package/tests/permission-gate.test.ts +0 -228
- package/tests/picture-reducer.test.ts +0 -25
- package/tests/privilege-axis.test.ts +0 -157
- package/tests/privilege-deprecation.test.ts +0 -65
- package/tests/privilege-directive.test.ts +0 -332
- package/tests/privilege-rejection.test.ts +0 -104
- package/tests/privilege-sync-db.test.ts +0 -58
- package/tests/profile-user.test.ts +0 -24
- package/tests/refusal-cause.test.ts +0 -80
- package/tests/refusals-have-korean.test.ts +0 -24
- package/tests/request-holds.test.ts +0 -53
- package/tests/role-inheritance-db.test.ts +0 -286
- package/tests/role-mutation-db.test.ts +0 -293
- package/tests/role-name-lookup-sentinel.test.ts +0 -79
- package/tests/role-name-standing.test.ts +0 -83
- package/tests/role-privileges-db.test.ts +0 -252
- package/tests/role-task.test.ts +0 -168
- package/tests/role-template-gap-db.test.ts +0 -268
- package/tests/role-template-seed-db.test.ts +0 -180
- package/tests/role-template.test.ts +0 -162
- package/tests/role-users-gate.test.ts +0 -22
- package/tests/signin-policy.test.ts +0 -200
- package/tests/signup-duplicate-db.test.ts +0 -68
- package/tests/token-issuance.test.ts +0 -244
- package/tests/user-roles-field-db.test.ts +0 -134
- package/tests/verification-token-db.test.ts +0 -285
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 인가 게이트(`checkPermission`) 판정 시험.
|
|
3
|
-
*
|
|
4
|
-
* 이 함수가 플랫폼의 **모든** 권한 판정의 마지막 자리다 — `@privilege` 지시자와
|
|
5
|
-
* `auth-private-process-router`·`integration-base` 의 시나리오 실행이 모두 여기로 모인다.
|
|
6
|
-
* 그런데 저장소의 시험 316개 중 이 함수를 부르는 것이 없었다. 리빌딩 전에 **지금 무엇을 하는지**
|
|
7
|
-
* 를 고정한다.
|
|
8
|
-
*
|
|
9
|
-
* ## 이 시험이 고정하는 사실 중 둘은 「옳다」가 아니라 「지금 그렇다」다
|
|
10
|
-
*
|
|
11
|
-
* ① **선언이 없으면 통과한다** — 게이트가 opt-in 이라는 뜻이고, 지시자를 잊은 리졸버는
|
|
12
|
-
* 조용히 열린다(무선언 mutation 509개). 「닫힘을 기본으로」는 결정 대기 사항이다.
|
|
13
|
-
* ② **unsafeIP + 이름 없는 선언**의 규칙이 뒤집혀 있다 — 안전하지 않은 IP 에서 *더* 열린다.
|
|
14
|
-
* 아래 해당 케이스에 그 자리를 표시해 두었다. 정책이 정해지면 시험을 먼저 바꾼다.
|
|
15
|
-
*
|
|
16
|
-
* 두 사실은 정책 결정(`docs/design/auth-rebuild.md` §1 · §7)이 나오기 전에는 바꾸지 않는다.
|
|
17
|
-
* 시험이 지금 거동을 붙잡고 있으므로, 바꿀 때 무엇이 달라지는지 보인다.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { loadCompiled } from './compiled'
|
|
21
|
-
|
|
22
|
-
const { checkPermission } = loadCompiled('utils/check-permission')
|
|
23
|
-
const { User } = loadCompiled('service/user/user')
|
|
24
|
-
|
|
25
|
-
const DOMAIN = { id: 'domain-1', subdomain: 'plant-a' } as any
|
|
26
|
-
const USER = { id: 'user-1', domains: [DOMAIN] } as any
|
|
27
|
-
|
|
28
|
-
/** 역할 기반 판정(`User.hasPrivilege`)은 DB 를 본다 — 판정 규칙만 보려고 그 답을 고정한다. */
|
|
29
|
-
function withRolePrivilege(answer: boolean) {
|
|
30
|
-
User.hasPrivilege = jest.fn().mockResolvedValue(answer)
|
|
31
|
-
return User.hasPrivilege
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 소유권 판정은 `process` 에 실린다(`domain-authenticate-middleware` 가 설치한다).
|
|
36
|
-
* 게이트가 **자기 안에 없는 것에 의존**한다는 사실 자체가 아래 마지막 케이스의 주제다.
|
|
37
|
-
*/
|
|
38
|
-
function withOwnership({ owner = false, superUser = false } = {}) {
|
|
39
|
-
;(process as any).domainOwnerGranted = jest.fn().mockResolvedValue(owner)
|
|
40
|
-
;(process as any).superUserGranted = jest.fn().mockResolvedValue(superUser)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
withOwnership()
|
|
45
|
-
withRolePrivilege(false)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
delete (process as any).domainOwnerGranted
|
|
50
|
-
delete (process as any).superUserGranted
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
describe('선언이 없는 자리', () => {
|
|
54
|
-
it('선언이 없으면 통과한다 — 게이트는 opt-in 이다', async () => {
|
|
55
|
-
for (const nothing of [undefined, null]) {
|
|
56
|
-
expect(await checkPermission(nothing, USER, DOMAIN)).toBe(true)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('사용자도 도메인도 없어도 통과한다 — 선언이 없으면 아무것도 보지 않는다', async () => {
|
|
61
|
-
expect(await checkPermission(undefined, undefined, undefined)).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('이름이 있는 권한 (category + privilege)', () => {
|
|
66
|
-
const NAMED = { category: 'board', privilege: 'mutation' }
|
|
67
|
-
|
|
68
|
-
it('역할이 그 권한을 가지면 통과한다', async () => {
|
|
69
|
-
const hasPrivilege = withRolePrivilege(true)
|
|
70
|
-
|
|
71
|
-
expect(await checkPermission(NAMED, USER, DOMAIN)).toBe(true)
|
|
72
|
-
expect(hasPrivilege).toHaveBeenCalledWith('mutation', 'board', DOMAIN, USER)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('역할이 없으면 거절한다', async () => {
|
|
76
|
-
expect(await checkPermission(NAMED, USER, DOMAIN)).toBe(false)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('소유권이 선언되지 않았으면 소유자여도 역할로만 판정한다', async () => {
|
|
80
|
-
withOwnership({ owner: true, superUser: true })
|
|
81
|
-
|
|
82
|
-
/* 선언에 owner/super 가 없다 — 우회는 **선언한 자리에서만** 일어난다. */
|
|
83
|
-
expect(await checkPermission(NAMED, USER, DOMAIN)).toBe(false)
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
describe('소유권 우회 (domainOwnerGranted / superUserGranted)', () => {
|
|
88
|
-
const OWNER_GRANTED = { category: 'board', privilege: 'mutation', owner: true }
|
|
89
|
-
const SUPER_GRANTED = { category: 'board', privilege: 'mutation', super: true }
|
|
90
|
-
|
|
91
|
-
it('도메인 소유자는 역할 없이 통과한다', async () => {
|
|
92
|
-
withOwnership({ owner: true })
|
|
93
|
-
const hasPrivilege = withRolePrivilege(false)
|
|
94
|
-
|
|
95
|
-
expect(await checkPermission(OWNER_GRANTED, USER, DOMAIN)).toBe(true)
|
|
96
|
-
/* 역할은 아예 보지 않는다 — 그래서 admin 계정으로는 거절 경로를 볼 수 없다. */
|
|
97
|
-
expect(hasPrivilege).not.toHaveBeenCalled()
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('수퍼유저는 역할 없이 통과한다', async () => {
|
|
101
|
-
withOwnership({ superUser: true })
|
|
102
|
-
|
|
103
|
-
expect(await checkPermission(SUPER_GRANTED, USER, DOMAIN)).toBe(true)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('선언이 owner 만인데 수퍼유저이면 우회하지 않는다', async () => {
|
|
107
|
-
withOwnership({ superUser: true })
|
|
108
|
-
|
|
109
|
-
expect(await checkPermission(OWNER_GRANTED, USER, DOMAIN)).toBe(false)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
describe('이름이 없는 선언 — 소유권만으로 판정한다', () => {
|
|
114
|
-
it('소유권만 선언한 자리는 소유자에게 열린다', async () => {
|
|
115
|
-
withOwnership({ owner: true })
|
|
116
|
-
|
|
117
|
-
expect(await checkPermission({ owner: true }, USER, DOMAIN)).toBe(true)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('소유자가 아니면 닫힌다 — 값은 `false` 다', async () => {
|
|
121
|
-
/* 2026-10-02 까지는 `(true && false) || (undefined && …)` = `undefined` 가 나왔다. 선언한 타입은
|
|
122
|
-
`Promise<boolean>` 이었고, `=== false` 로 받는 소비처가 생기는 순간 조용히 통과할 자리였다.
|
|
123
|
-
판정이 이유를 내게 되면서(`judgePermission`, ADR-0103) 값이 타입과 같아졌다. */
|
|
124
|
-
const decision = await checkPermission({ owner: true }, USER, DOMAIN)
|
|
125
|
-
|
|
126
|
-
expect(decision).toBe(false)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('refuses an object that names nothing', async () => {
|
|
130
|
-
/* `{}` is not `!privilegeObject`, so it passes the first gate and falls out falsy here.
|
|
131
|
-
The unsafe-address block below asserts the same answer, which is the point: it used to
|
|
132
|
-
answer the opposite. `privilege-directive.ts` refuses to build this shape at all now,
|
|
133
|
-
but `checkPermission` is exported and reachable without it. */
|
|
134
|
-
expect(await checkPermission({}, USER, DOMAIN)).toBeFalsy()
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
describe('금지 목록 (prohibitedPrivileges)', () => {
|
|
139
|
-
const NAMED = { category: 'board', privilege: 'mutation' }
|
|
140
|
-
const PROHIBITED = [{ category: 'board', privilege: 'mutation' }]
|
|
141
|
-
|
|
142
|
-
it('금지된 권한은 역할이 있어도 거절한다', async () => {
|
|
143
|
-
withRolePrivilege(true)
|
|
144
|
-
|
|
145
|
-
expect(await checkPermission(NAMED, USER, DOMAIN, false, PROHIBITED)).toBe(false)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('다른 권한의 금지는 영향이 없다', async () => {
|
|
149
|
-
withRolePrivilege(true)
|
|
150
|
-
|
|
151
|
-
expect(
|
|
152
|
-
await checkPermission(NAMED, USER, DOMAIN, false, [{ category: 'board', privilege: 'query' }])
|
|
153
|
-
).toBe(true)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('소유권 우회가 금지 목록보다 먼저 판정된다 — 금지 목록은 소유자를 막지 못한다', async () => {
|
|
157
|
-
withOwnership({ owner: true })
|
|
158
|
-
|
|
159
|
-
expect(
|
|
160
|
-
await checkPermission({ ...NAMED, owner: true }, USER, DOMAIN, false, PROHIBITED)
|
|
161
|
-
).toBe(true)
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe('안전하지 않은 IP (unsafeIP)', () => {
|
|
166
|
-
const NAMED = { category: 'board', privilege: 'mutation' }
|
|
167
|
-
|
|
168
|
-
it('이름이 있는 권한은 소유권을 무시하고 역할로만 판정한다', async () => {
|
|
169
|
-
withOwnership({ owner: true, superUser: true })
|
|
170
|
-
withRolePrivilege(false)
|
|
171
|
-
|
|
172
|
-
expect(await checkPermission({ ...NAMED, owner: true, super: true }, USER, DOMAIN, true)).toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('역할이 있으면 안전하지 않은 IP 에서도 통과한다', async () => {
|
|
176
|
-
withRolePrivilege(true)
|
|
177
|
-
|
|
178
|
-
expect(await checkPermission(NAMED, USER, DOMAIN, true)).toBe(true)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('금지 목록은 안전하지 않은 IP 에서 그대로 적용된다', async () => {
|
|
182
|
-
withRolePrivilege(true)
|
|
183
|
-
|
|
184
|
-
expect(
|
|
185
|
-
await checkPermission(NAMED, USER, DOMAIN, true, [{ category: 'board', privilege: 'mutation' }])
|
|
186
|
-
).toBe(false)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
/*
|
|
190
|
-
* ── An unsafe address is never the more permissive of the two ────────────────
|
|
191
|
-
* These two were marked 뒤집힘 and pinned the opposite answer until 2026-09-15. The rule
|
|
192
|
-
* read `return !domainOwnerGranted && !superUserGranted`: "ownership does not apply from an
|
|
193
|
-
* unsafe address" came out as the declaration inverted rather than as a refusal, so an
|
|
194
|
-
* object naming nothing opened from an unsafe address and closed from a safe one.
|
|
195
|
-
*
|
|
196
|
-
* The architect found the same asymmetry from the other side on 2026-09-15, looking at what
|
|
197
|
-
* a field would do if someone wrote `@privilege()` wanting "signed in is enough".
|
|
198
|
-
*/
|
|
199
|
-
it('refuses a declaration resting on ownership alone', async () => {
|
|
200
|
-
withOwnership({ owner: true })
|
|
201
|
-
|
|
202
|
-
expect(await checkPermission({ owner: true }, USER, DOMAIN, true)).toBe(false)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('refuses an object that names nothing, the same as a safe address does', async () => {
|
|
206
|
-
expect(await checkPermission({}, USER, DOMAIN, true)).toBe(false)
|
|
207
|
-
expect(await checkPermission({}, USER, DOMAIN, false)).toBeFalsy()
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
describe('게이트가 자기 밖에 의존하는 것', () => {
|
|
212
|
-
it('소유권 판정자가 설치되지 않은 경로에서 부르면 던진다', async () => {
|
|
213
|
-
delete (process as any).domainOwnerGranted
|
|
214
|
-
|
|
215
|
-
/* `domain-authenticate-middleware` 가 `process` 에 심는다. 미들웨어를 타지 않는
|
|
216
|
-
호출 경로(작업 스케줄러·구독 밖 내부 호출)에서 소유권을 선언한 자리를 판정하면
|
|
217
|
-
판정이 아니라 예외가 된다. */
|
|
218
|
-
await expect(checkPermission({ owner: true }, USER, DOMAIN)).rejects.toThrow()
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('소유권을 선언하지 않은 자리는 판정자가 없어도 동작한다', async () => {
|
|
222
|
-
delete (process as any).domainOwnerGranted
|
|
223
|
-
delete (process as any).superUserGranted
|
|
224
|
-
withRolePrivilege(true)
|
|
225
|
-
|
|
226
|
-
expect(await checkPermission({ category: 'board', privilege: 'mutation' }, USER, DOMAIN)).toBe(true)
|
|
227
|
-
})
|
|
228
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { SET_PICTURE, SET_PROFILE } from '../client/actions/auth'
|
|
2
|
-
import auth from '../client/reducers/auth'
|
|
3
|
-
|
|
4
|
-
/** The signed-in person's picture, which the header's avatar draws (`auth.picture`). */
|
|
5
|
-
describe('the signed-in person\'s picture', () => {
|
|
6
|
-
const picture = { picture: '/attachment/face.png', left: 0, top: 0, zoom: 1 }
|
|
7
|
-
|
|
8
|
-
it('starts with none - the avatar draws a monogram', () => {
|
|
9
|
-
expect(auth(undefined, { type: '@@INIT' }).picture).toBeNull()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('★ is set by SET_PICTURE and survives a later profile', () => {
|
|
13
|
-
let state = auth(undefined, { type: SET_PICTURE, picture })
|
|
14
|
-
state = auth(state, { type: SET_PROFILE, user: { name: '남상혁' } })
|
|
15
|
-
|
|
16
|
-
expect(state.picture).toEqual(picture)
|
|
17
|
-
expect(state.user).toEqual({ name: '남상혁' })
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('a missing picture clears it', () => {
|
|
21
|
-
const state = auth(auth(undefined, { type: SET_PICTURE, picture }), { type: SET_PICTURE, picture: undefined })
|
|
22
|
-
|
|
23
|
-
expect(state.picture).toBeNull()
|
|
24
|
-
})
|
|
25
|
-
})
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The axis vocabulary — who owns it, and what a value outside it does.
|
|
3
|
-
*
|
|
4
|
-
* The framework used to accept any string as an axis. `@privilege(privilege: "mutaiton")`
|
|
5
|
-
* registered a pair in `process['PRIVILEGES']` that nobody would ever be granted: a gate shut
|
|
6
|
-
* forever, with nothing anywhere to read. Closing the axis to `query | mutation` in the
|
|
7
|
-
* framework would have fixed that and broken dssp's 40 declarations, so the owner declares
|
|
8
|
-
* and the framework asks.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { loadCompiled } from './compiled'
|
|
12
|
-
|
|
13
|
-
const { declareAxes, declaredAxes, isDeclaredAxis, readOnlyAxes, readOnlyAxesAreKnown, DEFAULT_AXES } = loadCompiled(
|
|
14
|
-
'service/privilege/privilege-axis'
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
delete (process as any).PRIVILEGE_AXES
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
delete (process as any).PRIVILEGE_AXES
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe('an installation that declares nothing', () => {
|
|
26
|
-
it('gets query and mutation', () => {
|
|
27
|
-
expect([...declaredAxes()].sort()).toEqual(['mutation', 'query'])
|
|
28
|
-
expect(DEFAULT_AXES).toEqual(['query', 'mutation'])
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('still refuses a value outside them', () => {
|
|
32
|
-
/*
|
|
33
|
-
* The defaults are a vocabulary, not an off switch. An installation that forgot to declare
|
|
34
|
-
* is the common case, and it is the one that most needs the typo caught.
|
|
35
|
-
*/
|
|
36
|
-
expect(isDeclaredAxis('query')).toBe(true)
|
|
37
|
-
expect(isDeclaredAxis('mutaiton')).toBe(false)
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('an installation that declares its own words', () => {
|
|
42
|
-
it('accepts them', () => {
|
|
43
|
-
declareAxes(['read', 'input', 'finalize'])
|
|
44
|
-
|
|
45
|
-
expect(isDeclaredAxis('read')).toBe(true)
|
|
46
|
-
expect(isDeclaredAxis('finalize')).toBe(true)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('replaces the defaults rather than adding to them', () => {
|
|
50
|
-
/*
|
|
51
|
-
* A house that says "my axes are read and input" has said what its axes are. Keeping
|
|
52
|
-
* query and mutation alongside would let a declaration use a word this installation
|
|
53
|
-
* does not actually mean, which is the thing being prevented.
|
|
54
|
-
*/
|
|
55
|
-
declareAxes(['read', 'input'])
|
|
56
|
-
|
|
57
|
-
expect(isDeclaredAxis('query')).toBe(false)
|
|
58
|
-
expect([...declaredAxes()].sort()).toEqual(['input', 'read'])
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('more than one module declaring', () => {
|
|
63
|
-
it('takes the union, because one installation mixes vocabularies', () => {
|
|
64
|
-
/*
|
|
65
|
-
* This is dssp: `project` is declared with query/mutation while `kpi` uses eight words of
|
|
66
|
-
* its own, in the same installation. Each module says what it uses.
|
|
67
|
-
*/
|
|
68
|
-
declareAxes(['query', 'mutation'])
|
|
69
|
-
declareAxes(['read', 'assessment'])
|
|
70
|
-
|
|
71
|
-
expect([...declaredAxes()].sort()).toEqual(['assessment', 'mutation', 'query', 'read'])
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('does not treat the same axis twice as a conflict', () => {
|
|
75
|
-
/* Two modules reaching for `read` independently is the case this is built for. */
|
|
76
|
-
declareAxes(['read'])
|
|
77
|
-
declareAxes(['read'])
|
|
78
|
-
|
|
79
|
-
expect([...declaredAxes()]).toEqual(['read'])
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
describe('what declareAxes refuses', () => {
|
|
84
|
-
it('refuses an empty list, which would refuse every declaration', () => {
|
|
85
|
-
expect(() => declareAxes([])).toThrow(/at least one axis/)
|
|
86
|
-
expect(() => declareAxes(undefined as any)).toThrow(/at least one axis/)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('refuses a value that is not a word', () => {
|
|
90
|
-
expect(() => declareAxes([''])).toThrow(/non-empty strings/)
|
|
91
|
-
expect(() => declareAxes([' '])).toThrow(/non-empty strings/)
|
|
92
|
-
expect(() => declareAxes([null as any])).toThrow(/non-empty strings/)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('changes nothing when it refuses', () => {
|
|
96
|
-
declareAxes(['read'], { readOnly: ['read'] })
|
|
97
|
-
|
|
98
|
-
expect(() => declareAxes(['input', ''])).toThrow()
|
|
99
|
-
|
|
100
|
-
/*
|
|
101
|
-
* Every value is checked before any is stored, so a refused call leaves the vocabulary as
|
|
102
|
-
* it was. An earlier draft added axes as it walked the list and stopped at the bad one,
|
|
103
|
-
* which left `input` declared by a call that threw.
|
|
104
|
-
*/
|
|
105
|
-
expect(isDeclaredAxis('input')).toBe(false)
|
|
106
|
-
expect(isDeclaredAxis('read')).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('refuses a read-only axis that is not in the same call', () => {
|
|
110
|
-
/*
|
|
111
|
-
* A typo here is the quietest kind: `readOnly: ['querry']` would mark nothing, VIEWER
|
|
112
|
-
* would lose every category of this module, and the screen would count the shortfall
|
|
113
|
-
* correctly and present it as fact.
|
|
114
|
-
*/
|
|
115
|
-
expect(() => declareAxes(['query', 'mutation'], { readOnly: ['querry'] })).toThrow(/not in the same call/)
|
|
116
|
-
expect(() => declareAxes(['read'], { readOnly: ['query'] })).toThrow(/Declared here: read/)
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('which axes are read-only', () => {
|
|
121
|
-
it('defaults to query when nobody declared anything', () => {
|
|
122
|
-
/* The vocabulary and the answer to "which half is safe" travel together. */
|
|
123
|
-
expect([...readOnlyAxes()]).toEqual(['query'])
|
|
124
|
-
expect(readOnlyAxesAreKnown()).toBe(true)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('is unknown when a house declared its own words and did not say', () => {
|
|
128
|
-
declareAxes(['read', 'input', 'finalize'])
|
|
129
|
-
|
|
130
|
-
expect(readOnlyAxes().size).toBe(0)
|
|
131
|
-
expect(readOnlyAxesAreKnown()).toBe(false)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('takes the union across modules, like the axes themselves', () => {
|
|
135
|
-
declareAxes(['query', 'mutation'], { readOnly: ['query'] })
|
|
136
|
-
declareAxes(['read', 'finalize'], { readOnly: ['read'] })
|
|
137
|
-
|
|
138
|
-
expect([...readOnlyAxes()].sort()).toEqual(['query', 'read'])
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('does not mean the operation is a @Query', () => {
|
|
142
|
-
/*
|
|
143
|
-
* `exportLabelStudioAnnotations` is declared `@Mutation` and gated on `label-studio:query`
|
|
144
|
-
* [읽었음 task-management.ts:200-204]. It calls an external API's export and returns what
|
|
145
|
-
* comes back; nothing in our database moves. The axis is the correct one, and a role
|
|
146
|
-
* holding only that axis still changes nothing.
|
|
147
|
-
*
|
|
148
|
-
* This test carries no assertion about that file — it cannot, from here. It exists so the
|
|
149
|
-
* next person reading `readOnly` finds the distinction spelled out beside the code rather
|
|
150
|
-
* than only in a comment they might skip.
|
|
151
|
-
*/
|
|
152
|
-
declareAxes(['query', 'mutation'], { readOnly: ['query'] })
|
|
153
|
-
|
|
154
|
-
expect(readOnlyAxes().has('query')).toBe(true)
|
|
155
|
-
expect(readOnlyAxes().has('mutation')).toBe(false)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 유령 권한 판정 테스트.
|
|
3
|
-
*
|
|
4
|
-
* 계기: 트윈에서 조회 게이트 둘(`twin/query`)을 걷어낸 뒤 DB 에 그 행이 남았다. 부팅 동기화는 없는 행을
|
|
5
|
-
* **추가만** 하고 사라진 지시자의 행을 지우지 않는다. 그래서 관리자는 역할에 붙일 수 있고 붙여도 아무
|
|
6
|
-
* 일이 없는 권한을 목록에서 보게 된다.
|
|
7
|
-
*
|
|
8
|
-
* 이 판정이 틀리는 방향은 두 가지이고, 둘의 값이 다르다:
|
|
9
|
-
* · 살아 있는 권한을 유령이라고 하면 → 관리자가 **필요한 권한을 뗀다**(사용자가 못 쓰게 된다).
|
|
10
|
-
* · 유령을 살아 있다고 하면 → 목록이 조금 지저분한 상태로 남는다(지금과 같다).
|
|
11
|
-
* 그래서 확신이 없을 때는 **유령이라고 말하지 않는다.**
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { declarationKey, isDeprecatedPrivilege } from '../server/service/privilege/privilege-deprecation.js'
|
|
15
|
-
|
|
16
|
-
/** 지시자가 실제로 만드는 모양: `process['PRIVILEGES']['twin mutation'] = ['twin', 'mutation']`. */
|
|
17
|
-
const declared = (...pairs: [string, string][]) =>
|
|
18
|
-
pairs.reduce((sum, [category, privilege]) => ({ ...sum, [declarationKey(category, privilege)]: [category, privilege] }), {})
|
|
19
|
-
|
|
20
|
-
const TWIN_MUTATION = { category: 'twin', name: 'mutation' }
|
|
21
|
-
const TWIN_QUERY = { category: 'twin', name: 'query' }
|
|
22
|
-
|
|
23
|
-
describe('privilege deprecation', () => {
|
|
24
|
-
it('선언된 권한은 유령이 아니다', () => {
|
|
25
|
-
const declarations = declared(['twin', 'mutation'], ['board', 'query'])
|
|
26
|
-
|
|
27
|
-
expect(isDeprecatedPrivilege(declarations, TWIN_MUTATION)).toBe(false)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('선언이 사라진 권한은 유령이다 — 이 작업의 계기 그대로', () => {
|
|
31
|
-
/* 트윈 조회 게이트를 걷어낸 뒤의 상태: mutation 은 선언되어 있고 query 는 아니다. */
|
|
32
|
-
const declarations = declared(['twin', 'mutation'])
|
|
33
|
-
|
|
34
|
-
expect(isDeprecatedPrivilege(declarations, TWIN_QUERY)).toBe(true)
|
|
35
|
-
expect(isDeprecatedPrivilege(declarations, TWIN_MUTATION)).toBe(false)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('category 와 name 을 함께 본다 — 한쪽만 같은 것을 짝으로 세지 않는다', () => {
|
|
39
|
-
const declarations = declared(['twin', 'mutation'])
|
|
40
|
-
|
|
41
|
-
/* 이름이 같아도 다른 category 면 다른 권한이다(`query`/`mutation` 은 43개 category 가 공유한다). */
|
|
42
|
-
expect(isDeprecatedPrivilege(declarations, { category: 'board', name: 'mutation' })).toBe(true)
|
|
43
|
-
expect(isDeprecatedPrivilege(declarations, { category: 'twin', name: 'query' })).toBe(true)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('선언이 하나도 없으면 판단하지 않는다 — 전부 유령이라 답하지 않는다', () => {
|
|
47
|
-
/* 스키마가 아직 구성되지 않았거나 전역이 비어 있는 상황. 거짓 경보보다 무판단이 정직하다. */
|
|
48
|
-
for (const empty of [{}, undefined, null]) {
|
|
49
|
-
expect(isDeprecatedPrivilege(empty, TWIN_MUTATION)).toBe(false)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('반쪽 행은 어떤 선언과도 짝지을 수 없다', () => {
|
|
54
|
-
const declarations = declared(['twin', 'mutation'])
|
|
55
|
-
|
|
56
|
-
expect(isDeprecatedPrivilege(declarations, { category: 'twin' })).toBe(true)
|
|
57
|
-
expect(isDeprecatedPrivilege(declarations, { name: 'mutation' })).toBe(true)
|
|
58
|
-
expect(isDeprecatedPrivilege(declarations, {})).toBe(true)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('키 모양이 지시자와 같다 — 이 규약이 어긋나면 전부 유령이 된다', () => {
|
|
62
|
-
/* `privilege-directive.ts` 가 `${category} ${privilege}` 로 넣는다. 공백 하나가 규약이다. */
|
|
63
|
-
expect(declarationKey('twin', 'mutation')).toBe('twin mutation')
|
|
64
|
-
})
|
|
65
|
-
})
|