package com.mydreamjob.application; import com.mydreamjob.auth.User; import com.mydreamjob.company.Company; import com.mydreamjob.job.Job; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import org.hibernate.SessionFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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.boot.test.mock.mockito.MockBean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest(properties = "spring.jpa.properties.hibernate.generate_statistics=true") @AutoConfigureMockMvc @ActiveProfiles("integration-test") @Transactional class ApplicationSummaryIntegrationTest { @Autowired EntityManager em; @Autowired EntityManagerFactory emf; @Autowired ApplicationService service; @Autowired MockMvc mvc; @Autowired com.mydreamjob.auth.AuthSessionService sessions; @Autowired java.time.Clock clock; @MockBean RedisTemplate redis; long studentId; long otherStudentId; @BeforeEach void fixture() { User student = user("summary-student", User.Role.STUDENT); User other = user("summary-other", User.Role.STUDENT); User owner = user("summary-company", User.Role.COMPANY); studentId = student.getId(); otherStudentId = other.getId(); Company company = Company.builder().ownerUserId(owner.getId()).name("摘要测试企业").verified(true).build(); em.persist(company); for (int i = 0; i < 55; i++) { Job job = Job.builder().companyId(company.getId()).title("岗位" + i).description("测试") .city("上海").employmentType("FULL_TIME").status(i == 0 ? "CLOSED" : "PUBLISHED") .salaryMin(i == 0 ? null : 10000).salaryMax(i == 0 ? null : 20000).build(); em.persist(job); em.persist(Application.builder().jobId(job.getId()).studentUserId(studentId) .status(i % 2 == 0 ? "SUBMITTED" : "WITHDRAWN").build()); if (i == 0) em.persist(Application.builder().jobId(job.getId()).studentUserId(otherStudentId) .status("SUBMITTED").build()); } em.flush(); em.clear(); } private User user(String name, User.Role role) { User user = User.builder().phone("139" + Math.abs(System.nanoTime() % 1_000_000_000L)) .nickname(name).role(role).roles(Set.of(role)).active(true).verified(true).build(); em.persist(user); return user; } @Test void fixedQueryBudgetAndAllSummaryFieldsForOneTwentyAndFiftyRows() { var stats = emf.unwrap(SessionFactory.class).getStatistics(); for (int size : new int[]{1, 20, 50}) { em.clear(); stats.clear(); var page = service.listStudentApplicationViews(studentId, null, 0, size); long statements = stats.getPrepareStatementCount(); assertEquals(3, statements, "当前页、总数、批量岗位摘要共三条 SQL,size=" + size); assertEquals(55, page.getTotalElements()); assertEquals(size, page.getNumberOfElements()); for (var item : page) { assertEquals(studentId, item.get("studentUserId")); assertEquals("摘要测试企业", item.get("companyName")); assertEquals("上海", item.get("city")); assertEquals("FULL_TIME", item.get("employmentType")); assertTrue(item.get("jobTitle").toString().startsWith("岗位")); assertNotNull(item.get("companyId")); assertTrue(item.containsKey("salaryMin")); assertTrue(item.containsKey("salaryMax")); assertNotNull(item.get("jobStatus")); assertNotNull(item.get("status")); assertNotNull(item.get("createdAt")); } } } @Test void statusFilterPaginationAndIsolationArePreserved() { var page = service.listStudentApplicationViews(studentId, "SUBMITTED", 1, 20); assertEquals(28, page.getTotalElements()); assertEquals(2, page.getTotalPages()); assertEquals(1, page.getNumber()); assertEquals(8, page.getNumberOfElements()); page.forEach(item -> assertEquals("SUBMITTED", item.get("status"))); var other = service.listStudentApplicationViews(otherStudentId, null, 0, 20); assertEquals(1, other.getTotalElements()); var closed = other.getContent().get(0); assertEquals("CLOSED", closed.get("jobStatus")); assertEquals("SUBMITTED", closed.get("status")); assertNull(closed.get("salaryMin")); assertNull(closed.get("salaryMax")); } @Test void emptyPageSkipsSummarySql() { var stats = emf.unwrap(SessionFactory.class).getStatistics(); stats.clear(); var page = service.listStudentApplicationViews(studentId, "HIRED", 0, 20); assertTrue(page.isEmpty()); assertEquals(0, page.getTotalElements()); assertEquals(1, stats.getPrepareStatementCount()); } @Test void rejectsInvalidPagination() { assertThrows(com.mydreamjob.common.exception.BusinessException.class, () -> service.listStudentApplicationViews(studentId, null, -1, 20)); assertThrows(com.mydreamjob.common.exception.BusinessException.class, () -> service.listStudentApplicationViews(studentId, null, 0, 101)); } @Test void httpListReturnsSummaryAndUnchangedPageEnvelope() throws Exception { String bearer = "Bearer " + sessions.createSession(em.find(User.class, studentId), clock.instant(), com.mydreamjob.audit.AuditContext.builder().requestId("application-summary-test").build()).accessToken(); mvc.perform(get("/applications/me").header("Authorization", bearer).param("size", "20")) .andExpect(status().isOk()).andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.total").value(55)) .andExpect(jsonPath("$.data.totalPages").value(3)) .andExpect(jsonPath("$.data.items.length()").value(20)) .andExpect(jsonPath("$.data.items[0].companyName").value("摘要测试企业")) .andExpect(jsonPath("$.data.items[0].jobTitle").isString()); mvc.perform(get("/applications/me")).andExpect(status().isUnauthorized()); } }