diff --git a/lib/mastodon_api.dart b/lib/mastodon_api.dart index 030316b..8ded9ff 100644 --- a/lib/mastodon_api.dart +++ b/lib/mastodon_api.dart @@ -23,6 +23,7 @@ export 'package:mastodon_api/src/service/entities/announcement_status.dart'; export 'package:mastodon_api/src/service/entities/application.dart'; export 'package:mastodon_api/src/service/entities/block_severity.dart'; export 'package:mastodon_api/src/service/entities/blocked_domain.dart'; +export 'package:mastodon_api/src/service/entities/conversation.dart'; export 'package:mastodon_api/src/service/entities/display_media_setting.dart'; export 'package:mastodon_api/src/service/entities/emoji.dart'; export 'package:mastodon_api/src/service/entities/emoji_reaction.dart'; diff --git a/lib/src/service/entities/conversation.dart b/lib/src/service/entities/conversation.dart new file mode 100644 index 0000000..17bbcef --- /dev/null +++ b/lib/src/service/entities/conversation.dart @@ -0,0 +1,35 @@ +// Copyright 2022 Kato Shinya. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided the conditions. + +// ignore_for_file: invalid_annotation_target + +// 📦 Package imports: +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'account.dart'; +import 'status.dart'; + +part 'conversation.freezed.dart'; +part 'conversation.g.dart'; + +@freezed +class Conversation with _$Conversation { + @JsonSerializable(includeIfNull: false) + const factory Conversation({ + /// The ID of the conversation in the database. + required String id, + + /// Is the conversation currently marked as unread? + @JsonKey(name: 'unread') required bool isUnread, + + /// Participants in the conversation. + required List accounts, + + /// The last status in the conversation. + Status? lastStatus, + }) = _Conversation; + + factory Conversation.fromJson(Map json) => + _$ConversationFromJson(json); +} diff --git a/lib/src/service/entities/conversation.freezed.dart b/lib/src/service/entities/conversation.freezed.dart new file mode 100644 index 0000000..efbc46b --- /dev/null +++ b/lib/src/service/entities/conversation.freezed.dart @@ -0,0 +1,268 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'conversation.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Conversation _$ConversationFromJson(Map json) { + return _Conversation.fromJson(json); +} + +/// @nodoc +mixin _$Conversation { + /// The ID of the conversation in the database. + String get id => throw _privateConstructorUsedError; + + /// Is the conversation currently marked as unread? + @JsonKey(name: 'unread') + bool get isUnread => throw _privateConstructorUsedError; + + /// Participants in the conversation. + List get accounts => throw _privateConstructorUsedError; + + /// The last status in the conversation. + Status? get lastStatus => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ConversationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ConversationCopyWith<$Res> { + factory $ConversationCopyWith( + Conversation value, $Res Function(Conversation) then) = + _$ConversationCopyWithImpl<$Res, Conversation>; + @useResult + $Res call( + {String id, + @JsonKey(name: 'unread') bool isUnread, + List accounts, + Status? lastStatus}); + + $StatusCopyWith<$Res>? get lastStatus; +} + +/// @nodoc +class _$ConversationCopyWithImpl<$Res, $Val extends Conversation> + implements $ConversationCopyWith<$Res> { + _$ConversationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? isUnread = null, + Object? accounts = null, + Object? lastStatus = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + isUnread: null == isUnread + ? _value.isUnread + : isUnread // ignore: cast_nullable_to_non_nullable + as bool, + accounts: null == accounts + ? _value.accounts + : accounts // ignore: cast_nullable_to_non_nullable + as List, + lastStatus: freezed == lastStatus + ? _value.lastStatus + : lastStatus // ignore: cast_nullable_to_non_nullable + as Status?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $StatusCopyWith<$Res>? get lastStatus { + if (_value.lastStatus == null) { + return null; + } + + return $StatusCopyWith<$Res>(_value.lastStatus!, (value) { + return _then(_value.copyWith(lastStatus: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$_ConversationCopyWith<$Res> + implements $ConversationCopyWith<$Res> { + factory _$$_ConversationCopyWith( + _$_Conversation value, $Res Function(_$_Conversation) then) = + __$$_ConversationCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + @JsonKey(name: 'unread') bool isUnread, + List accounts, + Status? lastStatus}); + + @override + $StatusCopyWith<$Res>? get lastStatus; +} + +/// @nodoc +class __$$_ConversationCopyWithImpl<$Res> + extends _$ConversationCopyWithImpl<$Res, _$_Conversation> + implements _$$_ConversationCopyWith<$Res> { + __$$_ConversationCopyWithImpl( + _$_Conversation _value, $Res Function(_$_Conversation) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? isUnread = null, + Object? accounts = null, + Object? lastStatus = freezed, + }) { + return _then(_$_Conversation( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + isUnread: null == isUnread + ? _value.isUnread + : isUnread // ignore: cast_nullable_to_non_nullable + as bool, + accounts: null == accounts + ? _value._accounts + : accounts // ignore: cast_nullable_to_non_nullable + as List, + lastStatus: freezed == lastStatus + ? _value.lastStatus + : lastStatus // ignore: cast_nullable_to_non_nullable + as Status?, + )); + } +} + +/// @nodoc + +@JsonSerializable(includeIfNull: false) +class _$_Conversation implements _Conversation { + const _$_Conversation( + {required this.id, + @JsonKey(name: 'unread') required this.isUnread, + required final List accounts, + this.lastStatus}) + : _accounts = accounts; + + factory _$_Conversation.fromJson(Map json) => + _$$_ConversationFromJson(json); + + /// The ID of the conversation in the database. + @override + final String id; + + /// Is the conversation currently marked as unread? + @override + @JsonKey(name: 'unread') + final bool isUnread; + + /// Participants in the conversation. + final List _accounts; + + /// Participants in the conversation. + @override + List get accounts { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_accounts); + } + + /// The last status in the conversation. + @override + final Status? lastStatus; + + @override + String toString() { + return 'Conversation(id: $id, isUnread: $isUnread, accounts: $accounts, lastStatus: $lastStatus)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_Conversation && + (identical(other.id, id) || other.id == id) && + (identical(other.isUnread, isUnread) || + other.isUnread == isUnread) && + const DeepCollectionEquality().equals(other._accounts, _accounts) && + (identical(other.lastStatus, lastStatus) || + other.lastStatus == lastStatus)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, isUnread, + const DeepCollectionEquality().hash(_accounts), lastStatus); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_ConversationCopyWith<_$_Conversation> get copyWith => + __$$_ConversationCopyWithImpl<_$_Conversation>(this, _$identity); + + @override + Map toJson() { + return _$$_ConversationToJson( + this, + ); + } +} + +abstract class _Conversation implements Conversation { + const factory _Conversation( + {required final String id, + @JsonKey(name: 'unread') required final bool isUnread, + required final List accounts, + final Status? lastStatus}) = _$_Conversation; + + factory _Conversation.fromJson(Map json) = + _$_Conversation.fromJson; + + @override + + /// The ID of the conversation in the database. + String get id; + @override + + /// Is the conversation currently marked as unread? + @JsonKey(name: 'unread') + bool get isUnread; + @override + + /// Participants in the conversation. + List get accounts; + @override + + /// The last status in the conversation. + Status? get lastStatus; + @override + @JsonKey(ignore: true) + _$$_ConversationCopyWith<_$_Conversation> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/service/entities/conversation.g.dart b/lib/src/service/entities/conversation.g.dart new file mode 100644 index 0000000..36954df --- /dev/null +++ b/lib/src/service/entities/conversation.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'conversation.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$_Conversation _$$_ConversationFromJson(Map json) => $checkedCreate( + r'_$_Conversation', + json, + ($checkedConvert) { + final val = _$_Conversation( + id: $checkedConvert('id', (v) => v as String), + isUnread: $checkedConvert('unread', (v) => v as bool), + accounts: $checkedConvert( + 'accounts', + (v) => (v as List) + .map((e) => + Account.fromJson(Map.from(e as Map))) + .toList()), + lastStatus: $checkedConvert( + 'last_status', + (v) => v == null + ? null + : Status.fromJson(Map.from(v as Map))), + ); + return val; + }, + fieldKeyMap: const {'isUnread': 'unread', 'lastStatus': 'last_status'}, + ); + +Map _$$_ConversationToJson(_$_Conversation instance) { + final val = { + 'id': instance.id, + 'unread': instance.isUnread, + 'accounts': instance.accounts.map((e) => e.toJson()).toList(), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('last_status', instance.lastStatus?.toJson()); + return val; +} diff --git a/lib/src/service/v1/timelines/timelines_v1_service.dart b/lib/src/service/v1/timelines/timelines_v1_service.dart index ad60cb4..3608c66 100644 --- a/lib/src/service/v1/timelines/timelines_v1_service.dart +++ b/lib/src/service/v1/timelines/timelines_v1_service.dart @@ -6,6 +6,7 @@ import '../../../core/client/client_context.dart'; import '../../../core/client/user_context.dart'; import '../../base_service.dart'; +import '../../entities/conversation.dart'; import '../../entities/status.dart'; import '../../response/mastodon_response.dart'; @@ -181,6 +182,82 @@ abstract class TimelinesV1Service { String? sinceStatusId, int? limit, }); + + /// View all conversations + /// + /// ## Parameters + /// + /// - [limit]: Maximum number of results to return. Defaults to 20 + /// conversations. Max 40 conversations. + /// + /// ## Endpoint Url + /// + /// - GET /api/v1/conversations HTTP/1.1 + /// + /// ## Authentication Methods + /// + /// - OAuth 2.0 + /// + /// ## Required Scopes + /// + /// - read:statuses + /// + /// ## Reference + /// + /// - https://docs.joinmastodon.org/methods/conversations/#get + Future>> lookupConversations({ + int? limit, + }); + + /// Removes a conversation from your list of conversations. + /// + /// ## Parameters + /// + /// - [conversationId]: The ID of the Conversation in the database. + /// + /// ## Endpoint Url + /// + /// - DELETE /api/v1/conversations/:id HTTP/1.1 + /// + /// ## Authentication Methods + /// + /// - OAuth 2.0 + /// + /// ## Required Scopes + /// + /// - write:conversations + /// + /// ## Reference + /// + /// - https://docs.joinmastodon.org/methods/conversations/#delete + Future> destroyConversation({ + required String conversationId, + }); + + /// Mark a conversation as read. + /// + /// ## Parameters + /// + /// - [conversationId]: The ID of the Conversation in the database. + /// + /// ## Endpoint Url + /// + /// - POST /api/v1/conversations/:id/read HTTP/1.1 + /// + /// ## Authentication Methods + /// + /// - OAuth 2.0 + /// + /// ## Required Scopes + /// + /// - write:conversations + /// + /// ## Reference + /// + /// - https://docs.joinmastodon.org/methods/conversations/#delete + Future> createMarkConversationAsRead({ + required String conversationId, + }); } class _TimelinesV1Service extends BaseService implements TimelinesV1Service { @@ -287,4 +364,43 @@ class _TimelinesV1Service extends BaseService implements TimelinesV1Service { ), dataBuilder: Status.fromJson, ); + + @override + Future>> lookupConversations({ + int? limit, + }) async => + super.transformMultiDataResponse( + await super.get( + UserContext.oauth2Only, + '/api/v1/conversations', + queryParameters: { + 'limit': limit, + }, + ), + dataBuilder: Conversation.fromJson, + ); + + @override + Future> destroyConversation({ + required String conversationId, + }) async => + super.evaluateResponse( + await super.delete( + UserContext.oauth2Only, + '/api/v1/conversations/$conversationId', + ), + ); + + @override + Future> createMarkConversationAsRead({ + required String conversationId, + }) async => + super.transformSingleDataResponse( + await super.post( + UserContext.oauth2Only, + '/api/v1/conversations/$conversationId/read', + checkEntity: true, + ), + dataBuilder: Conversation.fromJson, + ); } diff --git a/test/src/service/v1/timelines/data/create_mark_conversation_as_read.json b/test/src/service/v1/timelines/data/create_mark_conversation_as_read.json new file mode 100644 index 0000000..66c7251 --- /dev/null +++ b/test/src/service/v1/timelines/data/create_mark_conversation_as_read.json @@ -0,0 +1,144 @@ +{ + "id": "418450", + "unread": true, + "accounts": [ + { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "created_at": "2016-03-16T14:34:26.392Z", + "note": "

Developer of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "followers_count": 318699, + "following_count": 453, + "statuses_count": 61013, + "last_status_at": "2019-11-30T20:02:08.277Z", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + }, + { + "name": "Homepage", + "value": "https://zeonfederated.com", + "verified_at": "2019-07-15T18:29:57.191+00:00" + } + ] + }, + { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "created_at": "2016-03-16T14:34:26.392Z", + "note": "

Developer of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "followers_count": 318699, + "following_count": 453, + "statuses_count": 61013, + "last_status_at": "2019-11-30T20:02:08.277Z", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + }, + { + "name": "Homepage", + "value": "https://zeonfederated.com", + "verified_at": "2019-07-15T18:29:57.191+00:00" + } + ] + } + ], + "last_status": { + "id": "109384580642337706", + "created_at": "2022-11-22T00:17:22.709Z", + "in_reply_to_id": null, + "in_reply_to_account_id": null, + "sensitive": false, + "spoiler_text": "", + "visibility": "public", + "language": "ja", + "uri": "https://fedibird.com/users/kato_shinya/statuses/109384580642337706", + "url": "https://fedibird.com/@kato_shinya/109384580642337706", + "replies_count": 0, + "reblogs_count": 0, + "favourites_count": 0, + "emoji_reactions_count": 0, + "emoji_reactions": [], + "status_reference_ids": [], + "status_references_count": 0, + "status_referred_by_count": 0, + "searchability": "direct", + "favourited": false, + "reblogged": false, + "muted": false, + "bookmarked": false, + "emoji_reactioned": false, + "pinned": false, + "content": "\u003cp\u003eToot!\u003c/p\u003e", + "account": { + "id": "109357329889773922", + "username": "kato_shinya", + "acct": "kato_shinya", + "display_name": "", + "locked": false, + "bot": false, + "cat": false, + "discoverable": null, + "group": false, + "created_at": "2022-11-17T00:00:00.000Z", + "note": "\u003cp\u003e\u003c/p\u003e", + "url": "https://fedibird.com/@kato_shinya", + "avatar": "https://fedibird.com/avatars/original/missing.png", + "avatar_static": "https://fedibird.com/avatars/original/missing.png", + "header": "https://fedibird.com/headers/original/missing.png", + "header_static": "https://fedibird.com/headers/original/missing.png", + "searchability": "direct", + "followers_count": 0, + "following_count": 1, + "subscribing_count": 0, + "statuses_count": 4, + "last_status_at": "2022-11-22", + "emojis": [], + "fields": [], + "other_settings": { + "noindex": false, + "hide_network": false, + "hide_statuses_count": false, + "hide_following_count": false, + "hide_followers_count": false, + "enable_reaction": true + } + }, + "reblog": null, + "quote": null, + "application": { + "name": "トゥース!", + "website": "https://github.com/myConsciousness" + }, + "media_attachments": [], + "mentions": [], + "tags": [], + "emojis": [], + "card": null, + "poll": null + } +} diff --git a/test/src/service/v1/timelines/data/destroy_conversation.json b/test/src/service/v1/timelines/data/destroy_conversation.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/src/service/v1/timelines/data/destroy_conversation.json @@ -0,0 +1 @@ +{} diff --git a/test/src/service/v1/timelines/data/lookup_conversations.json b/test/src/service/v1/timelines/data/lookup_conversations.json new file mode 100644 index 0000000..5b6ca8a --- /dev/null +++ b/test/src/service/v1/timelines/data/lookup_conversations.json @@ -0,0 +1,146 @@ +[ + { + "id": "418450", + "unread": false, + "accounts": [ + { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "created_at": "2016-03-16T14:34:26.392Z", + "note": "

Developer of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "followers_count": 318699, + "following_count": 453, + "statuses_count": 61013, + "last_status_at": "2019-11-30T20:02:08.277Z", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + }, + { + "name": "Homepage", + "value": "https://zeonfederated.com", + "verified_at": "2019-07-15T18:29:57.191+00:00" + } + ] + }, + { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "created_at": "2016-03-16T14:34:26.392Z", + "note": "

Developer of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png", + "followers_count": 318699, + "following_count": 453, + "statuses_count": 61013, + "last_status_at": "2019-11-30T20:02:08.277Z", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + }, + { + "name": "Homepage", + "value": "https://zeonfederated.com", + "verified_at": "2019-07-15T18:29:57.191+00:00" + } + ] + } + ], + "last_status": { + "id": "109384580642337706", + "created_at": "2022-11-22T00:17:22.709Z", + "in_reply_to_id": null, + "in_reply_to_account_id": null, + "sensitive": false, + "spoiler_text": "", + "visibility": "public", + "language": "ja", + "uri": "https://fedibird.com/users/kato_shinya/statuses/109384580642337706", + "url": "https://fedibird.com/@kato_shinya/109384580642337706", + "replies_count": 0, + "reblogs_count": 0, + "favourites_count": 0, + "emoji_reactions_count": 0, + "emoji_reactions": [], + "status_reference_ids": [], + "status_references_count": 0, + "status_referred_by_count": 0, + "searchability": "direct", + "favourited": false, + "reblogged": false, + "muted": false, + "bookmarked": false, + "emoji_reactioned": false, + "pinned": false, + "content": "\u003cp\u003eToot!\u003c/p\u003e", + "account": { + "id": "109357329889773922", + "username": "kato_shinya", + "acct": "kato_shinya", + "display_name": "", + "locked": false, + "bot": false, + "cat": false, + "discoverable": null, + "group": false, + "created_at": "2022-11-17T00:00:00.000Z", + "note": "\u003cp\u003e\u003c/p\u003e", + "url": "https://fedibird.com/@kato_shinya", + "avatar": "https://fedibird.com/avatars/original/missing.png", + "avatar_static": "https://fedibird.com/avatars/original/missing.png", + "header": "https://fedibird.com/headers/original/missing.png", + "header_static": "https://fedibird.com/headers/original/missing.png", + "searchability": "direct", + "followers_count": 0, + "following_count": 1, + "subscribing_count": 0, + "statuses_count": 4, + "last_status_at": "2022-11-22", + "emojis": [], + "fields": [], + "other_settings": { + "noindex": false, + "hide_network": false, + "hide_statuses_count": false, + "hide_following_count": false, + "hide_followers_count": false, + "enable_reaction": true + } + }, + "reblog": null, + "quote": null, + "application": { + "name": "トゥース!", + "website": "https://github.com/myConsciousness" + }, + "media_attachments": [], + "mentions": [], + "tags": [], + "emojis": [], + "card": null, + "poll": null + } + } +] diff --git a/test/src/service/v1/timelines/timelines_v1_service_test.dart b/test/src/service/v1/timelines/timelines_v1_service_test.dart index b3d9734..26ee5ee 100644 --- a/test/src/service/v1/timelines/timelines_v1_service_test.dart +++ b/test/src/service/v1/timelines/timelines_v1_service_test.dart @@ -4,6 +4,7 @@ // 🌎 Project imports: import 'package:mastodon_api/src/core/client/user_context.dart'; +import 'package:mastodon_api/src/service/entities/conversation.dart'; import 'package:mastodon_api/src/service/entities/rate_limit.dart'; import 'package:mastodon_api/src/service/entities/status.dart'; import 'package:mastodon_api/src/service/response/mastodon_response.dart'; @@ -505,4 +506,184 @@ void main() { expect(response.data, []); }); }); + + group('.lookupConversations', () { + test('normal case', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildGetStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations', + 'test/src/service/v1/timelines/data/lookup_conversations.json', + { + 'limit': '10', + }, + ), + ); + + final response = await timelinesService.lookupConversations(limit: 10); + + expect(response, isA()); + expect(response.rateLimit, isA()); + expect(response.data, isA>()); + }); + + test('when unauthorized', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildGetStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations', + 'test/src/service/v1/timelines/data/lookup_conversations.json', + { + 'limit': '10', + }, + statusCode: 401, + ), + ); + + expectUnauthorizedException( + () async => await timelinesService.lookupConversations(limit: 10), + ); + }); + + test('when rate limit exceeded', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildGetStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations', + 'test/src/service/v1/timelines/data/lookup_conversations.json', + { + 'limit': '10', + }, + statusCode: 429, + ), + ); + + expectRateLimitExceededException( + () async => await timelinesService.lookupConversations(limit: 10), + ); + }); + }); + + group('.destroyConversation', () { + test('normal case', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildDeleteStub( + 'test', + '/api/v1/conversations/1234', + 'test/src/service/v1/timelines/data/destroy_conversation.json', + ), + ); + + final response = await timelinesService.destroyConversation( + conversationId: '1234', + ); + + expect(response, isA()); + expect(response.rateLimit, isA()); + expect(response.data, isTrue); + }); + + test('when unauthorized', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildDeleteStub( + 'test', + '/api/v1/conversations/1234', + 'test/src/service/v1/timelines/data/destroy_conversation.json', + statusCode: 401, + ), + ); + + expectUnauthorizedException( + () async => await timelinesService.destroyConversation( + conversationId: '1234', + ), + ); + }); + + test('when rate limit exceeded', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildDeleteStub( + 'test', + '/api/v1/conversations/1234', + 'test/src/service/v1/timelines/data/destroy_conversation.json', + statusCode: 429, + ), + ); + + expectRateLimitExceededException( + () async => await timelinesService.destroyConversation( + conversationId: '1234', + ), + ); + }); + }); + + group('.createMarkConversationAsRead', () { + test('normal case', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildPostStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations/1234/read', + 'test/src/service/v1/timelines/data/create_mark_conversation_as_read.json', + ), + ); + + final response = await timelinesService.createMarkConversationAsRead( + conversationId: '1234', + ); + + expect(response, isA()); + expect(response.rateLimit, isA()); + expect(response.data, isA()); + }); + + test('when unauthorized', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildPostStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations/1234/read', + 'test/src/service/v1/timelines/data/create_mark_conversation_as_read.json', + statusCode: 401, + ), + ); + + expectUnauthorizedException( + () async => await timelinesService.createMarkConversationAsRead( + conversationId: '1234', + ), + ); + }); + + test('when rate limit exceeded', () async { + final timelinesService = TimelinesV1Service( + instance: 'test', + context: context.buildPostStub( + 'test', + UserContext.oauth2Only, + '/api/v1/conversations/1234/read', + 'test/src/service/v1/timelines/data/create_mark_conversation_as_read.json', + statusCode: 429, + ), + ); + + expectRateLimitExceededException( + () async => await timelinesService.createMarkConversationAsRead( + conversationId: '1234', + ), + ); + }); + }); }