From c6ebe307cd843cefda01448fc3417389cfa62ccd Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 12 Aug 2026 19:46:52 +0800 Subject: [PATCH] fix(doc): verify inline media from jsonml readback --- internal/helpers/doc.go | 23 ++++++++++++++++++--- internal/helpers/doc_command_edges_test.go | 24 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index d1c9ce4c..9a86e5e5 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -758,7 +758,7 @@ func readAllDocBlocksForVerification(ctx context.Context, nodeID string) ([]any, seen := map[string]bool{} for start := 0; start < maxItems; start += pageSize { text, err := callMCPToolReturnTextOnServer(ctx, "doc", "list_document_blocks", map[string]any{ - "nodeId": nodeID, "format": "element", "startIndex": start, "endIndex": start + pageSize - 1, + "nodeId": nodeID, "format": "jsonml", "startIndex": start, "endIndex": start + pageSize - 1, }) if err != nil { return nil, err @@ -834,16 +834,33 @@ func findVerifiedMediaBlock(blocks []any, blockID, resourceID, resourceURL strin if candidateID == "" || (blockID != "" && candidateID != blockID) { continue } - if resourceID != "" && nestedDocString(value, "resourceId") == resourceID { + mediaValue := docMediaReadbackValue(value) + if resourceID != "" && nestedDocString(mediaValue, "resourceId") == resourceID { return candidateID } - if resourceID == "" && resourceURL != "" && nestedDocString(value, "resourceUrl") == resourceURL { + if resourceURL != "" && nestedDocString(mediaValue, "resourceUrl", "src") == resourceURL { return candidateID } } return "" } +func docMediaReadbackValue(value any) any { + block, ok := value.(map[string]any) + if !ok { + return value + } + encoded, ok := block["jsonml"].(string) + if !ok || strings.TrimSpace(encoded) == "" { + return value + } + var decoded any + if json.Unmarshal([]byte(encoded), &decoded) != nil { + return value + } + return decoded +} + func docNumberAsInt(value any) (int, bool) { switch typed := value.(type) { case float64: diff --git a/internal/helpers/doc_command_edges_test.go b/internal/helpers/doc_command_edges_test.go index 17ab60ca..e4c21f25 100644 --- a/internal/helpers/doc_command_edges_test.go +++ b/internal/helpers/doc_command_edges_test.go @@ -206,6 +206,23 @@ func TestCrossPlatformCoverageDocUploadAndMediaErrorEdges(t *testing.T) { t.Fatal("invalid media insert response returned nil") } }) + t.Run("small image verifies src readback when upload also returns resource ID", func(t *testing.T) { + httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil } + caller := &scriptedToolCaller{steps: []scriptedToolStep{ + {text: `{"uploadUrl":"https://upload","resourceId":"resource","resourceUrl":"https://image"}`}, + {text: `{"blockId":"media-block"}`}, + {text: `{"blocks":[{"blockId":"media-block","jsonml":"[\"p\",{\"uuid\":\"media-block\"},[\"img\",{\"src\":\"https://image\"}]]"}],"hasMore":false}`}, + }} + if err := mediaCommand(t, caller, file, "image/png"); err != nil { + t.Fatal(err) + } + if caller.calls != 3 { + t.Fatalf("media insert calls = %d, want credential, insert, and one readback", caller.calls) + } + if caller.args["format"] != "jsonml" { + t.Fatalf("media readback format = %#v, want jsonml", caller.args["format"]) + } + }) t.Run("large image becomes attachment", func(t *testing.T) { httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil } large := filepath.Join(t.TempDir(), "large.png") @@ -337,10 +354,13 @@ func TestCrossPlatformCoverageDocMediaReadbackDefensiveEdges(t *testing.T) { if nestedDocString(map[string]any{"x": []any{map[string]any{"id": " nested "}}}, "id") != "nested" || nestedDocString(3, "id") != "" { t.Fatal("nested string traversal failed") } - blocks := []any{map[string]any{"id": "block", "resourceId": "rid"}, map[string]any{"id": "url-block", "resourceUrl": "https://media"}} - if findVerifiedMediaBlock(blocks, "block", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "", "https://media") != "url-block" || findVerifiedMediaBlock(blocks, "block", "wrong", "") != "" || findVerifiedMediaBlock(blocks, "", "missing", "") != "" { + blocks := []any{map[string]any{"id": "block", "resourceId": "rid"}, map[string]any{"id": "url-block", "resourceUrl": "https://media"}, map[string]any{"id": "src-block", "jsonml": `["p",{},["img",{"src":"https://image"}]]`}} + if findVerifiedMediaBlock(blocks, "block", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "", "https://media") != "url-block" || findVerifiedMediaBlock(blocks, "src-block", "upload-resource", "https://image") != "src-block" || findVerifiedMediaBlock(blocks, "other-block", "upload-resource", "https://image") != "" || findVerifiedMediaBlock(blocks, "block", "wrong", "") != "" || findVerifiedMediaBlock(blocks, "", "missing", "") != "" { t.Fatal("media block matching failed") } + if findVerifiedMediaBlock([]any{map[string]any{"id": "bad-jsonml", "jsonml": `[`}}, "bad-jsonml", "", "https://image") != "" { + t.Fatal("invalid JSONML unexpectedly verified") + } for _, tc := range []struct { value any want bool