diff --git a/lib/src/model/metric.dart b/lib/src/model/metric.dart index b69c008..6054141 100644 --- a/lib/src/model/metric.dart +++ b/lib/src/model/metric.dart @@ -107,52 +107,68 @@ sealed class SpinifyMetrics implements Comparable { @immutable final class SpinifyMetrics$Immutable extends SpinifyMetrics { /// {@macro metrics} - const SpinifyMetrics$Immutable(); + const SpinifyMetrics$Immutable({ + required this.timestamp, + required this.initializedAt, + required this.commandId, + required this.state, + required this.connects, + required this.lastConnectAt, + required this.reconnectUrl, + required this.reconnectAttempts, + required this.nextReconnectAt, + required this.disconnects, + required this.lastDisconnectAt, + required this.bytesReceived, + required this.bytesSent, + required this.messagesReceived, + required this.messagesSent, + }); @override - DateTime get timestamp => throw UnimplementedError(); + final DateTime timestamp; @override - DateTime get initializedAt => throw UnimplementedError(); + final DateTime initializedAt; @override - int get commandId => throw UnimplementedError(); + final int commandId; @override - SpinifyState get state => throw UnimplementedError(); + final SpinifyState state; @override - int get connects => throw UnimplementedError(); + final int connects; @override - DateTime? get lastConnectAt => throw UnimplementedError(); + final DateTime? lastConnectAt; @override - String? get reconnectUrl => throw UnimplementedError(); + final String? reconnectUrl; @override - int? get reconnectAttempts => throw UnimplementedError(); + final int? reconnectAttempts; @override - DateTime? get nextReconnectAt => throw UnimplementedError(); + final DateTime? nextReconnectAt; @override - int get disconnects => throw UnimplementedError(); + final int disconnects; @override - DateTime? get lastDisconnectAt => throw UnimplementedError(); + final DateTime? lastDisconnectAt; @override - BigInt get bytesReceived => throw UnimplementedError(); + final BigInt bytesReceived; @override - BigInt get bytesSent => throw UnimplementedError(); + final BigInt bytesSent; @override - BigInt get messagesReceived => throw UnimplementedError(); + final BigInt messagesReceived; @override - BigInt get messagesSent => throw UnimplementedError(); + final BigInt messagesSent; } /// {@macro metrics} @@ -206,5 +222,21 @@ final class SpinifyMetrics$Mutable extends SpinifyMetrics { BigInt messagesSent = BigInt.zero; /// Freezes the metrics. - SpinifyMetrics$Immutable freeze() => const SpinifyMetrics$Immutable(); + SpinifyMetrics$Immutable freeze() => SpinifyMetrics$Immutable( + timestamp: timestamp, + initializedAt: initializedAt, + commandId: commandId, + state: state, + connects: connects, + lastConnectAt: lastConnectAt, + reconnectUrl: reconnectUrl, + reconnectAttempts: reconnectAttempts, + nextReconnectAt: nextReconnectAt, + disconnects: disconnects, + lastDisconnectAt: lastDisconnectAt, + bytesReceived: bytesReceived, + bytesSent: bytesSent, + messagesReceived: messagesReceived, + messagesSent: messagesSent, + ); } diff --git a/lib/src/transport_fake.dart b/lib/src/transport_fake.dart index 953b011..52fa239 100644 --- a/lib/src/transport_fake.dart +++ b/lib/src/transport_fake.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_setters_without_getters import 'dart:async'; +import 'dart:convert'; import 'dart:math' as math; import 'package:fixnum/fixnum.dart'; @@ -69,6 +70,9 @@ class SpinifyTransportFake implements ISpinifyTransport { @override Future send(SpinifyCommand command) async { if (!_isConnected) throw StateError('Not connected'); + metrics + ..bytesSent += BigInt.one + ..messagesSent += BigInt.one; await _sleep(); switch (command) { case SpinifyPingRequest(:int id): @@ -156,6 +160,8 @@ class SpinifyTransportFake implements ISpinifyTransport { id: id, timestamp: now, data: switch (method) { + 'getCurrentYear' => + utf8.encode('{"year": ${DateTime.now().year}}'), 'echo' => data, _ => throw ArgumentError('Unknown method: $method'), }, @@ -190,6 +196,9 @@ class SpinifyTransportFake implements ISpinifyTransport { Duration(milliseconds: delay), () { if (!_isConnected) return; + metrics + ..bytesReceived += BigInt.one + ..messagesReceived += BigInt.one; _onReply?.call(reply(DateTime.now())).ignore(); }, ); diff --git a/test/unit/spinify_test.dart b/test/unit/spinify_test.dart index 5c0c93b..779dbae 100644 --- a/test/unit/spinify_test.dart +++ b/test/unit/spinify_test.dart @@ -98,7 +98,7 @@ void main() { })); test( - 'rpc_request', + 'Rpc_requests', () => fakeAsync((async) { final client = createFakeClient() ..connect('ws://localhost:8000/connection/websocket'); @@ -131,10 +131,132 @@ void main() { } async.elapse(client.config.timeout); + expect(client.state, isA()); + client.disconnect(); + async.elapse(client.config.timeout); + expect(client.state, isA()); + client.connect('ws://localhost:8000/connection/websocket'); + async.elapse(client.config.timeout); + expect(client.state, isA()); + + // Another request + expect( + client.rpc('getCurrentYear', []), + completion(isA>().having( + (data) => jsonDecode(utf8.decode(data))['year'], + 'year', + DateTime.now().year, + )), + ); + async.elapse(client.config.timeout); + expect(client.state, isA()); client.close(); async.elapse(client.config.timeout); expect(client.state, isA()); })); + + test( + 'Metrics', + () => fakeAsync((async) { + final client = createFakeClient(); + expect(() => client.metrics, returnsNormally); + expect( + client.metrics, + allOf([ + isA().having( + (m) => m.state.isConnected, + 'isConnected', + isFalse, + ), + isA().having( + (m) => m.state, + 'state', + equals(client.state), + ), + isA().having( + (m) => m.connects, + 'connects', + 0, + ), + isA().having( + (m) => m.disconnects, + 'disconnects', + 0, + ), + isA().having( + (m) => m.messagesReceived, + 'messagesReceived', + equals(BigInt.zero), + ), + isA().having( + (m) => m.messagesSent, + 'messagesSent', + equals(BigInt.zero), + ), + ])); + client.connect('ws://localhost:8000/connection/websocket'); + async.elapse(client.config.timeout); + expect( + client.metrics, + allOf([ + isA().having( + (m) => m.state.isConnected, + 'isConnected', + isTrue, + ), + isA().having( + (m) => m.state, + 'state', + equals(client.state), + ), + isA().having( + (m) => m.connects, + 'connects', + 1, + ), + isA().having( + (m) => m.disconnects, + 'disconnects', + 0, + ), + isA().having( + (m) => m.messagesReceived, + 'messagesReceived', + greaterThan(BigInt.zero), + ), + isA().having( + (m) => m.messagesSent, + 'messagesSent', + greaterThan(BigInt.zero), + ), + ])); + client.close(); + async.elapse(client.config.timeout); + expect( + client.metrics, + allOf([ + isA().having( + (m) => m.state.isConnected, + 'isConnected', + isFalse, + ), + isA().having( + (m) => m.state, + 'state', + equals(client.state), + ), + isA().having( + (m) => m.connects, + 'connects', + 1, + ), + isA().having( + (m) => m.disconnects, + 'disconnects', + 1, + ), + ])); + })); }); } diff --git a/tool/echo/echo.go b/tool/echo/echo.go index f7fd706..761fe2d 100644 --- a/tool/echo/echo.go +++ b/tool/echo/echo.go @@ -196,8 +196,13 @@ func Centrifuge() (*centrifuge.Node, error) { log.Printf("[user %s] sent RPC, data: %s, method: %s", client.UserID(), string(e.Data), e.Method) switch e.Method { case "getCurrentYear": - cb(centrifuge.RPCReply{Data: []byte(`{"year": "2020"}`)}, nil) + // Return current year. + cb(centrifuge.RPCReply{Data: []byte(`{"year": ` + strconv.Itoa(time.Now().Year()) + `}`)}, nil) + case "echo": + // Return back input data. + cb(centrifuge.RPCReply{Data: e.Data}, nil) default: + // Method not found. cb(centrifuge.RPCReply{}, centrifuge.ErrorMethodNotFound) } })