|
|
@@ -0,0 +1,173 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.Family;
|
|
|
+import com.etotem.cfc.entity.FamilyJoinRequest;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.FamilyJoinRequestMapper;
|
|
|
+import com.etotem.cfc.mapper.FamilyMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.MockitoAnnotations;
|
|
|
+
|
|
|
+import java.sql.Date;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 契约级测试:家庭加入准入校验门禁(TDD-RED,S-cfc-39 / family.invite.joinRequiredInfoTest)
|
|
|
+ *
|
|
|
+ * 需求锚点:docs/superpowers/specs/2026-11-07-family-join-required-info.md
|
|
|
+ * - §3 AC-R3 校验规则 / §5 WI-2(后端门禁)/ WI-3(规则细化)
|
|
|
+ * 设计锚点:FamilyJoinRequestService.createJoinRequest 校验块 validateJoinRequiredInfo(currentUser)
|
|
|
+ * - 真实姓名:trim 后非空,长度 1–20 字
|
|
|
+ * - 出生日期:非空、不晚于今天、不早于 1900-01-01(不设年龄上限,2026-11-07 主理人确认)
|
|
|
+ * - 性别:仅限 male / female 二值(大小写敏感)
|
|
|
+ *
|
|
|
+ * 三流覆盖:
|
|
|
+ * - 正常流:三项齐全且合法 → 请求正常创建,insert 调用一次
|
|
|
+ * - 缺失流:realName/birthday/gender 任一缺失 → 抛「请先完善真实姓名、出生日期与性别后再申请加入家庭」,不落库
|
|
|
+ * - 非法流:生日未来/极早、姓名超长、性别大小写混用 → 拒绝,不落库
|
|
|
+ */
|
|
|
+class FamilyJoinRequestServiceJoinRequiredInfoContractTest {
|
|
|
+
|
|
|
+ private static final String MSG_INCOMPLETE = "请先完善真实姓名、出生日期与性别后再申请加入家庭";
|
|
|
+ private static final String MSG_FUTURE_BIRTHDAY = "出生日期不能晚于今天";
|
|
|
+ private static final String MSG_ILLEGAL_BIRTHDAY = "出生日期不合法";
|
|
|
+ private static final Long USER_ID = 1L;
|
|
|
+ private static final Long FAMILY_ID = 100L;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private FamilyJoinRequestMapper familyJoinRequestMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private FamilyMapper familyMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private FamilyJoinRequestService service;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ MockitoAnnotations.openMocks(this);
|
|
|
+ Family family = new Family();
|
|
|
+ family.setId(FAMILY_ID);
|
|
|
+ when(familyMapper.selectById(FAMILY_ID)).thenReturn(family);
|
|
|
+ // 默认无待审批请求(避免「您已有待审批的加入请求」先于门禁抛错干扰断言)
|
|
|
+ when(familyJoinRequestMapper.selectOne(any())).thenReturn(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 正常流(AC2.4 / AC5.3) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withCompleteValidInfo_createsPendingRequest() {
|
|
|
+ givenCurrentUser(validUser());
|
|
|
+
|
|
|
+ FamilyJoinRequest result = service.createJoinRequest(USER_ID, String.valueOf(FAMILY_ID), null);
|
|
|
+
|
|
|
+ assertNotNull(result);
|
|
|
+ assertEquals("pending", result.getStatus());
|
|
|
+ verify(familyJoinRequestMapper).insert(any(FamilyJoinRequest.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 缺失流(AC2.1 / AC2.2 / AC2.3,WI-2 门禁) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withNullRealName_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setRealName(null);
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withBlankRealName_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setRealName(" ");
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withNullBirthday_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setBirthday(null);
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withNullGender_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setGender(null);
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withEmptyGender_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setGender("");
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 非法流(AC3.1 / AC3.2 / AC3.3 / AC3.4,WI-3 规则细化) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withRealNameOver20Chars_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setRealName("这是一个超过二十个字符的超长真实姓名测试用");
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withMixedCaseGender_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setGender("Male");
|
|
|
+ assertRejected(u, MSG_INCOMPLETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withFutureBirthday_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setBirthday(Date.valueOf("2099-01-01"));
|
|
|
+ assertRejected(u, MSG_FUTURE_BIRTHDAY);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createJoinRequest_withTooEarlyBirthday_rejectedAndNoInsert() {
|
|
|
+ User u = validUser();
|
|
|
+ u.setBirthday(Date.valueOf("1800-01-01"));
|
|
|
+ assertRejected(u, MSG_ILLEGAL_BIRTHDAY);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 辅助构造 ====================
|
|
|
+
|
|
|
+ private User validUser() {
|
|
|
+ User u = new User();
|
|
|
+ u.setId(USER_ID);
|
|
|
+ u.setNickname("zhangsan");
|
|
|
+ u.setRealName("张三");
|
|
|
+ u.setBirthday(Date.valueOf("2000-01-01"));
|
|
|
+ u.setGender("male");
|
|
|
+ return u;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void givenCurrentUser(User user) {
|
|
|
+ when(userMapper.selectById(USER_ID)).thenReturn(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertRejected(User user, String expectedMessage) {
|
|
|
+ givenCurrentUser(user);
|
|
|
+ RuntimeException ex = assertThrows(RuntimeException.class,
|
|
|
+ () -> service.createJoinRequest(USER_ID, String.valueOf(FAMILY_ID), null));
|
|
|
+ assertEquals(expectedMessage, ex.getMessage());
|
|
|
+ verify(familyJoinRequestMapper, never()).insert(any(FamilyJoinRequest.class));
|
|
|
+ }
|
|
|
+}
|