@oj-module/cli 0.2.54 → 0.2.57
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/devkit/README.md +7 -4
- package/devkit/SKILL.md +7 -4
- package/devkit/manual.md +63 -54
- package/devkit/module-dev-and-cdn-playground.md +7 -0
- package/devkit/publish-walkthrough.md +199 -0
- package/package.json +2 -2
- package/shell-dist/assets/{index-e_Mp2mb5.js → index-CImwskAz.js} +24 -11
- package/shell-dist/assets/runtime.js +5 -5
- package/shell-dist/index.html +3 -3
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/layout.test.ts +30 -1
- package/src/__tests__/templates-schema-columns.test.ts +99 -0
- package/src/__tests__/vendor-devkit.test.ts +87 -0
- package/src/alias-paths.ts +11 -12
- package/src/build/backend.ts +131 -0
- package/src/build/module-build.ts +364 -0
- package/src/build/module-def.ts +381 -0
- package/src/build/site-merge.ts +33 -0
- package/src/build.ts +16 -885
- package/src/builtin.ts +10 -3
- package/src/bundle.ts +61 -0
- package/src/cli.ts +4 -1
- package/src/contract/__tests__/emit-client.test.ts +4 -4
- package/src/contract/__tests__/market-client.integration.test.ts +2 -2
- package/src/contract/__tests__/run-api.test.ts +2 -2
- package/src/contract/check.ts +6 -2
- package/src/contract/emit-client.ts +10 -3
- package/src/contract/run.ts +10 -2
- package/src/init.ts +31 -46
- package/src/layout.ts +72 -16
- package/src/lifecycle/__tests__/bundle-summary.test.ts +106 -0
- package/src/lifecycle/__tests__/bundle.test.ts +6 -0
- package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
- package/src/market/__tests__/bootstrap.test.ts +1 -1
- package/src/site.ts +1 -1
- package/src/vendor.ts +34 -0
- package/templates/README.md +30 -20
- package/templates/init.include.yaml +19 -11
- package/templates/openapi.json +102 -86
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/installer.ts +8 -3
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/register.ts +53 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/roles.ts +4 -1
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/solution-perms.ts +7 -3
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/users.ts +27 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/oplog.ts +4 -2
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +7 -0
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +4 -0
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +3 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +3 -0
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +8 -0
- package/templates/src/{_platform/api → app/api/_platform}/schema.yaml +7 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/replay.ts +9 -3
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/tenant-admin.ts +18 -27
- package/templates/src/{system/api → app/api/system}/oplog/api.ts +5 -3
- package/templates/src/{system/api → app/api/system}/roles/api.ts +2 -1
- package/templates/src/{_platform/web/__client/api.ts → app/web/_platform/__client/index.ts} +5 -2
- package/templates/src/{auth/web/__client/api.ts → app/web/auth/__client/index.ts} +5 -2
- package/templates/src/app/web/notification/pages/center/index.tsx +1 -1
- package/templates/src/app/web/notification/routes.ts +1 -1
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +1 -1
- package/templates/src/app/web/personal-center/routes.ts +1 -1
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -0
- package/templates/src/app/web/system/locales/en-US.json +2 -3
- package/templates/src/app/web/system/locales/zh-CN.json +2 -3
- package/templates/src/app/web/system/pages/departments/index.tsx +2 -2
- package/templates/src/app/web/system/pages/login-methods/index.tsx +1 -1
- package/templates/src/app/web/system/pages/oplog/index.tsx +44 -19
- package/templates/src/app/web/system/pages/roles/index.tsx +12 -4
- package/templates/src/app/web/system/pages/roles/visible-roles.ts +14 -0
- package/templates/src/app/web/system/pages/settings/index.tsx +2 -2
- package/templates/src/app/web/system/pages/users/can-manage.ts +21 -0
- package/templates/src/app/web/system/pages/users/index.tsx +25 -17
- package/templates/src/app/web/system/pages/users/roles.tsx +1 -1
- package/templates/src/app/web/system/routes.ts +1 -1
- package/templates/src/{web/web/__client/api.ts → app/web/web/__client/index.ts} +5 -2
- package/templates/src/oj-market/web/api/defaultClient.ts +1 -1
- package/templates/tests/auth-ldap.spec.ts +2 -2
- package/templates/tests/auth-session.spec.ts +3 -3
- package/templates/tests/auth-throttle.spec.ts +1 -1
- package/templates/tests/fixture.ts +6 -4
- package/templates/tests/guard.spec.ts +1 -1
- package/templates/tests/lifecycle/atomic-state.spec.ts +3 -3
- package/templates/tests/lifecycle/bootstrap.spec.ts +3 -3
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +8 -8
- package/templates/tests/lifecycle/installer.spec.ts +10 -10
- package/templates/tests/lifecycle/market-api.spec.ts +14 -14
- package/templates/tests/lifecycle/migrate.spec.ts +6 -6
- package/templates/tests/lifecycle/preflight.spec.ts +3 -3
- package/templates/tests/lifecycle/register.spec.ts +1 -1
- package/templates/tests/lifecycle/role-title-translate.spec.ts +171 -0
- package/templates/tests/lifecycle/semver.spec.ts +1 -1
- package/templates/tests/lifecycle/sha384.spec.ts +1 -1
- package/templates/tests/lifecycle/solutions-api.spec.ts +1 -1
- package/templates/tests/lifecycle/source-seq.spec.ts +1 -1
- package/templates/tests/lifecycle/steps-fs.spec.ts +9 -9
- package/templates/tests/lifecycle/text-edit.spec.ts +1 -1
- package/templates/tests/lifecycle/tgz-guard.spec.ts +2 -2
- package/templates/tests/lifecycle/trust.spec.ts +5 -5
- package/templates/tests/lifecycle/uninstall.spec.ts +7 -7
- package/templates/tests/lifecycle/verify-checks.spec.ts +5 -5
- package/templates/tests/lifecycle/verify-report.spec.ts +4 -4
- package/templates/tests/org-departments.spec.ts +10 -6
- package/templates/tests/org-read.spec.ts +7 -7
- package/templates/tests/query-utils.spec.ts +1 -1
- package/templates/tests/rbac-audit.spec.ts +1 -1
- package/templates/tests/rbac-contacts.spec.ts +2 -2
- package/templates/tests/rbac-guard.spec.ts +1 -1
- package/templates/tests/rbac-roles.spec.ts +19 -2
- package/templates/tests/rbac-seed.spec.ts +1 -1
- package/templates/tests/rbac-settings.spec.ts +77 -7
- package/templates/tests/rbac-solution-perms.spec.ts +1 -1
- package/templates/tests/rbac-tables.spec.ts +29 -0
- package/templates/tests/rbac-users.spec.ts +55 -3
- package/templates/tests/roles-page.spec.ts +40 -0
- package/templates/tests/seed-role-accounts.spec.ts +1 -1
- package/templates/tests/tenant-access-cache.spec.ts +1 -1
- package/templates/tests/tenant-forgery.spec.ts +5 -5
- package/templates/tests/tenant-isolation.spec.ts +5 -4
- package/templates/tests/username-namespace.spec.ts +3 -3
- package/templates/tests/users-page.spec.ts +39 -0
- package/templates/tests/wecom-admin.spec.ts +6 -6
- package/templates/tests/wecom-client-timeout.spec.ts +1 -1
- package/templates/tests/wecom-connector.spec.ts +9 -9
- package/templates/tests/wecom-corp.spec.ts +9 -9
- package/templates/tests/wecom-crypt.spec.ts +1 -1
- package/templates/tests/wecom-endpoints.spec.ts +2 -2
- package/templates/tests/wecom-landing.spec.ts +1 -1
- package/templates/tests/wecom-org-sync.spec.ts +7 -7
- package/templates/tests/wecom-sync.spec.ts +3 -3
- package/templates/tsconfig.paths.json +11 -11
- package/templates/vitest.config.ts +2 -2
- package/templates/web.config.ts +5 -2
- package/vendor/host-versions.json +3 -3
- package/templates/src/{_platform/api → app/api/_platform}/README.md +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/atomic.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/history.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/limits.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/lock.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/semver.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/sha384.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/source-seq.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/state.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/deploy-web.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/download.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/extract.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/migrate.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/preflight.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/reload.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/smoke.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/verify.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tasks.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/text-edit.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tgz-guard.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/trust.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/types.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/uninstall.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-checks.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-report.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/respond.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/contacts.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org-read.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/settings.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/access.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/audit.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/guard.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/query.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/rate.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/_shared/site-facade.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/contacts/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/contacts/detail/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/fixtures/l1-test-user.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/manifest.yaml +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/bootstrap.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/market/index-source.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0007__users_email.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0013__users_profile.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/departments/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/is-member/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/members/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/picker/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/resolve/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/tree/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/org/user/api.ts +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.mysql.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.pg.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/seed.sql +0 -0
- package/templates/src/{_platform/api → app/api/_platform}/solutions/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/ldap-login.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/ldap.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/login-throttle.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/session.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/admin.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/client.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/config.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/contacts.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/crypt.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/events.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/login.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/payload.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/store.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/_shared/wecom/sync.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/ldap-login/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/manifest.yaml +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.mysql.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.pg.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.sql +0 -0
- package/templates/src/{auth/api → app/api/auth}/schema.yaml +0 -0
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/api.ts +0 -0
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/api.ts +1 -1
- package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/password/api.ts +1 -1
- /package/templates/src/{auth/api → app/api/auth}/wecom/api.ts +0 -0
- /package/templates/src/{auth/api → app/api/auth}/wecom/sync/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/line/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/manifest.yaml +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0001__create_home_tables.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.mysql.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.sql +0 -0
- /package/templates/src/{home/api → app/api/home}/pie/api.ts +0 -0
- /package/templates/src/{home/api → app/api/home}/schema.yaml +0 -0
- /package/templates/src/{home/api → app/api/home}/seed.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/_shared/notify.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/api.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/del/api.ts +0 -0
- /package/templates/src/{notification/api → app/api/notification}/manifest.yaml +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0001__create_notifications.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0002__notifications_user.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0003__notifications_broadcast.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.mysql.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.sql +0 -0
- /package/templates/src/{notification/api → app/api/notification}/schema.yaml +0 -0
- /package/templates/src/{notification/api → app/api/notification}/seed.sql +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/manifest.yaml +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/profile/api.ts +0 -0
- /package/templates/src/{personal-center/api → app/api/personal-center}/upload/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/detach/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/members/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/departments/move/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/login-methods/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/manifest.yaml +0 -0
- /package/templates/src/{system/api → app/api/system}/permissions/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/roles/bindings/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/settings/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/forbidden/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/password/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/profile/api.ts +0 -0
- /package/templates/src/{system/api → app/api/system}/users/roles/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/get-async-routes/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/hello/api.ts +0 -0
- /package/templates/src/{web/api → app/api/web}/manifest.yaml +0 -0
- /package/templates/src/{web/api → app/api/web}/user-info/api.ts +0 -0
- /package/templates/src/{_platform/web → app/web/_platform}/__client/api.schemas.ts +0 -0
- /package/templates/src/{auth/web → app/web/auth}/__client/api.schemas.ts +0 -0
- /package/templates/src/app/web/home/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/notification/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/personal-center/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/app/web/system/__client/{api.ts → index.ts} +0 -0
- /package/templates/src/{web → app/web}/web/__client/api.schemas.ts +0 -0
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
/* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
|
|
3
3
|
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
4
4
|
|
|
5
|
-
import { listLoginMethods, listSettings, updateSetting } from "../src/
|
|
6
|
-
import {
|
|
5
|
+
import { listLoginMethods, listSettings, updateSetting } from "../src/app/api/_platform/_shared/service/domain/settings";
|
|
6
|
+
import { recordOp } from "../src/app/api/_platform/_shared/service/infra/audit";
|
|
7
|
+
import { searchOplog } from "../src/app/api/_platform/_shared/service/infra/oplog";
|
|
7
8
|
import { installDb } from "./fixture";
|
|
8
9
|
|
|
9
10
|
let dbo: ReturnType<typeof installDb>;
|
|
@@ -27,6 +28,8 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
|
|
|
27
28
|
// 按 api-manual §1153 契约建模(引擎语义的忠实复刻,非新逻辑)。
|
|
28
29
|
function fakeTable() {
|
|
29
30
|
const conds: Array<{ test: unknown, cond: any }> = [];
|
|
31
|
+
// 分页同样只记录不执行:count 查询不该带 limit/offset,list 查询必须带上
|
|
32
|
+
const paging: { limit: number | null, offset: number | null } = { limit: null, offset: null };
|
|
30
33
|
const b: any = {
|
|
31
34
|
whereIf: (test: unknown, cond: any) => {
|
|
32
35
|
if (test !== undefined && test !== null && test !== false && !(Array.isArray(test) && test.length === 0))
|
|
@@ -35,17 +38,23 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
|
|
|
35
38
|
},
|
|
36
39
|
select: () => b,
|
|
37
40
|
orderBy: () => b,
|
|
38
|
-
limit: () =>
|
|
39
|
-
|
|
41
|
+
limit: (n: number) => {
|
|
42
|
+
paging.limit = n;
|
|
43
|
+
return b;
|
|
44
|
+
},
|
|
45
|
+
offset: (n: number) => {
|
|
46
|
+
paging.offset = n;
|
|
47
|
+
return b;
|
|
48
|
+
},
|
|
40
49
|
// 固定非零 total:保证 count/list 两条路径都构造(total=0 跳过 list 的行为
|
|
41
50
|
// 由 query-utils.spec 的 pagedFactory 用例覆盖,这里只关心条件发射)
|
|
42
51
|
all: async () => [{ c: 42 }],
|
|
43
52
|
};
|
|
44
|
-
return { b, conds };
|
|
53
|
+
return { b, conds, paging };
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
/** 以假构造器跑 searchOplog,回传 count
|
|
48
|
-
async function
|
|
56
|
+
/** 以假构造器跑 searchOplog,回传 count/list 两次构造([0]=count,[1]=list) */
|
|
57
|
+
async function captureBuilt(filter: Parameters<typeof searchOplog>[0]) {
|
|
49
58
|
const built: Array<ReturnType<typeof fakeTable>> = [];
|
|
50
59
|
const prevDb = g.db;
|
|
51
60
|
g.db = {
|
|
@@ -63,6 +72,12 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
|
|
|
63
72
|
}
|
|
64
73
|
expect(built).toHaveLength(2); // count + list 各构造一次
|
|
65
74
|
expect(built[1].conds).toEqual(built[0].conds); // mk 工厂:两组条件一致
|
|
75
|
+
return built;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 回传 count 构造发射的条件 */
|
|
79
|
+
async function captureConds(filter: Parameters<typeof searchOplog>[0]) {
|
|
80
|
+
const built = await captureBuilt(filter);
|
|
66
81
|
return built[0].conds;
|
|
67
82
|
}
|
|
68
83
|
|
|
@@ -87,6 +102,61 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
|
|
|
87
102
|
]);
|
|
88
103
|
expect(conds.some(c => (c.cond as { field?: string }).field === "level")).toBe(false);
|
|
89
104
|
});
|
|
105
|
+
|
|
106
|
+
it("page/limit 只作用在 list 查询:limit=size、offset=(page-1)*size,count 不带分页", async () => {
|
|
107
|
+
const [count, list] = await captureBuilt({ action: "user.create", page: 3, limit: 10 });
|
|
108
|
+
expect(count!.paging).toEqual({ limit: null, offset: null });
|
|
109
|
+
expect(list!.paging).toEqual({ limit: 10, offset: 20 });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("缺省/非法分页参数:第 1 页 20 条,size 静默钳 200", async () => {
|
|
113
|
+
expect((await captureBuilt({}))[1]!.paging).toEqual({ limit: 20, offset: 0 });
|
|
114
|
+
expect((await captureBuilt({ page: 0, limit: "abc" }))[1]!.paging).toEqual({ limit: 20, offset: 0 });
|
|
115
|
+
expect((await captureBuilt({ page: 2, limit: 10_000 }))[1]!.paging).toEqual({ limit: 200, offset: 200 });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 真库分页语义:上面用假构造器只钉「参数怎么发」,这里钉「切片/总数/还剩多少」算得对——
|
|
120
|
+
// 页导航能否正确显示页数全靠 total,靠 truncated 提示「结果被截断」的旧交互已删除。
|
|
121
|
+
describe("AC1-29:操作日志分页(真库切片 + total)", () => {
|
|
122
|
+
async function seedOplog(n: number) {
|
|
123
|
+
for (let i = 1; i <= n; i++)
|
|
124
|
+
await recordOp({ action: "test.page", module: "framework", target: `t${i}` });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
it("按 id 倒序切页:total 是全量而不是本页条数", async () => {
|
|
128
|
+
await seedOplog(5);
|
|
129
|
+
const p1 = await searchOplog({ page: 1, limit: 2 });
|
|
130
|
+
expect(p1.total).toBe(5);
|
|
131
|
+
expect(p1.list.map(r => r.target)).toEqual(["t5", "t4"]);
|
|
132
|
+
expect(p1.truncated).toBe(true);
|
|
133
|
+
|
|
134
|
+
const p2 = await searchOplog({ page: 2, limit: 2 });
|
|
135
|
+
expect(p2.list.map(r => r.target)).toEqual(["t3", "t2"]);
|
|
136
|
+
expect(p2.truncated).toBe(true);
|
|
137
|
+
|
|
138
|
+
// 末页:5 条 / 每页 2 → 第 3 页只剩 1 条,truncated=false(页导航据此停在第 3 页)
|
|
139
|
+
const p3 = await searchOplog({ page: 3, limit: 2 });
|
|
140
|
+
expect(p3.list.map(r => r.target)).toEqual(["t1"]);
|
|
141
|
+
expect(p3.truncated).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("越界页:list 为空但 total 仍为全量(前端据此纠正页码,而不是显示 0 条)", async () => {
|
|
145
|
+
await seedOplog(3);
|
|
146
|
+
const far = await searchOplog({ page: 99, limit: 20 });
|
|
147
|
+
expect(far.list).toEqual([]);
|
|
148
|
+
expect(far.total).toBe(3);
|
|
149
|
+
expect(far.truncated).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("过滤与分页叠加:total 按过滤后的集合算", async () => {
|
|
153
|
+
await seedOplog(5);
|
|
154
|
+
await recordOp({ action: "other.act", module: "framework", target: "x" });
|
|
155
|
+
const r = await searchOplog({ action: "test.page", page: 1, limit: 2 });
|
|
156
|
+
expect(r.total).toBe(5);
|
|
157
|
+
expect(r.list).toHaveLength(2);
|
|
158
|
+
expect(r.list.every(x => x.action === "test.page")).toBe(true);
|
|
159
|
+
});
|
|
90
160
|
});
|
|
91
161
|
|
|
92
162
|
describe("PRD §4.3:系统设置", () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
|
|
5
5
|
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
6
6
|
|
|
7
|
-
import { uninstallSolution, upsertSolutionPerms } from "../src/
|
|
7
|
+
import { uninstallSolution, upsertSolutionPerms } from "../src/app/api/_platform/_shared/service/domain/solution-perms";
|
|
8
8
|
import { installDb } from "./fixture";
|
|
9
9
|
|
|
10
10
|
let dbo: ReturnType<typeof installDb>;
|
|
@@ -61,3 +61,32 @@ describe("users 表扩展(PRD §4.2 数据模型)", () => {
|
|
|
61
61
|
expect(String(source?.dflt_value)).toContain("local");
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
// 回归:0017 给 users 加工号列。事故形态是「列进了库、schema.yaml 白名单漏了」,
|
|
66
|
+
// 构造器在发 SQL 前就拒成 `unknown column 'employee_no' in on 'users'`,日志里没有 SQL 可查。
|
|
67
|
+
// 白名单那半边在 packages/cli/src/__tests__/templates-schema-columns.test.ts 钉;
|
|
68
|
+
// 这里钉库侧语义:列真的落库、租户内唯一、NULL 不占位、跨租户可重复。
|
|
69
|
+
describe("users.employee_no(0017 工号列)", () => {
|
|
70
|
+
function addUser(username: string, tenant: string, employeeNo: string | null) {
|
|
71
|
+
dbo.prepare("INSERT INTO users (username, password_hash, tenant_id, employee_no) VALUES (?, 'x', ?, ?)")
|
|
72
|
+
.run(username, tenant, employeeNo);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
it("迁移落出 employee_no 列", () => {
|
|
76
|
+
expect(colsOf("users")).toContain("employee_no");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("唯一索引按 (tenant_id, employee_no) 且是部分索引(NULL 不占位)", () => {
|
|
80
|
+
const idx = dbo.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'uk_users_employee_no'").get() as { sql: string | null } | undefined;
|
|
81
|
+
expect(idx?.sql).toContain("tenant_id");
|
|
82
|
+
expect(idx?.sql).toContain("employee_no IS NOT NULL");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("同租户工号重复被拒,跨租户可重复,NULL 可多行", () => {
|
|
86
|
+
addUser("eno-a", "acme", "E-100");
|
|
87
|
+
expect(() => addUser("eno-b", "acme", "E-100")).toThrow(/UNIQUE/);
|
|
88
|
+
expect(() => addUser("eno-c", "globex", "E-100")).not.toThrow();
|
|
89
|
+
expect(() => addUser("eno-d", "acme", null)).not.toThrow();
|
|
90
|
+
expect(() => addUser("eno-e", "acme", null)).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// 禁止操作自己(AC1-10 注);roles 写 sys_user_role 唯一权威源 + 写时失效(AC1-7/D10);
|
|
4
4
|
// 禁用/重置口令级联删 refresh(§3.1 吊销联动)。
|
|
5
5
|
/* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
|
|
6
|
-
import type { SessionDeps } from "../src/
|
|
6
|
+
import type { SessionDeps } from "../src/app/api/auth/_shared/session";
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
9
|
-
import { changePasswordByAdmin, createUser, listUsers, setForbidden, updateUserRoles } from "../src/
|
|
10
|
-
import { createSession } from "../src/
|
|
9
|
+
import { changePasswordByAdmin, createUser, deleteUser, listUsers, setForbidden, updateUserProfile, updateUserRoles } from "../src/app/api/_platform/_shared/service/domain/users";
|
|
10
|
+
import { createSession } from "../src/app/api/auth/_shared/session";
|
|
11
11
|
import { installDb } from "./fixture";
|
|
12
12
|
|
|
13
13
|
let dbo: ReturnType<typeof installDb>;
|
|
@@ -150,6 +150,58 @@ describe("B7:口令重置与禁用(级联吊销)", () => {
|
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
describe("红线:只有 superadmin 能改 superadmin 账号", () => {
|
|
154
|
+
function mkUserWithRole(username: string, role: string): number {
|
|
155
|
+
seedRole(role);
|
|
156
|
+
const uid = mkUser(username);
|
|
157
|
+
dbo.prepare("INSERT INTO sys_user_role (user_id, role_name) VALUES (?, ?)").run(uid, role);
|
|
158
|
+
return uid;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
it("admin 重置 superadmin 口令 / 启停 / 改资料 / 删除 → 403,且库里一个字段都没动", async () => {
|
|
162
|
+
seedRole("admin");
|
|
163
|
+
const actor = mkUserWithRole("u_admin1", "admin");
|
|
164
|
+
const target = mkUserWithRole("u_super1", "superadmin");
|
|
165
|
+
await expect(changePasswordByAdmin(actor, target, "newpw123", bcryptDeps)).rejects.toMatchObject({ status: 403 });
|
|
166
|
+
await expect(setForbidden(actor, target, true)).rejects.toMatchObject({ status: 403 });
|
|
167
|
+
await expect(updateUserProfile(actor, target, { displayName: "renamed", email: "x@y.z" })).rejects.toMatchObject({ status: 403 });
|
|
168
|
+
await expect(deleteUser(actor, target)).rejects.toMatchObject({ status: 403 });
|
|
169
|
+
const row = dbo.prepare("SELECT password_hash, is_forbidden, display_name, email FROM users WHERE id = ?").get(target) as { password_hash: string, is_forbidden: number, display_name: string | null, email: string | null };
|
|
170
|
+
expect(row.password_hash).toBe("x");
|
|
171
|
+
expect(Number(row.is_forbidden)).toBe(0);
|
|
172
|
+
expect(row.display_name).toBeNull();
|
|
173
|
+
expect(row.email).toBeNull();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("admin 清空 superadmin 的角色 → 403(成员资格只拦授予方向,反向降级靠这条)", async () => {
|
|
177
|
+
seedRole("admin");
|
|
178
|
+
const actor = mkUserWithRole("u_admin2", "admin");
|
|
179
|
+
const target = mkUserWithRole("u_super2", "superadmin");
|
|
180
|
+
await expect(updateUserRoles(actor, target, [])).rejects.toMatchObject({ status: 403 });
|
|
181
|
+
await expect(updateUserRoles(actor, target, ["viewer"])).rejects.toMatchObject({ status: 403 });
|
|
182
|
+
expect(rolesOf(target)).toEqual(["superadmin"]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("superadmin 操作 superadmin → 放行(红线不是禁止改超管,而是限定谁能改)", async () => {
|
|
186
|
+
const actor = mkUserWithRole("u_super3", "superadmin");
|
|
187
|
+
const target = mkUserWithRole("u_super4", "superadmin");
|
|
188
|
+
await changePasswordByAdmin(actor, target, "newpw123", bcryptDeps);
|
|
189
|
+
await setForbidden(actor, target, true);
|
|
190
|
+
const row = dbo.prepare("SELECT password_hash, is_forbidden FROM users WHERE id = ?").get(target) as { password_hash: string, is_forbidden: number };
|
|
191
|
+
expect(row.password_hash).toBe("hashed:newpw123");
|
|
192
|
+
expect(Number(row.is_forbidden)).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("admin 操作普通用户照旧放行(门禁只盯 superadmin 目标,不能把整页封死)", async () => {
|
|
196
|
+
seedRole("admin");
|
|
197
|
+
const actor = mkUserWithRole("u_admin3", "admin");
|
|
198
|
+
const target = mkUser("u_plain");
|
|
199
|
+
await changePasswordByAdmin(actor, target, "newpw123", bcryptDeps);
|
|
200
|
+
await setForbidden(actor, target, true);
|
|
201
|
+
expect(Number((dbo.prepare("SELECT is_forbidden FROM users WHERE id = ?").get(target) as { is_forbidden: number }).is_forbidden)).toBe(1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
153
205
|
describe("B7:列表(用户管理页数据源)", () => {
|
|
154
206
|
it("listUsers 带角色数组,排除口令哈希", async () => {
|
|
155
207
|
seedRole("editor");
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// BDD:/system/roles 展示页隐去 superadmin(事故:角色页把平台级角色当成站点可分配角色列出)。
|
|
2
|
+
//
|
|
3
|
+
// 过滤只发生在**展示层**,所以这里测纯函数而不是整页:页面本身只做 query + 渲染,
|
|
4
|
+
// 真正会回归的是「哪些行算可见」。后端那半边(listRoles 必须原样返回 superadmin)在
|
|
5
|
+
// tests/rbac-roles.spec.ts 里钉,两处合起来才防住「顺手把后端也过滤掉」的改法。
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { HIDDEN_ROLE, visibleRoles } from "../src/app/web/system/pages/roles/visible-roles";
|
|
9
|
+
|
|
10
|
+
const ROWS = [
|
|
11
|
+
{ name: "superadmin", module: "_platform" },
|
|
12
|
+
{ name: "admin", module: "_platform" },
|
|
13
|
+
{ name: "custom.wiki", module: "wiki" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe("角色展示页:隐去 superadmin、其余原样", () => {
|
|
17
|
+
it("不列 superadmin,其余顺序不变", () => {
|
|
18
|
+
expect(visibleRoles(ROWS).map(r => r.name)).toEqual(["admin", "custom.wiki"]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("只按名字精确匹配:superadmin.* / 前缀相近的角色照常显示", () => {
|
|
22
|
+
const rows = [...ROWS, { name: "superadmin.readonly", module: "_platform" }, { name: "Admin", module: "_platform" }];
|
|
23
|
+
expect(visibleRoles(rows).map(r => r.name)).toEqual(["admin", "custom.wiki", "superadmin.readonly", "Admin"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("不改入参(useQuery 缓存对象不能被就地改写)", () => {
|
|
27
|
+
const input = ROWS.map(r => ({ ...r }));
|
|
28
|
+
const before = input.map(r => r.name);
|
|
29
|
+
visibleRoles(input);
|
|
30
|
+
expect(input.map(r => r.name)).toEqual(before);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("空列表不炸(查询未返回时的形态)", () => {
|
|
34
|
+
expect(visibleRoles([])).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("隐藏项与后端保留角色名同值(改名必须两处一起改)", () => {
|
|
38
|
+
expect(HIDDEN_ROLE).toBe("superadmin");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -16,7 +16,7 @@ import { beforeAll, describe, expect, it } from "vitest";
|
|
|
16
16
|
const req = createRequire(import.meta.url);
|
|
17
17
|
const { DatabaseSync: Db } = req("node:sqlite") as { DatabaseSync: typeof DatabaseSync };
|
|
18
18
|
|
|
19
|
-
const PLATFORM_API = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "
|
|
19
|
+
const PLATFORM_API = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "app", "api", "_platform");
|
|
20
20
|
|
|
21
21
|
let dbo: InstanceType<typeof DatabaseSync>;
|
|
22
22
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 这里把「同一 uid 换租户必须重查」钉死,顺手钉住写时失效不得漏掉别的租户。
|
|
12
12
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
13
13
|
|
|
14
|
-
import { currentAccess, invalidateAllUsers, invalidateUser, isSelfTenant } from "../src/
|
|
14
|
+
import { currentAccess, invalidateAllUsers, invalidateUser, isSelfTenant } from "../src/app/api/_platform/_shared/service/infra/access";
|
|
15
15
|
import { installDb } from "./fixture";
|
|
16
16
|
|
|
17
17
|
const g = globalThis as any;
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
/* eslint-disable test/prefer-lowercase-title -- 用例标题带端点路径,保留原样更易检索 */
|
|
15
15
|
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
16
16
|
|
|
17
|
-
import { invalidateUser } from "../src/
|
|
18
|
-
import notification from "../src/
|
|
19
|
-
import notificationDel from "../src/
|
|
20
|
-
import profile from "../src/personal-center/
|
|
21
|
-
import upload from "../src/personal-center/
|
|
17
|
+
import { invalidateUser } from "../src/app/api/_platform/_shared/service/infra/access";
|
|
18
|
+
import notification from "../src/app/api/notification/api";
|
|
19
|
+
import notificationDel from "../src/app/api/notification/del/api";
|
|
20
|
+
import profile from "../src/app/api/personal-center/profile/api";
|
|
21
|
+
import upload from "../src/app/api/personal-center/upload/api";
|
|
22
22
|
import { installDb } from "./fixture";
|
|
23
23
|
|
|
24
24
|
const g = globalThis as any;
|
|
@@ -80,14 +80,15 @@ describe("§5.7-6 读写自动按当前租户过滤", () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it("truncate 只清本租户(A 组表)", async () => {
|
|
83
|
+
// 用 home_pie 而非旧 demo 的 todos:后者已随示例模块删除,迁移里没有这张表。
|
|
83
84
|
asTenant("acme");
|
|
84
|
-
await db.table("
|
|
85
|
+
await db.table("home_pie").insert({ category: "acme-pie", value: 1 }).run();
|
|
85
86
|
asTenant("globex");
|
|
86
|
-
await db.table("
|
|
87
|
+
await db.table("home_pie").insert({ category: "globex-pie", value: 2 }).run();
|
|
87
88
|
|
|
88
89
|
asTenant("acme");
|
|
89
|
-
await db.table("
|
|
90
|
-
expect(rows("SELECT
|
|
90
|
+
await db.table("home_pie").truncate().run();
|
|
91
|
+
expect(rows("SELECT category FROM home_pie")).toEqual([{ category: "globex-pie" }]);
|
|
91
92
|
});
|
|
92
93
|
|
|
93
94
|
it("refresh token 换票:别家的 token_hash 在本租户查不到", async () => {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
// · 端到端:改名被拒之后,真正的外部成员 JIT 登录拿到的是**新建**的那一行。
|
|
17
17
|
|
|
18
18
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
19
|
-
import { invalidateAllUsers } from "../src/
|
|
20
|
-
import { ensureWecomUser, externalUsername } from "../src/
|
|
21
|
-
import profile from "../src/personal-center/
|
|
19
|
+
import { invalidateAllUsers } from "../src/app/api/_platform/_shared/service/infra/access";
|
|
20
|
+
import { ensureWecomUser, externalUsername } from "../src/app/api/auth/_shared/wecom/login";
|
|
21
|
+
import profile from "../src/app/api/personal-center/profile/api";
|
|
22
22
|
import { installDb } from "./fixture";
|
|
23
23
|
|
|
24
24
|
const g = globalThis as any;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// BDD:/system/users 行级门禁——只有 superadmin 能改 superadmin 账号。
|
|
2
|
+
//
|
|
3
|
+
// 与 tests/rbac-users.spec.ts 的 assertCanManageTarget 用例互为两面:那边钉服务端 403,
|
|
4
|
+
// 这边钉「前端不给会 403 的按钮」(置灰)。两处都测同一个判定口径,改名必须一起改。
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { canManageUser, PLATFORM_ROLE } from "../src/app/web/system/pages/users/can-manage";
|
|
8
|
+
|
|
9
|
+
describe("用户管理页:superadmin 行对非超管置灰", () => {
|
|
10
|
+
it("admin 看普通用户 → 可操作", () => {
|
|
11
|
+
expect(canManageUser(["admin"], ["editor"])).toBe(true);
|
|
12
|
+
expect(canManageUser(["admin"], [])).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("admin 看 superadmin → 灰(口令/状态/成员资格一律禁用)", () => {
|
|
16
|
+
expect(canManageUser(["admin"], ["superadmin"])).toBe(false);
|
|
17
|
+
// 兼有 admin + editor 也不够:这条红线只认 superadmin
|
|
18
|
+
expect(canManageUser(["admin", "editor", "viewer"], ["superadmin", "editor"])).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("superadmin 看 superadmin → 可操作(红线是限定谁能改,不是禁止改)", () => {
|
|
22
|
+
expect(canManageUser(["superadmin"], ["superadmin"])).toBe(true);
|
|
23
|
+
expect(canManageUser(["superadmin"], ["admin"])).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("不看自己:本人行的禁用由页面另外用 uid 判,别混进来", () => {
|
|
27
|
+
// 空角色 + 空目标 = 可操作,说明本函数不承担 assertNotSelf 的职责
|
|
28
|
+
expect(canManageUser([], [])).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("角色名精确匹配:superadmin.readonly 不是平台超管", () => {
|
|
32
|
+
expect(canManageUser(["admin"], ["superadmin.readonly"])).toBe(true);
|
|
33
|
+
expect(canManageUser(["SuperAdmin"], ["superadmin"])).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("门禁常量与后端保留角色名同值(改名必须两处一起改)", () => {
|
|
37
|
+
expect(PLATFORM_ROLE).toBe("superadmin");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
import { Buffer } from "node:buffer";
|
|
13
13
|
import { webcrypto } from "node:crypto";
|
|
14
14
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
15
|
-
import { assertSuperAdmin, invalidateAllUsers } from "../src/
|
|
16
|
-
import { listApps, setAppStatus } from "../src/
|
|
17
|
-
import { createWecomClient, runtimeCache, runtimeHttp } from "../src/
|
|
18
|
-
import { readSuiteConfig } from "../src/
|
|
15
|
+
import { assertSuperAdmin, invalidateAllUsers } from "../src/app/api/_platform/_shared/service/infra/access";
|
|
16
|
+
import { listApps, setAppStatus } from "../src/app/api/auth/_shared/wecom/admin";
|
|
17
|
+
import { createWecomClient, runtimeCache, runtimeHttp } from "../src/app/api/auth/_shared/wecom/client";
|
|
18
|
+
import { readSuiteConfig } from "../src/app/api/auth/_shared/wecom/config";
|
|
19
19
|
import { installDb } from "./fixture";
|
|
20
20
|
|
|
21
21
|
const g = globalThis as any;
|
|
@@ -124,11 +124,11 @@ const deps = { store: { encrypt: (p: string) => `gcm(${p})`, decrypt: (b: string
|
|
|
124
124
|
|
|
125
125
|
/** createInstall 的薄包装:省掉动态 import 的样板 */
|
|
126
126
|
async function createInstallForTest(cfg: ReturnType<typeof readSuiteConfig>, client: ReturnType<typeof createWecomClient>, state: string) {
|
|
127
|
-
const { createInstall } = await import("../src/
|
|
127
|
+
const { createInstall } = await import("../src/app/api/auth/_shared/wecom/admin");
|
|
128
128
|
return createInstall(cfg, client, state);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const api = async () => (await import("../src/
|
|
131
|
+
const api = async () => (await import("../src/app/api/auth/wecom/admin/api")).default;
|
|
132
132
|
|
|
133
133
|
beforeAll(() => {
|
|
134
134
|
dbo = installDb();
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 4. tasks 缺失 → 安全降级为「不超时」,快速请求正常返回。
|
|
12
12
|
|
|
13
13
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
-
import { runtimeHttp } from "../src/
|
|
14
|
+
import { runtimeHttp } from "../src/app/api/auth/_shared/wecom/client";
|
|
15
15
|
|
|
16
16
|
const g = globalThis as any;
|
|
17
17
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
// deps 全注入:store 用 `enc:` 前后缀假加密(Node 侧 crypto 只读,设计 §9.1),
|
|
11
11
|
// client 的 http/kv 用内存桩——真实网络与 AES 已由 wecom-crypt.spec / 运行时保证。
|
|
12
12
|
/* eslint-disable test/prefer-lowercase-title -- D/AC 编号是验收锚点,保留大写 */
|
|
13
|
-
import type { WecomCache, WecomHttp } from "../src/
|
|
14
|
-
import type { WecomConfig } from "../src/
|
|
15
|
-
import type { WecomStoreDeps } from "../src/
|
|
13
|
+
import type { WecomCache, WecomHttp } from "../src/app/api/auth/_shared/wecom/client";
|
|
14
|
+
import type { WecomConfig } from "../src/app/api/auth/_shared/wecom/config";
|
|
15
|
+
import type { WecomStoreDeps } from "../src/app/api/auth/_shared/wecom/store";
|
|
16
16
|
|
|
17
17
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
18
|
-
import { createWecomClient, runtimeCache } from "../src/
|
|
18
|
+
import { createWecomClient, runtimeCache } from "../src/app/api/auth/_shared/wecom/client";
|
|
19
19
|
import {
|
|
20
20
|
assertEncKey,
|
|
21
21
|
eventUrlOf,
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
readCallbackConfig,
|
|
24
24
|
readSuiteConfig,
|
|
25
25
|
siteRootOf,
|
|
26
|
-
} from "../src/
|
|
27
|
-
import { envOfState, handleSuiteEvent } from "../src/
|
|
26
|
+
} from "../src/app/api/auth/_shared/wecom/config";
|
|
27
|
+
import { envOfState, handleSuiteEvent } from "../src/app/api/auth/_shared/wecom/events";
|
|
28
28
|
import {
|
|
29
29
|
assertUserActive,
|
|
30
30
|
buildAuthorizeUrl,
|
|
@@ -36,8 +36,8 @@ import {
|
|
|
36
36
|
loginLandingUrl,
|
|
37
37
|
putLoginState,
|
|
38
38
|
takeLoginState,
|
|
39
|
-
} from "../src/
|
|
40
|
-
import { callbackFields, field } from "../src/
|
|
39
|
+
} from "../src/app/api/auth/_shared/wecom/login";
|
|
40
|
+
import { callbackFields, field } from "../src/app/api/auth/_shared/wecom/payload";
|
|
41
41
|
import {
|
|
42
42
|
claimLoginTicket,
|
|
43
43
|
ensureTenant,
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
saveProfile,
|
|
49
49
|
setProfileStatus,
|
|
50
50
|
tenantOfCorp,
|
|
51
|
-
} from "../src/
|
|
51
|
+
} from "../src/app/api/auth/_shared/wecom/store";
|
|
52
52
|
import { installDb } from "./fixture";
|
|
53
53
|
|
|
54
54
|
let dbo: ReturnType<typeof installDb>;
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
//
|
|
16
16
|
// 夹具即真实库(真迁移 + 真 sqlite):0003__credential_kind 的默认值也一起被这层覆盖到。
|
|
17
17
|
/* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
|
|
18
|
-
import type { WecomStoreDeps } from "../src/
|
|
18
|
+
import type { WecomStoreDeps } from "../src/app/api/auth/_shared/wecom/store";
|
|
19
19
|
import { Buffer } from "node:buffer";
|
|
20
20
|
import { createCipheriv, webcrypto } from "node:crypto";
|
|
21
21
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
22
|
-
import { connectCorp } from "../src/
|
|
23
|
-
import { createWecomClient, runtimeCache, runtimeHttp } from "../src/
|
|
24
|
-
import { callbackCreds, configuredKinds, corpIdentity, readBase, readCorpConfig, readSuiteConfig, wecomKinds } from "../src/
|
|
25
|
-
import { bytesToB64, utf8Bytes, wecomSignature } from "../src/
|
|
26
|
-
import { handleSuiteEvent } from "../src/
|
|
27
|
-
import { findCorpAuth, findProfile, probeProfile, saveProfile } from "../src/
|
|
22
|
+
import { connectCorp } from "../src/app/api/auth/_shared/wecom/admin";
|
|
23
|
+
import { createWecomClient, runtimeCache, runtimeHttp } from "../src/app/api/auth/_shared/wecom/client";
|
|
24
|
+
import { callbackCreds, configuredKinds, corpIdentity, readBase, readCorpConfig, readSuiteConfig, wecomKinds } from "../src/app/api/auth/_shared/wecom/config";
|
|
25
|
+
import { bytesToB64, utf8Bytes, wecomSignature } from "../src/app/api/auth/_shared/wecom/crypt";
|
|
26
|
+
import { handleSuiteEvent } from "../src/app/api/auth/_shared/wecom/events";
|
|
27
|
+
import { findCorpAuth, findProfile, probeProfile, saveProfile } from "../src/app/api/auth/_shared/wecom/store";
|
|
28
28
|
import { installDb } from "./fixture";
|
|
29
29
|
|
|
30
30
|
const g = globalThis as any;
|
|
@@ -209,8 +209,8 @@ function stubUpstream(opts: { depts?: Array<{ id: string, name?: string }>, memb
|
|
|
209
209
|
return { calls };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
const wecomApi = async () => (await import("../src/
|
|
213
|
-
const adminApi = async () => (await import("../src/
|
|
212
|
+
const wecomApi = async () => (await import("../src/app/api/auth/wecom/api")).default;
|
|
213
|
+
const adminApi = async () => (await import("../src/app/api/auth/wecom/admin/api")).default;
|
|
214
214
|
|
|
215
215
|
/** 回调 timestamp 必须新鲜:重放守卫拒 ±5 分钟以外的报文,用固定值会让整段用例假红 */
|
|
216
216
|
const nowSecs = () => String(Math.floor(Date.now() / 1000));
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
unwrapWecomMessage,
|
|
18
18
|
utf8Bytes,
|
|
19
19
|
wecomSignature,
|
|
20
|
-
} from "../src/
|
|
20
|
+
} from "../src/app/api/auth/_shared/wecom/crypt";
|
|
21
21
|
|
|
22
22
|
const req = createRequire(import.meta.url);
|
|
23
23
|
const nodeCrypto = req("node:crypto") as typeof import("node:crypto");
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { Buffer } from "node:buffer";
|
|
12
12
|
import { createCipheriv, webcrypto } from "node:crypto";
|
|
13
13
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
-
import { bytesToB64, sha1Hex, utf8Bytes, wecomSignature } from "../src/
|
|
14
|
+
import { bytesToB64, sha1Hex, utf8Bytes, wecomSignature } from "../src/app/api/auth/_shared/wecom/crypt";
|
|
15
15
|
import { installDb } from "./fixture";
|
|
16
16
|
|
|
17
17
|
const g = globalThis as any;
|
|
@@ -147,7 +147,7 @@ function seedCorp(corpId = "ww_corp_a", agentId = "100001"): void {
|
|
|
147
147
|
.run(SUITE, corpId, agentId, corpId, `gcm(perm_${corpId})`);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
const api = async () => (await import("../src/
|
|
150
|
+
const api = async () => (await import("../src/app/api/auth/wecom/api")).default;
|
|
151
151
|
|
|
152
152
|
beforeAll(() => {
|
|
153
153
|
dbo = installDb();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// · 参数要裁剪、要能容忍少了 tenant(此时沿用本地租户,不是登录失败);
|
|
6
6
|
// · 授权入口即使没填企业代码也要跳(后端会回 400 说明缺什么,比前端静默不动好排查)。
|
|
7
7
|
import { describe, expect, it } from "vitest";
|
|
8
|
-
import { parseWecomLanding, wecomAuthorizePath } from "../src/
|
|
8
|
+
import { parseWecomLanding, wecomAuthorizePath } from "../src/app/web/login/wecom";
|
|
9
9
|
|
|
10
10
|
describe("p2-A:回跳落地参数解析", () => {
|
|
11
11
|
it("带齐 ticket 与 tenant → 解析出两者", () => {
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
// · **登录自更新节流**:上万员工每天登录,不节流就是把 user/get 打成常驻流量;
|
|
10
10
|
// 但 `user/get` 拿不到(无权限/不在可见范围)时**不能**写节流键,否则这一整天都补不上资料。
|
|
11
11
|
// · **D10 不建占位部门**:登录路径若能顺手建部门,任何成员都能凭空造出组织节点。
|
|
12
|
-
import type { WecomCache, WecomHttp } from "../src/
|
|
13
|
-
import type { WecomStoreDeps } from "../src/
|
|
12
|
+
import type { WecomCache, WecomHttp } from "../src/app/api/auth/_shared/wecom/client";
|
|
13
|
+
import type { WecomStoreDeps } from "../src/app/api/auth/_shared/wecom/store";
|
|
14
14
|
|
|
15
15
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
16
|
-
import { upsertDepartment } from "../src/
|
|
17
|
-
import { createWecomClient, WecomApiError } from "../src/
|
|
18
|
-
import { handleSuiteEvent } from "../src/
|
|
19
|
-
import { SELF_UPDATE_TTL_SECS, selfUpdate } from "../src/
|
|
20
|
-
import { findProfile, probeProfile, putSuiteTicket, saveProfile, setProfileStatus } from "../src/
|
|
16
|
+
import { upsertDepartment } from "../src/app/api/_platform/_shared/service/domain/org";
|
|
17
|
+
import { createWecomClient, WecomApiError } from "../src/app/api/auth/_shared/wecom/client";
|
|
18
|
+
import { handleSuiteEvent } from "../src/app/api/auth/_shared/wecom/events";
|
|
19
|
+
import { SELF_UPDATE_TTL_SECS, selfUpdate } from "../src/app/api/auth/_shared/wecom/login";
|
|
20
|
+
import { findProfile, probeProfile, putSuiteTicket, saveProfile, setProfileStatus } from "../src/app/api/auth/_shared/wecom/store";
|
|
21
21
|
import { installDb } from "./fixture";
|
|
22
22
|
|
|
23
23
|
/* eslint-disable test/prefer-lowercase-title -- D/AC 编号是验收锚点,保留大写 */
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// · 权限类错码是「跳过并计数」——一旦有跳过就**不许回收**(否则一次上游抖会被读成「部门被删」);
|
|
6
6
|
// · 限流/可恢复致命错要停在原部门(游标不前进),下一次重试同一部门;
|
|
7
7
|
// · 本地建的部门/成员一根毛都不碰(D13 归属权)。
|
|
8
|
-
import type { WecomClient, WecomMember } from "../src/
|
|
8
|
+
import type { WecomClient, WecomMember } from "../src/app/api/auth/_shared/wecom/client";
|
|
9
9
|
|
|
10
10
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
11
|
-
import { WecomApiError } from "../src/
|
|
12
|
-
import { attachProfile, cancelSync, emptyView, startSync, syncStatus } from "../src/
|
|
11
|
+
import { WecomApiError } from "../src/app/api/auth/_shared/wecom/client";
|
|
12
|
+
import { attachProfile, cancelSync, emptyView, startSync, syncStatus } from "../src/app/api/auth/_shared/wecom/sync";
|
|
13
13
|
import { installDb } from "./fixture";
|
|
14
14
|
|
|
15
15
|
/* eslint-disable test/prefer-lowercase-title -- D/AC 编号是验收锚点,保留大写 */
|