| // Copyright (c) 2014, 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 'dart:async'; | |
| import 'dart:convert'; | |
| import 'package:json_rpc_2/error_code.dart' as error_code; | |
| import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; | |
| import 'package:test/test.dart'; | |
| import 'utils.dart'; | |
| void main() { | |
| late ServerController controller; | |
| setUp(() => controller = ServerController()); | |
| test('calls a registered method with the given name', () { | |
| controller.server.registerMethod('foo', (json_rpc.Parameters params) { | |
| return {'params': params.value}; | |
| }); | |
| expect( | |
| controller.handleRequest({ | |
| 'jsonrpc': '2.0', | |
| 'method': 'foo', | |
| 'params': {'param': 'value'}, | |
| 'id': 1234 | |
| }), | |
| completion(equals({ | |
| 'jsonrpc': '2.0', | |
| 'result': { | |
| 'params': {'param': 'value'} | |
| }, | |
| 'id': 1234 | |
| }))); | |
| }); | |
| test('passes the ID of the request being answered', () { | |
| controller.server.registerMethod( | |
| 'foo', | |
| (json_rpc.Parameters params) => | |
| {'id': params.id, 'nested': params['param'].id}); | |
| expect( | |
| controller.handleRequest({ | |
| 'jsonrpc': '2.0', | |
| 'method': 'foo', | |
| 'params': {'param': 'value'}, | |
| 'id': 1234 | |
| }), | |
| completion(equals({ | |
| 'jsonrpc': '2.0', | |
| 'result': {'id': 1234, 'nested': 1234}, | |
| 'id': 1234 | |
| }))); | |
| }); | |
| test('a null ID is not a notification', () { | |
| controller.server.registerMethod( | |
| 'foo', | |
| (json_rpc.Parameters params) => | |
| {'id': params.id, 'notification': params.isNotification}); | |
| expect( | |
| controller | |
| .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': null}), | |
| completion(equals({ | |
| 'jsonrpc': '2.0', | |
| 'result': {'id': null, 'notification': false}, | |
| 'id': null | |
| }))); | |
| }); | |
| test('a request with no ID is a notification', () async { | |
| json_rpc.Parameters? received; | |
| final handled = Completer<void>(); | |
| controller.server.registerMethod('foo', (json_rpc.Parameters params) { | |
| received = params; | |
| handled.complete(); | |
| }); | |
| unawaited(controller.handleRequest({ | |
| 'jsonrpc': '2.0', | |
| 'method': 'foo', | |
| 'params': {'param': 'value'} | |
| })); | |
| await handled.future; | |
| expect(received!.id, isNull); | |
| expect(received!.isNotification, isTrue); | |
| expect(received!['param'].isNotification, isTrue); | |
| }); | |
| test('calls a method that takes no parameters', () { | |
| controller.server.registerMethod('foo', () => 'foo'); | |
| expect( | |
| controller | |
| .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), | |
| completion(equals({'jsonrpc': '2.0', 'result': 'foo', 'id': 1234}))); | |
| }); | |
| test('Allows a `null` result', () { | |
| controller.server.registerMethod('foo', () => null); | |
| expect( | |
| controller | |
| .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), | |
| completion(equals({'jsonrpc': '2.0', 'result': null, 'id': 1234}))); | |
| }); | |
| test('a method that takes no parameters rejects parameters', () { | |
| controller.server.registerMethod('foo', () => 'foo'); | |
| expectErrorResponse( | |
| controller, | |
| { | |
| 'jsonrpc': '2.0', | |
| 'method': 'foo', | |
| 'params': <String, dynamic>{}, | |
| 'id': 1234 | |
| }, | |
| error_code.INVALID_PARAMS, | |
| 'No parameters are allowed for method "foo".'); | |
| }); | |
| test('an unexpected error in a method is captured', () { | |
| controller.server | |
| .registerMethod('foo', () => throw const FormatException('bad format')); | |
| expect( | |
| controller | |
| .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), | |
| completion({ | |
| 'jsonrpc': '2.0', | |
| 'id': 1234, | |
| 'error': { | |
| 'code': error_code.SERVER_ERROR, | |
| 'message': 'bad format', | |
| 'data': { | |
| 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, | |
| 'full': 'FormatException: bad format', | |
| 'stack': isA<String>() | |
| } | |
| } | |
| })); | |
| }); | |
| test('doesn\'t return a result for a notification', () { | |
| controller.server.registerMethod('foo', (args) => 'result'); | |
| expect( | |
| controller.handleRequest( | |
| {'jsonrpc': '2.0', 'method': 'foo', 'params': <String, dynamic>{}}), | |
| doesNotComplete); | |
| }); | |
| test('includes the error data in the response', () { | |
| controller.server.registerMethod('foo', (params) { | |
| throw json_rpc.RpcException(5, 'Error message.', data: 'data value'); | |
| }); | |
| expectErrorResponse( | |
| controller, | |
| { | |
| 'jsonrpc': '2.0', | |
| 'method': 'foo', | |
| 'params': <String, dynamic>{}, | |
| 'id': 1234 | |
| }, | |
| 5, | |
| 'Error message.', | |
| data: 'data value'); | |
| }); | |
| test('a JSON parse error is rejected', () { | |
| return controller.handleJsonRequest('invalid json {').then((result) { | |
| expect(jsonDecode(result), { | |
| 'jsonrpc': '2.0', | |
| 'error': { | |
| 'code': error_code.PARSE_ERROR, | |
| 'message': startsWith('Invalid JSON: '), | |
| // TODO(nweiz): Always expect the source when sdk#25655 is fixed. | |
| 'data': { | |
| 'request': anyOf([isNull, 'invalid json {']) | |
| } | |
| }, | |
| 'id': null | |
| }); | |
| }); | |
| }); | |
| group('fallbacks', () { | |
| test('calls a fallback if no method matches', () { | |
| controller.server | |
| ..registerMethod('foo', () => 'foo') | |
| ..registerMethod('bar', () => 'foo') | |
| ..registerFallback((params) => {'fallback': params.value}); | |
| expect( | |
| controller.handleRequest({ | |
| 'jsonrpc': '2.0', | |
| 'method': 'baz', | |
| 'params': {'param': 'value'}, | |
| 'id': 1234 | |
| }), | |
| completion(equals({ | |
| 'jsonrpc': '2.0', | |
| 'result': { | |
| 'fallback': {'param': 'value'} | |
| }, | |
| 'id': 1234 | |
| }))); | |
| }); | |
| test('calls the first matching fallback', () { | |
| controller.server | |
| ..registerFallback((params) => | |
| throw json_rpc.RpcException.methodNotFound(params.method)) | |
| ..registerFallback((params) => 'fallback 2') | |
| ..registerFallback((params) => 'fallback 3'); | |
| expect( | |
| controller.handleRequest( | |
| {'jsonrpc': '2.0', 'method': 'fallback 2', 'id': 1234}), | |
| completion( | |
| equals({'jsonrpc': '2.0', 'result': 'fallback 2', 'id': 1234}))); | |
| }); | |
| test('an unexpected error in a fallback is captured', () { | |
| controller.server | |
| .registerFallback((_) => throw const FormatException('bad format')); | |
| expect( | |
| controller | |
| .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}), | |
| completion({ | |
| 'jsonrpc': '2.0', | |
| 'id': 1234, | |
| 'error': { | |
| 'code': error_code.SERVER_ERROR, | |
| 'message': 'bad format', | |
| 'data': { | |
| 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}, | |
| 'full': 'FormatException: bad format', | |
| 'stack': isA<String>() | |
| } | |
| } | |
| })); | |
| }); | |
| }); | |
| test('disallows multiple methods with the same name', () { | |
| controller.server.registerMethod('foo', () => null); | |
| expect(() => controller.server.registerMethod('foo', () => null), | |
| throwsArgumentError); | |
| }); | |
| } |