blob: d51c8ef96606f0e1d56f164a88af10fd273580d4 [file]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:dart_mcp/client.dart';
import 'package:dart_mcp/src/utils/constants.dart';
import 'package:test/test.dart';
void main() {
test('the method names match the schema', () {
expect(SubscriptionsListenRequest.methodName, 'subscriptions/listen');
expect(
SubscriptionsAcknowledgedNotification.methodName,
'notifications/subscriptions/acknowledged',
);
});
group('SubscriptionFilter', () {
test('writes only the fields it is given', () {
expect(SubscriptionFilter() as Map<String, Object?>, isEmpty);
expect(
SubscriptionFilter(toolsListChanged: true) as Map<String, Object?>,
{'toolsListChanged': true},
);
expect(
SubscriptionFilter(
promptsListChanged: true,
resourcesListChanged: true,
resourceSubscriptions: ['file:///a'],
)
as Map<String, Object?>,
{
'promptsListChanged': true,
'resourcesListChanged': true,
'resourceSubscriptions': ['file:///a'],
},
);
expect(
SubscriptionFilter(resourceSubscriptions: []) as Map<String, Object?>,
{'resourceSubscriptions': <String>[]},
);
});
test('keeps an explicit false apart from an absent field', () {
expect(
SubscriptionFilter(toolsListChanged: false) as Map<String, Object?>,
{'toolsListChanged': false},
);
expect(
SubscriptionFilter(toolsListChanged: false).toolsListChanged,
false,
);
expect(SubscriptionFilter().toolsListChanged, null);
});
test('reads the fields off a decoded map', () {
final filter = SubscriptionFilter.fromMap({
'toolsListChanged': true,
'promptsListChanged': false,
'resourcesListChanged': true,
'resourceSubscriptions': <Object?>['file:///a', 'file:///b'],
});
expect(filter.toolsListChanged, true);
expect(filter.promptsListChanged, false);
expect(filter.resourcesListChanged, true);
expect(filter.resourceSubscriptions, ['file:///a', 'file:///b']);
final absent = SubscriptionFilter.fromMap({});
expect(absent.toolsListChanged, null);
expect(absent.promptsListChanged, null);
expect(absent.resourcesListChanged, null);
expect(absent.resourceSubscriptions, null);
});
});
group('SubscriptionsListenRequest', () {
test('writes the filter it is given', () {
final request = SubscriptionsListenRequest(
notifications: SubscriptionFilter(toolsListChanged: true),
);
expect(request as Map<String, Object?>, {
'notifications': {'toolsListChanged': true},
});
expect(request.notifications.toolsListChanged, true);
});
test('throws when the filter is missing', () {
expect(
() => SubscriptionsListenRequest.fromMap({}).notifications,
throwsArgumentError,
);
});
});
group('SubscriptionsListenResult', () {
test('writes the subscription id into the result metadata', () {
final result = SubscriptionsListenResult(
meta: MetaWithSubscriptionId(subscriptionId: RequestId(7)),
);
expect(result as Map<String, Object?>, {
'_meta': {'io.modelcontextprotocol/subscriptionId': 7},
});
expect(result.subscriptionId, 7);
});
test('stores the metadata object it is given', () {
final meta = MetaWithSubscriptionId.fromMap({
'com.example/trace': 'abc',
'io.modelcontextprotocol/subscriptionId': RequestId('stream-1'),
});
final result = SubscriptionsListenResult(meta: meta);
expect(result.meta, same(meta));
expect(result as Map<String, Object?>, {
'_meta': {
'com.example/trace': 'abc',
'io.modelcontextprotocol/subscriptionId': 'stream-1',
},
});
expect(result.subscriptionId, 'stream-1');
});
test('throws when the subscription id is missing', () {
expect(
() => SubscriptionsListenResult.fromMap({}).subscriptionId,
throwsArgumentError,
);
expect(
() =>
SubscriptionsListenResult.fromMap({
'_meta': <String, Object?>{},
}).subscriptionId,
throwsArgumentError,
);
});
});
group('SubscriptionsAcknowledgedNotification', () {
test('writes the filter and the subscription id it is given', () {
final acknowledged = SubscriptionsAcknowledgedNotification(
notifications: SubscriptionFilter(toolsListChanged: true),
meta: MetaWithSubscriptionId(subscriptionId: RequestId(7)),
);
expect(acknowledged as Map<String, Object?>, {
'notifications': {'toolsListChanged': true},
'_meta': {'io.modelcontextprotocol/subscriptionId': 7},
});
expect(acknowledged.notifications.toolsListChanged, true);
expect(acknowledged.subscriptionId, 7);
});
test('stores the metadata object it is given', () {
final meta = MetaWithSubscriptionId.fromMap({
'com.example/trace': 'abc',
'io.modelcontextprotocol/subscriptionId': RequestId('stream-1'),
});
final acknowledged = SubscriptionsAcknowledgedNotification(
notifications: SubscriptionFilter(),
meta: meta,
);
expect(acknowledged.meta, same(meta));
expect((acknowledged as Map<String, Object?>)['_meta'], {
'com.example/trace': 'abc',
'io.modelcontextprotocol/subscriptionId': 'stream-1',
});
expect(acknowledged.subscriptionId, 'stream-1');
});
test('throws when the filter is missing', () {
expect(
() => SubscriptionsAcknowledgedNotification.fromMap({}).notifications,
throwsArgumentError,
);
});
test('throws when the subscription id is missing', () {
expect(
() => SubscriptionsAcknowledgedNotification.fromMap({}).subscriptionId,
throwsArgumentError,
);
expect(
() =>
SubscriptionsAcknowledgedNotification.fromMap({
'_meta': <String, Object?>{},
}).subscriptionId,
throwsArgumentError,
);
});
});
group('MetaWithSubscriptionId', () {
test("reads the id beside a caller's own key", () {
final meta = MetaWithSubscriptionId.fromMap({
'com.example/trace': 'abc',
'io.modelcontextprotocol/subscriptionId': 's-1',
});
expect(meta.subscriptionId, RequestId('s-1'));
expect(meta['com.example/trace'], 'abc');
});
test('hands the metadata back typed on both messages', () {
final meta = MetaWithSubscriptionId(subscriptionId: RequestId(7));
expect(SubscriptionsListenResult(meta: meta).meta.subscriptionId, 7);
expect(
SubscriptionsAcknowledgedNotification(
notifications: SubscriptionFilter(),
meta: meta,
).meta.subscriptionId,
7,
);
});
test('throws when the message carries no metadata', () {
final namesTheField = isA<ArgumentError>().having(
(error) => error.message,
'message',
contains(Keys.meta),
);
expect(
() => SubscriptionsListenResult.fromMap({}).meta,
throwsA(namesTheField),
);
expect(
() =>
SubscriptionsAcknowledgedNotification.fromMap({
'notifications': SubscriptionFilter(),
}).meta,
throwsA(namesTheField),
);
});
test('throws when the id is missing', () {
final meta = MetaWithSubscriptionId.fromMap({});
expect(() => meta.subscriptionId, throwsArgumentError);
});
});
}