feat(M02-D): 完成用户与员工权限管理
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from '../dist/tenancy/platform-config-repository.js';
|
||||
import { AuthRepository } from '../dist/auth/auth-repository.js';
|
||||
import { RbacRepository } from '../dist/auth/rbac-repository.js';
|
||||
import { UserManagementRepository } from '../dist/auth/user-management-repository.js';
|
||||
import {
|
||||
executeMigrationPlan,
|
||||
loadMigrationPlan,
|
||||
@@ -38,6 +39,7 @@ const expectedTables = [
|
||||
'qipai_tenant_apps',
|
||||
'qipai_tenant_configs',
|
||||
'qipai_tenants',
|
||||
'qipai_user_admin_profiles',
|
||||
'qipai_user_identities',
|
||||
'qipai_user_roles',
|
||||
'qipai_user_store_scopes',
|
||||
@@ -62,9 +64,9 @@ async function readMigrationVersions(pool) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT version, name
|
||||
FROM qipai_schema_migrations
|
||||
WHERE version IN (?, ?, ?, ?, ?)
|
||||
WHERE version IN (?, ?, ?, ?, ?, ?)
|
||||
ORDER BY version`,
|
||||
['2026061601', '2026061802', '2026061803', '2026061804', '2026061805']
|
||||
['2026061601', '2026061802', '2026061803', '2026061804', '2026061805', '2026061806']
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
@@ -250,6 +252,69 @@ async function assertRevocableAuthSession(pool, context) {
|
||||
assert.equal(await repository.validateSession(roleSessionId, context.tenantId, session.user.id), null);
|
||||
}
|
||||
|
||||
async function assertUserManagement(pool, context) {
|
||||
const [adminResult] = await pool.query(
|
||||
`INSERT INTO qipai_users (tenant_id, user_type, nickname, phone)
|
||||
VALUES (?, 'STAFF', 'Tenant Admin', '13800000001')`,
|
||||
[context.tenantId]
|
||||
);
|
||||
const adminId = String(adminResult.insertId);
|
||||
await pool.query(
|
||||
`INSERT INTO qipai_user_roles (tenant_id, user_id, role_id)
|
||||
SELECT ?, ?, id FROM qipai_roles WHERE tenant_id = ? AND code = 'TENANT_ADMIN'`,
|
||||
[context.tenantId, adminId, context.tenantId]
|
||||
);
|
||||
const rbac = new RbacRepository(pool);
|
||||
const access = await rbac.getAccessProfile(context.tenantId, adminId);
|
||||
assert.ok(access.capabilities.includes('tenant.manage'));
|
||||
assert.ok(access.capabilities.includes('staff.manage'));
|
||||
const [storeResult] = await pool.query(
|
||||
`INSERT INTO qipai_stores (tenant_id, name) VALUES (?, 'M02D Staff Store')`,
|
||||
[context.tenantId]
|
||||
);
|
||||
const repository = new UserManagementRepository(pool);
|
||||
const actor = {
|
||||
tenantId: context.tenantId,
|
||||
userId: adminId,
|
||||
access,
|
||||
traceId: 'm02d-live-test',
|
||||
ip: '127.0.0.1',
|
||||
userAgent: 'M02-D live test'
|
||||
};
|
||||
const created = await repository.createStaff(actor, {
|
||||
nickname: 'Live Staff',
|
||||
phone: '13800000002',
|
||||
note: 'sanitized live test',
|
||||
roles: ['STAFF'],
|
||||
storeIds: [String(storeResult.insertId)]
|
||||
});
|
||||
const sessionId = '7197e528-f727-4c85-a490-f5ec1721594c';
|
||||
await pool.query(
|
||||
`INSERT INTO qipai_auth_sessions
|
||||
(id, tenant_id, platform_app_id, user_id, role_version, expires_at)
|
||||
SELECT ?, ?, ?, id, role_version, DATE_ADD(UTC_TIMESTAMP(3), INTERVAL 1 HOUR)
|
||||
FROM qipai_users WHERE tenant_id = ? AND id = ?`,
|
||||
[sessionId, context.tenantId, context.platformAppId, context.tenantId, created.userId]
|
||||
);
|
||||
await repository.updateUser(actor, created.userId, {
|
||||
status: 'DISABLED',
|
||||
roles: ['STAFF'],
|
||||
storeIds: [String(storeResult.insertId)]
|
||||
});
|
||||
const [sessionRows] = await pool.query(
|
||||
'SELECT status, revoke_reason AS revokeReason FROM qipai_auth_sessions WHERE id = ?',
|
||||
[sessionId]
|
||||
);
|
||||
assert.deepEqual(sessionRows, [{ status: 'REVOKED', revokeReason: 'ACCESS_CHANGED' }]);
|
||||
const [auditRows] = await pool.query(
|
||||
`SELECT action FROM qipai_audit_logs
|
||||
WHERE tenant_id = ? AND resource_type = 'USER' AND resource_id = ?
|
||||
ORDER BY id`,
|
||||
[context.tenantId, created.userId]
|
||||
);
|
||||
assert.deepEqual(auditRows.map((row) => row.action), ['STAFF_CREATED', 'USER_UPDATED']);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
assert.equal(config.mysql.passwordConfigured, true, 'Live migration test requires a temporary password.');
|
||||
assert.match(
|
||||
@@ -278,11 +343,13 @@ try {
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' },
|
||||
{ version: '2026061803', name: 'm02a_tenant_apps' },
|
||||
{ version: '2026061804', name: 'm02b_wechat_auth' },
|
||||
{ version: '2026061805', name: 'm02c_rbac' }
|
||||
{ version: '2026061805', name: 'm02c_rbac' },
|
||||
{ version: '2026061806', name: 'm02d_user_management' }
|
||||
]);
|
||||
await assertTaskDurability(pool);
|
||||
const loginContext = await assertPlatformTenantIsolation(pool);
|
||||
await assertRevocableAuthSession(pool, loginContext);
|
||||
await assertUserManagement(pool, loginContext);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: first up, verify, tenant isolation and revocable auth checks completed.');
|
||||
|
||||
@@ -299,7 +366,8 @@ try {
|
||||
{ version: '2026061802', name: 'm01c_async_tasks' },
|
||||
{ version: '2026061803', name: 'm02a_tenant_apps' },
|
||||
{ version: '2026061804', name: 'm02b_wechat_auth' },
|
||||
{ version: '2026061805', name: 'm02c_rbac' }
|
||||
{ version: '2026061805', name: 'm02c_rbac' },
|
||||
{ version: '2026061806', name: 'm02d_user_management' }
|
||||
]);
|
||||
await assertLegacyCompatibility(pool);
|
||||
console.log('PASS: second up and verify restored the schema.');
|
||||
@@ -332,7 +400,10 @@ try {
|
||||
'session revocation',
|
||||
'role-version invalidation',
|
||||
'customer capabilities',
|
||||
'cross-tenant store grant rejection'
|
||||
'cross-tenant store grant rejection',
|
||||
'staff creation and store assignment',
|
||||
'access-change session revocation',
|
||||
'user-management audit log'
|
||||
]
|
||||
}, null, 2));
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user