You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
16 KiB
362 lines
16 KiB
package com.mydreamjob.job;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.mydreamjob.auth.User;
|
|
import com.mydreamjob.common.exception.BusinessException;
|
|
import com.mydreamjob.common.exception.ErrorCode;
|
|
import com.mydreamjob.company.Company;
|
|
import com.mydreamjob.job.dto.JobCreateRequest;
|
|
import com.mydreamjob.job.dto.JobUpdateRequest;
|
|
import jakarta.persistence.EntityManager;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.function.Executable;
|
|
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.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
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.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
/**
|
|
* 岗位展示字段(experience / education / benefits / salaryMonths / workLocation)与企业摘要字段的
|
|
* 端到端行为:写入、清空、校验、公开响应与企业摘要复用。
|
|
*/
|
|
@SpringBootTest
|
|
@AutoConfigureMockMvc
|
|
@ActiveProfiles("integration-test")
|
|
@Transactional
|
|
class JobDisplayFieldsIntegrationTest {
|
|
|
|
private static final String CITY = "DISPLAYTESTCITY";
|
|
private static final String BENEFITS_JSON = "[\"五险一金\",\"弹性工作\"]";
|
|
|
|
@Autowired EntityManager em;
|
|
@Autowired JobService jobService;
|
|
@Autowired ObjectMapper objectMapper;
|
|
@Autowired MockMvc mvc;
|
|
@MockBean RedisTemplate<String, Object> redis;
|
|
|
|
private Long companyId;
|
|
private String ownerPhone;
|
|
|
|
@BeforeEach
|
|
void fixture() {
|
|
ownerPhone = "188" + String.format("%08d", Math.abs(System.nanoTime() % 100_000_000L));
|
|
User owner = User.builder().phone(ownerPhone).nickname("display-company")
|
|
.role(User.Role.COMPANY).roles(Set.of(User.Role.COMPANY))
|
|
.active(true).verified(true).build();
|
|
em.persist(owner);
|
|
Company company = Company.builder()
|
|
.ownerUserId(owner.getId())
|
|
.name("展示字段测试企业")
|
|
.logoUrl("https://logo.example/display.png")
|
|
.description("企业简介-展示")
|
|
.industry("互联网")
|
|
.companySize("100-499人")
|
|
.website("https://display.example")
|
|
.address("企业登记地址不应外泄")
|
|
.verified(true)
|
|
.build();
|
|
em.persist(company);
|
|
companyId = company.getId();
|
|
em.flush();
|
|
em.clear();
|
|
}
|
|
|
|
// ── 创建:带全部新字段,各类视图一致 ────────────────────────
|
|
|
|
@Test
|
|
void createWithAllNewFieldsViewsStayConsistent() throws Exception {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("后端工程师"));
|
|
|
|
// benefits_json 以 TEXT 保存 JSON 数组字符串(非 JSON 列)
|
|
assertEquals(BENEFITS_JSON, job.getBenefitsJson());
|
|
|
|
Map<String, Object> view = jobService.toView(job);
|
|
assertEquals("3-5年", view.get("experience"));
|
|
assertEquals("本科", view.get("education"));
|
|
assertEquals(List.of("五险一金", "弹性工作"), view.get("benefits"));
|
|
assertEquals(14, view.get("salaryMonths"));
|
|
assertEquals("上海市浦东新区", view.get("workLocation"));
|
|
|
|
// 公开详情(需 PUBLISHED/CLOSED)
|
|
job.setStatus("PUBLISHED");
|
|
em.flush();
|
|
Map<String, Object> pub = jobService.getPublicJob(job.getId());
|
|
assertEquals(companyId, pub.get("companyId"));
|
|
assertEquals("3-5年", pub.get("experience"));
|
|
assertEquals("本科", pub.get("education"));
|
|
assertEquals(List.of("五险一金", "弹性工作"), pub.get("benefits"));
|
|
assertEquals(14, pub.get("salaryMonths"));
|
|
assertEquals("上海市浦东新区", pub.get("workLocation"));
|
|
}
|
|
|
|
// ── 旧请求兼容:不传新字段 ─────────────────────────────────
|
|
|
|
@Test
|
|
void legacyRequestWithoutNewFieldsStaysCompatible() {
|
|
JobCreateRequest req = new JobCreateRequest();
|
|
req.setTitle("旧请求岗位");
|
|
req.setDescription("岗位描述");
|
|
req.setCity(CITY);
|
|
req.setEmploymentType("FULL_TIME");
|
|
|
|
Job job = jobService.createJob(companyId, req);
|
|
assertNullFieldsOn(job);
|
|
|
|
Map<String, Object> view = jobService.toView(job);
|
|
assertEquals(null, view.get("experience"));
|
|
assertEquals(null, view.get("salaryMonths"));
|
|
assertEquals(List.of(), view.get("benefits"));
|
|
|
|
job.setStatus("PUBLISHED");
|
|
em.flush();
|
|
assertEquals(List.of(), jobService.getPublicJob(job.getId()).get("benefits"));
|
|
}
|
|
|
|
// ── 更新语义 ───────────────────────────────────────────────
|
|
|
|
@Test
|
|
void updateWithNullFieldsKeepsOriginalValues() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("更新保持"));
|
|
Job updated = jobService.updateJob(companyId, job.getId(), new JobUpdateRequest());
|
|
|
|
assertEquals("3-5年", updated.getExperience());
|
|
assertEquals("本科", updated.getEducation());
|
|
assertEquals(14, updated.getSalaryMonths());
|
|
assertEquals("上海市浦东新区", updated.getWorkLocation());
|
|
assertEquals(BENEFITS_JSON, updated.getBenefitsJson());
|
|
}
|
|
|
|
@Test
|
|
void updateWithBlankStringClearsToNull() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("更新清空"));
|
|
JobUpdateRequest req = new JobUpdateRequest();
|
|
req.setExperience(" ");
|
|
req.setEducation("");
|
|
req.setWorkLocation(" ");
|
|
|
|
Job updated = jobService.updateJob(companyId, job.getId(), req);
|
|
assertEquals(null, updated.getExperience());
|
|
assertEquals(null, updated.getEducation());
|
|
assertEquals(null, updated.getWorkLocation());
|
|
|
|
em.flush();
|
|
em.clear();
|
|
Job reloaded = jobService.getJob(job.getId());
|
|
assertEquals(null, reloaded.getExperience());
|
|
assertEquals(null, reloaded.getEducation());
|
|
assertEquals(null, reloaded.getWorkLocation());
|
|
}
|
|
|
|
@Test
|
|
void updateWithNonBlankValueTrimsEnds() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("更新去空白"));
|
|
JobUpdateRequest req = new JobUpdateRequest();
|
|
req.setExperience(" 5-10年 ");
|
|
req.setWorkLocation(" 北京市海淀区 ");
|
|
|
|
Job updated = jobService.updateJob(companyId, job.getId(), req);
|
|
assertEquals("5-10年", updated.getExperience());
|
|
assertEquals("北京市海淀区", updated.getWorkLocation());
|
|
}
|
|
|
|
@Test
|
|
void updateWithEmptyBenefitsListClears() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("福利清空"));
|
|
JobUpdateRequest req = new JobUpdateRequest();
|
|
req.setBenefits(List.of());
|
|
|
|
Job updated = jobService.updateJob(companyId, job.getId(), req);
|
|
assertEquals(null, updated.getBenefitsJson());
|
|
assertEquals(List.of(), jobService.toView(updated).get("benefits"));
|
|
}
|
|
|
|
@Test
|
|
void updateWithNullSalaryMonthsKeepsOriginal() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("月数保持"));
|
|
jobService.updateJob(companyId, job.getId(), new JobUpdateRequest());
|
|
assertEquals(14, jobService.getJob(job.getId()).getSalaryMonths());
|
|
}
|
|
|
|
// ── salaryMonths 校验 1..24 ────────────────────────────────
|
|
|
|
@Test
|
|
void salaryMonthsBoundaries() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("月数边界"));
|
|
|
|
JobUpdateRequest one = new JobUpdateRequest();
|
|
one.setSalaryMonths(1);
|
|
assertEquals(1, jobService.updateJob(companyId, job.getId(), one).getSalaryMonths());
|
|
|
|
JobUpdateRequest twentyFour = new JobUpdateRequest();
|
|
twentyFour.setSalaryMonths(24);
|
|
assertEquals(24, jobService.updateJob(companyId, job.getId(), twentyFour).getSalaryMonths());
|
|
|
|
for (int invalid : new int[]{0, 25, -1}) {
|
|
JobUpdateRequest bad = new JobUpdateRequest();
|
|
bad.setSalaryMonths(invalid);
|
|
assertBadRequest(() -> jobService.updateJob(companyId, job.getId(), bad));
|
|
}
|
|
}
|
|
|
|
// ── benefits 校验与去重保序 ────────────────────────────────
|
|
|
|
@Test
|
|
void benefitsValidationAndDeduplication() {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("福利校验"));
|
|
|
|
List<String> tooMany = new ArrayList<>();
|
|
for (int i = 0; i < 21; i++) tooMany.add("benefit-" + i);
|
|
assertBadRequest(() -> jobService.updateJob(companyId, job.getId(), updateWithBenefits(tooMany)));
|
|
|
|
assertBadRequest(() -> jobService.updateJob(companyId, job.getId(),
|
|
updateWithBenefits(List.of("x".repeat(41)))));
|
|
|
|
assertBadRequest(() -> jobService.updateJob(companyId, job.getId(),
|
|
updateWithBenefits(List.of("五险一金", " "))));
|
|
|
|
assertBadRequest(() -> jobService.updateJob(companyId, job.getId(),
|
|
updateWithBenefits(Arrays.asList("五险一金", null))));
|
|
|
|
Job updated = jobService.updateJob(companyId, job.getId(),
|
|
updateWithBenefits(List.of("A", "B", "A", "C", "B")));
|
|
assertEquals("[\"A\",\"B\",\"C\"]", updated.getBenefitsJson());
|
|
assertEquals(List.of("A", "B", "C"), jobService.toView(updated).get("benefits"));
|
|
}
|
|
|
|
// ── 公开响应:新字段 + 企业摘要,且无敏感字段泄漏 ──────────
|
|
|
|
@Test
|
|
void publicListAndDetailExposeNewFieldsAndCompanySummary() throws Exception {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("公开字段岗位"));
|
|
job.setStatus("PUBLISHED");
|
|
em.flush();
|
|
em.clear();
|
|
|
|
String listBody = mvc.perform(get("/jobs").param("city", CITY).param("size", "50"))
|
|
.andExpect(status().isOk())
|
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
|
JsonNode data = objectMapper.readTree(listBody).path("data");
|
|
assertEquals(1, data.path("total").asLong());
|
|
JsonNode item = data.path("items").get(0);
|
|
assertEquals(job.getId().longValue(), item.path("id").asLong());
|
|
assertEquals("3-5年", item.path("experience").asText());
|
|
assertEquals("本科", item.path("education").asText());
|
|
assertEquals(14, item.path("salaryMonths").asInt());
|
|
assertEquals("上海市浦东新区", item.path("workLocation").asText());
|
|
assertTrue(item.path("benefits").isArray());
|
|
assertEquals(2, item.path("benefits").size());
|
|
assertEquals("五险一金", item.path("benefits").get(0).asText());
|
|
|
|
JsonNode company = item.path("company");
|
|
assertNotNull(company);
|
|
assertEquals(companyId.longValue(), company.path("id").asLong());
|
|
assertEquals("展示字段测试企业", company.path("name").asText());
|
|
assertEquals("https://logo.example/display.png", company.path("logoUrl").asText());
|
|
assertEquals("企业简介-展示", company.path("description").asText());
|
|
assertEquals("互联网", company.path("industry").asText());
|
|
assertEquals("100-499人", company.path("companySize").asText());
|
|
assertTrue(company.path("verified").asBoolean());
|
|
|
|
String detailBody = mvc.perform(get("/jobs/public/" + job.getId()))
|
|
.andExpect(status().isOk())
|
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
|
JsonNode detail = objectMapper.readTree(detailBody).path("data");
|
|
assertEquals(companyId.longValue(), detail.path("companyId").asLong());
|
|
assertEquals("3-5年", detail.path("experience").asText());
|
|
assertEquals("100-499人", detail.path("company").path("companySize").asText());
|
|
assertEquals(List.of("五险一金", "弹性工作"), objectMapper.convertValue(
|
|
detail.path("benefits"),
|
|
objectMapper.getTypeFactory().constructCollectionType(List.class, String.class)));
|
|
}
|
|
|
|
@Test
|
|
void publicResponsesDoNotLeakSensitiveFields() throws Exception {
|
|
Job job = jobService.createJob(companyId, fullCreateRequest("隐私体检岗位"));
|
|
job.setStatus("PUBLISHED");
|
|
em.flush();
|
|
em.clear();
|
|
|
|
String listBody = mvc.perform(get("/jobs").param("city", CITY)).andExpect(status().isOk())
|
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
|
String detailBody = mvc.perform(get("/jobs/public/" + job.getId())).andExpect(status().isOk())
|
|
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
|
for (String body : new String[]{listBody, detailBody}) {
|
|
assertTrue(!body.contains("ownerUserId"), "公开响应不得包含 ownerUserId");
|
|
assertTrue(!body.contains(ownerPhone), "公开响应不得包含企业主手机号");
|
|
assertTrue(!body.contains("企业登记地址不应外泄"), "公开响应不得包含企业登记地址");
|
|
assertTrue(!body.contains("password"), "公开响应不得包含密码字段");
|
|
}
|
|
}
|
|
|
|
// ── 草稿岗位公开不可见 ────────────────────────────────────
|
|
|
|
@Test
|
|
void draftJobIsNotVisibleOnPublicDetail() throws Exception {
|
|
Job draft = jobService.createJob(companyId, fullCreateRequest("草稿岗位"));
|
|
em.flush();
|
|
|
|
BusinessException ex = assertThrows(BusinessException.class,
|
|
() -> jobService.getPublicJob(draft.getId()));
|
|
assertEquals(ErrorCode.JOB_NOT_FOUND.getCode(), ex.getErrorCodeInt());
|
|
|
|
mvc.perform(get("/jobs/public/" + draft.getId())).andExpect(status().isNotFound());
|
|
}
|
|
|
|
// ── Helper ────────────────────────────────────────────────
|
|
|
|
private JobCreateRequest fullCreateRequest(String title) {
|
|
JobCreateRequest req = new JobCreateRequest();
|
|
req.setTitle(title);
|
|
req.setDescription("岗位描述");
|
|
req.setRequirements("岗位要求");
|
|
req.setCity(CITY);
|
|
req.setEmploymentType("FULL_TIME");
|
|
req.setSalaryMin(20000);
|
|
req.setSalaryMax(40000);
|
|
req.setExperience("3-5年");
|
|
req.setEducation("本科");
|
|
req.setBenefits(List.of("五险一金", "弹性工作"));
|
|
req.setSalaryMonths(14);
|
|
req.setWorkLocation("上海市浦东新区");
|
|
return req;
|
|
}
|
|
|
|
private JobUpdateRequest updateWithBenefits(List<String> benefits) {
|
|
JobUpdateRequest req = new JobUpdateRequest();
|
|
req.setBenefits(benefits);
|
|
return req;
|
|
}
|
|
|
|
private void assertNullFieldsOn(Job job) {
|
|
assertEquals(null, job.getExperience());
|
|
assertEquals(null, job.getEducation());
|
|
assertEquals(null, job.getBenefitsJson());
|
|
assertEquals(null, job.getSalaryMonths());
|
|
assertEquals(null, job.getWorkLocation());
|
|
}
|
|
|
|
private void assertBadRequest(Executable callable) {
|
|
BusinessException ex = assertThrows(BusinessException.class, callable);
|
|
assertEquals(ErrorCode.BAD_REQUEST.getCode(), ex.getErrorCodeInt());
|
|
}
|
|
}
|
|
|