14 changed files with 679 additions and 15 deletions
@ -0,0 +1,30 @@ |
|||
package com.mydreamjob.job; |
|||
|
|||
import jakarta.persistence.criteria.Predicate; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
import org.springframework.data.jpa.domain.Specification; |
|||
|
|||
/** 企业私有岗位列表查询;租户条件始终是必选条件。 */ |
|||
public final class CompanyJobManagementSpecification { |
|||
private CompanyJobManagementSpecification() {} |
|||
|
|||
public static Specification<Job> forCompany(long companyId, String status, String keyword, String city) { |
|||
return (root, query, cb) -> { |
|||
List<Predicate> predicates = new ArrayList<>(); |
|||
predicates.add(cb.equal(root.get("companyId"), companyId)); |
|||
if (status != null) predicates.add(cb.equal(root.get("status"), status)); |
|||
if (keyword != null) { |
|||
// LOCATE treats % and _ literally and is supported by both H2 and MySQL.
|
|||
predicates.add(cb.greaterThan(cb.locate( |
|||
cb.lower(root.get("title")), keyword.toLowerCase(Locale.ROOT)), 0)); |
|||
} |
|||
if (city != null) { |
|||
predicates.add(cb.equal(cb.lower(root.get("city")), city.toLowerCase(Locale.ROOT))); |
|||
} |
|||
return cb.and(predicates.toArray(Predicate[]::new)); |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
package com.mydreamjob.job.dto; |
|||
|
|||
import com.mydreamjob.job.Job; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import java.time.LocalDateTime; |
|||
|
|||
/** 企业自身岗位管理列表项,不包含招聘统计、候选人或内部审核数据。 */ |
|||
@Schema(name = "CompanyManagedJobItem", description = "企业自身岗位管理安全摘要") |
|||
public record CompanyManagedJobItem( |
|||
@Schema(description = "岗位 ID") Long id, |
|||
@Schema(description = "所属企业 ID") Long companyId, |
|||
@Schema(description = "岗位名称") String title, |
|||
@Schema(description = "岗位描述") String description, |
|||
@Schema(description = "任职要求") String requirements, |
|||
@Schema(description = "工作城市") String city, |
|||
@Schema(description = "用工类型") String employmentType, |
|||
@Schema(description = "最低月薪,单位元") Integer salaryMin, |
|||
@Schema(description = "最高月薪,单位元") Integer salaryMax, |
|||
@Schema(description = "岗位状态:DRAFT、PUBLISHED、CLOSED") String status, |
|||
@Schema(description = "发布时间") LocalDateTime publishedAt, |
|||
@Schema(description = "创建时间") LocalDateTime createdAt, |
|||
@Schema(description = "更新时间") LocalDateTime updatedAt) { |
|||
|
|||
public static CompanyManagedJobItem from(Job job) { |
|||
return new CompanyManagedJobItem(job.getId(), job.getCompanyId(), job.getTitle(), |
|||
job.getDescription(), job.getRequirements(), job.getCity(), job.getEmploymentType(), |
|||
job.getSalaryMin(), job.getSalaryMax(), job.getStatus(), job.getPublishedAt(), |
|||
job.getCreatedAt(), job.getUpdatedAt()); |
|||
} |
|||
} |
|||
@ -0,0 +1,235 @@ |
|||
package com.mydreamjob.company; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.mydreamjob.audit.AuditContext; |
|||
import com.mydreamjob.auth.AuthSessionService; |
|||
import com.mydreamjob.auth.User; |
|||
import com.mydreamjob.auth.UserRepository; |
|||
import com.mydreamjob.job.Job; |
|||
import com.mydreamjob.job.JobRepository; |
|||
import java.time.Instant; |
|||
import java.time.LocalDateTime; |
|||
import java.util.HashSet; |
|||
import java.util.Set; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import org.junit.jupiter.api.AfterEach; |
|||
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.jdbc.core.JdbcTemplate; |
|||
import org.springframework.test.context.ActiveProfiles; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.springframework.test.web.servlet.MockMvc; |
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
/** 企业岗位管理列表:租户隔离、状态/关键词/城市过滤、分页及只读行为。 */ |
|||
@SpringBootTest |
|||
@AutoConfigureMockMvc |
|||
@ActiveProfiles("integration-test") |
|||
@TestPropertySource(properties = "spring.datasource.url=jdbc:h2:mem:company_job_management;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") |
|||
class CompanyJobManagementIntegrationTest { |
|||
private static final AtomicInteger SEQUENCE = new AtomicInteger(); |
|||
|
|||
@MockBean private RedisTemplate<String, Object> redis; |
|||
@Autowired private MockMvc mvc; |
|||
@Autowired private JdbcTemplate jdbc; |
|||
@Autowired private UserRepository users; |
|||
@Autowired private CompanyRepository companies; |
|||
@Autowired private JobRepository jobs; |
|||
@Autowired private AuthSessionService sessions; |
|||
@Autowired private ObjectMapper mapper; |
|||
|
|||
private long companyId; |
|||
private long otherCompanyId; |
|||
private String companyToken; |
|||
private String otherCompanyToken; |
|||
private String studentToken; |
|||
private String adminToken; |
|||
private long draftId; |
|||
private long publishedId; |
|||
private long closedId; |
|||
|
|||
@BeforeEach |
|||
void setUp() { |
|||
Identity owner = identity(User.Role.COMPANY); |
|||
Identity otherOwner = identity(User.Role.COMPANY); |
|||
Identity student = identity(User.Role.STUDENT); |
|||
Identity admin = identity(User.Role.ADMIN); |
|||
companyToken = owner.token(); |
|||
otherCompanyToken = otherOwner.token(); |
|||
studentToken = student.token(); |
|||
adminToken = admin.token(); |
|||
companyId = companies.saveAndFlush(Company.builder().ownerUserId(owner.id()) |
|||
.name("岗位列表测试企业A").verified(false).build()).getId(); |
|||
otherCompanyId = companies.saveAndFlush(Company.builder().ownerUserId(otherOwner.id()) |
|||
.name("岗位列表测试企业B").verified(true).build()).getId(); |
|||
draftId = job(companyId, "Java%工程师", "南京", "DRAFT"); |
|||
publishedId = job(companyId, "Python工程师", "北京", "PUBLISHED"); |
|||
closedId = job(companyId, "Java工程师", "南京", "CLOSED"); |
|||
job(otherCompanyId, "Java外部岗位", "南京", "PUBLISHED"); |
|||
jdbc.update("update jobs set created_at=? where company_id in (?, ?)", |
|||
LocalDateTime.of(2026, 10, 10, 12, 0), companyId, otherCompanyId); |
|||
} |
|||
|
|||
@AfterEach |
|||
void tearDown() { |
|||
jdbc.update("delete from jobs where company_id in (?, ?)", companyId, otherCompanyId); |
|||
jdbc.update("delete from companies where id in (?, ?)", companyId, otherCompanyId); |
|||
jdbc.update("delete from auth_tokens where user_id in (select id from users where phone like '1990099%')"); |
|||
jdbc.update("delete from user_roles where user_id in (select id from users where phone like '1990099%')"); |
|||
jdbc.update("delete from users where phone like '1990099%'"); |
|||
} |
|||
|
|||
@Test |
|||
void companyCanListAllOwnStatusesWithTypedPageAndStablePagination() throws Exception { |
|||
String before = jobSnapshot(); |
|||
long auditsBefore = count("audit_logs"); |
|||
long notificationsBefore = count("notifications"); |
|||
long messagesBefore = count("messages"); |
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.code").value(200)) |
|||
.andExpect(jsonPath("$.data.total").value(3)) |
|||
.andExpect(jsonPath("$.data.page").value(0)) |
|||
.andExpect(jsonPath("$.data.size").value(20)) |
|||
.andExpect(jsonPath("$.data.totalPages").value(1)) |
|||
.andExpect(jsonPath("$.data.items.length()").value(3)); |
|||
JsonNode first = mapper.readTree(mvc.perform(get("/companies/me/jobs?page=0&size=1") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andReturn().getResponse().getContentAsByteArray()).path("data"); |
|||
JsonNode second = mapper.readTree(mvc.perform(get("/companies/me/jobs?page=1&size=1") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andReturn().getResponse().getContentAsByteArray()).path("data"); |
|||
assertThat(first.path("total").asLong()).isEqualTo(3); |
|||
assertThat(second.path("total").asLong()).isEqualTo(3); |
|||
assertThat(first.path("items").get(0).path("id").asLong()) |
|||
.isNotEqualTo(second.path("items").get(0).path("id").asLong()); |
|||
assertThat(first.path("items").get(0).path("id").asLong()) |
|||
.isEqualTo(Math.max(draftId, Math.max(publishedId, closedId))); |
|||
assertThat(jobSnapshot()).isEqualTo(before); |
|||
assertThat(count("audit_logs")).isEqualTo(auditsBefore); |
|||
assertThat(count("notifications")).isEqualTo(notificationsBefore); |
|||
assertThat(count("messages")).isEqualTo(messagesBefore); |
|||
} |
|||
|
|||
@Test |
|||
void statusKeywordCityFiltersAreAppliedTogetherAndWildcardIsLiteral() throws Exception { |
|||
mvc.perform(get("/companies/me/jobs").param("status", "DRAFT") |
|||
.param("keyword", "%").param("city", "南京") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.data.total").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(draftId)) |
|||
.andExpect(jsonPath("$.data.items[0].status").value("DRAFT")) |
|||
.andExpect(jsonPath("$.data.items[0].salaryMin").value(12000)); |
|||
mvc.perform(get("/companies/me/jobs?status=REJECTED") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@Test |
|||
void tenantScopeAppliesToContentAndCountAndAnonymousOrNonCompanyRemainDenied() throws Exception { |
|||
mvc.perform(get("/companies/me/jobs?status=PUBLISHED&companyId=" + otherCompanyId) |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.data.total").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(publishedId)) |
|||
.andExpect(jsonPath("$.data.items[0].companyId").value(companyId)); |
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(otherCompanyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.total").value(1)); |
|||
mvc.perform(get("/companies/me/jobs")).andExpect(status().isUnauthorized()); |
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(studentToken))) |
|||
.andExpect(status().isForbidden()); |
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(adminToken))) |
|||
.andExpect(status().isForbidden()); |
|||
} |
|||
|
|||
@Test |
|||
void rejectsInvalidPagingAndDoesNotExposeAnotherTenant() throws Exception { |
|||
mvc.perform(get("/companies/me/jobs?page=-1").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isBadRequest()); |
|||
mvc.perform(get("/companies/me/jobs?size=101").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isBadRequest()); |
|||
mvc.perform(get("/companies/me/jobs?size=0").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isBadRequest()); |
|||
String body = mvc.perform(get("/companies/me/jobs?size=1&page=99") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.items.length()").value(0)) |
|||
.andExpect(jsonPath("$.data.total").value(3)).andReturn().getResponse().getContentAsString(); |
|||
assertThat(body).doesNotContain("Java外部岗位").doesNotContain("1990099"); |
|||
} |
|||
|
|||
@Test |
|||
void closedJobsRemainClosedAndPublicListDoesNotExposeDraftOrClosed() throws Exception { |
|||
mvc.perform(get("/companies/me/jobs?status=CLOSED").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.total").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(closedId)); |
|||
mvc.perform(get("/jobs?companyId=" + companyId)) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.data.items[?(@.status == 'DRAFT')]").doesNotExist()) |
|||
.andExpect(jsonPath("$.data.items[?(@.status == 'CLOSED')]").doesNotExist()); |
|||
} |
|||
|
|||
@Test |
|||
void openApiDocumentsThePrivateManagementRouteAndTypedResponse() throws Exception { |
|||
JsonNode openApi = mapper.readTree(mvc.perform(get("/v3/api-docs")) |
|||
.andExpect(status().isOk()).andReturn().getResponse().getContentAsByteArray()); |
|||
JsonNode operation = openApi.path("paths").path("/companies/me/jobs").path("get"); |
|||
assertThat(operation.path("operationId").asText()).isEqualTo("getMyJobs"); |
|||
assertThat(operation.path("security").toString()).contains("bearerAuth"); |
|||
assertThat(operation.path("parameters").toString()).contains("status", "keyword", "city", "page", "size"); |
|||
assertThat(operation.path("responses").path("200").path("content").path("*/*") |
|||
.path("schema").path("$ref").asText()).contains("ApiResponsePageResponseCompanyManagedJobItem"); |
|||
JsonNode itemProperties = openApi.path("components").path("schemas") |
|||
.path("CompanyManagedJobItem").path("properties"); |
|||
assertThat(itemProperties.has("status")).isTrue(); |
|||
assertThat(itemProperties.has("salaryMin")).isTrue(); |
|||
assertThat(itemProperties.has("salaryMax")).isTrue(); |
|||
assertThat(itemProperties.has("companyId")).isTrue(); |
|||
} |
|||
|
|||
private Identity identity(User.Role role) { |
|||
int sequence = SEQUENCE.incrementAndGet(); |
|||
User user = users.saveAndFlush(User.builder() |
|||
.phone("1990099" + String.format("%04d", sequence)) |
|||
.role(role).roles(new HashSet<>(Set.of(role))).active(true).verified(true).build()); |
|||
var token = sessions.createSession(user, Instant.now(), new AuditContext()).accessToken(); |
|||
return new Identity(user.getId(), token); |
|||
} |
|||
|
|||
private long job(long ownerCompanyId, String title, String city, String status) { |
|||
return jobs.saveAndFlush(Job.builder().companyId(ownerCompanyId).title(title) |
|||
.description("岗位描述").requirements("岗位要求").city(city).employmentType("FULL_TIME") |
|||
.salaryMin(12000).salaryMax(18000).status(status).build()).getId(); |
|||
} |
|||
|
|||
private String jobSnapshot() { |
|||
return jdbc.query("select id,company_id,title,description,requirements,city,employment_type,salary_min,salary_max,status,published_at,created_at,updated_at from jobs order by id", |
|||
rs -> { |
|||
StringBuilder snapshot = new StringBuilder(); |
|||
while (rs.next()) snapshot.append(rs.getString(1)).append('|').append(rs.getString(2)) |
|||
.append('|').append(rs.getString(3)).append('|').append(rs.getString(4)) |
|||
.append('|').append(rs.getString(5)).append('|').append(rs.getString(6)) |
|||
.append('|').append(rs.getString(7)).append('|').append(rs.getString(8)) |
|||
.append('|').append(rs.getString(9)).append('|').append(rs.getString(10)) |
|||
.append('|').append(rs.getString(11)).append('|').append(rs.getString(12)) |
|||
.append('|').append(rs.getString(13)).append('\n'); |
|||
return snapshot.toString(); |
|||
}); |
|||
} |
|||
|
|||
private long count(String table) { |
|||
return jdbc.queryForObject("select count(*) from " + table, Long.class); |
|||
} |
|||
|
|||
private String bearer(String token) { return "Bearer " + token; } |
|||
private record Identity(long id, String token) {} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
package com.mydreamjob.integration; |
|||
|
|||
import com.mydreamjob.audit.AuditContext; |
|||
import com.mydreamjob.auth.AuthSessionService; |
|||
import com.mydreamjob.auth.User; |
|||
import com.mydreamjob.auth.UserRepository; |
|||
import com.mydreamjob.company.Company; |
|||
import com.mydreamjob.company.CompanyRepository; |
|||
import com.mydreamjob.job.Job; |
|||
import com.mydreamjob.job.JobRepository; |
|||
import java.sql.Timestamp; |
|||
import java.time.Instant; |
|||
import java.time.LocalDateTime; |
|||
import java.util.ArrayList; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import org.junit.jupiter.api.AfterEach; |
|||
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.jdbc.core.JdbcTemplate; |
|||
import org.springframework.test.context.ActiveProfiles; |
|||
import org.springframework.test.context.DynamicPropertyRegistry; |
|||
import org.springframework.test.context.DynamicPropertySource; |
|||
import org.springframework.test.web.servlet.MockMvc; |
|||
import org.testcontainers.containers.MySQLContainer; |
|||
import org.testcontainers.junit.jupiter.Container; |
|||
import org.testcontainers.junit.jupiter.Testcontainers; |
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
/** 企业私有岗位管理接口在真实 MySQL 上的租户、分页、筛选和只读验收。 */ |
|||
@Testcontainers(disabledWithoutDocker = false) |
|||
@SpringBootTest |
|||
@AutoConfigureMockMvc |
|||
@ActiveProfiles("mysql-it") |
|||
class CompanyJobManagementMySqlTest { |
|||
@Container |
|||
static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.4") |
|||
.withDatabaseName("company_job_management_test") |
|||
.withUsername("company_job_test") |
|||
.withPassword("company-job-test-only"); |
|||
|
|||
@DynamicPropertySource |
|||
static void database(DynamicPropertyRegistry registry) { |
|||
registry.add("spring.datasource.url", MYSQL::getJdbcUrl); |
|||
registry.add("spring.datasource.username", MYSQL::getUsername); |
|||
registry.add("spring.datasource.password", MYSQL::getPassword); |
|||
} |
|||
|
|||
private static final AtomicInteger SEQUENCE = new AtomicInteger(); |
|||
@MockBean private RedisTemplate<String, Object> redis; |
|||
@Autowired private MockMvc mvc; |
|||
@Autowired private JdbcTemplate jdbc; |
|||
@Autowired private UserRepository users; |
|||
@Autowired private CompanyRepository companies; |
|||
@Autowired private JobRepository jobs; |
|||
@Autowired private AuthSessionService sessions; |
|||
|
|||
private final List<Long> userIds = new ArrayList<>(); |
|||
private final List<Long> companyIds = new ArrayList<>(); |
|||
private long companyId; |
|||
private long otherCompanyId; |
|||
private long draftId; |
|||
private long publishedId; |
|||
private long closedId; |
|||
private String companyToken; |
|||
private String studentToken; |
|||
|
|||
@BeforeEach |
|||
void fixtures() { |
|||
assertThat(jdbc.queryForObject("select version()", String.class)).startsWith("8."); |
|||
Identity company = identity(User.Role.COMPANY); |
|||
Identity otherCompany = identity(User.Role.COMPANY); |
|||
Identity student = identity(User.Role.STUDENT); |
|||
companyToken = company.token(); |
|||
studentToken = student.token(); |
|||
companyId = company(company.userId(), false); |
|||
otherCompanyId = company(otherCompany.userId(), true); |
|||
draftId = job(companyId, "Java%工程师", "南京", "DRAFT"); |
|||
publishedId = job(companyId, "Python工程师", "北京", "PUBLISHED"); |
|||
closedId = job(companyId, "Java工程师", "南京", "CLOSED"); |
|||
job(otherCompanyId, "Java其他企业岗位", "南京", "PUBLISHED"); |
|||
jdbc.update("update jobs set created_at=? where company_id in (?, ?)", |
|||
Timestamp.valueOf(LocalDateTime.of(2026, 10, 10, 12, 0)), companyId, otherCompanyId); |
|||
} |
|||
|
|||
@AfterEach |
|||
void cleanup() { |
|||
for (long id : companyIds) jdbc.update("delete from jobs where company_id=?", id); |
|||
for (long id : companyIds) { |
|||
jdbc.update("delete from company_verifications where company_id=?", id); |
|||
jdbc.update("delete from companies where id=?", id); |
|||
} |
|||
for (long id : userIds) jdbc.update("delete from auth_tokens where user_id=?", id); |
|||
for (long id : userIds) jdbc.update("delete from user_roles where user_id=?", id); |
|||
for (long id : userIds) jdbc.update("delete from users where id=?", id); |
|||
} |
|||
|
|||
@Test |
|||
void realMysqlListsAllOwnStatusesAndFiltersWithoutWritesOrCrossTenantLeakage() throws Exception { |
|||
String before = snapshot(companyId); |
|||
long auditsBefore = count("audit_logs"); |
|||
long notificationsBefore = count("notifications"); |
|||
long messagesBefore = count("messages"); |
|||
|
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.total").value(3)) |
|||
.andExpect(jsonPath("$.data.items.length()").value(3)); |
|||
mvc.perform(get("/companies/me/jobs").param("status", "DRAFT") |
|||
.param("keyword", "%").param("city", "南京") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.total").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(draftId)); |
|||
mvc.perform(get("/companies/me/jobs").param("status", "CLOSED") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.items[0].id").value(closedId)); |
|||
mvc.perform(get("/companies/me/jobs").param("status", "PUBLISHED") |
|||
.param("companyId", Long.toString(otherCompanyId)) |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.total").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(publishedId)) |
|||
.andExpect(jsonPath("$.data.items[0].companyId").value(companyId)); |
|||
mvc.perform(get("/companies/me/jobs").param("page", "0").param("size", "1") |
|||
.header("Authorization", bearer(companyToken))) |
|||
.andExpect(status().isOk()).andExpect(jsonPath("$.data.items.length()").value(1)) |
|||
.andExpect(jsonPath("$.data.items[0].id").value(Math.max(draftId, Math.max(publishedId, closedId)))); |
|||
mvc.perform(get("/companies/me/jobs").header("Authorization", bearer(studentToken))) |
|||
.andExpect(status().isForbidden()); |
|||
mvc.perform(get("/companies/me/jobs")).andExpect(status().isUnauthorized()); |
|||
mvc.perform(get("/companies/" + companyId + "/jobs")) |
|||
.andExpect(status().isOk()) |
|||
.andExpect(jsonPath("$.data.items[?(@.id == " + draftId + ")]").doesNotExist()) |
|||
.andExpect(jsonPath("$.data.items[?(@.id == " + closedId + ")]").doesNotExist()); |
|||
|
|||
assertThat(snapshot(companyId)).isEqualTo(before); |
|||
assertThat(count("audit_logs")).isEqualTo(auditsBefore); |
|||
assertThat(count("notifications")).isEqualTo(notificationsBefore); |
|||
assertThat(count("messages")).isEqualTo(messagesBefore); |
|||
} |
|||
|
|||
private Identity identity(User.Role role) { |
|||
int sequence = SEQUENCE.incrementAndGet(); |
|||
User user = users.saveAndFlush(User.builder() |
|||
.phone("1990098" + String.format("%04d", sequence)) |
|||
.role(role).roles(new HashSet<>(Set.of(role))).active(true).verified(true).build()); |
|||
userIds.add(user.getId()); |
|||
String token = sessions.createSession(user, Instant.now(), new AuditContext()).accessToken(); |
|||
return new Identity(user.getId(), token); |
|||
} |
|||
|
|||
private long company(long ownerUserId, boolean verified) { |
|||
Company value = companies.saveAndFlush(Company.builder().ownerUserId(ownerUserId) |
|||
.name("临时岗位管理企业").verified(verified).build()); |
|||
companyIds.add(value.getId()); |
|||
return value.getId(); |
|||
} |
|||
|
|||
private long job(long ownerCompanyId, String title, String city, String status) { |
|||
return jobs.saveAndFlush(Job.builder().companyId(ownerCompanyId).title(title) |
|||
.description("临时岗位描述").requirements("临时岗位要求").city(city) |
|||
.employmentType("FULL_TIME").salaryMin(12000).salaryMax(18000).status(status).build()).getId(); |
|||
} |
|||
|
|||
private long count(String table) { |
|||
return jdbc.queryForObject("select count(*) from " + table, Long.class); |
|||
} |
|||
|
|||
private String snapshot(long ownerCompanyId) { |
|||
return jdbc.query("select id,company_id,title,description,requirements,city,employment_type,salary_min,salary_max,status,published_at,created_at,updated_at from jobs where company_id=? order by id", |
|||
rs -> { |
|||
StringBuilder result = new StringBuilder(); |
|||
while (rs.next()) for (int column = 1; column <= 13; column++) |
|||
result.append(rs.getString(column)).append(column == 13 ? '\n' : '|'); |
|||
return result.toString(); |
|||
}, ownerCompanyId); |
|||
} |
|||
|
|||
private static String bearer(String token) { return "Bearer " + token; } |
|||
private record Identity(long userId, String token) {} |
|||
} |
|||
Loading…
Reference in new issue