6 changed files with 453 additions and 0 deletions
@ -0,0 +1,50 @@ |
|||
# T1 任务简报:V38/V39 迁移与通用清单种子 |
|||
|
|||
## Where This Fits |
|||
|
|||
- Wave: W1 |
|||
- Depends on: 无 |
|||
- Contract: `changes/n04-onboard/execution-contract.md` |
|||
- Specs: `changes/n04-onboard/specs/onboarding-checklist/spec.md`、`changes/n04-onboard/specs/company-knowledge/spec.md` |
|||
- Design: `changes/n04-onboard/design.md` 的 Data Model 与 Architecture 部分 |
|||
|
|||
## Objective |
|||
|
|||
新增 V38 建表迁移和 V39 通用清单种子,并先用失败测试锁定表结构、约束、通用项数量和稳定排序。 |
|||
|
|||
## Files In Scope |
|||
|
|||
- `mydreamjob-server/src/main/resources/db/migration/V38__onboarding_checklist_and_company_knowledge.sql` |
|||
- `mydreamjob-server/src/main/resources/db/migration/V39__onboarding_standard_checklist_seed.sql` |
|||
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingMigrationTest.java` |
|||
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingSeedDataIntegrationTest.java` |
|||
- 如项目迁移测试需要共享测试配置,仅可修改同一测试包内的辅助文件。 |
|||
|
|||
## Required Behavior |
|||
|
|||
- 创建 `onboarding_checklist_items`、`student_onboarding_progress`、`company_knowledge`。 |
|||
- `onboarding_checklist_items` 至少包含合同规定的字段、唯一键、活动/排序索引、`company_id` 对 `companies(id)` 的外键。 |
|||
- `student_onboarding_progress` 必须对学生与条目唯一,并建立 `users(id)`、`onboarding_checklist_items(id)` 外键。 |
|||
- `company_knowledge.company_id` 唯一,并建立 `companies(id)` 外键。 |
|||
- V39 必须写入稳定的通用清单;每一步使用明确 `item_key`、分类、排序和必填标记,不得使用随机值。 |
|||
- 迁移必须兼容 H2 `MODE=MySQL` 与 MySQL 8.4。 |
|||
|
|||
## TDD Requirement |
|||
|
|||
1. 先写 `OnboardingMigrationTest`、`OnboardingSeedDataIntegrationTest`,确认因表或种子不存在而失败。 |
|||
2. 再写 V38/V39 让测试通过。 |
|||
3. 不得先写迁移再补测试。 |
|||
|
|||
## Evidence Commands |
|||
|
|||
```powershell |
|||
cd D:\1\zhi\zhilai\mydreamjob-server |
|||
mvn -Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest test |
|||
``` |
|||
|
|||
## Done Definition |
|||
|
|||
- 两条证据测试通过。 |
|||
- 测试断言三张表存在、关键唯一约束/外键/索引可验证、通用种子数量大于 0 且排序稳定。 |
|||
- 不修改 V1-V37。 |
|||
- 不创建 W2 实体、Repository、Service 或 Controller。 |
|||
@ -0,0 +1,53 @@ |
|||
# T2 任务简报:Onboarding 实体、Repository 与评价聚合查询 |
|||
|
|||
## Where This Fits |
|||
|
|||
- Wave: W1 |
|||
- Depends on: T1 |
|||
- Contract: `changes/n04-onboard/execution-contract.md` |
|||
- Design: `changes/n04-onboard/design.md` 的 Architecture、Data Model、Decisions |
|||
|
|||
## Objective |
|||
|
|||
在 V38/V39 表结构之上新增实体、Repository 和公司评价聚合查询,先写持久化失败测试,再实现最小映射。 |
|||
|
|||
## Files In Scope |
|||
|
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItem.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgress.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledge.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItemRepository.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgressRepository.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeRepository.java` |
|||
- 必要时只读扩展 `mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewRepository.java` 和 `JobReviewDimensionScoreRepository.java` 的公司级聚合查询。 |
|||
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingRepositoryIntegrationTest.java` |
|||
|
|||
## Required Behavior |
|||
|
|||
- `OnboardingChecklistItem` 映射 `onboarding_checklist_items`,含 `scopeType`、`scopeId`、`companyId`、`itemKey`、title、description、category、sortOrder、required、active、idempotencyKey、requestHash、version、审计时间。 |
|||
- `StudentOnboardingProgress` 映射 `student_onboarding_progress`,含 studentUserId、itemId、completed、completedAt、version、审计时间。 |
|||
- `CompanyKnowledge` 映射 `company_knowledge`,含 companyId、culture、workPolicies、onboardingGuide、version、审计时间。 |
|||
- 清单 Repository 支持按 `companyId` 查询活动通用项+活动企业项,并按 `sortOrder ASC, id ASC` 稳定排序。 |
|||
- 进度 Repository 支持按学生和多个 itemId 批量读取,并按学生+条目唯一查询。 |
|||
- 知识 Repository 支持按 companyId 查询。 |
|||
- 评价聚合查询支持公司下全部 `PUBLISHED` 且 `AUTO_APPROVED/APPROVED` 的评价评分分布、tags 原始列和八维汇总;不得逐岗位或逐评价产生 N+1。 |
|||
|
|||
## TDD Requirement |
|||
|
|||
1. 先写 `OnboardingRepositoryIntegrationTest`,确认新实体/Repository 不存在或查询失败。 |
|||
2. 再实现最小实体和 Repository 使测试通过。 |
|||
3. 不创建 Service、Controller、DTO 或业务状态机。 |
|||
|
|||
## Evidence Commands |
|||
|
|||
```powershell |
|||
cd D:\1\zhi\zhilai\mydreamjob-server |
|||
mvn -Dtest=OnboardingRepositoryIntegrationTest test |
|||
``` |
|||
|
|||
## Done Definition |
|||
|
|||
- 新实体与三张表映射通过 Hibernate `ddl-auto=validate`。 |
|||
- 合并查询只返回活动通用项和当前企业活动项。 |
|||
- 多学生、多企业、软删除项和排序测试通过。 |
|||
- 公司评分/tags/八维聚合仅统计公开评价。 |
|||
@ -0,0 +1,57 @@ |
|||
-- Flyway V38: 入职清单与企业知识表结构 |
|||
-- 本迁移只建立三张业务表;通用清单内容由 V39 导入。 |
|||
|
|||
CREATE TABLE onboarding_checklist_items ( |
|||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
|||
scope_type VARCHAR(20) NOT NULL COMMENT 'STANDARD 或 COMPANY', |
|||
scope_id BIGINT NOT NULL COMMENT '通用项为 0,企业项为 company_id', |
|||
company_id BIGINT NULL COMMENT '企业项所属企业;通用项为空', |
|||
item_key VARCHAR(80) NOT NULL COMMENT '清单项稳定业务键', |
|||
title VARCHAR(120) NOT NULL COMMENT '标题', |
|||
description VARCHAR(1000) NULL COMMENT '说明', |
|||
category VARCHAR(30) NOT NULL COMMENT 'DOCUMENT/ADMIN/EQUIPMENT/TRAINING/LOGISTICS/OTHER', |
|||
sort_order INT NOT NULL DEFAULT 1000 COMMENT '排序值', |
|||
required BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否必填', |
|||
active BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否生效', |
|||
idempotency_key VARCHAR(64) NULL COMMENT '企业创建幂等键', |
|||
request_hash CHAR(64) NULL COMMENT '规范化创建请求 SHA-256', |
|||
version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', |
|||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
|||
CONSTRAINT ck_onboarding_checklist_scope_type CHECK (scope_type IN ('STANDARD', 'COMPANY')), |
|||
CONSTRAINT ck_onboarding_checklist_category CHECK ( |
|||
category IN ('DOCUMENT', 'ADMIN', 'EQUIPMENT', 'TRAINING', 'LOGISTICS', 'OTHER') |
|||
), |
|||
CONSTRAINT ck_onboarding_checklist_sort_order CHECK (sort_order BETWEEN 0 AND 9999), |
|||
CONSTRAINT uk_onboarding_checklist_scope_item UNIQUE (scope_type, scope_id, item_key), |
|||
CONSTRAINT uk_onboarding_checklist_company_idempotency UNIQUE (company_id, idempotency_key), |
|||
CONSTRAINT fk_onboarding_checklist_company FOREIGN KEY (company_id) REFERENCES companies (id), |
|||
INDEX idx_onboarding_checklist_scope_active_sort (scope_type, scope_id, active, sort_order, id) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='入职清单项'; |
|||
|
|||
CREATE TABLE student_onboarding_progress ( |
|||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
|||
student_user_id BIGINT NOT NULL COMMENT '学生用户', |
|||
item_id BIGINT NOT NULL COMMENT '清单项', |
|||
completed BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否完成', |
|||
completed_at DATETIME NULL COMMENT '完成时间;取消完成时为空', |
|||
version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', |
|||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
|||
CONSTRAINT uk_student_onboarding_progress_student_item UNIQUE (student_user_id, item_id), |
|||
CONSTRAINT fk_student_onboarding_progress_student FOREIGN KEY (student_user_id) REFERENCES users (id), |
|||
CONSTRAINT fk_student_onboarding_progress_item FOREIGN KEY (item_id) REFERENCES onboarding_checklist_items (id) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生入职清单完成状态'; |
|||
|
|||
CREATE TABLE company_knowledge ( |
|||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
|||
company_id BIGINT NOT NULL COMMENT '企业', |
|||
culture VARCHAR(2000) NULL COMMENT '企业文化', |
|||
work_policies VARCHAR(4000) NULL COMMENT '工作制度', |
|||
onboarding_guide VARCHAR(4000) NULL COMMENT '入职指南', |
|||
version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', |
|||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
|||
CONSTRAINT uk_company_knowledge_company UNIQUE (company_id), |
|||
CONSTRAINT fk_company_knowledge_company FOREIGN KEY (company_id) REFERENCES companies (id) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='企业知识介绍'; |
|||
@ -0,0 +1,31 @@ |
|||
-- Flyway V39: 通用入职准备清单种子 |
|||
-- 通用项使用 scope_id=0、company_id=NULL,内容、分类、顺序和必填标记均为固定值。 |
|||
|
|||
INSERT INTO onboarding_checklist_items |
|||
(scope_type, scope_id, company_id, item_key, title, description, category, sort_order, |
|||
required, active, idempotency_key, request_hash, version) |
|||
VALUES |
|||
('STANDARD', 0, NULL, 'standard-id-card', '准备身份证件', |
|||
'准备身份证原件或有效身份证明,用于入职登记。', 'DOCUMENT', 100, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-education-proof', '准备学历学位证明', |
|||
'按公司要求准备学历、学位证书或学信网证明。', 'DOCUMENT', 110, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-id-photo', '准备证件照', |
|||
'准备符合公司要求的近期证件照。', 'DOCUMENT', 120, FALSE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-bank-account', '提供工资卡信息', |
|||
'按公司要求提供本人工资卡账户信息。', 'ADMIN', 200, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-emergency-contact', '填写紧急联系人', |
|||
'填写必要的紧急联系人和联系方式。', 'ADMIN', 210, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-background-check', '完成背景核验', |
|||
'按公司通知配合完成背景信息核验。', 'ADMIN', 220, FALSE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-medical-check', '确认入职体检要求', |
|||
'确认岗位是否需要体检及指定机构要求。', 'ADMIN', 230, FALSE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-equipment-request', '确认办公设备需求', |
|||
'提前确认工位、电脑或其他办公设备需求。', 'EQUIPMENT', 300, FALSE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-report-location', '确认报到地址和时间', |
|||
'与 HR 确认入职日期、报到地址和接待人。', 'LOGISTICS', 400, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-commute-plan', '规划通勤路线', |
|||
'提前规划到岗路线和交通时间。', 'LOGISTICS', 410, FALSE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-compliance-training', '完成合规与保密培训', |
|||
'完成公司要求的合规、安全和保密培训。', 'TRAINING', 500, TRUE, TRUE, NULL, NULL, 0), |
|||
('STANDARD', 0, NULL, 'standard-onboarding-guide', '阅读入职指南', |
|||
'阅读公司入职指南及相关准备说明。', 'TRAINING', 510, FALSE, TRUE, NULL, NULL, 0); |
|||
@ -0,0 +1,157 @@ |
|||
package com.mydreamjob.onboarding; |
|||
|
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
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.context.TestPropertySource; |
|||
|
|||
import javax.sql.DataSource; |
|||
import java.sql.Connection; |
|||
import java.sql.DatabaseMetaData; |
|||
import java.sql.ResultSet; |
|||
import java.sql.SQLException; |
|||
import java.util.ArrayList; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.TreeMap; |
|||
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue; |
|||
|
|||
@SpringBootTest |
|||
@TestPropertySource(properties = |
|||
"spring.datasource.url=jdbc:h2:mem:integrationdb_onboarding_migration;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") |
|||
@ActiveProfiles("integration-test") |
|||
class OnboardingMigrationTest { |
|||
|
|||
@Autowired |
|||
private DataSource dataSource; |
|||
|
|||
@MockBean |
|||
private RedisTemplate<String, Object> redisTemplate; |
|||
|
|||
@Test |
|||
void migrationCreatesOnboardingTablesColumnsConstraintsAndIndexes() throws SQLException { |
|||
assertTableAndColumns("onboarding_checklist_items", |
|||
"id", "scope_type", "scope_id", "company_id", "item_key", "title", |
|||
"description", "category", "sort_order", "required", "active", |
|||
"idempotency_key", "request_hash", "version", "created_at", "updated_at"); |
|||
assertTableAndColumns("student_onboarding_progress", |
|||
"id", "student_user_id", "item_id", "completed", "completed_at", |
|||
"version", "created_at", "updated_at"); |
|||
assertTableAndColumns("company_knowledge", |
|||
"id", "company_id", "culture", "work_policies", "onboarding_guide", |
|||
"version", "created_at", "updated_at"); |
|||
|
|||
assertUniqueIndex("onboarding_checklist_items", "scope_type", "scope_id", "item_key"); |
|||
assertUniqueIndex("onboarding_checklist_items", "company_id", "idempotency_key"); |
|||
assertUniqueIndex("student_onboarding_progress", "student_user_id", "item_id"); |
|||
assertUniqueIndex("company_knowledge", "company_id"); |
|||
|
|||
assertForeignKey("onboarding_checklist_items", "company_id", "companies", "id"); |
|||
assertForeignKey("student_onboarding_progress", "student_user_id", "users", "id"); |
|||
assertForeignKey("student_onboarding_progress", "item_id", "onboarding_checklist_items", "id"); |
|||
assertForeignKey("company_knowledge", "company_id", "companies", "id"); |
|||
|
|||
assertIndex("onboarding_checklist_items", |
|||
"scope_type", "scope_id", "active", "sort_order", "id"); |
|||
} |
|||
|
|||
private void assertTableAndColumns(String table, String... columns) throws SQLException { |
|||
try (Connection connection = dataSource.getConnection()) { |
|||
DatabaseMetaData metadata = connection.getMetaData(); |
|||
String actualTable = findTable(metadata, table); |
|||
if (actualTable == null) { |
|||
throw new AssertionError("Missing table " + table); |
|||
} |
|||
List<String> actualColumns = new ArrayList<>(); |
|||
try (ResultSet resultSet = metadata.getColumns(null, null, actualTable, null)) { |
|||
while (resultSet.next()) { |
|||
actualColumns.add(resultSet.getString("COLUMN_NAME").toLowerCase()); |
|||
} |
|||
} |
|||
for (String column : columns) { |
|||
assertTrue(actualColumns.contains(column.toLowerCase()), |
|||
"Missing column " + table + "." + column + "; actual=" + actualColumns); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void assertUniqueIndex(String table, String... columns) throws SQLException { |
|||
assertTrue(hasIndex(table, true, columns), |
|||
"Missing unique index on " + table + "(" + String.join(", ", columns) + ")"); |
|||
} |
|||
|
|||
private void assertIndex(String table, String... columns) throws SQLException { |
|||
assertTrue(hasIndex(table, false, columns), |
|||
"Missing index on " + table + "(" + String.join(", ", columns) + ")"); |
|||
} |
|||
|
|||
private boolean hasIndex(String table, boolean unique, String... expectedColumns) |
|||
throws SQLException { |
|||
try (Connection connection = dataSource.getConnection()) { |
|||
DatabaseMetaData metadata = connection.getMetaData(); |
|||
String actualTable = findTable(metadata, table); |
|||
if (actualTable == null) { |
|||
return false; |
|||
} |
|||
Map<String, TreeMap<Short, String>> indexes = new LinkedHashMap<>(); |
|||
try (ResultSet resultSet = metadata.getIndexInfo(null, null, actualTable, false, false)) { |
|||
while (resultSet.next()) { |
|||
String indexName = resultSet.getString("INDEX_NAME"); |
|||
if (indexName == null || resultSet.getBoolean("NON_UNIQUE") == unique) { |
|||
continue; |
|||
} |
|||
indexes.computeIfAbsent(indexName, ignored -> new TreeMap<>()) |
|||
.put(resultSet.getShort("ORDINAL_POSITION"), |
|||
resultSet.getString("COLUMN_NAME").toLowerCase()); |
|||
} |
|||
} |
|||
List<String> expected = new ArrayList<>(); |
|||
for (String column : expectedColumns) { |
|||
expected.add(column.toLowerCase()); |
|||
} |
|||
return indexes.values().stream() |
|||
.map(TreeMap::values) |
|||
.map(values -> new ArrayList<>(values)) |
|||
.anyMatch(expected::equals); |
|||
} |
|||
} |
|||
|
|||
private void assertForeignKey(String childTable, String childColumn, |
|||
String parentTable, String parentColumn) throws SQLException { |
|||
try (Connection connection = dataSource.getConnection()) { |
|||
DatabaseMetaData metadata = connection.getMetaData(); |
|||
String actualChildTable = findTable(metadata, childTable); |
|||
String actualParentTable = findTable(metadata, parentTable); |
|||
if (actualChildTable == null || actualParentTable == null) { |
|||
throw new AssertionError("Missing table for foreign key " + childTable + "." + childColumn); |
|||
} |
|||
try (ResultSet resultSet = metadata.getImportedKeys(null, null, actualChildTable)) { |
|||
while (resultSet.next()) { |
|||
if (actualParentTable.equalsIgnoreCase(resultSet.getString("PKTABLE_NAME")) |
|||
&& childColumn.equalsIgnoreCase(resultSet.getString("FKCOLUMN_NAME")) |
|||
&& parentColumn.equalsIgnoreCase(resultSet.getString("PKCOLUMN_NAME"))) { |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
throw new AssertionError("Missing foreign key " + childTable + "." + childColumn |
|||
+ " -> " + parentTable + "." + parentColumn); |
|||
} |
|||
|
|||
private String findTable(DatabaseMetaData metadata, String expected) throws SQLException { |
|||
try (ResultSet resultSet = metadata.getTables(null, null, null, new String[]{"TABLE"})) { |
|||
while (resultSet.next()) { |
|||
if (expected.equalsIgnoreCase(resultSet.getString("TABLE_NAME"))) { |
|||
return resultSet.getString("TABLE_NAME"); |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
package com.mydreamjob.onboarding; |
|||
|
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
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 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.assertNull; |
|||
import static org.junit.jupiter.api.Assertions.assertTrue; |
|||
|
|||
@SpringBootTest |
|||
@TestPropertySource(properties = |
|||
"spring.datasource.url=jdbc:h2:mem:integrationdb_onboarding_seed;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") |
|||
@ActiveProfiles("integration-test") |
|||
class OnboardingSeedDataIntegrationTest { |
|||
|
|||
private static final Set<String> SUPPORTED_CATEGORIES = Set.of( |
|||
"DOCUMENT", "ADMIN", "EQUIPMENT", "TRAINING", "LOGISTICS", "OTHER"); |
|||
|
|||
private static final List<ExpectedStandardItem> EXPECTED_ITEMS = List.of( |
|||
new ExpectedStandardItem("standard-id-card", "准备身份证件", "DOCUMENT", 100, true), |
|||
new ExpectedStandardItem("standard-education-proof", "准备学历学位证明", "DOCUMENT", 110, true), |
|||
new ExpectedStandardItem("standard-id-photo", "准备证件照", "DOCUMENT", 120, false), |
|||
new ExpectedStandardItem("standard-bank-account", "提供工资卡信息", "ADMIN", 200, true), |
|||
new ExpectedStandardItem("standard-emergency-contact", "填写紧急联系人", "ADMIN", 210, true), |
|||
new ExpectedStandardItem("standard-background-check", "完成背景核验", "ADMIN", 220, false), |
|||
new ExpectedStandardItem("standard-medical-check", "确认入职体检要求", "ADMIN", 230, false), |
|||
new ExpectedStandardItem("standard-equipment-request", "确认办公设备需求", "EQUIPMENT", 300, false), |
|||
new ExpectedStandardItem("standard-report-location", "确认报到地址和时间", "LOGISTICS", 400, true), |
|||
new ExpectedStandardItem("standard-commute-plan", "规划通勤路线", "LOGISTICS", 410, false), |
|||
new ExpectedStandardItem("standard-compliance-training", "完成合规与保密培训", "TRAINING", 500, true), |
|||
new ExpectedStandardItem("standard-onboarding-guide", "阅读入职指南", "TRAINING", 510, false) |
|||
); |
|||
|
|||
@Autowired |
|||
private JdbcTemplate jdbcTemplate; |
|||
|
|||
@MockBean |
|||
private RedisTemplate<String, Object> redisTemplate; |
|||
|
|||
@Test |
|||
void standardChecklistSeedIsStableAndComplete() { |
|||
List<Map<String, Object>> rows = jdbcTemplate.queryForList( |
|||
"SELECT item_key, title, category, sort_order, required, active, " + |
|||
"scope_type, scope_id, company_id " + |
|||
"FROM onboarding_checklist_items " + |
|||
"WHERE scope_type = 'STANDARD' ORDER BY sort_order ASC, id ASC"); |
|||
|
|||
assertEquals(EXPECTED_ITEMS.size(), rows.size()); |
|||
for (int index = 0; index < EXPECTED_ITEMS.size(); index++) { |
|||
ExpectedStandardItem expected = EXPECTED_ITEMS.get(index); |
|||
Map<String, Object> actual = rows.get(index); |
|||
|
|||
assertEquals(expected.itemKey(), actual.get("item_key")); |
|||
assertEquals(expected.title(), actual.get("title")); |
|||
assertEquals(expected.category(), actual.get("category")); |
|||
assertEquals(expected.sortOrder(), ((Number) actual.get("sort_order")).intValue()); |
|||
assertEquals(expected.required(), asBoolean(actual.get("required"))); |
|||
assertTrue(asBoolean(actual.get("active"))); |
|||
assertEquals("STANDARD", actual.get("scope_type")); |
|||
assertEquals(0L, ((Number) actual.get("scope_id")).longValue()); |
|||
assertNull(actual.get("company_id")); |
|||
assertTrue(SUPPORTED_CATEGORIES.contains(expected.category())); |
|||
assertNotNull(actual.get("title")); |
|||
} |
|||
|
|||
assertEquals(EXPECTED_ITEMS.size(), count( |
|||
"SELECT COUNT(DISTINCT item_key) FROM onboarding_checklist_items " + |
|||
"WHERE scope_type = 'STANDARD'")); |
|||
} |
|||
|
|||
private int count(String sql) { |
|||
Integer count = jdbcTemplate.queryForObject(sql, Integer.class); |
|||
return count == null ? 0 : count; |
|||
} |
|||
|
|||
private boolean asBoolean(Object value) { |
|||
if (value instanceof Boolean booleanValue) { |
|||
return booleanValue; |
|||
} |
|||
if (value instanceof Number numberValue) { |
|||
return numberValue.intValue() != 0; |
|||
} |
|||
return Boolean.parseBoolean(String.valueOf(value)); |
|||
} |
|||
|
|||
private record ExpectedStandardItem( |
|||
String itemKey, |
|||
String title, |
|||
String category, |
|||
int sortOrder, |
|||
boolean required |
|||
) { |
|||
} |
|||
} |
|||
Loading…
Reference in new issue