test(p1r): 收口 Events SSE 门禁
This commit is contained in:
parent
f0ab1b4fce
commit
fa29753972
@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-05-25T13:33:07+00:00",
|
||||
"generatedAt": "2026-05-25T13:45:55+00:00",
|
||||
"sourceContracts": [
|
||||
"docs/api-contracts/account/openapi.yaml",
|
||||
"docs/api-contracts/ai/openapi.yaml",
|
||||
@ -12,11 +12,11 @@
|
||||
"summary": {
|
||||
"totalOperations": 233,
|
||||
"completedOperations": 100,
|
||||
"incompleteOperations": 1,
|
||||
"needsVerificationOperations": 132,
|
||||
"incompleteOperations": 0,
|
||||
"needsVerificationOperations": 133,
|
||||
"catchAllOperations": 0,
|
||||
"genericPersistenceOperations": 0,
|
||||
"ssePlaceholderOperations": 1,
|
||||
"ssePlaceholderOperations": 0,
|
||||
"missingOperations": 0,
|
||||
"blockedOperations": 0
|
||||
},
|
||||
@ -9667,13 +9667,18 @@
|
||||
"requiresCommandId": false,
|
||||
"externalDependencies": [],
|
||||
"targetStage": "P1R-7 End-to-End Acceptance",
|
||||
"implementationStatus": "sse_placeholder",
|
||||
"completionStatus": "incomplete",
|
||||
"implementationStatus": "dedicated",
|
||||
"completionStatus": "needs_verification",
|
||||
"controllerFiles": [
|
||||
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppMuseEventsController.java"
|
||||
"muse-cloud/muse-module-events/muse-module-events-server/src/main/java/cn/iocoder/muse/module/events/controller/app/AppMuseEventsController.java"
|
||||
],
|
||||
"serviceFiles": [],
|
||||
"notes": "SSE endpoint returns placeholder events and needs P1R real task stream implementation."
|
||||
"serviceFiles": [
|
||||
"muse-cloud/muse-module-events/muse-module-events-server/src/main/java/cn/iocoder/muse/module/events/application/publish/EventsPublishService.java",
|
||||
"muse-cloud/muse-module-events/muse-module-events-server/src/main/java/cn/iocoder/muse/module/events/application/publish/EventsPublishServiceImpl.java",
|
||||
"muse-cloud/muse-module-events/muse-module-events-server/src/main/java/cn/iocoder/muse/module/events/application/stream/EventsStreamService.java",
|
||||
"muse-cloud/muse-module-events/muse-module-events-server/src/main/java/cn/iocoder/muse/module/events/application/stream/EventsStreamServiceImpl.java"
|
||||
],
|
||||
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
|
||||
},
|
||||
{
|
||||
"domain": "knowledge",
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# P1R API Coverage Report
|
||||
|
||||
- Generated at: `2026-05-25T13:33:07+00:00`
|
||||
- Generated at: `2026-05-25T13:45:55+00:00`
|
||||
- Total operations: `233`
|
||||
- Completed: `100`
|
||||
- Needs verification: `132`
|
||||
- Incomplete: `1`
|
||||
- Needs verification: `133`
|
||||
- Incomplete: `0`
|
||||
- Catch-all: `0`
|
||||
- Generic persistence: `0`
|
||||
- SSE placeholder: `1`
|
||||
- SSE placeholder: `0`
|
||||
- Missing: `0`
|
||||
|
||||
## Operations
|
||||
@ -139,7 +139,7 @@
|
||||
| content | app | POST | `/app-api/muse/works/{workId}/planning/style-checks` | `createStyleCheck` | dedicated | needs_verification | P1R-1 Content Real API |
|
||||
| content | app | GET | `/app-api/muse/works/{workId}/planning/style-checks/{jobId}` | `getStyleCheckResult` | dedicated | needs_verification | P1R-1 Content Real API |
|
||||
| content | app | PUT | `/app-api/muse/works/{workId}/planning/{sectionKey}` | `savePlanningItem` | dedicated | needs_verification | P1R-1 Content Real API |
|
||||
| events | app | GET | `/app-api/muse/events` | `streamEvents` | sse_placeholder | incomplete | P1R-7 End-to-End Acceptance |
|
||||
| events | app | GET | `/app-api/muse/events` | `streamEvents` | dedicated | needs_verification | P1R-7 End-to-End Acceptance |
|
||||
| knowledge | admin | GET | `/admin-api/muse/knowledge/drafts` | `listKnowledgeDraftsGovernance` | dedicated | completed | P1R-5 Knowledge Real API |
|
||||
| knowledge | admin | GET | `/admin-api/muse/knowledge/global-kbs` | `listGlobalKnowledgeBases` | dedicated | completed | P1R-5 Knowledge Real API |
|
||||
| knowledge | admin | POST | `/admin-api/muse/knowledge/global-kbs` | `createGlobalKnowledgeBase` | dedicated | completed | P1R-5 Knowledge Real API |
|
||||
@ -250,4 +250,3 @@
|
||||
|
||||
## Incomplete Or Blocked
|
||||
|
||||
- `streamEvents` `GET /app-api/muse/events`: sse_placeholder - SSE endpoint returns placeholder events and needs P1R real task stream implementation.
|
||||
|
||||
@ -0,0 +1,156 @@
|
||||
package cn.iocoder.muse.server.framework.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
/**
|
||||
* P1R-7a Task 8 Events 真实 API 覆盖门禁。
|
||||
*
|
||||
* <p>该测试只证明 Events streamEvents 已退出 SSE placeholder,
|
||||
* 并进入 dedicated / needs_verification;它不代表 P1R-7a 已获准标记为 completed。</p>
|
||||
*/
|
||||
class P1rEventsRealApiGateTest {
|
||||
|
||||
/** P1R-7a 当前只允许 Events 域拥有 streamEvents 一个 operation。 */
|
||||
private static final int EXPECTED_EVENTS_OPERATION_COUNT = 1;
|
||||
|
||||
/** P1R-6 Market 合同固定 operation 数量,P1R-7a 不能顺手改变 Market 覆盖状态。 */
|
||||
private static final int EXPECTED_MARKET_OPERATION_COUNT = 32;
|
||||
|
||||
/** Events 真实入口门禁必须拒绝的占位或缺失状态。 */
|
||||
private static final Set<String> NON_REAL_IMPLEMENTATION_STATUSES = Set.of(
|
||||
"sse_placeholder",
|
||||
"generic_persistence",
|
||||
"catch_all",
|
||||
"missing"
|
||||
);
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void should_keep_events_inventory_limited_to_stream_events() throws IOException {
|
||||
JsonNode eventsOperations = readEventsOperations();
|
||||
|
||||
assertEquals(EXPECTED_EVENTS_OPERATION_COUNT, eventsOperations.size(),
|
||||
"P1R-7a Events 域只能包含 streamEvents 一个 operation");
|
||||
|
||||
JsonNode streamEvents = eventsOperations.get(0);
|
||||
assertEquals("streamEvents", streamEvents.path("operationId").asText(),
|
||||
"P1R-7a Events 唯一 operation 必须是 streamEvents");
|
||||
assertEquals("GET", streamEvents.path("method").asText(),
|
||||
"streamEvents 必须保持 GET 方法");
|
||||
assertEquals("/app-api/muse/events", streamEvents.path("path").asText(),
|
||||
"streamEvents 必须保持既有合同路径");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_promote_stream_events_to_dedicated_needs_verification_only() throws IOException {
|
||||
JsonNode streamEvents = readSingleEventsOperation();
|
||||
|
||||
assertEquals("dedicated", streamEvents.path("implementationStatus").asText(),
|
||||
"streamEvents 必须进入 dedicated,不能继续使用 SSE placeholder");
|
||||
assertEquals("needs_verification", streamEvents.path("completionStatus").asText(),
|
||||
"streamEvents 在 P1R-7a 只能保持 needs_verification,不能自动提升 completed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_allow_events_to_keep_non_real_entrypoints() throws IOException {
|
||||
for (JsonNode operation : readEventsOperations()) {
|
||||
String operationId = operation.path("operationId").asText("<missing-operationId>");
|
||||
String implementationStatus = operation.path("implementationStatus").asText();
|
||||
|
||||
assertFalse(NON_REAL_IMPLEMENTATION_STATUSES.contains(implementationStatus),
|
||||
operationId + " 不能继续使用 Events 非真实入口: " + implementationStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_p1r7a_summary_at_needs_verification_boundary() throws IOException {
|
||||
JsonNode summary = readReport().path("summary");
|
||||
|
||||
assertEquals(100, summary.path("completedOperations").asInt(),
|
||||
"completedOperations 必须仍只来自已批准域,P1R-7a 不能自动提升 Events completed");
|
||||
assertEquals(133, summary.path("needsVerificationOperations").asInt(),
|
||||
"needsVerificationOperations 必须包含 Events dedicated 后的待验收 operation");
|
||||
assertEquals(0, summary.path("incompleteOperations").asInt(),
|
||||
"Events 退出 placeholder 后不应再留下 incomplete operation");
|
||||
assertEquals(0, summary.path("genericPersistenceOperations").asInt(),
|
||||
"P1R-7a 后不能引入 generic_persistence operation");
|
||||
assertEquals(0, summary.path("ssePlaceholderOperations").asInt(),
|
||||
"P1R-7a 后 summary.ssePlaceholderOperations 必须为 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_all_market_operations_dedicated_and_needs_verification() throws IOException {
|
||||
JsonNode marketOperations = readMarketOperations();
|
||||
|
||||
assertEquals(EXPECTED_MARKET_OPERATION_COUNT, marketOperations.size(),
|
||||
"P1R-7a 不能改变 Market 32 个 operation 的覆盖清单");
|
||||
|
||||
for (JsonNode operation : marketOperations) {
|
||||
String operationId = operation.path("operationId").asText("<missing-operationId>");
|
||||
assertEquals("dedicated", operation.path("implementationStatus").asText(),
|
||||
"Market/" + operationId + " 必须继续保持 dedicated");
|
||||
assertEquals("needs_verification", operation.path("completionStatus").asText(),
|
||||
"Market/" + operationId + " 必须继续保持 needs_verification,不能提前 completed");
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode readSingleEventsOperation() throws IOException {
|
||||
JsonNode eventsOperations = readEventsOperations();
|
||||
assertEquals(EXPECTED_EVENTS_OPERATION_COUNT, eventsOperations.size(),
|
||||
"P1R-7a Events 域只能包含 streamEvents 一个 operation");
|
||||
return eventsOperations.get(0);
|
||||
}
|
||||
|
||||
private JsonNode readEventsOperations() throws IOException {
|
||||
return readOperationsByDomain("events");
|
||||
}
|
||||
|
||||
private JsonNode readMarketOperations() throws IOException {
|
||||
return readOperationsByDomain("market");
|
||||
}
|
||||
|
||||
private JsonNode readOperationsByDomain(String domain) throws IOException {
|
||||
ArrayNode matchingOperations = objectMapper.createArrayNode();
|
||||
for (JsonNode operation : readOperations()) {
|
||||
if (domain.equals(operation.path("domain").asText())) {
|
||||
matchingOperations.add(operation);
|
||||
}
|
||||
}
|
||||
return matchingOperations;
|
||||
}
|
||||
|
||||
private JsonNode readOperations() throws IOException {
|
||||
return readReport().path("operations");
|
||||
}
|
||||
|
||||
private JsonNode readReport() throws IOException {
|
||||
return objectMapper.readTree(findReportPath().toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前 Maven 执行目录逐级向上查找仓库根目录,避免 surefire 在不同模块目录执行时路径失效。
|
||||
*/
|
||||
private static Path findReportPath() {
|
||||
Path current = Path.of("").toAbsolutePath();
|
||||
for (Path candidate = current; candidate != null; candidate = candidate.getParent()) {
|
||||
Path reportPath = candidate.resolve("docs/superpowers/reports/p1r-api-coverage.json");
|
||||
if (Files.exists(reportPath)) {
|
||||
return reportPath;
|
||||
}
|
||||
}
|
||||
return current.resolve("docs/superpowers/reports/p1r-api-coverage.json");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user