Просмотр исходного кода

fix(backend): 重写applyPrivacyMask+4个隐私裁剪测试

wangkangyjy 1 неделя назад
Родитель
Сommit
44b56d7f9a

+ 21 - 30
backend/src/main/java/com/yimeng/okr/service/PerformanceService.java

@@ -182,9 +182,6 @@ public class PerformanceService {
             dto.setSuperiorItems(queryScoreItems(dto.getSuperiorScore().getId()));
         }
         if (dto.getFinalScore() != null) {
-            dto.setFinalItems(null);
-        dto.setSelfItems(null);
-        dto.setSuperiorItems(null);
             dto.setFinalItems(queryScoreItems(dto.getFinalScore().getId()));
         }
 
@@ -211,21 +208,17 @@ public class PerformanceService {
         boolean isSuperior = targetUser.getSuperiorId() != null
                 && targetUser.getSuperiorId().equals(actorId);
 
-        if (isSelf || isHrOrAbove) {
-            if (isSelf && dto.getFinalScore() != null && !isPublished(dto.getFinalScore())) {
+        if (isSelf && dto.getFinalScore() != null && !isPublished(dto.getFinalScore())) {
+            // 本人查看未发布的终评:隐藏终评内容
+            dto.setFinalScore(null);
             dto.setFinalItems(null);
-        dto.setSelfItems(null);
-        dto.setSuperiorItems(null);
-                dto.setFinalScore(null);
-                dto.setFinalItems(null);
-        dto.setSelfItems(null);
-        dto.setSuperiorItems(null);
-            }
-            return; // 全量可见
+        }
+        if (isSelf || isHrOrAbove) {
+            return; // 本人和HR/超管:不额外裁剪
         }
 
         if (isSuperior) {
-            // 上级可见总分,但屏蔽自评的详细评分明细
+            // 直属上级:屏蔽自评详细内容
             if (dto.getSelfScore() != null) {
                 dto.getSelfScore().setCommentsJson(null);
                 dto.setSelfItems(null);
@@ -233,26 +226,24 @@ public class PerformanceService {
             return;
         }
 
-        // 防御性裁剪:非上级非HR不应到达此处(前置认证已拦截),
-        // 但作为纵深防御,仅保留最终评分的总分和等级
-        if (dto.getSelfScore() != null) {
-            dto.getSelfScore().setCommentsJson(null);
-            dto.setSelfItems(null);
-            dto.getSelfScore().setOkrScore(null);
-            dto.getSelfScore().setKpiScore(null);
-        }
-        if (dto.getSuperiorScore() != null) {
-            dto.getSuperiorScore().setCommentsJson(null);
-            dto.getSuperiorScore().setOkrScore(null);
-            dto.getSuperiorScore().setKpiScore(null);
-        }
-        if (dto.getFinalScore() != null) {
-            dto.setFinalItems(null);
+        // 防御性裁剪:仅保留最终评分的总分和等级
+        clearScoreFields(dto.getSelfScore());
         dto.setSelfItems(null);
+        clearScoreFields(dto.getSuperiorScore());
         dto.setSuperiorItems(null);
+        if (dto.getFinalScore() != null) {
             dto.getFinalScore().setCommentsJson(null);
         }
-        dto.setFeedback(null); // 面谈记录不可见
+        dto.setFinalItems(null);
+        dto.setFeedback(null);
+    }
+
+    private void clearScoreFields(PerformanceScore score) {
+        if (score != null) {
+            score.setCommentsJson(null);
+            score.setOkrScore(null);
+            score.setKpiScore(null);
+        }
     }
 
     public List<ScoringDto> getSubordinateScores(Long periodId, Long superiorId) {

+ 104 - 1
backend/src/test/java/com/yimeng/okr/service/PerformanceServiceTest.java

@@ -1,8 +1,13 @@
 package com.yimeng.okr.service;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
 
 import com.yimeng.okr.entity.AssessmentPeriod;
 import com.yimeng.okr.entity.AuditCorrection;
 import com.yimeng.okr.entity.PerformanceScore;
+import com.yimeng.okr.entity.PerformanceFeedback;
+import com.yimeng.okr.dto.OkrDetailDto;
+import com.yimeng.okr.dto.ScoringDto;
 import com.yimeng.okr.entity.SysUser;
 import com.yimeng.okr.enums.PeriodStatus;
 import com.yimeng.okr.enums.ScoreType;
@@ -24,6 +29,7 @@ import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
 @ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
 class PerformanceServiceTest {
 
     @Mock PerformanceScoreMapper scoreMapper;
@@ -35,6 +41,11 @@ class PerformanceServiceTest {
     @Mock OkrService okrService;
     @Mock AuthorizationService authorizationService;
     @Mock ApplicationEventPublisher publisher;
+    private PerformanceScore selfScore;
+    private PerformanceScore finalScore;
+    private PerformanceFeedback feedback;
+    private PerformanceScoreItem sampleItem;
+    private OkrDetailDto okrDetail;
     @InjectMocks PerformanceService performanceService;
 
     @Test
@@ -245,7 +256,7 @@ class PerformanceServiceTest {
 
     private AssessmentPeriod period() {
         AssessmentPeriod period = new AssessmentPeriod();
-        period.setId(1L);
+        period.setId(1L); period.setStartDate(java.time.LocalDate.of(2026,1,1)); period.setEndDate(java.time.LocalDate.of(2026,12,31));
         period.setStatus(PeriodStatus.ASSESSING);
         return period;
     }
@@ -300,4 +311,96 @@ class PerformanceServiceTest {
         assertEquals(8.0, dimItem.getScore());
         assertEquals(10.0, dimItem.getMaxScore());
     }
+
+    @Test
+    void selfCannotSeeUnpublishedFinalItems() {
+        setupPrivacyMocks(1L, 2L, 2L, "SELF_SUBMITTED", "PENDING_PUBLISH");
+
+        ScoringDto dto = performanceService.getScoringPage(1L, 2L, 2L);
+
+        assertNull(dto.getFinalScore(), "Self should not see unpublished final score");
+        assertNull(dto.getFinalItems(), "Self should not see unpublished final items");
+        assertNotNull(dto.getSelfScore(), "Self can see their self score");
+        assertNotNull(dto.getSelfItems(), "Self can see their self items");
+    }
+
+    @Test
+    void superiorCannotSeeSelfItems() {
+        setupPrivacyMocks(1L, 2L, 3L, "SELF_SUBMITTED", "PUBLISHED");
+
+        ScoringDto dto = performanceService.getScoringPage(1L, 2L, 3L);
+
+        assertNotNull(dto.getSelfScore(), "Superior can see self score exists");
+        assertNull(dto.getSelfScore().getCommentsJson(), "Superior should not see self commentsJson");
+        assertNull(dto.getSelfItems(), "Superior should not see selfItems");
+        assertNotNull(dto.getFinalScore(), "Superior can see final score");
+        assertNotNull(dto.getFinalItems(), "Superior can see final items");
+    }
+
+    @Test
+    void hrCanSeeAllItems() {
+        setupPrivacyMocks(1L, 2L, 99L, "SELF_SUBMITTED", "PUBLISHED"); when(authorizationService.isHrOrAbove(99L)).thenReturn(true);
+
+        ScoringDto dto = performanceService.getScoringPage(1L, 2L, 99L);
+
+        assertNotNull(dto.getSelfScore());
+        assertNotNull(dto.getSelfItems());
+        assertNotNull(dto.getFinalScore());
+        assertNotNull(dto.getFinalItems());
+    }
+
+    @Test
+    void selfCanSeePublishedFinalItems() {
+        setupPrivacyMocks(1L, 2L, 2L, "SELF_SUBMITTED", "PUBLISHED");
+
+        ScoringDto dto = performanceService.getScoringPage(1L, 2L, 2L);
+
+        assertNotNull(dto.getFinalScore(), "Self can see published final score");
+        assertNotNull(dto.getFinalItems(), "Self can see published final items");
+        assertNotNull(dto.getSelfScore(), "Self can see their self score");
+    }
+
+    private void setupPrivacyMocks(Long periodId, Long userId, Long actorId, 
+                                    String selfStatus, String finalStatus) {
+        lenient().when(periodMapper.selectById(periodId)).thenReturn(period());
+        SysUser targetUser = user(userId, 3L);
+        lenient().when(userMapper.selectById(userId)).thenReturn(targetUser);
+        lenient().doNothing().when(authorizationService).requireCanViewUserData(actorId, userId);
+
+        selfScore = new PerformanceScore();
+        selfScore.setId(10L);
+        selfScore.setPeriodId(periodId);
+        selfScore.setUserId(userId);
+        selfScore.setType(ScoreType.SELF);
+        selfScore.setStatus(selfStatus);
+        selfScore.setOkrScore(50.0);
+        selfScore.setKpiScore(30.0);
+        selfScore.setCommentsJson("{\"details\":\"self\"}");
+
+        finalScore = new PerformanceScore();
+        finalScore.setId(20L);
+        finalScore.setPeriodId(periodId);
+        finalScore.setUserId(userId);
+        finalScore.setType(ScoreType.FINAL);
+        finalScore.setStatus(finalStatus);
+        finalScore.setTotal(80.0);
+        finalScore.setGrade(com.yimeng.okr.enums.Grade.A);
+
+        sampleItem = new PerformanceScoreItem();
+        sampleItem.setId(1L);
+        sampleItem.setScoreId(10L);
+        sampleItem.setItemType("KR");
+        sampleItem.setScore(0.8);
+
+        okrDetail = new OkrDetailDto();
+        okrDetail.setUserId(userId);
+        okrDetail.setPeriodId(periodId);
+
+        lenient().when(scoreMapper.selectOne(any())).thenReturn(selfScore, finalScore);
+        lenient().when(scoreItemMapper.selectList(any())).thenReturn(List.of(sampleItem));
+        lenient().when(okrService.getMyOkr(periodId, userId)).thenReturn(okrDetail);
+        feedback = new PerformanceFeedback();
+        lenient().when(feedbackMapper.selectOne(any())).thenReturn(feedback);
+    }
+
 }