Skip to content

fix: pass aggregatedCounts only when all component queries succeed - #96

Open
marekdano wants to merge 2 commits into
mainfrom
6507-virtual-server-test-handshake-count
Open

fix: pass aggregatedCounts only when all component queries succeed#96
marekdano wants to merge 2 commits into
mainfrom
6507-virtual-server-test-handshake-count

Conversation

@marekdano

Copy link
Copy Markdown
Contributor

Closes #6507

Summary

The virtual server drawer's Test Connection tab cross-checks handshake component counts against a locally-built aggregate, and shows a mismatch banner when they disagree. Two paths let that aggregate be built from data that isn't comparable to what the handshake sees, producing false-positive banners:

  1. A single failed query silently contributed 0. The aggregate was summed over whichever of the three /servers/{id}/{tools, resources, prompts} that isn't comparable to what the handshake sees, producing false-positive banners:

  2. A single failed query silently contributed 0. The aggregate was summed over whichever of the three /servers/{id}/{tools, resources, prompts} queries returned data. If one query failed (permission denial, backend error) while the others succeeded, it contributed 0 to the aggregate instead of being excluded, firing a spurious mismatch.

  3. The fallback population couldn't be filtered. When all three queries failed or returned empty, allComponents fell back to buildComponentItems(server), built from the associatedTools / associatedResources / associatedPrompts name lists. Those items carry no enabled field, so the enabled === false filter could never fire and disabled components got double-counted against a handshake that only ever sees enabled ones.

Fix

  • Destructure error from each of the three useQuery calls (tools/resources/prompts).
  • aggregatedComponentCounts now returns undefined when any of the three queries errored, instead of silently treating a failed query as 0. HandshakeTestPanel already handles an absent aggregate: hasComparison goes false and the comparison column/banner drop out.
  • The aggregate is now derived from fetchedComponents only, never the buildComponentItems fallback, so the enabled === false filter always has a field to check.

Testing

  • Added test: one failing query (e.g. resources 500) with tools succeeding no longer trips the mismatch banner.
  • Added test: all three queries failing, with a server that has disabled components in its fallback data, no longer trips the banner.
  • Existing coverage for the successful-mismatch and disabled-component exclusion cases still passes.

Signed-off-by: Marek Dano <mk.dano@gmail.com>

@a-effort a-effort left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! LLM-assisted feedback from testing/analysis:

Moving the aggregate onto fetchedComponents looks right, since the fallback items carry no enabled field and were never comparable to a handshake that only sees enabled components. Couple of things worth sorting before merge.

The one-query-fails test passes without the fix. With VirtualServerDetailsPanel.tsx reverted to the parent commit, suppresses the mismatch banner when one component query fails still passes. The test's handshake returns componentCounts: { tools: 1 }, and in getCountMismatchKeys the missing resources key resolves through componentCounts?.[key] ?? 0 to 0, matching the aggregate's 0. Giving the handshake a count for the failed type makes it fail before and pass after:

-          componentCounts: { tools: 1 },
+          componentCounts: { tools: 1, resources: 2 },

The all-queries-fail test does catch the regression as written.

The banner also still shows while a query is in flight. The aggregate is now built only from fetchedComponents, so before those resolve it is { tools: 0, resources: 0, prompts: 0 }, and that gets compared. The buildComponentItems fallback used to fill that window from the server prop. Repro: tools query delayed, resources and prompts empty, handshake returning { tools: 1 }, server with one associated tool. No banner before, banner after. Worst in the case the PR targets, since a backend that hangs rather than 500s holds isLoading for the full 30s useQuery timeout while the handshake returns much sooner. Nothing else covers the window; componentsLoading is only read by the Components tab.

A not-yet-loaded guard next to the error guard clears it, with toolsData, resourcesData, promptsData added to the dep array:

   const aggregatedComponentCounts = useMemo(() => {
     if (toolsError || resourcesError || promptsError) return undefined;
+    if (!toolsData || !resourcesData || !promptsData) return undefined;
     const counts: Record<string, number> = { tools: 0, resources: 0, prompts: 0 };

Keep both guards, since useQuery never clears data and a query failing after an earlier success still has data set. With that, VirtualServerDetailsPanel.test.tsx and HandshakeTestPanel.test.tsx both pass, including the mismatch cases.

Signed-off-by: Marek Dano <mk.dano@gmail.com>
@marekdano
marekdano requested a review from a-effort September 3, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Virtual server handshake count cross-check can compare against an incomplete aggregate

2 participants