Skip to content

Commit e183d26

Browse files
fix(test): record a rejected app resource read as a sweep failure in the contract matrix (#417)
1 parent bffb19c commit e183d26

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

‎packages/agent-bundle/src/test/contract.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,12 @@ const runSweep = async (
918918
if (uri === undefined) {
919919
return failed(`${descriptor.kind} route config exports no uri to read`);
920920
}
921-
const read = await client.readResource({ uri }) as { contents?: unknown };
921+
let read: { contents?: unknown };
922+
try {
923+
read = await client.readResource({ uri }) as { contents?: unknown };
924+
} catch (error) {
925+
return failed(`readResource threw for ${JSON.stringify(uri)}: ${error instanceof Error ? error.message : captured(error)}`);
926+
}
922927
const contents = Array.isArray(read.contents) ? read.contents : [];
923928
return contents.length === 0
924929
? failed(`readResource returned no contents for ${JSON.stringify(uri)}`)

‎packages/agent-bundle/tests/projection/contract-matrix.test.ts‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ const PANEL_MIME = 'text/html;profile=mcp-app';
7272
* call, and the progress-handler map the lifecycle replay composes, delegates
7373
* to the real client.
7474
*/
75-
const withAppSurface = (client: Client): Client => {
75+
const withAppSurface = (client: Client): Client => appSurface(client, 'serves');
76+
77+
const withRejectingAppSurface = (client: Client): Client => appSurface(client, 'rejects');
78+
79+
const appSurface = (client: Client, read: 'rejects' | 'serves'): Client => {
7680
const panelListing = { mimeType: PANEL_MIME, name: 'panel', uri: PANEL_URI };
7781
const wrapper = {
7882
_notificationHandlers: (client as unknown as { readonly _notificationHandlers: unknown })
@@ -88,6 +92,7 @@ const withAppSurface = (client: Client): Client => {
8892
readResource: async (...arguments_: Parameters<Client['readResource']>) => {
8993
const [params] = arguments_;
9094
if (params.uri !== PANEL_URI) return client.readResource(...arguments_);
95+
if (read === 'rejects') throw new Error('panel resource handler exploded');
9196
return { contents: [{ mimeType: PANEL_MIME, text: '<!doctype html><span>route-harness panel</span>', uri: PANEL_URI }] };
9297
},
9398
};
@@ -635,6 +640,24 @@ describe('the generated-plugin contract matrix', () => {
635640
});
636641
}, 30_000);
637642

643+
it('records a rejected auto-covered app read as a sweep failure inside the aggregated violation', async () => {
644+
const error = await withPackedShapedSession(
645+
{ decorate: withRejectingAppSurface, entry: 'in-memory rejecting app read fixture', includeApps: true },
646+
(session, manifest) => runPackedContractMatrix({
647+
fixtures: routeHarnessContractFixtures(),
648+
manifest,
649+
server: 'harness',
650+
session,
651+
}).catch((thrown: unknown) => thrown),
652+
);
653+
654+
expect(error).toBeInstanceOf(AgentTestError);
655+
expect((error as AgentTestError).code).toBe('contract-violation');
656+
expect((error as AgentTestError).message).toContain('app:harness/panel / sweep');
657+
expect((error as AgentTestError).message).toContain('readResource threw for "ui://harness/panel"');
658+
expect((error as AgentTestError).message).toContain('panel resource handler exploded');
659+
}, 30_000);
660+
638661
it('requires an app fixture entry only when apps: "explicit" is requested', async () => {
639662
const error = await withPackedShapedSession(
640663
{ decorate: withAppSurface, entry: 'in-memory explicit app coverage fixture', includeApps: true },

0 commit comments

Comments
 (0)