first
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package com.writeoff;
|
||||
|
||||
import com.writeoff.common.exception.BusinessException;
|
||||
import com.writeoff.module.scheduler.repository.InMemoryAsyncJobRepository;
|
||||
import com.writeoff.module.scheduler.service.AsyncJobService;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AsyncJobServiceTest {
|
||||
|
||||
@Test
|
||||
void shouldEnforceIdempotency() {
|
||||
AsyncJobService asyncJobService = new AsyncJobService(new InMemoryAsyncJobRepository());
|
||||
asyncJobService.enqueue("AUDIT_REMIND", "meetingId=1", "idem-job-1");
|
||||
Assertions.assertThrows(BusinessException.class, () ->
|
||||
asyncJobService.enqueue("AUDIT_REMIND", "meetingId=1", "idem-job-1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.writeoff;
|
||||
|
||||
import com.writeoff.module.audit.dto.AuditActionRequest;
|
||||
import com.writeoff.module.audit.model.AuditTask;
|
||||
import com.writeoff.module.audit.repository.InMemoryAuditTaskRepository;
|
||||
import com.writeoff.module.audit.service.AuditService;
|
||||
import com.writeoff.module.finance.dto.ConfirmPaymentRequest;
|
||||
import com.writeoff.module.finance.repository.InMemoryPaymentRepository;
|
||||
import com.writeoff.module.finance.service.FinanceService;
|
||||
import com.writeoff.module.meeting.dto.CreateMeetingRequest;
|
||||
import com.writeoff.module.meeting.dto.SubmitMeetingRequest;
|
||||
import com.writeoff.module.meeting.model.Meeting;
|
||||
import com.writeoff.module.meeting.repository.InMemoryMeetingRepository;
|
||||
import com.writeoff.module.meeting.service.MeetingService;
|
||||
import com.writeoff.module.project.dto.CreateProjectRequest;
|
||||
import com.writeoff.module.project.model.Project;
|
||||
import com.writeoff.module.project.repository.InMemoryProjectRepository;
|
||||
import com.writeoff.module.project.service.ProjectService;
|
||||
import com.writeoff.module.scheduler.repository.InMemoryAsyncJobRepository;
|
||||
import com.writeoff.module.scheduler.service.AsyncJobService;
|
||||
import com.writeoff.security.AuthContext;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
class MvpFlowIntegrationTest {
|
||||
|
||||
@Test
|
||||
void shouldFinishCoreFlow() throws Exception {
|
||||
AuthContext.set(1001L, 1L);
|
||||
try {
|
||||
InMemoryProjectRepository projectRepository = new InMemoryProjectRepository();
|
||||
InMemoryMeetingRepository meetingRepository = new InMemoryMeetingRepository();
|
||||
InMemoryAuditTaskRepository auditTaskRepository = new InMemoryAuditTaskRepository();
|
||||
InMemoryPaymentRepository paymentRepository = new InMemoryPaymentRepository();
|
||||
AsyncJobService asyncJobService = new AsyncJobService(new InMemoryAsyncJobRepository());
|
||||
|
||||
ProjectService projectService = new ProjectService(projectRepository);
|
||||
MeetingService meetingService = new MeetingService(meetingRepository, projectService, auditTaskRepository, asyncJobService);
|
||||
AuditService auditService = new AuditService(auditTaskRepository, meetingService, asyncJobService);
|
||||
FinanceService financeService = new FinanceService(paymentRepository, meetingService);
|
||||
|
||||
Project project = projectService.create(new CreateProjectRequestWrapper("P1", 1000000L, 3).toRequest());
|
||||
Meeting meeting = meetingService.create(new CreateMeetingRequestWrapper(project.getId(), "M1", 300000L).toRequest());
|
||||
meetingService.submit(meeting.getId(), new SubmitMeetingRequestWrapper("submit-key-1", "提交审核").toRequest());
|
||||
|
||||
List<AuditTask> tasks = auditService.listTasks().getList();
|
||||
Assertions.assertFalse(tasks.isEmpty());
|
||||
|
||||
AuditTask first = tasks.get(0);
|
||||
auditService.approve(first.getId(), new AuditActionRequestWrapper("audit-key-1", "初审通过").toRequest());
|
||||
AuditTask second = auditService.listTasks().getList().stream().filter(t -> t.getId() > first.getId()).findFirst().orElseThrow(IllegalStateException::new);
|
||||
auditService.approve(second.getId(), new AuditActionRequestWrapper("audit-key-2", "复审通过").toRequest());
|
||||
AuditTask third = auditService.listTasks().getList().stream().filter(t -> t.getId() > second.getId()).findFirst().orElseThrow(IllegalStateException::new);
|
||||
auditService.approve(third.getId(), new AuditActionRequestWrapper("audit-key-3", "终审通过").toRequest());
|
||||
|
||||
Object paymentResult = financeService.confirmPayment(new ConfirmPaymentRequestWrapper(
|
||||
"pay-key-1", project.getId(), meeting.getId(), 200000L, "oss/voucher.pdf").toRequest());
|
||||
Assertions.assertNotNull(paymentResult);
|
||||
} finally {
|
||||
AuthContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CreateProjectRequestWrapper {
|
||||
private final String name;
|
||||
private final long budgetCent;
|
||||
private final int meetingTotal;
|
||||
|
||||
private CreateProjectRequestWrapper(String name, long budgetCent, int meetingTotal) {
|
||||
this.name = name;
|
||||
this.budgetCent = budgetCent;
|
||||
this.meetingTotal = meetingTotal;
|
||||
}
|
||||
|
||||
private CreateProjectRequest toRequest() throws Exception {
|
||||
CreateProjectRequest request = new CreateProjectRequest();
|
||||
setField(request, "name", name);
|
||||
setField(request, "startDate", LocalDate.of(2026, 1, 1));
|
||||
setField(request, "endDate", LocalDate.of(2026, 12, 31));
|
||||
setField(request, "budgetCent", budgetCent);
|
||||
setField(request, "meetingTotal", meetingTotal);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CreateMeetingRequestWrapper {
|
||||
private final long projectId;
|
||||
private final String topic;
|
||||
private final long budgetCent;
|
||||
|
||||
private CreateMeetingRequestWrapper(long projectId, String topic, long budgetCent) {
|
||||
this.projectId = projectId;
|
||||
this.topic = topic;
|
||||
this.budgetCent = budgetCent;
|
||||
}
|
||||
|
||||
private CreateMeetingRequest toRequest() throws Exception {
|
||||
CreateMeetingRequest request = new CreateMeetingRequest();
|
||||
setField(request, "projectId", projectId);
|
||||
setField(request, "topic", topic);
|
||||
setField(request, "budgetCent", budgetCent);
|
||||
setField(request, "meetingCategory", "学术会");
|
||||
setField(request, "meetingForm", "线下");
|
||||
setField(request, "location", "线下");
|
||||
setField(request, "startTime", "2026-01-10 09:00:00");
|
||||
setField(request, "endTime", "2026-01-10 18:00:00");
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SubmitMeetingRequestWrapper {
|
||||
private final String key;
|
||||
private final String remark;
|
||||
|
||||
private SubmitMeetingRequestWrapper(String key, String remark) {
|
||||
this.key = key;
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
private SubmitMeetingRequest toRequest() throws Exception {
|
||||
SubmitMeetingRequest request = new SubmitMeetingRequest();
|
||||
setField(request, "idempotencyKey", key);
|
||||
setField(request, "remark", remark);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuditActionRequestWrapper {
|
||||
private final String key;
|
||||
private final String opinion;
|
||||
|
||||
private AuditActionRequestWrapper(String key, String opinion) {
|
||||
this.key = key;
|
||||
this.opinion = opinion;
|
||||
}
|
||||
|
||||
private AuditActionRequest toRequest() throws Exception {
|
||||
AuditActionRequest request = new AuditActionRequest();
|
||||
setField(request, "idempotencyKey", key);
|
||||
setField(request, "opinion", opinion);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfirmPaymentRequestWrapper {
|
||||
private final String key;
|
||||
private final long projectId;
|
||||
private final long meetingId;
|
||||
private final long amount;
|
||||
private final String voucher;
|
||||
|
||||
private ConfirmPaymentRequestWrapper(String key, long projectId, long meetingId, long amount, String voucher) {
|
||||
this.key = key;
|
||||
this.projectId = projectId;
|
||||
this.meetingId = meetingId;
|
||||
this.amount = amount;
|
||||
this.voucher = voucher;
|
||||
}
|
||||
|
||||
private ConfirmPaymentRequest toRequest() throws Exception {
|
||||
ConfirmPaymentRequest request = new ConfirmPaymentRequest();
|
||||
setField(request, "idempotencyKey", key);
|
||||
setField(request, "projectId", projectId);
|
||||
setField(request, "meetingId", meetingId);
|
||||
setField(request, "amountCent", amount);
|
||||
setField(request, "paymentVoucherOssKey", voucher);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.writeoff.module.system.service;
|
||||
|
||||
import com.writeoff.security.AuthContext;
|
||||
import com.writeoff.security.PasswordCodecService;
|
||||
import com.writeoff.security.PasswordPolicyService;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class SystemUserServicePasswordTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAcceptLegacyPlaintextOldPasswordAndUpgradeToHashWhenChangingPassword() {
|
||||
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
|
||||
PasswordCodecService passwordCodecService = new PasswordCodecService();
|
||||
SystemUserService systemUserService = new SystemUserService(
|
||||
jdbcTemplate,
|
||||
null,
|
||||
new PasswordPolicyService(),
|
||||
passwordCodecService
|
||||
);
|
||||
AuthContext.set(1001L, 2001L);
|
||||
when(jdbcTemplate.queryForObject(
|
||||
eq("SELECT COUNT(1) FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0"),
|
||||
eq(Integer.class),
|
||||
eq(2001L),
|
||||
eq(1001L)
|
||||
)).thenReturn(1);
|
||||
when(jdbcTemplate.queryForObject(
|
||||
eq("SELECT password_hash FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0 LIMIT 1"),
|
||||
eq(String.class),
|
||||
eq(2001L),
|
||||
eq(1001L)
|
||||
)).thenReturn("legacy-plain-password");
|
||||
|
||||
systemUserService.changeMyPassword(1001L, "legacy-plain-password", "Abcd1234!");
|
||||
|
||||
verify(jdbcTemplate).update(
|
||||
eq("UPDATE sys_user SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?"),
|
||||
argThat(matchesEncodedPassword("Abcd1234!", passwordCodecService)),
|
||||
eq(2001L),
|
||||
eq(1001L)
|
||||
);
|
||||
}
|
||||
|
||||
private ArgumentMatcher<Object> matchesEncodedPassword(String rawPassword, PasswordCodecService passwordCodecService) {
|
||||
return value -> value instanceof String
|
||||
&& passwordCodecService.isEncoded((String) value)
|
||||
&& passwordCodecService.matches(rawPassword, (String) value);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.writeoff.module.system.service;
|
||||
|
||||
import com.writeoff.common.exception.BusinessException;
|
||||
import com.writeoff.module.system.dto.CreateUserDelegationRequest;
|
||||
import com.writeoff.security.AuthContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
class UserDelegationServiceValidationTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSelfDelegation() throws Exception {
|
||||
JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class);
|
||||
UserDelegationService service = new UserDelegationService(jdbcTemplate);
|
||||
AuthContext.set(1L, 1L);
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForObject(
|
||||
Mockito.eq("SELECT COUNT(1) FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0"),
|
||||
Mockito.eq(Integer.class),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq(10L)
|
||||
)).thenReturn(1);
|
||||
|
||||
CreateUserDelegationRequest request = new CreateUserDelegationRequest();
|
||||
setField(request, "delegateUserId", 10L);
|
||||
setField(request, "effectiveFrom", "2026-03-10 10:00:00");
|
||||
setField(request, "effectiveTo", "2026-03-10 18:00:00");
|
||||
|
||||
Assertions.assertThrows(BusinessException.class, () -> service.create(10L, request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInvalidTimeWindow() throws Exception {
|
||||
JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class);
|
||||
UserDelegationService service = new UserDelegationService(jdbcTemplate);
|
||||
AuthContext.set(1L, 1L);
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForObject(
|
||||
Mockito.eq("SELECT COUNT(1) FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0"),
|
||||
Mockito.eq(Integer.class),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq(10L)
|
||||
)).thenReturn(1);
|
||||
Mockito.when(jdbcTemplate.queryForObject(
|
||||
Mockito.eq("SELECT COUNT(1) FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0"),
|
||||
Mockito.eq(Integer.class),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq(20L)
|
||||
)).thenReturn(1);
|
||||
|
||||
CreateUserDelegationRequest request = new CreateUserDelegationRequest();
|
||||
setField(request, "delegateUserId", 20L);
|
||||
setField(request, "effectiveFrom", "2026-03-10 18:00:00");
|
||||
setField(request, "effectiveTo", "2026-03-10 10:00:00");
|
||||
|
||||
Assertions.assertThrows(BusinessException.class, () -> service.create(10L, request));
|
||||
Mockito.verify(jdbcTemplate, Mockito.never()).update(
|
||||
Mockito.contains("INSERT INTO user_delegation"),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any()
|
||||
);
|
||||
}
|
||||
|
||||
private static void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.writeoff.security;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class LoginPasswordCryptoServiceTest {
|
||||
|
||||
private static final OAEPParameterSpec OAEP_SHA256_MGF1_SHA256 = new OAEPParameterSpec(
|
||||
"SHA-256",
|
||||
"MGF1",
|
||||
MGF1ParameterSpec.SHA256,
|
||||
PSource.PSpecified.DEFAULT
|
||||
);
|
||||
|
||||
@Test
|
||||
void shouldUnwrapBrowserCompatibleEncryptedPassword() throws Exception {
|
||||
LoginPasswordCryptoService service = new LoginPasswordCryptoService();
|
||||
service.init();
|
||||
|
||||
String rawPassword = "Abcd1234!";
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(
|
||||
new X509EncodedKeySpec(Base64.getDecoder().decode(service.getEncodedPublicKey()))
|
||||
);
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey, OAEP_SHA256_MGF1_SHA256);
|
||||
String encryptedPassword = LoginPasswordCryptoService.PASSWORD_PREFIX
|
||||
+ Base64.getEncoder().encodeToString(cipher.doFinal(rawPassword.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
assertEquals(rawPassword, service.unwrapPassword(encryptedPassword));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepPlainPasswordUntouched() {
|
||||
LoginPasswordCryptoService service = new LoginPasswordCryptoService();
|
||||
|
||||
assertEquals("123456", service.unwrapPassword("123456"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.writeoff.security;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PasswordCodecServiceTest {
|
||||
|
||||
private final PasswordCodecService passwordCodecService = new PasswordCodecService();
|
||||
|
||||
@Test
|
||||
void shouldEncodePasswordWithSaltAndMatch() {
|
||||
String rawPassword = "Abcd1234!";
|
||||
|
||||
String first = passwordCodecService.encode(rawPassword);
|
||||
String second = passwordCodecService.encode(rawPassword);
|
||||
|
||||
assertTrue(passwordCodecService.isEncoded(first));
|
||||
assertTrue(passwordCodecService.matches(rawPassword, first));
|
||||
assertTrue(passwordCodecService.matches(rawPassword, second));
|
||||
assertNotEquals(first, second);
|
||||
assertFalse(passwordCodecService.matches("wrong-password", first));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemainCompatibleWithLegacyPlaintextPassword() {
|
||||
assertTrue(passwordCodecService.matches("123456", "123456"));
|
||||
assertFalse(passwordCodecService.isEncoded("123456"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.writeoff.security;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class PermissionServiceDelegationTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
AuthContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInheritPermissionFromPrincipalWhenDelegationEnabled() {
|
||||
JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class);
|
||||
PermissionService permissionService = new PermissionService(jdbcTemplate);
|
||||
AuthContext.set(2001L, 1L);
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForObject(
|
||||
Mockito.eq("SELECT COUNT(1) FROM user_role ur " +
|
||||
"JOIN role_permission rp ON ur.role_id=rp.role_id AND ur.tenant_id=rp.tenant_id " +
|
||||
"JOIN permission p ON rp.permission_id=p.id " +
|
||||
"WHERE ur.user_id=? AND ur.tenant_id=? AND p.permission_code=?"),
|
||||
Mockito.eq(Integer.class),
|
||||
Mockito.eq(2001L),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq("meeting.approve")
|
||||
)).thenReturn(0);
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForList(
|
||||
Mockito.eq("SELECT user_id FROM user_delegation " +
|
||||
"WHERE tenant_id=? AND delegate_user_id=? AND is_deleted=0 AND status='ENABLED' " +
|
||||
"AND effective_from<=CURRENT_TIMESTAMP AND effective_to>=CURRENT_TIMESTAMP"),
|
||||
Mockito.eq(Long.class),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq(2001L)
|
||||
)).thenReturn(Collections.singletonList(1001L));
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForObject(
|
||||
Mockito.eq("SELECT COUNT(1) FROM user_role ur " +
|
||||
"JOIN role_permission rp ON ur.role_id=rp.role_id AND ur.tenant_id=rp.tenant_id " +
|
||||
"JOIN permission p ON rp.permission_id=p.id " +
|
||||
"WHERE ur.user_id=? AND ur.tenant_id=? AND p.permission_code=?"),
|
||||
Mockito.eq(Integer.class),
|
||||
Mockito.eq(1001L),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq("meeting.approve")
|
||||
)).thenReturn(1);
|
||||
|
||||
boolean ok = permissionService.hasPermission(2001L, "meeting.approve");
|
||||
Assertions.assertTrue(ok);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMergePermissionsFromSelfAndDelegationWithoutDuplicates() {
|
||||
JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class);
|
||||
PermissionService permissionService = new PermissionService(jdbcTemplate);
|
||||
AuthContext.set(2001L, 1L);
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForList(
|
||||
Mockito.eq("SELECT DISTINCT p.permission_code FROM user_role ur " +
|
||||
"JOIN role_permission rp ON ur.role_id=rp.role_id AND ur.tenant_id=rp.tenant_id " +
|
||||
"JOIN permission p ON rp.permission_id=p.id " +
|
||||
"WHERE ur.user_id=? AND ur.tenant_id=?"),
|
||||
Mockito.eq(String.class),
|
||||
Mockito.eq(2001L),
|
||||
Mockito.eq(1L)
|
||||
)).thenReturn(Arrays.asList("user.read", "user.delegation.manage"));
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForList(
|
||||
Mockito.eq("SELECT user_id FROM user_delegation " +
|
||||
"WHERE tenant_id=? AND delegate_user_id=? AND is_deleted=0 AND status='ENABLED' " +
|
||||
"AND effective_from<=CURRENT_TIMESTAMP AND effective_to>=CURRENT_TIMESTAMP"),
|
||||
Mockito.eq(Long.class),
|
||||
Mockito.eq(1L),
|
||||
Mockito.eq(2001L)
|
||||
)).thenReturn(Collections.singletonList(1001L));
|
||||
|
||||
Mockito.when(jdbcTemplate.queryForList(
|
||||
Mockito.eq("SELECT DISTINCT p.permission_code FROM user_role ur " +
|
||||
"JOIN role_permission rp ON ur.role_id=rp.role_id AND ur.tenant_id=rp.tenant_id " +
|
||||
"JOIN permission p ON rp.permission_id=p.id " +
|
||||
"WHERE ur.user_id=? AND ur.tenant_id=?"),
|
||||
Mockito.eq(String.class),
|
||||
Mockito.eq(1001L),
|
||||
Mockito.eq(1L)
|
||||
)).thenReturn(Arrays.asList("meeting.approve", "user.read"));
|
||||
|
||||
List<String> perms = permissionService.getPermissions(2001L);
|
||||
Assertions.assertTrue(perms.contains("user.read"));
|
||||
Assertions.assertTrue(perms.contains("user.delegation.manage"));
|
||||
Assertions.assertTrue(perms.contains("meeting.approve"));
|
||||
Assertions.assertEquals(3, perms.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user