Loading...
Loading...
02-reusable-code-java/07-testing/BaseE2ETest.java
/**
* Spring MockMvc 기반 E2E 통합 테스트 베이스 클래스
*
* <p>실제 서비스/리포지토리를 사용하는 E2E 통합 테스트의 공통 기반 클래스입니다.</p>
*
* <p>특징:</p>
* <ul>
* <li>@SpringBootTest + @AutoConfigureMockMvc: 전체 컨텍스트 로드</li>
* <li>@ActiveProfiles("e2e"): H2 인메모리 DB 사용 (application-e2e.yml 필요)</li>
* <li>@Transactional: 테스트 후 DB 롤백 (데이터 격리)</li>
* <li>Spring Security 인증 헬퍼: portalUser, superAdmin 등</li>
* <li>공통 엔티티 생성 헬퍼: createTestSurvey, createTestPost 등</li>
* </ul>
*
* <p>의존:</p>
* <ul>
* <li>spring-boot-test, spring-security-test</li>
* <li>H2 (테스트 스코프)</li>
* <li>Lombok</li>
* </ul>
*
* @source 260307-kcsi-smpa-internal
* @extracted 2026-03-22
* @version 1.0.0
*/
package com.example.e2e;
import com.example.domain.User;
import com.example.domain.UserRole;
import com.example.domain.polygraph.*;
import com.example.domain.sinmungo.*;
import com.example.domain.survey.*;
import com.example.repository.*;
import com.example.security.CustomUserDetails;
import com.example.util.PasswordUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.UUID;
/**
* E2E 통합 테스트 베이스 클래스
*
* <p>사용법: 이 클래스를 상속하고 리포지토리/헬퍼를 활용하세요.</p>
* <pre>{@code
* class MyE2ETest extends BaseE2ETest {
* @Test
* void 게시글_조회() throws Exception {
* Survey survey = createTestSurvey("테스트 설문", false);
* mockMvc.perform(get("/survey-portal")
* .with(portalUser("user1", "홍길동", "테스트부")))
* .andExpect(status().isOk());
* }
* }
* }</pre>
*
* <p>프로파일 설정 (application-e2e.yml 예시):</p>
* <pre>
* spring:
* datasource:
* url: jdbc:h2:mem:testdb;MODE=MySQL
* jpa:
* hibernate.ddl-auto: create-drop
* </pre>
*/
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("e2e")
@Transactional
public abstract class BaseE2ETest {
@Autowired
protected MockMvc mockMvc;
// ── 리포지토리 (프로젝트에 맞게 추가/제거) ──
@Autowired
protected SurveyRepository surveyRepository;
@Autowired
protected QuestionRepository questionRepository;
@Autowired
protected ResponseRepository responseRepository;
@Autowired
protected SinmungoPostRepository sinmungoPostRepository;
@Autowired
protected SinmungoReplyRepository sinmungoReplyRepository;
@Autowired
protected ReservationRepository reservationRepository;
@Autowired
protected FacilityRepository facilityRepository;
@Autowired
protected UserRepository userRepository;
@Autowired
protected SurveyTemplateRepository surveyTemplateRepository;
@Autowired
protected PasswordUtil passwordUtil;
// ══════════════════════════════════════════════
// Spring Security 인증 헬퍼
// ══════════════════════════════════════════════
/**
* 일반 사용자 인증 (USER 역할)
*/
protected RequestPostProcessor portalUser(String userno, String name, String department) {
return portalUserWithRole(userno, name, department, UserRole.USER);
}
/**
* 역할 지정 사용자 인증
*/
protected RequestPostProcessor portalUserWithRole(String userno, String name,
String department, UserRole role) {
User user = User.builder()
.id(UUID.randomUUID().toString())
.userno(userno)
.userid(userno)
.password("test")
.name(name)
.department(department)
.userrank("경위")
.role(role)
.active(true)
.build();
CustomUserDetails userDetails = new CustomUserDetails(user, "TEST");
return SecurityMockMvcRequestPostProcessors.user(userDetails);
}
/** 슈퍼관리자 인증 */
protected RequestPostProcessor superAdmin() {
return portalUserWithRole("admin1", "관리자", "관리부", UserRole.SUPER_ADMIN);
}
// ══════════════════════════════════════════════
// 공통 테스트 데이터 생성 헬퍼
// ══════════════════════════════════════════════
/**
* 테스트용 설문 생성
*/
protected Survey createTestSurvey(String title, boolean isEnded) {
Survey survey = Survey.builder()
.title(title)
.password(passwordUtil.encode("1234"))
.instructions("테스트 설문입니다.")
.isEnded(isEnded)
.creatorName("테스터")
.creatorDepartment("테스트부서")
.creatorPhone("010-0000-0000")
.endDate(isEnded
? LocalDateTime.now().minusDays(1)
: LocalDateTime.now().plusDays(30))
.build();
surveyRepository.save(survey);
return survey;
}
/**
* 테스트용 설문 + 질문 생성
*/
protected Survey createTestSurveyWithQuestions(String title, boolean isEnded) {
Survey survey = createTestSurvey(title, isEnded);
Question q1 = Question.builder()
.survey(survey)
.text("테스트 질문 1")
.type(QuestionType.RADIO)
.options("[\"옵션A\",\"옵션B\",\"옵션C\"]")
.orderNum(0)
.isRequired(true)
.build();
questionRepository.save(q1);
Question q2 = Question.builder()
.survey(survey)
.text("테스트 질문 2")
.type(QuestionType.TEXT)
.orderNum(1)
.isRequired(false)
.build();
questionRepository.save(q2);
return survey;
}
/**
* 테스트용 민원 게시글 생성
*/
protected SinmungoPost createTestPost(SinmungoCategory category, SinmungoStatus status) {
SinmungoPost post = SinmungoPost.builder()
.category(category)
.title("테스트 민원 - " + category.getLabel())
.content("테스트 민원 내용입니다.")
.authorName("테스터")
.authorDepartment("테스트부서")
.authorPhone("010-0000-0000")
.password(passwordUtil.encode("1234"))
.displayNumber(1)
.status(status)
.priority(SinmungoPriority.NORMAL)
.createdAt(LocalDateTime.now().minusDays(1))
.build();
if (status == SinmungoStatus.COMPLETED) {
post.setCompletedAt(LocalDateTime.now());
}
if (status != SinmungoStatus.PENDING) {
post.setAssignedTo("관리자");
post.setAssignedDepartment("관리부");
}
post.setDueDate(LocalDateTime.now().plusDays(14));
sinmungoPostRepository.save(post);
return post;
}
/**
* 활성 시설 조회 (없으면 생성)
*/
protected Facility getTestFacility() {
List<Facility> facilities = facilityRepository.findByIsActiveTrueOrderByFacilityTypeAscNameAsc();
if (facilities.isEmpty()) {
Facility f = Facility.builder()
.name("테스트 검사실")
.facilityType(FacilityType.POLYGRAPH)
.description("테스트용 시설")
.isActive(true)
.build();
facilityRepository.save(f);
return f;
}
return facilities.get(0);
}
/**
* 시설 생성 (이름, 유형 지정)
*/
protected Facility createTestFacility(String name, FacilityType type) {
Facility f = Facility.builder()
.name(name)
.facilityType(type)
.description(name + " 설명")
.isActive(true)
.build();
facilityRepository.save(f);
return f;
}
/**
* 예약 생성
*/
protected Reservation createTestReservation(Facility facility, String userno) {
Reservation res = Reservation.builder()
.facility(facility)
.reservationDate(LocalDate.now().plusDays(5))
.startTime(LocalTime.of(10, 0))
.endTime(LocalTime.of(12, 0))
.caseNumber("2026-TEST-" + System.currentTimeMillis())
.reserverUserno(userno)
.reserverName("테스터")
.reserverDepartment("테스트부서")
.reserverPhone("010-0000-0000")
.examineeName("피검사자")
.status(ReservationStatus.RESERVED)
.build();
reservationRepository.save(res);
return res;
}
/**
* 답변 포함 민원 게시글 생성
*/
protected SinmungoPost createTestPostWithReply(SinmungoCategory category, SinmungoStatus status) {
SinmungoPost post = createTestPost(category, status);
SinmungoReply reply = SinmungoReply.builder()
.post(post)
.content("테스트 답변입니다.")
.authorName("관리자")
.authorRole(AuthorRole.ADMIN)
.build();
sinmungoReplyRepository.save(reply);
return post;
}
/**
* DB에 실제 User 생성 (계정 관리 테스트용)
*/
protected User createTestUser(String userno, UserRole role) {
User user = User.builder()
.userno(userno)
.userid(userno)
.password(passwordUtil.encode("test1234"))
.name("테스트유저-" + userno)
.department("테스트부서")
.userrank("경위")
.role(role)
.build();
userRepository.save(user);
return user;
}
}