From 253e0ecda64f4a4e5226eda917c3a7b5a191c729 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 21 Jun 2019 07:41:09 +0000 Subject: [PATCH] [client][pushrules] Add GET Pushrules Endpoint [sdk] Add "build_runner", "json_annotation" and "json_serializable" to dependencies required for [client][pushrules] [test][client][pushrules] Add Tests for [client][pushrules] Took 2 hours 13 minutes --- README.md | 10 + lib/src/Client.dart | 17 ++ lib/src/responses/PushrulesResponse.dart | 111 ++++++++ lib/src/responses/PushrulesResponse.g.dart | 81 ++++++ pubspec.lock | 289 ++++++++++++++++++++- pubspec.yaml | 5 + test/Client_test.dart | 8 + test/FakeMatrixApi.dart | 188 ++++++++++++++ 8 files changed, 708 insertions(+), 1 deletion(-) create mode 100644 lib/src/responses/PushrulesResponse.dart create mode 100644 lib/src/responses/PushrulesResponse.g.dart diff --git a/README.md b/README.md index 8d7967c..c9bbd4a 100644 --- a/README.md +++ b/README.md @@ -63,4 +63,14 @@ final resp = await matrix.connection.jsonRequest( "body": "hello" } ); +``` + +## Development + +### Regenerating JSON Classes + +To regenerate the part files of JSON Classes you need to run this command: + +```bash +flutter pub run build_runner build ``` \ No newline at end of file diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 3b85da4..3ae68cb 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -27,6 +27,7 @@ import 'responses/ErrorResponse.dart'; import 'Connection.dart'; import 'Store.dart'; import 'User.dart'; +import 'responses/PushrulesResponse.dart'; /// Represents a Matrix client to communicate with a /// [Matrix](https://matrix.org) homeserver and is the entry point for this @@ -206,4 +207,20 @@ class Client { return resp["room_id"]; } + + /// Fetches the pushrules for the logged in user. + /// These are needed for notifications on Android + Future getPushrules() async { + final dynamic resp = await connection.jsonRequest( + type: "GET", + action: "/client/r0/pushrules", + ); + + if (resp is ErrorResponse) { + connection.onError.add(resp); + return null; + } + + return PushrulesResponse.fromJson(resp); + } } diff --git a/lib/src/responses/PushrulesResponse.dart b/lib/src/responses/PushrulesResponse.dart new file mode 100644 index 0000000..0e039ac --- /dev/null +++ b/lib/src/responses/PushrulesResponse.dart @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedly. If not, see . + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'PushrulesResponse.g.dart'; + +@JsonSerializable(explicitToJson: true, nullable: false) +class PushrulesResponse { + @JsonKey(nullable: false) + Global global; + + PushrulesResponse( + this.global, + ); + + factory PushrulesResponse.fromJson(Map json) => + _$PushrulesResponseFromJson(json); + + Map toJson() => _$PushrulesResponseToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class Global { + List content; + List room; + List sender; + List override; + List underride; + + Global( + this.content, + this.room, + this.sender, + this.override, + this.underride, + ); + + factory Global.fromJson(Map json) => _$GlobalFromJson(json); + + Map toJson() => _$GlobalToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class PushRule { + @JsonKey(nullable: false) + List actions; + List conditions; + @JsonKey(nullable: false, name: "default") + bool contentDefault; + @JsonKey(nullable: false) + bool enabled; + @JsonKey(nullable: false) + String ruleId; + String pattern; + + PushRule( + this.actions, + this.conditions, + this.contentDefault, + this.enabled, + this.ruleId, + this.pattern, + ); + + factory PushRule.fromJson(Map json) => + _$PushRuleFromJson(json); + + Map toJson() => _$PushRuleToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class Condition { + String key; + @JsonKey(name: "is") + String conditionIs; + @JsonKey(nullable: false) + String kind; + String pattern; + + Condition( + this.key, + this.conditionIs, + this.kind, + this.pattern, + ); + + factory Condition.fromJson(Map json) => + _$ConditionFromJson(json); + + Map toJson() => _$ConditionToJson(this); +} diff --git a/lib/src/responses/PushrulesResponse.g.dart b/lib/src/responses/PushrulesResponse.g.dart new file mode 100644 index 0000000..ea0977c --- /dev/null +++ b/lib/src/responses/PushrulesResponse.g.dart @@ -0,0 +1,81 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'PushrulesResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushrulesResponse _$PushrulesResponseFromJson(Map json) { + return PushrulesResponse( + Global.fromJson(json['global'] as Map)); +} + +Map _$PushrulesResponseToJson(PushrulesResponse instance) => + {'global': instance.global.toJson()}; + +Global _$GlobalFromJson(Map json) { + return Global( + (json['content'] as List) + ?.map((e) => + e == null ? null : PushRule.fromJson(e as Map)) + ?.toList(), + (json['room'] as List) + ?.map((e) => + e == null ? null : PushRule.fromJson(e as Map)) + ?.toList(), + (json['sender'] as List) + ?.map((e) => + e == null ? null : PushRule.fromJson(e as Map)) + ?.toList(), + (json['override'] as List) + ?.map((e) => + e == null ? null : PushRule.fromJson(e as Map)) + ?.toList(), + (json['underride'] as List) + ?.map((e) => + e == null ? null : PushRule.fromJson(e as Map)) + ?.toList()); +} + +Map _$GlobalToJson(Global instance) => { + 'content': instance.content?.map((e) => e?.toJson())?.toList(), + 'room': instance.room?.map((e) => e?.toJson())?.toList(), + 'sender': instance.sender?.map((e) => e?.toJson())?.toList(), + 'override': instance.override?.map((e) => e?.toJson())?.toList(), + 'underride': instance.underride?.map((e) => e?.toJson())?.toList() + }; + +PushRule _$PushRuleFromJson(Map json) { + return PushRule( + json['actions'] as List, + (json['conditions'] as List) + ?.map((e) => + e == null ? null : Condition.fromJson(e as Map)) + ?.toList(), + json['default'] as bool, + json['enabled'] as bool, + json['ruleId'] as String, + json['pattern'] as String); +} + +Map _$PushRuleToJson(PushRule instance) => { + 'actions': instance.actions, + 'conditions': instance.conditions?.map((e) => e?.toJson())?.toList(), + 'default': instance.contentDefault, + 'enabled': instance.enabled, + 'ruleId': instance.ruleId, + 'pattern': instance.pattern + }; + +Condition _$ConditionFromJson(Map json) { + return Condition(json['key'] as String, json['is'] as String, + json['kind'] as String, json['pattern'] as String); +} + +Map _$ConditionToJson(Condition instance) => { + 'key': instance.key, + 'is': instance.conditionIs, + 'kind': instance.kind, + 'pattern': instance.pattern + }; diff --git a/pubspec.lock b/pubspec.lock index a464171..d790ad6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://www.dartlang.org/tools/pub/glossary#lockfile packages: + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.36.3" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" async: dependency: transitive description: @@ -15,6 +29,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.4" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.2" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "6.6.0" charcode: dependency: transitive description: @@ -22,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" collection: dependency: transitive description: @@ -29,6 +106,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.7" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" flutter: dependency: "direct main" description: flutter @@ -39,6 +151,34 @@ packages: description: flutter source: sdk version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.18" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" http: dependency: "direct main" description: @@ -46,6 +186,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.0+2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" http_parser: dependency: transitive description: @@ -60,6 +207,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.15.8" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.18" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" matcher: dependency: transitive description: @@ -74,6 +263,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6+3" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" path: dependency: "direct main" description: @@ -88,6 +298,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.2" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" quiver: dependency: transitive description: @@ -95,11 +326,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.5" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.4+2" source_span: dependency: transitive description: @@ -128,6 +380,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.19" string_scanner: dependency: transitive description: @@ -156,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.4" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+1" typed_data: dependency: transitive description: @@ -170,6 +436,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+10" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.13" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.16" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.3.0-dev.0.1 <3.0.0" flutter: ">=1.2.1 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 76d1c50..eaf5ab1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,10 +21,15 @@ dependencies: # Time formatting intl: ^0.15.8 + json_annotation: ^2.4.0 + dev_dependencies: flutter_test: sdk: flutter + build_runner: ^1.5.2 + json_serializable: ^3.0.0 + # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/test/Client_test.dart b/test/Client_test.dart index 6b8896f..1ba8488 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -21,6 +21,7 @@ * along with Foobar. If not, see . */ +import 'package:famedlysdk/src/responses/PushrulesResponse.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Connection.dart'; @@ -238,6 +239,13 @@ void main() { expect(newID, "!1234:fakeServer.notExisting"); }); + test('getPushrules', () async { + final PushrulesResponse pushrules = await matrix.getPushrules(); + final PushrulesResponse awaited_resp = PushrulesResponse.fromJson( + FakeMatrixApi.api["GET"]["/client/r0/pushrules"]("")); + expect(pushrules.toJson(), awaited_resp.toJson()); + }); + test('Logout when token is unknown', () async { Future loginStateFuture = matrix.connection.onLoginStateChanged.stream.first; diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index 10e3411..f27b057 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -89,6 +89,194 @@ class FakeMatrixApi extends MockClient { } ] }, + "/client/r0/pushrules": (var req) => { + "global": { + "content": [ + { + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ], + "default": true, + "enabled": true, + "pattern": "alice", + "rule_id": ".m.rule.contains_user_name" + } + ], + "override": [ + { + "actions": [ + "dont_notify" + ], + "conditions": [], + "default": true, + "enabled": false, + "rule_id": ".m.rule.master" + }, + { + "actions": [ + "dont_notify" + ], + "conditions": [ + { + "key": "content.msgtype", + "kind": "event_match", + "pattern": "m.notice" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.suppress_notices" + } + ], + "room": [], + "sender": [], + "underride": [ + { + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "ring" + }, + { + "set_tweak": "highlight", + "value": false + } + ], + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.call.invite" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.call" + }, + { + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight" + } + ], + "conditions": [ + { + "kind": "contains_display_name" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.contains_display_name" + }, + { + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight", + "value": false + } + ], + "conditions": [ + { + "is": "2", + "kind": "room_member_count" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.room_one_to_one" + }, + { + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight", + "value": false + } + ], + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" + }, + { + "key": "content.membership", + "kind": "event_match", + "pattern": "invite" + }, + { + "key": "state_key", + "kind": "event_match", + "pattern": "@alice:example.com" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.invite_for_me" + }, + { + "actions": [ + "notify", + { + "set_tweak": "highlight", + "value": false + } + ], + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.member_event" + }, + { + "actions": [ + "notify", + { + "set_tweak": "highlight", + "value": false + } + ], + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.message" + } + ], + "default": true, + "enabled": true, + "rule_id": ".m.rule.message" + } + ] + } + }, "/client/r0/sync": (var req) => { "next_batch": Random().nextDouble().toString(), "presence": {