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:
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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 嵌套 executeIgnore:ignore 正确恢复")
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user