fix: preserve record query preview contract
This commit is contained in:
1 parent
37b9a1dc31
commit
7aa8240629
4 files changed
+59
-13
No files matched your search
@@ -334,9 +334,8 @@ func queryDeletedRecordsByIDs(rt *shortcut.RuntimeContext, baseID, tableID strin
|
||||
}
|
||||
return nil, fmt.Errorf("query_records deletion read-back chunk %d is missing records", offset/recordQueryServicePageSize+1)
|
||||
}
|
||||
if responseHasMore(data) {
|
||||
return nil, fmt.Errorf("query_records deletion read-back chunk %d is incomplete", offset/recordQueryServicePageSize+1)
|
||||
}
|
||||
// Exact-ID absence is complete evidence for deletion even when the
|
||||
// service retains an unrelated continuation marker.
|
||||
remaining = append(remaining, records...)
|
||||
}
|
||||
return remaining, nil
|
||||
|
||||
@@ -21,10 +21,12 @@ const (
|
||||
)
|
||||
|
||||
type recordQueryWindow struct {
|
||||
Records []map[string]any
|
||||
HasMore bool
|
||||
NextCursor string
|
||||
Pages int
|
||||
Records []map[string]any
|
||||
HasMore bool
|
||||
NextCursor string
|
||||
Pages int
|
||||
TotalCount any
|
||||
HasTotalCount bool
|
||||
}
|
||||
|
||||
func queryRecordWindow(rt *shortcut.RuntimeContext, params map[string]any, limit int) (recordQueryWindow, error) {
|
||||
@@ -53,6 +55,9 @@ func queryRecordWindow(rt *shortcut.RuntimeContext, params map[string]any, limit
|
||||
if err != nil {
|
||||
return recordQueryWindow{}, err
|
||||
}
|
||||
if !window.HasTotalCount {
|
||||
window.TotalCount, window.HasTotalCount = responseTotalCount(data)
|
||||
}
|
||||
records, found := findRecords(data)
|
||||
if !found {
|
||||
if explicitEmptyRecordQuery(data) {
|
||||
@@ -119,6 +124,14 @@ func explicitEmptyRecordQuery(data map[string]any) bool {
|
||||
}
|
||||
|
||||
func executeRecordQuery(rt *shortcut.RuntimeContext, params map[string]any) error {
|
||||
if rt.DryRun() {
|
||||
return rt.Output(map[string]any{
|
||||
"dry_run": true,
|
||||
"executed": false,
|
||||
"tool": "query_records",
|
||||
"arguments": params,
|
||||
})
|
||||
}
|
||||
limit := 100
|
||||
if rt.Changed("limit") {
|
||||
limit = rt.Int("limit")
|
||||
@@ -168,6 +181,9 @@ func executeRecordQuery(rt *shortcut.RuntimeContext, params map[string]any) erro
|
||||
if window.NextCursor != "" {
|
||||
data["nextCursor"] = window.NextCursor
|
||||
}
|
||||
if window.HasTotalCount {
|
||||
data["totalCount"] = window.TotalCount
|
||||
}
|
||||
return rt.Output(map[string]any{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
@@ -175,6 +191,19 @@ func executeRecordQuery(rt *shortcut.RuntimeContext, params map[string]any) erro
|
||||
})
|
||||
}
|
||||
|
||||
func responseTotalCount(data map[string]any) (any, bool) {
|
||||
for {
|
||||
if totalCount, exists := data["totalCount"]; exists {
|
||||
return totalCount, true
|
||||
}
|
||||
nested, ok := data["data"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
data = nested
|
||||
}
|
||||
}
|
||||
|
||||
func recordQueryRequestedIDs(params map[string]any) ([]string, bool, error) {
|
||||
raw, exists := params["recordIds"]
|
||||
if !exists {
|
||||
|
||||
@@ -280,7 +280,7 @@ func pagedRecordQueryResponse(t *testing.T, records []map[string]any, args map[s
|
||||
}
|
||||
end := minInt(offset+limit, len(records))
|
||||
pageRecords := records[offset:end]
|
||||
data := map[string]any{"records": pageRecords, "hasMore": end < len(records)}
|
||||
data := map[string]any{"records": pageRecords, "hasMore": end < len(records), "totalCount": len(records)}
|
||||
if end < len(records) {
|
||||
data["nextCursor"] = fmt.Sprintf("offset-%d", end)
|
||||
}
|
||||
@@ -337,7 +337,7 @@ func TestCrossPlatformCoverageRecordQueryServicePageBoundariesE2E(t *testing.T)
|
||||
t.Fatalf("record query size %d: %v", size, err)
|
||||
}
|
||||
data, ok := payload["data"].(map[string]any)
|
||||
if !ok || payload["success"] != true || data["hasMore"] != false || len(data["records"].([]any)) != size {
|
||||
if !ok || payload["success"] != true || data["hasMore"] != false || data["totalCount"] != float64(size) || len(data["records"].([]any)) != size {
|
||||
t.Fatalf("record query size %d payload = %#v", size, payload)
|
||||
}
|
||||
wantCalls := (size + recordQueryServicePageSize - 1) / recordQueryServicePageSize
|
||||
@@ -348,6 +348,22 @@ func TestCrossPlatformCoverageRecordQueryServicePageBoundariesE2E(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageRecordQueryDryRunStopsBeforeTransportE2E(t *testing.T) {
|
||||
caller := &upsertByKeyCaller{}
|
||||
payload, err := runRecordQueryShortcutCLI(t, caller, 5, "--query", "needle", "--dry-run")
|
||||
if err != nil {
|
||||
t.Fatalf("record query dry-run error = %v", err)
|
||||
}
|
||||
if len(caller.calls) != 0 {
|
||||
t.Fatalf("record query dry-run crossed transport: %#v", caller.calls)
|
||||
}
|
||||
arguments, _ := payload["arguments"].(map[string]any)
|
||||
if payload["dry_run"] != true || payload["executed"] != false || payload["tool"] != "query_records" ||
|
||||
arguments["baseId"] != "base" || arguments["tableId"] != "table" || arguments["keyword"] != "needle" || arguments["limit"] != float64(5) {
|
||||
t.Fatalf("record query dry-run payload = %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageRecordQueryExactIDsIgnoreResidualContinuationE2E(t *testing.T) {
|
||||
caller := &upsertByKeyCaller{callFn: func(call int, _, tool string, args map[string]any) (string, error) {
|
||||
if tool != "query_records" {
|
||||
@@ -538,15 +554,16 @@ func TestCrossPlatformCoverageRecordQueryFailureAndContinuationBranches(t *testi
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("delete readback rejects continuation", func(t *testing.T) {
|
||||
t.Run("delete readback ignores residual continuation", func(t *testing.T) {
|
||||
caller := &upsertByKeyCaller{callFn: func(_ int, _, tool string, _ map[string]any) (string, error) {
|
||||
if tool == "query_records" {
|
||||
return `{"success":true,"data":{"records":[],"hasMore":true,"nextCursor":"unexpected"}}`, nil
|
||||
}
|
||||
return `{"deletedCount":1}`, nil
|
||||
}}
|
||||
if _, err := runRecordDeleteCLI(t, caller, []string{"r1"}); err == nil || len(caller.calls) != 2 || caller.calls[1].tool != "query_records" {
|
||||
t.Fatalf("delete continuation error = %v calls=%#v", err, caller.calls)
|
||||
out, err := runRecordDeleteCLI(t, caller, []string{"r1"})
|
||||
if err != nil || out == "" || len(caller.calls) != 2 || caller.calls[1].tool != "query_records" {
|
||||
t.Fatalf("delete residual continuation output=%q error=%v calls=%#v", out, err, caller.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user