Skip to content

Commit

Permalink
Migrate cqrs to null-safety (#7)
Browse files Browse the repository at this point in the history
* Set min SDK version to 2.12 prerelease

* Migrate cqrs to null-safety

* Bump version to 5.0.0 and add changelog note

* Remove meta from pubspec

* Use null-safety mockito and test

* Bump min SDK version

* Remove deprecated success parameter from CommandResult

Resolves #14.

* Add generated mocks and fix tests

* Push cqrs code coverage to 100%

* Bump http to null-safe version

* Fix http mocks

* Fix http version to ^
  • Loading branch information
Albert221 authored Feb 8, 2021
1 parent 7469aa8 commit d247f7e
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 59 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/cqrs-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: google/dart:2.10
image: google/dart:2.12-beta

defaults:
run:
Expand All @@ -29,4 +29,4 @@ jobs:
echo $CREDENTIALS > ~/.pub-cache/credentials.json
- name: Publish
run: pub publish -f
run: dart pub publish -f
2 changes: 1 addition & 1 deletion .github/workflows/cqrs-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
dart_release: ['2.10', '2.11-beta']
dart_release: ['2.12-beta']

container:
image: google/dart:${{ matrix.dart_release }}
Expand Down
6 changes: 6 additions & 0 deletions packages/cqrs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 5.0.0-nullsafety.0

- **Breaking:** Migrate to null-safety.
- **Breaking:** Bump minimum Dart version to 2.12 prerelease.
- **Breaking:** Remove deprecated `success` parameter from `CommandResult` constructor.

# 4.1.1

- Fix exception thrown when the query result is `null`.
Expand Down
12 changes: 3 additions & 9 deletions packages/cqrs/lib/src/command_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@ import 'transport_types.dart';

/// The result of running a [Command].
class CommandResult {
const CommandResult(
this.errors, {
@Deprecated("Success is derived from the `errors` emptiness.") bool success,
}) : assert(errors != null);
const CommandResult(this.errors);

/// Creates a success [CommandResult] without any errors.
const CommandResult.success() : errors = const [];

/// Creates a failed [CommandResult] and ensures it has errors.
CommandResult.failed(this.errors)
: assert(errors != null && errors.isNotEmpty);
CommandResult.failed(this.errors) : assert(errors.isNotEmpty);

CommandResult.fromJson(Map<String, dynamic> json)
: errors = (json['ValidationErrors'] as List)
Expand All @@ -50,9 +46,7 @@ class CommandResult {

/// A validation error.
class ValidationError {
const ValidationError(this.code, this.message)
: assert(code != null),
assert(message != null);
const ValidationError(this.code, this.message);

ValidationError.fromJson(Map<String, dynamic> json)
: code = json['ErrorCode'] as int,
Expand Down
21 changes: 3 additions & 18 deletions packages/cqrs/lib/src/cqrs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

import 'command_result.dart';
import 'cqrs_exception.dart';
Expand All @@ -41,11 +40,7 @@ class CQRS {
this._apiUri, {
Duration timeout = const Duration(seconds: 30),
Map<String, String> headers = const {},
}) : assert(_client != null),
assert(_apiUri != null),
assert(timeout != null),
assert(headers != null),
_timeout = timeout,
}) : _timeout = timeout,
_headers = headers;

final http.Client _client;
Expand All @@ -64,9 +59,6 @@ class CQRS {
Query<T> query, {
Map<String, String> headers = const {},
}) async {
assert(query != null);
assert(headers != null);

final response = await _send(query, pathPrefix: 'query', headers: headers);

if (response.statusCode == 200) {
Expand All @@ -75,7 +67,7 @@ class CQRS {

// Fix for https://github.com/leancodepl/corelibrary/issues/193
if (json == null) {
return null;
return null as T;
}

return query.resultFactory(json);
Expand Down Expand Up @@ -105,9 +97,6 @@ class CQRS {
Command command, {
Map<String, String> headers = const {},
}) async {
assert(command != null);
assert(headers != null);

final response = await _send(
command,
pathPrefix: 'command',
Expand Down Expand Up @@ -136,13 +125,9 @@ class CQRS {

Future<http.Response> _send(
CQRSMethod cqrsMethod, {
@required String pathPrefix,
required String pathPrefix,
Map<String, String> headers = const {},
}) async {
assert(cqrsMethod != null);
assert(pathPrefix != null);
assert(headers != null);

return _client.post(
_apiUri.resolve('$pathPrefix/${cqrsMethod.getFullName()}'),
body: jsonEncode(cqrsMethod),
Expand Down
4 changes: 2 additions & 2 deletions packages/cqrs/lib/src/cqrs_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import 'package:http/http.dart';

class CQRSException implements Exception {
const CQRSException(this.response, [this.message]) : assert(response != null);
const CQRSException(this.response, [this.message]);

final Response response;
final String message;
final String? message;

@override
String toString() {
Expand Down
40 changes: 20 additions & 20 deletions packages/cqrs/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
name: cqrs
version: 4.1.1
homepage: https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/cqrs
repository: https://github.com/leancodepl/flutter_corelibrary
description: >-
A library for convenient communication with CQRS-compatible backends, using
queries and commands.
environment:
sdk: '>=2.10.0 <3.0.0'

dependencies:
http: ^0.12.2
meta: ^1.2.3

dev_dependencies:
lint: ^1.3.0
mockito: ^4.1.2
test: ^1.15.4
test_coverage: ^0.5.0
name: cqrs
version: 5.0.0-nullsafety.0
homepage: https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/cqrs
repository: https://github.com/leancodepl/flutter_corelibrary
description: >-
A library for convenient communication with CQRS-compatible backends, using
queries and commands.
environment:
sdk: '>=2.12.0-0 <3.0.0'

dependencies:
http: ^0.13.0-nullsafety.0

dev_dependencies:
build_runner: ^1.10.13
lint: ^1.5.1
mockito: ^5.0.0-nullsafety.4
test: ^1.16.0-nullsafety.13
test_coverage: ^0.5.0
33 changes: 26 additions & 7 deletions packages/cqrs/test/cqrs_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import 'package:cqrs/src/cqrs.dart';
import 'package:cqrs/src/cqrs_exception.dart';
import 'package:cqrs/src/transport_types.dart';
import 'package:http/http.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

import 'cqrs_test.mocks.dart';

@GenerateMocks([Client])
void main() {
group('CQRS', () {
Client client;
CQRS cqrs;
late MockClient client;
late CQRS cqrs;
setUp(() {
client = MockClient();
cqrs = CQRS(client, Uri.parse('https://example.org/api/'));
Expand Down Expand Up @@ -108,6 +112,23 @@ void main() {
)).called(1);
});

test('throws CQRSException on json decoding failure', () async {
mockClientPost(client, Response('this is not a valid json', 200));

final result = cqrs.run(ExampleCommand());

expect(
result,
throwsA(
isA<CQRSException>().having(
(e) => e.message,
'message',
startsWith('An error occured while decoding response body JSON:'),
),
),
);
});

test('throws CQRSException when response code is other than 200 and 422',
() {
mockClientPost(client, Response('', 500));
Expand All @@ -130,22 +151,20 @@ void main() {
});
}

void mockClientPost(Client client, Response response) {
void mockClientPost(MockClient client, Response response) {
when(client.post(
any,
body: anyNamed('body'),
headers: anyNamed('headers'),
)).thenAnswer((_) async => response);
}

class MockClient extends Mock implements Client {}

class ExampleQuery extends Query<bool> {
class ExampleQuery extends Query<bool?> {
@override
String getFullName() => 'ExampleQuery';

@override
bool resultFactory(dynamic json) => json as bool;
bool? resultFactory(dynamic json) => json as bool?;

@override
Map<String, dynamic> toJson() => {};
Expand Down
98 changes: 98 additions & 0 deletions packages/cqrs/test/cqrs_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Mocks generated by Mockito 5.0.0-nullsafety.7 from annotations
// in cqrs/test/cqrs_test.dart.
// Do not manually edit this file.

import 'dart:async' as _i6;
import 'dart:convert' as _i7;
import 'dart:typed_data' as _i3;

import 'package:http/src/base_request.dart' as _i8;
import 'package:http/src/client.dart' as _i5;
import 'package:http/src/response.dart' as _i2;
import 'package:http/src/streamed_response.dart' as _i4;
import 'package:mockito/mockito.dart' as _i1;

// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis

class _FakeResponse extends _i1.Fake implements _i2.Response {}

class _FakeUint8List extends _i1.Fake implements _i3.Uint8List {}

class _FakeStreamedResponse extends _i1.Fake implements _i4.StreamedResponse {}

/// A class which mocks [Client].
///
/// See the documentation for Mockito's code generation for more information.
class MockClient extends _i1.Mock implements _i5.Client {
MockClient() {
_i1.throwOnMissingStub(this);
}

@override
_i6.Future<_i2.Response> head(Uri? url, {Map<String, String>? headers}) =>
(super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<_i2.Response> get(Uri? url, {Map<String, String>? headers}) =>
(super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<_i2.Response> post(Uri? url,
{Map<String, String>? headers,
Object? body,
_i7.Encoding? encoding}) =>
(super.noSuchMethod(
Invocation.method(#post, [url],
{#headers: headers, #body: body, #encoding: encoding}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<_i2.Response> put(Uri? url,
{Map<String, String>? headers,
Object? body,
_i7.Encoding? encoding}) =>
(super.noSuchMethod(
Invocation.method(#put, [url],
{#headers: headers, #body: body, #encoding: encoding}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<_i2.Response> patch(Uri? url,
{Map<String, String>? headers,
Object? body,
_i7.Encoding? encoding}) =>
(super.noSuchMethod(
Invocation.method(#patch, [url],
{#headers: headers, #body: body, #encoding: encoding}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<_i2.Response> delete(Uri? url,
{Map<String, String>? headers,
Object? body,
_i7.Encoding? encoding}) =>
(super.noSuchMethod(
Invocation.method(#delete, [url],
{#headers: headers, #body: body, #encoding: encoding}),
returnValue: Future.value(_FakeResponse()))
as _i6.Future<_i2.Response>);
@override
_i6.Future<String> read(Uri? url, {Map<String, String>? headers}) =>
(super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}),
returnValue: Future.value('')) as _i6.Future<String>);
@override
_i6.Future<_i3.Uint8List> readBytes(Uri? url,
{Map<String, String>? headers}) =>
(super.noSuchMethod(
Invocation.method(#readBytes, [url], {#headers: headers}),
returnValue: Future.value(_FakeUint8List()))
as _i6.Future<_i3.Uint8List>);
@override
_i6.Future<_i4.StreamedResponse> send(_i8.BaseRequest? request) =>
(super.noSuchMethod(Invocation.method(#send, [request]),
returnValue: Future.value(_FakeStreamedResponse()))
as _i6.Future<_i4.StreamedResponse>);
}

0 comments on commit d247f7e

Please sign in to comment.