Skip to content

Commit e444236

Browse files
committed
New tests: client and server capability checks
1 parent ccaa3cc commit e444236

File tree

2 files changed

+423
-0
lines changed

2 files changed

+423
-0
lines changed

src/client/index.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,174 @@ test('should accept form-mode elicitation request when client advertises empty e
671671
});
672672
});
673673

674+
test('should reject form-mode elicitation when client only supports URL mode', async () => {
675+
const client = new Client(
676+
{
677+
name: 'test-client',
678+
version: '1.0.0'
679+
},
680+
{
681+
capabilities: {
682+
elicitation: {
683+
url: {}
684+
}
685+
}
686+
}
687+
);
688+
689+
const handler = jest.fn().mockResolvedValue({
690+
action: 'cancel'
691+
});
692+
client.setRequestHandler(ElicitRequestSchema, handler);
693+
694+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
695+
696+
let resolveResponse: ((message: unknown) => void) | undefined;
697+
const responsePromise = new Promise<unknown>(resolve => {
698+
resolveResponse = resolve;
699+
});
700+
701+
serverTransport.onmessage = async message => {
702+
if ('method' in message) {
703+
if (message.method === 'initialize') {
704+
if (!('id' in message) || message.id === undefined) {
705+
throw new Error('Expected initialize request to include an id');
706+
}
707+
const messageId = message.id;
708+
await serverTransport.send({
709+
jsonrpc: '2.0',
710+
id: messageId,
711+
result: {
712+
protocolVersion: LATEST_PROTOCOL_VERSION,
713+
capabilities: {},
714+
serverInfo: {
715+
name: 'test-server',
716+
version: '1.0.0'
717+
}
718+
}
719+
});
720+
} else if (message.method === 'notifications/initialized') {
721+
// ignore
722+
}
723+
} else {
724+
resolveResponse?.(message);
725+
}
726+
};
727+
728+
await client.connect(clientTransport);
729+
730+
// Server shouldn't send this, because the client capabilities
731+
// only advertised URL mode. Test that it's rejected by the client:
732+
const requestId = 1;
733+
await serverTransport.send({
734+
jsonrpc: '2.0',
735+
id: requestId,
736+
method: 'elicitation/create',
737+
params: {
738+
mode: 'form',
739+
message: 'Provide your username',
740+
requestedSchema: {
741+
type: 'object',
742+
properties: {
743+
username: {
744+
type: 'string'
745+
}
746+
}
747+
}
748+
}
749+
});
750+
751+
const response = (await responsePromise) as { id: number; error: { code: number; message: string } };
752+
753+
expect(response.id).toBe(requestId);
754+
expect(response.error.code).toBe(ErrorCode.InvalidParams);
755+
expect(response.error.message).toContain('Client does not support form-mode elicitation requests');
756+
expect(handler).not.toHaveBeenCalled();
757+
758+
await client.close();
759+
});
760+
761+
test('should reject URL-mode elicitation when client only supports form mode', async () => {
762+
const client = new Client(
763+
{
764+
name: 'test-client',
765+
version: '1.0.0'
766+
},
767+
{
768+
capabilities: {
769+
elicitation: {
770+
form: {}
771+
}
772+
}
773+
}
774+
);
775+
776+
const handler = jest.fn().mockResolvedValue({
777+
action: 'cancel'
778+
});
779+
client.setRequestHandler(ElicitRequestSchema, handler);
780+
781+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
782+
783+
let resolveResponse: ((message: unknown) => void) | undefined;
784+
const responsePromise = new Promise<unknown>(resolve => {
785+
resolveResponse = resolve;
786+
});
787+
788+
serverTransport.onmessage = async message => {
789+
if ('method' in message) {
790+
if (message.method === 'initialize') {
791+
if (!('id' in message) || message.id === undefined) {
792+
throw new Error('Expected initialize request to include an id');
793+
}
794+
const messageId = message.id;
795+
await serverTransport.send({
796+
jsonrpc: '2.0',
797+
id: messageId,
798+
result: {
799+
protocolVersion: LATEST_PROTOCOL_VERSION,
800+
capabilities: {},
801+
serverInfo: {
802+
name: 'test-server',
803+
version: '1.0.0'
804+
}
805+
}
806+
});
807+
} else if (message.method === 'notifications/initialized') {
808+
// ignore
809+
}
810+
} else {
811+
resolveResponse?.(message);
812+
}
813+
};
814+
815+
await client.connect(clientTransport);
816+
817+
// Server shouldn't send this, because the client capabilities
818+
// only advertised form mode. Test that it's rejected by the client:
819+
const requestId = 2;
820+
await serverTransport.send({
821+
jsonrpc: '2.0',
822+
id: requestId,
823+
method: 'elicitation/create',
824+
params: {
825+
mode: 'url',
826+
message: 'Open the authorization page',
827+
elicitationId: 'elicitation-123',
828+
url: 'https://example.com/authorize'
829+
}
830+
});
831+
832+
const response = (await responsePromise) as { id: number; error: { code: number; message: string } };
833+
834+
expect(response.id).toBe(requestId);
835+
expect(response.error.code).toBe(ErrorCode.InvalidParams);
836+
expect(response.error.message).toContain('Client does not support URL-mode elicitation requests');
837+
expect(handler).not.toHaveBeenCalled();
838+
839+
await client.close();
840+
});
841+
674842
/***
675843
* Test: Type Checking
676844
* Test that custom request/notification/result schemas can be used with the Client class.

0 commit comments

Comments
 (0)