test(tenant): Phase 4 — 项目隔离集成测试

新增测试 (33个全部通过):
- ProjectContextHolderTest (8 tests): set/get/clear/ignore/线程隔离
- ProjectUtilsTest (14 tests): execute/executeIgnore/嵌套调用/异常恢复
- DualInterceptorTest (11 tests): 双拦截器 SQL 注入验证 (已有)

DO 迁移验证:
- 15 个 DO 已迁移到 ProjectBaseDO (grep 确认)
- 3 个非迁移 DO 正确保持 TenantBaseDO

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-16 22:52:19 +08:00
parent a2f500fa20
commit c85f84ea46
2 changed files with 296 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
package com.viewsh.framework.tenant.core.context;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link ProjectContextHolder} 单元测试
*
* @author lzh
*/
@DisplayName("ProjectContextHolder 测试")
class ProjectContextHolderTest {
@AfterEach
void tearDown() {
ProjectContextHolder.clear();
}
@Test
@DisplayName("set/get projectId正常存取")
void testSetAndGetProjectId() {
assertNull(ProjectContextHolder.getProjectId(), "初始应为 null");
ProjectContextHolder.setProjectId(100L);
assertEquals(100L, ProjectContextHolder.getProjectId());
}
@Test
@DisplayName("getRequiredProjectId存在时返回值")
void testGetRequiredProjectId_present() {
ProjectContextHolder.setProjectId(200L);
assertEquals(200L, ProjectContextHolder.getRequiredProjectId());
}
@Test
@DisplayName("getRequiredProjectId不存在时抛出 NullPointerException")
void testGetRequiredProjectId_throwsWhenNull() {
assertNull(ProjectContextHolder.getProjectId(), "初始应为 null");
assertThrows(NullPointerException.class, ProjectContextHolder::getRequiredProjectId);
}
@Test
@DisplayName("isIgnore 默认值为 false")
void testIsIgnoreDefault() {
assertFalse(ProjectContextHolder.isIgnore(), "默认 isIgnore 应为 false");
}
@Test
@DisplayName("setIgnore(true) → isIgnore() 返回 true")
void testSetIgnoreTrue() {
ProjectContextHolder.setIgnore(true);
assertTrue(ProjectContextHolder.isIgnore());
}
@Test
@DisplayName("setIgnore(false) → isIgnore() 返回 false")
void testSetIgnoreFalse() {
ProjectContextHolder.setIgnore(true);
ProjectContextHolder.setIgnore(false);
assertFalse(ProjectContextHolder.isIgnore());
}
@Test
@DisplayName("clear() 清除 projectId 和 ignore")
void testClear() {
ProjectContextHolder.setProjectId(300L);
ProjectContextHolder.setIgnore(true);
ProjectContextHolder.clear();
assertNull(ProjectContextHolder.getProjectId(), "clear 后 projectId 应为 null");
assertFalse(ProjectContextHolder.isIgnore(), "clear 后 isIgnore 应为 false");
}
@Test
@DisplayName("线程隔离:子线程修改 context 不影响主线程")
void testThreadIsolation() throws InterruptedException {
// 主线程设置 projectId
ProjectContextHolder.setProjectId(999L);
CountDownLatch latch = new CountDownLatch(1);
Thread child = new Thread(() -> {
// 子线程独立修改自己的 context
ProjectContextHolder.setProjectId(888L);
ProjectContextHolder.setIgnore(true);
latch.countDown();
});
child.start();
latch.await();
// 主线程的值不受子线程影响
assertEquals(999L, ProjectContextHolder.getProjectId(),
"子线程修改后,主线程 projectId 应仍为 999");
assertFalse(ProjectContextHolder.isIgnore(),
"子线程修改后,主线程 ignore 应仍为 false");
}
}

View File

@@ -0,0 +1,194 @@
package com.viewsh.framework.tenant.core.util;
import com.viewsh.framework.tenant.core.context.ProjectContextHolder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link ProjectUtils} 单元测试
*
* @author lzh
*/
@DisplayName("ProjectUtils 测试")
class ProjectUtilsTest {
@AfterEach
void tearDown() {
ProjectContextHolder.clear();
}
// ==================== execute(Long, Runnable) ====================
@Test
@DisplayName("execute(Runnable):执行期间 projectId 被设置为指定值")
void testExecuteRunnable_setsProjectId() {
AtomicLong capturedId = new AtomicLong();
ProjectUtils.execute(42L, () -> capturedId.set(ProjectContextHolder.getProjectId()));
assertEquals(42L, capturedId.get());
}
@Test
@DisplayName("execute(Runnable):执行后恢复原 projectId")
void testExecuteRunnable_restoresProjectId() {
ProjectContextHolder.setProjectId(10L);
ProjectUtils.execute(42L, () -> {
// 内部期望为 42
assertEquals(42L, ProjectContextHolder.getProjectId());
});
// 执行后恢复
assertEquals(10L, ProjectContextHolder.getProjectId(), "执行后应恢复原 projectId=10");
}
@Test
@DisplayName("execute(Runnable):执行期间 ignore 被强制置为 false")
void testExecuteRunnable_forcesIgnoreFalse() {
ProjectContextHolder.setIgnore(true);
AtomicReference<Boolean> capturedIgnore = new AtomicReference<>();
ProjectUtils.execute(42L, () -> capturedIgnore.set(ProjectContextHolder.isIgnore()));
assertFalse(capturedIgnore.get(), "执行期间 isIgnore 应被强制为 false");
}
@Test
@DisplayName("execute(Runnable):执行后恢复原 ignore=true")
void testExecuteRunnable_restoresIgnoreTrue() {
ProjectContextHolder.setIgnore(true);
ProjectUtils.execute(42L, () -> { /* do nothing */ });
assertTrue(ProjectContextHolder.isIgnore(), "执行后应恢复原 ignore=true");
}
@Test
@DisplayName("execute(Runnable):原 projectId 为 null 时执行后恢复为 null")
void testExecuteRunnable_restoresNullProjectId() {
assertNull(ProjectContextHolder.getProjectId());
ProjectUtils.execute(55L, () -> assertEquals(55L, ProjectContextHolder.getProjectId()));
assertNull(ProjectContextHolder.getProjectId(), "原 projectId 为 null执行后应恢复 null");
}
// ==================== execute(Long, Callable) ====================
@Test
@DisplayName("execute(Callable):返回 Callable 的结果值")
void testExecuteCallable_returnsValue() {
String result = ProjectUtils.execute(77L, () -> "hello-" + ProjectContextHolder.getProjectId());
assertEquals("hello-77", result);
}
@Test
@DisplayName("execute(Callable):执行后恢复原 projectId")
void testExecuteCallable_restoresProjectId() {
ProjectContextHolder.setProjectId(5L);
ProjectUtils.execute(77L, () -> "ok");
assertEquals(5L, ProjectContextHolder.getProjectId(), "执行后应恢复 projectId=5");
}
@Test
@DisplayName("execute(Callable)Callable 抛出异常时包装为 RuntimeException")
void testExecuteCallable_wrapsException() {
RuntimeException ex = assertThrows(RuntimeException.class,
() -> ProjectUtils.execute(1L, () -> {
throw new Exception("test-error");
}));
assertEquals("test-error", ex.getCause().getMessage());
}
@Test
@DisplayName("execute(Callable)Callable 抛出异常后仍恢复 projectId")
void testExecuteCallable_restoresOnException() {
ProjectContextHolder.setProjectId(3L);
try {
ProjectUtils.execute(99L, () -> {
throw new Exception("boom");
});
} catch (RuntimeException ignored) {
}
assertEquals(3L, ProjectContextHolder.getProjectId(), "异常后仍应恢复 projectId=3");
}
// ==================== executeIgnore(Runnable) ====================
@Test
@DisplayName("executeIgnore(Runnable):执行期间 ignore=true")
void testExecuteIgnoreRunnable_setsIgnoreTrue() {
AtomicReference<Boolean> capturedIgnore = new AtomicReference<>();
ProjectUtils.executeIgnore(() -> capturedIgnore.set(ProjectContextHolder.isIgnore()));
assertTrue(capturedIgnore.get(), "执行期间 isIgnore 应为 true");
}
@Test
@DisplayName("executeIgnore(Runnable):执行后恢复原 ignore=false")
void testExecuteIgnoreRunnable_restoresIgnoreFalse() {
assertFalse(ProjectContextHolder.isIgnore());
ProjectUtils.executeIgnore(() -> { /* do nothing */ });
assertFalse(ProjectContextHolder.isIgnore(), "执行后应恢复 ignore=false");
}
@Test
@DisplayName("executeIgnore(Runnable):执行后恢复原 ignore=true")
void testExecuteIgnoreRunnable_restoresIgnoreTrue() {
ProjectContextHolder.setIgnore(true);
ProjectUtils.executeIgnore(() -> { /* do nothing */ });
assertTrue(ProjectContextHolder.isIgnore(), "执行后应恢复 ignore=true");
}
// ==================== 嵌套调用 ====================
@Test
@DisplayName("嵌套 execute内层完成后外层 projectId 正确恢复")
void testNestedExecute_restoresCorrectly() {
ProjectContextHolder.setProjectId(1L);
ProjectUtils.execute(2L, () -> {
assertEquals(2L, ProjectContextHolder.getProjectId(), "外层 execute 应设为 2");
ProjectUtils.execute(3L, () ->
assertEquals(3L, ProjectContextHolder.getProjectId(), "内层 execute 应设为 3"));
assertEquals(2L, ProjectContextHolder.getProjectId(), "内层结束后应恢复为 2");
});
assertEquals(1L, ProjectContextHolder.getProjectId(), "所有结束后应恢复为 1");
}
@Test
@DisplayName("execute 嵌套 executeIgnoreignore 正确恢复")
void testNestedExecuteAndIgnore() {
ProjectContextHolder.setProjectId(10L);
ProjectContextHolder.setIgnore(false);
ProjectUtils.execute(20L, () -> {
assertFalse(ProjectContextHolder.isIgnore(), "execute 内 ignore 应为 false");
ProjectUtils.executeIgnore(() ->
assertTrue(ProjectContextHolder.isIgnore(), "executeIgnore 内 ignore 应为 true"));
assertFalse(ProjectContextHolder.isIgnore(), "executeIgnore 结束后 ignore 应恢复 false");
});
assertEquals(10L, ProjectContextHolder.getProjectId());
assertFalse(ProjectContextHolder.isIgnore());
}
}