feat(iam): split domestic and overseas container permissions

This commit is contained in:
2026-09-08 19:11:29 +08:00
parent b0c81e337d
commit 19d9a89651
4 changed files with 258 additions and 1 deletions
@@ -35,6 +35,14 @@ class IamOpenApiContractTest {
.contains("platform:overseas-warehouse:customer-visit:list")
.contains("platform:overseas-warehouse:customer-visit:edit")
.contains("platform:overseas-warehouse:outbound:cancel")
.contains("platform:firstleg:container:list/create/edit")
.contains("platform:firstleg:terminal-drayage:list")
.contains("platform:firstleg:terminal-drayage:document:edit")
.contains("platform:firstleg:terminal-drayage:fleet:edit")
.contains("platform:firstleg:terminal-drayage:pickup-appointment:edit")
.contains("platform:firstleg:terminal-drayage:pickup:confirm")
.contains("platform:firstleg:terminal-drayage:empty-return:confirm")
.contains("不以 `container:list/edit` 兼容放行国外操作")
.contains("merchant:warehouse:delivery:list")
.contains("merchant:warehouse:delivery:create")
.contains("merchant:cross-border:procurement")
@@ -0,0 +1,110 @@
package com.baiyue.iam.repository;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat;
class ContainerWorkspacePermissionSqlContractTest {
private static final Pattern CODE = Pattern.compile("platform:firstleg:(?:container|terminal-drayage):[a-z0-9:-]+");
private static final Set<String> REQUIRED = Set.of(
"platform:firstleg:container:list",
"platform:firstleg:container:create",
"platform:firstleg:container:edit",
"platform:firstleg:terminal-drayage:list",
"platform:firstleg:terminal-drayage:document:edit",
"platform:firstleg:terminal-drayage:fleet:edit",
"platform:firstleg:terminal-drayage:pickup-appointment:edit",
"platform:firstleg:terminal-drayage:pickup:confirm",
"platform:firstleg:terminal-drayage:empty-return:confirm");
@Test
void definesSeparateDomesticAndOverseasMenusWithoutAliases() throws Exception {
String sql = sql();
assertThat(codes(catalog(sql))).containsExactlyInAnyOrderElementsOf(REQUIRED);
assertThat(sql).contains(
"'platform:firstleg:container:list',\n '国内送柜报关','/container-loading/domestic','GET'",
"'platform:firstleg:terminal-drayage:list',\n '国外清关提柜','/container-loading/overseas','GET'",
"permission.permission_code='platform:firstleg:container:view'",
"SET status='INACTIVE', deleted=1");
assertThat(codes(catalog(sql))).noneMatch(code -> code.endsWith(":view"));
assertThat(sql).doesNotContain(
"platform:firstleg:domestic-container:",
"platform:firstleg:overseas-container:",
"platform:firstleg:terminal-drayage:edit");
}
@Test
void keepsDomesticWritesAndDefinesFineGrainedOverseasWrites() throws Exception {
assertThat(sql()).contains(
"'platform:firstleg:container:create',\n '建立国内货柜',NULL,'POST'",
"'platform:firstleg:container:edit',\n '维护国内货柜资料',NULL,'PUT'",
"'platform:firstleg:terminal-drayage:document:edit',\n '维护目的港清关与DO/PU',NULL,'POST'",
"'platform:firstleg:terminal-drayage:fleet:edit',\n '维护目的港码头车队',NULL,'POST'",
"'platform:firstleg:terminal-drayage:pickup-appointment:edit',\n '预约目的港提柜',NULL,'POST'",
"'platform:firstleg:terminal-drayage:pickup:confirm',\n '确认目的港实际提柜',NULL,'POST'",
"'platform:firstleg:terminal-drayage:empty-return:confirm',\n '确认目的港实际还柜',NULL,'POST'");
}
@Test
void grantsTheCompleteMatrixOnlyToTheExistingDefaultPlatformRoles() throws Exception {
String sql = sql();
int grantStart = sql.indexOf("-- 平台超级管理员和现有订单运营保持货柜全链路能力");
int assertionStart = sql.indexOf("IF (SELECT COUNT(*) FROM iam_permission", grantStart);
String grant = sql.substring(grantStart, assertionStart);
assertThat(grant).contains(
"role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')",
"platform:firstleg:container:list",
"platform:firstleg:terminal-drayage:list",
"platform:firstleg:terminal-drayage:empty-return:confirm");
assertThat(grant).doesNotContain(
"PLATFORM_CUSTOM_ADMIN",
"WAREHOUSE_OPERATOR",
"WAREHOUSE_TALLY",
"OVERSEAS_WAREHOUSE_PDA_OPERATOR",
"CN_FIRSTLEG_PDA_OPERATOR",
"MERCHANT_OWNER");
assertThat(sql).contains(
"permission_code IN (",
")) <> 9 THEN",
")) <> 18 THEN");
}
@Test
void failsClosedForFixedIdentityConflictsAndRemainsReplaySafe() throws Exception {
assertThat(sql()).contains(
"migrate_container_workspace_permissions",
"id = 5102090 AND permission_code <> 'platform:firstleg:container:list'",
"id = 5102205 AND permission_code <> 'platform:firstleg:terminal-drayage:empty-return:confirm'",
"permission_code = 'platform:firstleg:terminal-drayage:list' AND id <> 5102200",
"SIGNAL SQLSTATE '45000'",
"ON DUPLICATE KEY UPDATE",
"INSERT IGNORE INTO iam_role_permission");
}
private static String sql() throws Exception {
return new ClassPathResource("db/container-workspace-permissions.sql")
.getContentAsString(StandardCharsets.UTF_8);
}
private static Set<String> codes(String source) {
Set<String> values = new LinkedHashSet<>();
CODE.matcher(source).results().map(result -> result.group()).forEach(values::add);
return values;
}
private static String catalog(String sql) {
int start = sql.indexOf("INSERT INTO iam_permission");
int end = sql.indexOf("ON DUPLICATE KEY UPDATE", start);
assertThat(start).isGreaterThanOrEqualTo(0);
assertThat(end).isGreaterThan(start);
return sql.substring(start, end);
}
}