test(market): U-retrieve 检索打通(D0-fork 第三单元、RetrievalApiImpl 零改动)
D0-fork 第三单元:验证 installed_ref 物化后检索链自然打通、消除 no_dataset。RetrievalApiImpl 生产代码 0 改动(临时-04 判断属实):U-materialize 已在数据层闭合去污染链(binding.kb_id=本地 kbId、ragflow_binding 指公开副本)→第四门 selectActiveDatasetByKbId 返非空→no_dataset 分支不进→进 RAGFlow 检索;授权快照 knowledge/api 全程 String 透传无 parseLong(ADR-020、无 NumberFormatException);document_ids/metadata_filter 保持 null(命中纯公开副本、无需运行时隔离)。 加 2 单测(MuseKnowledgeRetrievalApiImplTest 15/15):no_dataset 消除+命中(对照基线无 dataset binding 时 no_dataset)、字符串授权快照透传。发布者私有不泄露(真 RAGFlow)+真 PG 端到端留 U-verify。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
30a693e1b7
commit
d4309ab0e9
@ -85,6 +85,15 @@ class MuseKnowledgeRetrievalApiImplTest extends BaseMockitoUnitTest {
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* installed_ref binding 范式(D0-fork / U-materialize 写出形态):kb_id 已去污染为【本地 installed_ref kbId】、
|
||||
* authorizationSnapshotId 承载【字符串 envelope】(ADR-020 V14 VARCHAR),bindingScope 含 search 检索用途。
|
||||
* 对应 U-materialize 的 binding.setKbId(本地kbId)/binding.setAuthorizationSnapshotId(字符串)。
|
||||
*/
|
||||
private MuseKnowledgeBindingDO installedRefBinding(Long localKbId, String authSnapshotEnvelope) {
|
||||
return binding(localKbId, "search,generate", authSnapshotEnvelope);
|
||||
}
|
||||
|
||||
private RuntimeResult retrievalSuccess(String responseJson) {
|
||||
Map<String, Object> summary = new LinkedHashMap<>();
|
||||
summary.put("responseBody", JsonUtils.parseTree(responseJson));
|
||||
@ -286,4 +295,95 @@ class MuseKnowledgeRetrievalApiImplTest extends BaseMockitoUnitTest {
|
||||
assertNull(sent.ragflowDocumentIds(), "document_ids 为 null → RAGFlow 对该 dataset 整库返回,无文档级隔离");
|
||||
assertNull(sent.metadataFilter(), "metadata_filter 为 null → 无元数据级 chunk 过滤");
|
||||
}
|
||||
|
||||
// ========================================================================================
|
||||
// U-retrieve(临时-04 §U-retrieve :270-290 / D0-fork 第三单元):检索打通。
|
||||
// 验证 RetrievalApiImpl 主路径【无需改动】——靠 U-materialize 把 binding.kb_id 去污染为本地 installed_ref
|
||||
// kbId、并建好指向公开副本 dataset 的 ragflow_binding 后,第四门 selectActiveDatasetByKbId(本地kbId) 自然
|
||||
// 返非空 → no_dataset 静默省略消除 → 进 RAGFlow 检索公开副本。授权快照沿用 ADR-020 VARCHAR 字符串 envelope。
|
||||
// 这是数据形态测试(mock 出 U-materialize 的写出形态),不改任何检索/绑定主路径代码。
|
||||
// ========================================================================================
|
||||
|
||||
/**
|
||||
* U-retrieve 核心:no_dataset 消除 + 命中。
|
||||
* <p>输入:U-materialize 物化后的 installed_ref——binding.kb_id=本地 kbId(K2=9002,已去污染、非 assetId),
|
||||
* 该本地 kbId 的 active dataset binding 指向公开副本 D_pub;work 绑定含 search 用途 + 授权快照。
|
||||
* <p>预期:结果<b>非</b> {@code empty("no_dataset")},进入 RAGFlow 检索(第四门拿到副本 dataset),
|
||||
* {@code RetrievalResult.ok(chunks)},chunk 带齐 §5.3 合同字段。
|
||||
* <p>对照基线:{@link #should_returnNoDataset_whenAuthorizedButNoActiveDataset()} 钉死了"无 dataset binding
|
||||
* 时必返 no_dataset"——本 case 证明 U-materialize 建好指向副本的 dataset binding 后,同一检索链 no_dataset 消失。
|
||||
*/
|
||||
@Test
|
||||
void uRetrieve_should_hitPublicForkDataset_eliminatingNoDataset_whenInstalledRefMaterialized() {
|
||||
Long localInstalledRefKbId = 9002L; // U-materialize 新建的本地 installed_ref kbId(K2),非市场 assetId
|
||||
String publicForkDatasetId = "ds-public-fork-Dpub"; // U-materialize 指向的公开副本 dataset
|
||||
// installed_ref binding:kb_id=本地 kbId(去污染后),不再是 assetId
|
||||
when(bindingMapper.selectActiveByWorkId(4001L))
|
||||
.thenReturn(List.of(installedRefBinding(localInstalledRefKbId, "auth-envelope-installed")));
|
||||
when(projectionMapper.selectActiveByWorkId(4001L))
|
||||
.thenReturn(List.of(projection(localInstalledRefKbId, "active")));
|
||||
// 第四门:本地 kbId → 命中指向公开副本的 active dataset binding(U-materialize ensureForkDatasetBinding 写出)
|
||||
when(ragflowBindingMapper.selectActiveDatasetByKbId(localInstalledRefKbId))
|
||||
.thenReturn(dataset(publicForkDatasetId));
|
||||
// 公开副本整库返回的本就只有公开内容(发布者私有物理不在副本内,无需运行时隔离)
|
||||
String json = "{\"code\":0,\"data\":{\"chunks\":[{"
|
||||
+ "\"content\":\"公开副本里的写作技巧\",\"document_id\":\"doc-pub-1\",\"id\":\"c-1\","
|
||||
+ "\"kb_id\":\"" + publicForkDatasetId + "\",\"similarity\":0.9}],\"total\":1}}";
|
||||
when(ragFlowClient.retrieveChunks(any(RetrieveChunksCommand.class))).thenReturn(retrievalSuccess(json));
|
||||
|
||||
RetrievalResult result = retrievalApi.retrieveForWork(req());
|
||||
|
||||
// no_dataset 消除:命中后 omittedReason 为 null(对照基线 should_returnNoDataset 的 omittedReason="no_dataset")
|
||||
assertNull(result.omittedReason(), "no_dataset 应已消除(U-materialize 建好指向副本的 dataset binding,第四门命中)");
|
||||
// 进 RAGFlow 检索且命中 → ok
|
||||
assertTrue(result.hasChunks(), "应进入 RAGFlow 检索并命中公开副本");
|
||||
assertEquals("ok", result.status());
|
||||
assertEquals(1, result.chunks().size());
|
||||
// 检索发往 RAGFlow 的确是公开副本 dataset,且授权来源归因到本地 installed_ref kbId
|
||||
ArgumentCaptor<RetrieveChunksCommand> captor = ArgumentCaptor.forClass(RetrieveChunksCommand.class);
|
||||
verify(ragFlowClient).retrieveChunks(captor.capture());
|
||||
assertEquals(List.of(publicForkDatasetId), captor.getValue().ragflowDatasetIds(),
|
||||
"检索命中的是公开副本 dataset(纯公开内容,无需运行时隔离)");
|
||||
MuseKnowledgeRetrievalApi.RetrievedChunk chunk = result.chunks().get(0);
|
||||
assertEquals(localInstalledRefKbId, chunk.sourceKbId(), "chunk 归因到本地 installed_ref kbId(去污染后)");
|
||||
assertEquals(publicForkDatasetId, chunk.datasetId());
|
||||
assertEquals("公开副本里的写作技巧", chunk.contentSummary());
|
||||
}
|
||||
|
||||
/**
|
||||
* U-retrieve:字符串授权快照(ADR-020 V14 VARCHAR envelope)。
|
||||
* <p>输入:installed_ref binding 的 authorization_snapshot_id 为<b>字符串 envelope 形态</b>
|
||||
* (非纯数字,如 {@code "snap:installed-ref:v1:abc-123"})。
|
||||
* <p>预期:§5.3 授权快照门正常放行(hasText 判定,不 parseLong);chunk 原样透传该字符串到
|
||||
* {@code authorizationSnapshotId} 合同字段;全程无 NumberFormatException、该字段不落 null。
|
||||
* <p>这钉死 ADR-020 的核心约束:检索门读 binding.getAuthorizationSnapshotId() 当字符串用,
|
||||
* 绝不回退 BIGINT/parseLong(否则字符串 envelope 会 NumberFormatException 或被丢成 null)。
|
||||
*/
|
||||
@Test
|
||||
void uRetrieve_should_passThroughStringAuthorizationSnapshot_withoutNumberFormatException() {
|
||||
Long localInstalledRefKbId = 9003L;
|
||||
// 字符串 envelope 形态的授权快照(绝非可 parseLong 的纯数字)
|
||||
String stringEnvelopeSnapshot = "snap:installed-ref:v1:abc-123";
|
||||
when(bindingMapper.selectActiveByWorkId(4001L))
|
||||
.thenReturn(List.of(installedRefBinding(localInstalledRefKbId, stringEnvelopeSnapshot)));
|
||||
when(projectionMapper.selectActiveByWorkId(4001L))
|
||||
.thenReturn(List.of(projection(localInstalledRefKbId, "active")));
|
||||
when(ragflowBindingMapper.selectActiveDatasetByKbId(localInstalledRefKbId))
|
||||
.thenReturn(dataset("ds-public-fork-Dpub"));
|
||||
when(ragFlowClient.retrieveChunks(any(RetrieveChunksCommand.class)))
|
||||
.thenReturn(retrievalSuccess("{\"code\":0,\"data\":{\"chunks\":[{"
|
||||
+ "\"content\":\"片段\",\"document_id\":\"doc-1\",\"id\":\"c-1\","
|
||||
+ "\"kb_id\":\"ds-public-fork-Dpub\",\"similarity\":0.7}]}}"));
|
||||
|
||||
// 全程不应抛 NumberFormatException(若检索门误 parseLong 字符串 envelope 即会抛 → 被 catch 降级成 retrieval_error)
|
||||
RetrievalResult result = retrievalApi.retrieveForWork(req());
|
||||
|
||||
// 放行且命中(未因字符串 envelope 被门拒、也未异常降级)
|
||||
assertTrue(result.hasChunks(), "字符串授权快照应放行检索门并命中,不应被当作无授权/异常降级");
|
||||
assertEquals("ok", result.status());
|
||||
// chunk 原样透传字符串 envelope,不落 null、不被 parseLong 截断
|
||||
MuseKnowledgeRetrievalApi.RetrievedChunk chunk = result.chunks().get(0);
|
||||
assertEquals(stringEnvelopeSnapshot, chunk.authorizationSnapshotId(),
|
||||
"授权快照字符串 envelope 原样透传到 chunk 合同字段,未 parseLong、未落 null");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user