Phase 4 完成:US2 原始资料上传(SourceData / SourceService / SourceController)
新增:
- SourceData 实体 + SourceDataMapper(含 updateStatus 方法)
- SourceResponse DTO(上传/列表/详情复用)
- SourceService(upload/list/findById/delete,upload 先 INSERT 获取 ID
再构造 RustFS 路径,delete 仅允许 PENDING 状态)
- SourceController(POST /api/source/upload 返回 201,GET /list,
GET /{id},DELETE /{id};@RequiresRoles 声明权限)
- SourceIntegrationTest(权限校验、空列表、删除不存在资料、
已进入流水线资料删除返回 409)
- application.yml 添加 token.ttl-seconds 配置项
This commit is contained in:
166
src/test/java/com/label/integration/SourceIntegrationTest.java
Normal file
166
src/test/java/com/label/integration/SourceIntegrationTest.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package com.label.integration;
|
||||
|
||||
import com.label.AbstractIntegrationTest;
|
||||
import com.label.common.redis.RedisKeyManager;
|
||||
import com.label.common.redis.RedisService;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* 原始资料管理集成测试(US2)。
|
||||
*
|
||||
* 测试场景:
|
||||
* 1. UPLOADER 上传文本 → 列表仅返回自己的资料
|
||||
* 2. ADMIN 查看列表 → 返回全公司资料
|
||||
* 3. 上传视频 → status = PENDING(视频预处理由 Phase 9 处理)
|
||||
* 4. 已进入流水线的资料删除 → 409 SOURCE_IN_PIPELINE
|
||||
*
|
||||
* 注意:本测试不连接真实 RustFS,上传操作会失败并返回 500/503。
|
||||
* 测试仅验证可访问的业务逻辑(权限、状态机)。
|
||||
* 如需覆盖文件上传,需在测试环境配置 Mock RustFsClient 或启动 MinIO 容器。
|
||||
*/
|
||||
public class SourceIntegrationTest extends AbstractIntegrationTest {
|
||||
|
||||
private static final String UPLOADER_TOKEN = "test-uploader-token-source";
|
||||
private static final String UPLOADER2_TOKEN = "test-uploader2-token-source";
|
||||
private static final String ADMIN_TOKEN = "test-admin-token-source";
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@BeforeEach
|
||||
void setupTokens() {
|
||||
// uploader01 token (userId=4 from init.sql seed)
|
||||
redisService.hSetAll(RedisKeyManager.tokenKey(UPLOADER_TOKEN),
|
||||
Map.of("userId", "4", "role", "UPLOADER", "companyId", "1", "username", "uploader01"),
|
||||
3600L);
|
||||
// admin token (userId=1 from init.sql seed)
|
||||
redisService.hSetAll(RedisKeyManager.tokenKey(ADMIN_TOKEN),
|
||||
Map.of("userId", "1", "role", "ADMIN", "companyId", "1", "username", "admin"),
|
||||
3600L);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanupTokens() {
|
||||
redisService.delete(RedisKeyManager.tokenKey(UPLOADER_TOKEN));
|
||||
redisService.delete(RedisKeyManager.tokenKey(UPLOADER2_TOKEN));
|
||||
redisService.delete(RedisKeyManager.tokenKey(ADMIN_TOKEN));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ 权限测试 --
|
||||
|
||||
@Test
|
||||
@DisplayName("无 Token 访问上传接口 → 401")
|
||||
void upload_withoutToken_returns401() {
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(
|
||||
baseUrl("/api/source/upload"), null, String.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UPLOADER 访问列表接口(无数据)→ 200,items 为空")
|
||||
void list_uploaderWithNoData_returnsEmptyList() {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl("/api/source/list"),
|
||||
HttpMethod.GET,
|
||||
bearerRequest(UPLOADER_TOKEN),
|
||||
Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) response.getBody().get("data");
|
||||
assertThat(data.get("items")).isInstanceOf(List.class);
|
||||
assertThat(((List<?>) data.get("items"))).isEmpty();
|
||||
assertThat(((Number) data.get("total")).longValue()).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ADMIN 访问列表接口(无数据)→ 200,items 为空")
|
||||
void list_adminWithNoData_returnsEmptyList() {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl("/api/source/list"),
|
||||
HttpMethod.GET,
|
||||
bearerRequest(ADMIN_TOKEN),
|
||||
Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> data = (Map<String, Object>) response.getBody().get("data");
|
||||
assertThat(((List<?>) data.get("items"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除不存在的资料 → 404")
|
||||
void delete_nonExistentSource_returns404() {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl("/api/source/9999"),
|
||||
HttpMethod.DELETE,
|
||||
bearerRequest(ADMIN_TOKEN),
|
||||
Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("非 ADMIN 删除资料 → 403 Forbidden")
|
||||
void delete_byUploader_returns403() {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl("/api/source/9999"),
|
||||
HttpMethod.DELETE,
|
||||
bearerRequest(UPLOADER_TOKEN),
|
||||
Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ADMIN 删除已进入流水线的资料 → 409 SOURCE_IN_PIPELINE")
|
||||
void delete_sourceInPipeline_returns409() {
|
||||
// 直接向 DB 插入一条 EXTRACTING 状态的资料(模拟已进入流水线)
|
||||
jdbcTemplate.execute(
|
||||
"INSERT INTO source_data (company_id, uploader_id, data_type, file_path, " +
|
||||
"file_name, file_size, bucket_name, status) " +
|
||||
"VALUES (1, 1, 'TEXT', 'test/path/file.txt', 'file.txt', 100, 'test-bucket', 'EXTRACTING')");
|
||||
|
||||
Long sourceId = jdbcTemplate.queryForObject(
|
||||
"SELECT id FROM source_data WHERE status='EXTRACTING' LIMIT 1", Long.class);
|
||||
|
||||
assertThat(sourceId).isNotNull();
|
||||
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl("/api/source/" + sourceId),
|
||||
HttpMethod.DELETE,
|
||||
bearerRequest(ADMIN_TOKEN),
|
||||
Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = response.getBody();
|
||||
assertThat(body.get("code")).isEqualTo("SOURCE_IN_PIPELINE");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ 工具方法 --
|
||||
|
||||
private HttpEntity<Void> bearerRequest(String token) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + token);
|
||||
return new HttpEntity<>(headers);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user