From b42c0605bfbff4c8d173eeb2f6d28f2da6fdf8e9 Mon Sep 17 00:00:00 2001 From: Patrick Geselbracht Date: Sun, 29 Oct 2023 11:02:16 +0100 Subject: [PATCH] Implement instance API methods (#303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace deprecated v1 getInstance method with v2 version * Add missing API methods and entities * Add old getInstance method back in as deprecated getInstanceV1 --------- Co-authored-by: André Gasser --- .../social/bigbone/rx/RxInstanceMethods.kt | 62 +++ .../social/bigbone/api/entity/Instance.kt | 327 +++++++++++---- .../social/bigbone/api/entity/InstanceV1.kt | 288 +++++++++++++ .../bigbone/api/method/InstanceMethods.kt | 89 +++- bigbone/src/test/assets/instance.json | 29 +- .../assets/instance_activity_success.json | 74 ++++ .../instance_domain_blocks_success.json | 14 + .../src/test/assets/instance_extended.json | 172 ++++---- ...instance_extended_description_success.json | 4 + .../test/assets/instance_peers_success.json | 5 + .../test/assets/instance_rules_success.json | 26 ++ .../src/test/assets/instance_v1_extended.json | 126 ++++++ .../bigbone/api/method/InstanceMethodsTest.kt | 382 ++++++++++++++++-- 13 files changed, 1395 insertions(+), 203 deletions(-) create mode 100644 bigbone/src/main/kotlin/social/bigbone/api/entity/InstanceV1.kt create mode 100644 bigbone/src/test/assets/instance_activity_success.json create mode 100644 bigbone/src/test/assets/instance_domain_blocks_success.json create mode 100644 bigbone/src/test/assets/instance_extended_description_success.json create mode 100644 bigbone/src/test/assets/instance_peers_success.json create mode 100644 bigbone/src/test/assets/instance_rules_success.json create mode 100644 bigbone/src/test/assets/instance_v1_extended.json diff --git a/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxInstanceMethods.kt b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxInstanceMethods.kt index f0a63d447..bcf734904 100644 --- a/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxInstanceMethods.kt +++ b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxInstanceMethods.kt @@ -2,7 +2,12 @@ package social.bigbone.rx import io.reactivex.rxjava3.core.Single import social.bigbone.MastodonClient +import social.bigbone.api.entity.DomainBlock +import social.bigbone.api.entity.ExtendedDescription import social.bigbone.api.entity.Instance +import social.bigbone.api.entity.InstanceActivity +import social.bigbone.api.entity.InstanceV1 +import social.bigbone.api.entity.Rule import social.bigbone.api.method.InstanceMethods /** @@ -14,5 +19,62 @@ class RxInstanceMethods(client: MastodonClient) { private val instanceMethods = InstanceMethods(client) + /** + * Obtain general information about the server. + * @see Mastodon API documentation: methods/instance/#v2 + */ fun getInstance(): Single = Single.fromCallable { instanceMethods.getInstance().execute() } + + /** + * Obtain general information about the server. + * @see Mastodon API documentation: methods/instance/#v1 + */ + @Deprecated( + message = "Deprecated since 4.0.0. This method is just kept for backward compatibility.", + replaceWith = ReplaceWith("getInstance()"), + level = DeprecationLevel.WARNING + ) + fun getInstanceV1(): Single = Single.fromCallable { instanceMethods.getInstanceV1().execute() } + + /** + * Get the list of connected domains: Domains that this instance is aware of. + * @see Mastodon API documentation: methods/instance/#peers + */ + fun getPeers(): Single> = Single.fromCallable { + instanceMethods.getPeers().execute() + } + + /** + * Instance activity over the last 3 months, binned weekly. + * @see Mastodon API documentation: methods/instance/#activity + */ + fun getActivity(): Single> = Single.fromCallable { + instanceMethods.getActivity().execute() + } + + /** + * Rules that the users of this service should follow. + * @see Mastodon API documentation: methods/instance/#rules + */ + fun getRules(): Single> = Single.fromCallable { + instanceMethods.getRules().execute() + } + + /** + * View moderated servers + * Obtain a list of domains that have been blocked. + * @see Mastodon API documentation: methods/instance/#domain_blocks + */ + fun getBlockedDomains(): Single> = Single.fromCallable { + instanceMethods.getBlockedDomains().execute() + } + + /** + * Obtain an extended description of this server. + * @see + * Mastodon API documentation: methods/instance/#extended_description + */ + fun getExtendedDescription(): Single = Single.fromCallable { + instanceMethods.getExtendedDescription().execute() + } } diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt index 1cb68fa2e..61542bf12 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Instance.kt @@ -12,8 +12,8 @@ data class Instance( /** * The domain name of the instance. */ - @SerialName("uri") - val uri: String = "", + @SerialName("domain") + val domain: String = "", /** * The title of the website. @@ -21,24 +21,6 @@ data class Instance( @SerialName("title") val title: String = "", - /** - * A short, plain-text description defined by the admin. - */ - @SerialName("short_description") - val shortDescription: String = "", - - /** - * An HTML-permitted description of the Mastodon site. - */ - @SerialName("description") - val description: String = "", - - /** - * An email that may be contacted for any inquiries. - */ - @SerialName("email") - val email: String = "", - /** * The version of Mastodon installed on the instance. */ @@ -46,133 +28,179 @@ data class Instance( val version: String = "", /** - * URLs of interest for clients apps. + * The URL for the source code of the software running on this instance, in keeping with AGPL license requirements. */ - @SerialName("urls") - val urls: Urls? = null, + @SerialName("source_url") + val sourceUrl: String = "", /** - * Statistics about how much information the instance contains. + * A short, plain-text description defined by the admin. */ - @SerialName("stats") - val stats: Stats? = null, + @SerialName("description") + val description: String = "", + + @SerialName("usage") + val usage: Usage = Usage(), /** * Banner image for the website. */ @SerialName("thumbnail") - val thumbnail: String = "", + val thumbnail: Thumbnail = Thumbnail(), /** * Primary languages of the website and its staff. + * [List] of [String] (ISO 639-1 two-letter code) */ @SerialName("languages") val languages: List = emptyList(), /** - * Whether registrations are enabled. - */ - @SerialName("registrations") - val registrations: Boolean = false, - - /** - * Whether registrations require moderator approval. - */ - @SerialName("approval_required") - val approvalRequired: Boolean = false, - - /** - * Whether invites are enabled. + * Configured values and limits for this website. */ - @SerialName("invites_enabled") - val invitesEnabled: Boolean = false, + @SerialName("configuration") + val configuration: Configuration = Configuration(), /** - * Configured values and limits for this website. + * Information about registering for this website. */ - @SerialName("configuration") - val configuration: Configuration? = null, + @SerialName("registrations") + val registrations: Registrations = Registrations(), /** - * A user that can be contacted, as an alternative to email. + * Hints related to contacting a representative of the website. */ - @SerialName("contact_account") - val contactAccount: Account? = null, + @SerialName("contact") + val contact: Contact = Contact(), /** * An itemized list of rules for this website. */ @SerialName("rules") - val rules: List? = null + val rules: List = emptyList() ) { /** - * URLs of interest for clients apps. - * @see Mastodon API V1::Instance + * Usage data for this instance. */ @Serializable - data class Urls( + data class Usage( /** - * The Websockets URL for connecting to the streaming API. + * Usage data related to users on this instance. */ - @SerialName("streaming_api") - val streamingApi: String = "" - ) + @SerialName("users") + val users: Users = Users() + ) { + /** + * Usage data related to users on this instance. + */ + @Serializable + data class Users( + /** + * The number of active users in the past 4 weeks. + */ + @SerialName("active_month") + val activeMonth: Int = 0 + ) + } /** - * Statistics about how much information the instance contains. - * @see Mastodon API V1::Instance + * An image used to represent this instance. */ @Serializable - data class Stats( + data class Thumbnail( /** - * Total users on this instance. + * The URL for the thumbnail image. */ - @SerialName("user_count") - val userCount: Long = 0, + @SerialName("url") + val url: String = "", /** - * Total statuses on this instance. + * A hash computed by the BlurHash algorithm, + * for generating colorful preview thumbnails when media has not been downloaded yet. */ - @SerialName("status_count") - val statusCount: Long = 0, + @SerialName("blurhash") + val blurHash: String? = "", /** - * Total domains discovered by this instance. + * Links to scaled resolution images, for high DPI screens. */ - @SerialName("domain_count") - val domainCount: Long = 0 - ) + @SerialName("versions") + val versions: Versions? = null + ) { + /** + * Links to scaled resolution images, for high DPI screens. + */ + @Serializable + data class Versions( + /** + * The URL for the thumbnail image at 1x resolution. + */ + @SerialName("@1x") + val resolution1x: String? = null, + + /** + * The URL for the thumbnail image at 2x resolution. + */ + @SerialName("@2x") + val resolution2x: String? = null + ) + } /** * Configured values and limits for this website. - * @see Mastodon API V1::Instance + * @see Mastodon API Instance/#configuration */ @Serializable data class Configuration( + /** + * URLs of interest for clients apps. + */ + @SerialName("urls") + val urls: Urls = Urls(), + /** * Limits related to accounts. */ @SerialName("accounts") - val accounts: Accounts? = null, + val accounts: Accounts = Accounts(), /** * Limits related to authoring statuses. */ @SerialName("statuses") - val statuses: Statuses? = null, + val statuses: Statuses = Statuses(), /** * Hints for which attachments will be accepted. */ @SerialName("media_attachments") - val mediaAttachments: MediaAttachments? = null, + val mediaAttachments: MediaAttachments = MediaAttachments(), /** * Limits related to polls. */ @SerialName("polls") - val polls: Polls? = null + val polls: Polls = Polls(), + + /** + * Hints related to translation. + */ + @SerialName("translation") + val translation: Translation = Translation() ) { + /** + * URLs of interest for clients apps. + * @see Mastodon API Instance/#urls + */ + @Serializable + data class Urls( + /** + * The Websockets URL for connecting to the streaming API. + */ + @SerialName("streaming") + val streaming: String = "" + ) + /** * Limits related to accounts. * @see Mastodon API V1::Instance @@ -284,5 +312,156 @@ data class Instance( @SerialName("max_expiration") val maxExpiration: Int = 0 ) + + /** + * Hints related to translation. + */ + @Serializable + data class Translation( + /** + * Whether the Translations API is available on this instance. + */ + @SerialName("enabled") + val enabled: Boolean = false + ) + } + + /** + * Information about registering for this website. + */ + @Serializable + data class Registrations( + /** + * Whether registrations are enabled. + */ + @SerialName("enabled") + val enabled: Boolean = false, + + /** + * Whether registrations require moderator approval. + */ + @SerialName("approval_required") + val approvalRequired: Boolean = false, + + /** + * A custom message to be shown when registrations are closed. + * Nullable String (HTML) or null + */ + @SerialName("message") + val message: String? = null + ) + + /** + * Hints related to contacting a representative of the website. + */ + @Serializable + data class Contact( + /** + * An email address that can be messaged regarding inquiries or issues. + */ + @SerialName("email") + val email: String = "", + + /** + * An account that can be contacted natively over the network regarding inquiries or issues. + */ + @SerialName("account") + val account: Account = Account() + ) +} + +/** + * Instance activity class that can be returned by e.g. methods/instance/#activity + */ +@Serializable +data class InstanceActivity( + /** + * String (UNIX Timestamp). Midnight at the first day of the week. + */ + @SerialName("week") + val week: String = "", + /** + * String (cast from an integer). The number of Statuses created since the week began. + */ + @SerialName("statuses") + val statuses: String = "", + /** + * String (cast from an integer). The number of user logins since the week began. + */ + @SerialName("logins") + val logins: String = "", + /** + * String (cast from an integer). The number of user registrations since the week began. + */ + @SerialName("registrations") + val registrations: String = "" +) + +/** + * Represents a domain that is blocked by the instance. + * @see entities/DomainBlock + */ +@Serializable +data class DomainBlock( + /** + * The domain which is blocked. This may be obfuscated or partially censored. + */ + @SerialName("domain") + val domain: String = "", + + /** + * The SHA256 hash digest of the domain string. + */ + @SerialName("digest") + val digest: String = "", + + /** + * The level to which the domain is blocked. + */ + @SerialName("severity") + val severity: Severity = Severity.SILENCE, + + /** + * An optional reason for the domain block. + */ + @SerialName("comment") + val comment: String? = null +) { + /** + * The level to which the domain is blocked. + */ + @Serializable + enum class Severity { + /** + * Users from this domain will be hidden from timelines, threads, and notifications (unless you follow the user). + */ + @SerialName("silence") + SILENCE, + + /** + * Incoming messages from this domain will be rejected and dropped entirely. + */ + @SerialName("suspend") + SUSPEND } } + +/** + * Represents an extended description for the instance, to be shown on its about page. + */ +@Serializable +data class ExtendedDescription( + /** + * A timestamp of when the extended description was last updated. + * String (ISO 8601 Datetime) + */ + @SerialName("updated_at") + val updatedAt: String = "", + + /** + * The rendered HTML content of the extended description. + * String (HTML) + */ + @SerialName("content") + val content: String = "" +) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/InstanceV1.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/InstanceV1.kt new file mode 100644 index 000000000..d15ff054f --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/InstanceV1.kt @@ -0,0 +1,288 @@ +package social.bigbone.api.entity + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the software instance of Mastodon running on this domain. + * @see Mastodon API V1::Instance + */ +@Serializable +data class InstanceV1( + /** + * The domain name of the instance. + */ + @SerialName("uri") + val uri: String = "", + + /** + * The title of the website. + */ + @SerialName("title") + val title: String = "", + + /** + * A short, plain-text description defined by the admin. + */ + @SerialName("short_description") + val shortDescription: String = "", + + /** + * An HTML-permitted description of the Mastodon site. + */ + @SerialName("description") + val description: String = "", + + /** + * An email that may be contacted for any inquiries. + */ + @SerialName("email") + val email: String = "", + + /** + * The version of Mastodon installed on the instance. + */ + @SerialName("version") + val version: String = "", + + /** + * URLs of interest for clients apps. + */ + @SerialName("urls") + val urls: Urls? = null, + + /** + * Statistics about how much information the instance contains. + */ + @SerialName("stats") + val stats: Stats? = null, + + /** + * Banner image for the website. + */ + @SerialName("thumbnail") + val thumbnail: String = "", + + /** + * Primary languages of the website and its staff. + */ + @SerialName("languages") + val languages: List = emptyList(), + + /** + * Whether registrations are enabled. + */ + @SerialName("registrations") + val registrations: Boolean = false, + + /** + * Whether registrations require moderator approval. + */ + @SerialName("approval_required") + val approvalRequired: Boolean = false, + + /** + * Whether invites are enabled. + */ + @SerialName("invites_enabled") + val invitesEnabled: Boolean = false, + + /** + * Configured values and limits for this website. + */ + @SerialName("configuration") + val configuration: Configuration? = null, + + /** + * A user that can be contacted, as an alternative to email. + */ + @SerialName("contact_account") + val contactAccount: Account? = null, + + /** + * An itemized list of rules for this website. + */ + @SerialName("rules") + val rules: List? = null +) { + /** + * URLs of interest for clients apps. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Urls( + /** + * The Websockets URL for connecting to the streaming API. + */ + @SerialName("streaming_api") + val streamingApi: String = "" + ) + + /** + * Statistics about how much information the instance contains. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Stats( + /** + * Total users on this instance. + */ + @SerialName("user_count") + val userCount: Long = 0, + + /** + * Total statuses on this instance. + */ + @SerialName("status_count") + val statusCount: Long = 0, + + /** + * Total domains discovered by this instance. + */ + @SerialName("domain_count") + val domainCount: Long = 0 + ) + + /** + * Configured values and limits for this website. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Configuration( + /** + * Limits related to accounts. + */ + @SerialName("accounts") + val accounts: Accounts? = null, + + /** + * Limits related to authoring statuses. + */ + @SerialName("statuses") + val statuses: Statuses? = null, + + /** + * Hints for which attachments will be accepted. + */ + @SerialName("media_attachments") + val mediaAttachments: MediaAttachments? = null, + + /** + * Limits related to polls. + */ + @SerialName("polls") + val polls: Polls? = null + ) { + /** + * Limits related to accounts. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Accounts( + /** + * The maximum number of featured tags allowed for each account. + */ + @SerialName("max_featured_tags") + val maxFeaturedTags: Int = 0 + ) + + /** + * Limits related to authoring statuses. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Statuses( + /** + * The maximum number of allowed characters per status. + */ + @SerialName("max_characters") + val maxCharacters: Int = 0, + + /** + * The maximum number of media attachments that can be added to a status. + */ + @SerialName("max_media_attachments") + val maxMediaAttachments: Int = 0, + + /** + * Each URL in a status will be assumed to be exactly this many characters. + */ + @SerialName("characters_reserved_per_url") + val charactersReservedPerUrl: Int = 0 + ) + + /** + * Hints for which attachments will be accepted. + * @see Mastodon API V1::Instance + */ + @Serializable + data class MediaAttachments( + /** + * Contains MIME types that can be uploaded. + */ + @SerialName("supported_mime_types") + val supportedMimeTypes: List = emptyList(), + + /** + * The maximum size of any uploaded image, in bytes. + */ + @SerialName("image_size_limit") + val imageSizeLimit: Int = 0, + + /** + * The maximum number of pixels (width times height) for image uploads. + */ + @SerialName("image_matrix_limit") + val imageMatrixLimit: Int = 0, + + /** + * The maximum size of any uploaded video, in bytes. + */ + @SerialName("video_size_limit") + val videoSizeLimit: Int = 0, + + /** + * The maximum frame rate for any uploaded video. + */ + @SerialName("video_frame_rate_limit") + val videoFrameRateLimit: Int = 0, + + /** + * The maximum number of pixels (width times height) for video uploads. + */ + @SerialName("video_matrix_limit") + val videoMatrixLimit: Int = 0 + ) + + /** + * Limits related to polls. + * @see Mastodon API V1::Instance + */ + @Serializable + data class Polls( + /** + * Each poll is allowed to have up to this many options. + */ + @SerialName("max_options") + val maxOptions: Int = 0, + + /** + * Each poll option is allowed to have this many characters. + */ + @SerialName("max_characters_per_option") + val maxCharactersPerOption: Int = 0, + + /** + * The shortest allowed poll duration, in seconds. + */ + @SerialName("min_expiration") + val minExpiration: Int = 0, + + /** + * The longest allowed poll duration, in seconds. + */ + @SerialName("max_expiration") + val maxExpiration: Int = 0 + ) + } +} diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/InstanceMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/InstanceMethods.kt index 682c9552f..dd44e2292 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/method/InstanceMethods.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/method/InstanceMethods.kt @@ -2,20 +2,103 @@ package social.bigbone.api.method import social.bigbone.MastodonClient import social.bigbone.MastodonRequest +import social.bigbone.api.entity.DomainBlock +import social.bigbone.api.entity.ExtendedDescription import social.bigbone.api.entity.Instance +import social.bigbone.api.entity.InstanceActivity +import social.bigbone.api.entity.InstanceV1 +import social.bigbone.api.entity.Rule /** * Allows access to API methods with endpoints having an "api/vX/instance" prefix. + * Discover information about a Mastodon website. * @see Mastodon instance API methods */ class InstanceMethods(private val client: MastodonClient) { + + private val instanceEndpointV1 = "/api/v1/instance" + private val instanceEndpointV2 = "/api/v2/instance" + /** - * Retrieve instance details. - * @see Mastodon API documentation: entities/V1_Instance + * Obtain general information about the server. + * @see Mastodon API documentation: methods/instance/#v2 */ fun getInstance(): MastodonRequest { return client.getMastodonRequest( - endpoint = "api/v1/instance", + endpoint = instanceEndpointV2, + method = MastodonClient.Method.GET + ) + } + + /** + * Obtain general information about the server. + * @see Mastodon API documentation: methods/instance/#v1 + */ + @Deprecated( + message = "Deprecated since 4.0.0. This method is just kept for backward compatibility.", + replaceWith = ReplaceWith("getInstance()"), + level = DeprecationLevel.WARNING + ) + fun getInstanceV1(): MastodonRequest { + return client.getMastodonRequest( + endpoint = instanceEndpointV1, + method = MastodonClient.Method.GET + ) + } + + /** + * Get the list of connected domains: Domains that this instance is aware of. + * @see Mastodon API documentation: methods/instance/#peers + */ + fun getPeers(): MastodonRequest> { + return client.getMastodonRequestForList( + endpoint = "$instanceEndpointV1/peers", + method = MastodonClient.Method.GET + ) + } + + /** + * Instance activity over the last 3 months, binned weekly. + * @see Mastodon API documentation: methods/instance/#activity + */ + fun getActivity(): MastodonRequest> { + return client.getMastodonRequestForList( + endpoint = "$instanceEndpointV1/activity", + method = MastodonClient.Method.GET + ) + } + + /** + * Rules that the users of this service should follow. + * @see Mastodon API documentation: methods/instance/#rules + */ + fun getRules(): MastodonRequest> { + return client.getMastodonRequestForList( + endpoint = "$instanceEndpointV1/rules", + method = MastodonClient.Method.GET + ) + } + + /** + * View moderated servers + * Obtain a list of domains that have been blocked. + * @see Mastodon API documentation: methods/instance/#domain_blocks + */ + fun getBlockedDomains(): MastodonRequest> { + return client.getMastodonRequestForList( + endpoint = "$instanceEndpointV1/domain_blocks", + method = MastodonClient.Method.GET + ) + } + + /** + * Obtain an extended description of this server. + * @see + * Mastodon API documentation: methods/instance/#extended_description + */ + fun getExtendedDescription(): MastodonRequest { + return client.getMastodonRequest( + endpoint = "$instanceEndpointV1/extended_description", method = MastodonClient.Method.GET ) } diff --git a/bigbone/src/test/assets/instance.json b/bigbone/src/test/assets/instance.json index 63d1c55f8..1cf18919a 100644 --- a/bigbone/src/test/assets/instance.json +++ b/bigbone/src/test/assets/instance.json @@ -1,17 +1,22 @@ { - "uri": "test.com", + "domain": "test.com", "title": "test.com", - "short_description": "short description", - "description": "description", - "email": "owner@test.com", "version": "1.3.2", - "stats": { - "user_count": 123456, - "status_count": 512023, - "domain_count": 13002 + "source_url": "https://github.com/mastodon/mastodon", + "description": "description", + "contact": { + "email": "owner@test.com" + }, + "usage": { + "users": { + "active_month": 123456 + } + }, + "thumbnail": { + "url": "https://www.server.com/testimage.svg" }, - "thumbnail": "https://www.server.com/testimage.svg", - "registrations": true, - "approval_required": false, - "invites_enabled": true + "registrations": { + "enabled": true, + "approval_required": false + } } diff --git a/bigbone/src/test/assets/instance_activity_success.json b/bigbone/src/test/assets/instance_activity_success.json new file mode 100644 index 000000000..43e1b941f --- /dev/null +++ b/bigbone/src/test/assets/instance_activity_success.json @@ -0,0 +1,74 @@ +[ + { + "week": "1574640000", + "statuses": "37125", + "logins": "14239", + "registrations": "542" + }, + { + "week": "1574035200", + "statuses": "244447", + "logins": "28820", + "registrations": "4425" + }, + { + "week": "1573430400", + "statuses": "270615", + "logins": "35388", + "registrations": "8781" + }, + { + "week": "1572825600", + "statuses": "309722", + "logins": "44433", + "registrations": "26165" + }, + { + "week": "1572220800", + "statuses": "116227", + "logins": "19739", + "registrations": "2926" + }, + { + "week": "1571616000", + "statuses": "119932", + "logins": "19247", + "registrations": "3188" + }, + { + "week": "1571011200", + "statuses": "117892", + "logins": "19164", + "registrations": "3107" + }, + { + "week": "1570406400", + "statuses": "109092", + "logins": "18763", + "registrations": "2986" + }, + { + "week": "1569801600", + "statuses": "107554", + "logins": "19614", + "registrations": "2904" + }, + { + "week": "1569196800", + "statuses": "118067", + "logins": "19703", + "registrations": "3295" + }, + { + "week": "1568592000", + "statuses": "110199", + "logins": "19791", + "registrations": "3026" + }, + { + "week": "1567987200", + "statuses": "106029", + "logins": "19089", + "registrations": "2769" + } +] diff --git a/bigbone/src/test/assets/instance_domain_blocks_success.json b/bigbone/src/test/assets/instance_domain_blocks_success.json new file mode 100644 index 000000000..19f93d5f3 --- /dev/null +++ b/bigbone/src/test/assets/instance_domain_blocks_success.json @@ -0,0 +1,14 @@ +[ + { + "domain": "birb.elfenban.de", + "digest": "5d2c6e02a0cced8fb05f32626437e3d23096480b47efbba659b6d9e80c85d280", + "severity": "suspend", + "comment": "Third-party bots" + }, + { + "domain": "birdbots.leptonics.com", + "digest": "ce019d8d32cce8e369ac4367f4dc232103e6f489fbdd247fb99f9c8a646078a4", + "severity": "suspend", + "comment": "Third-party bots" + } +] diff --git a/bigbone/src/test/assets/instance_extended.json b/bigbone/src/test/assets/instance_extended.json index d7786e53e..b7d0dde1e 100644 --- a/bigbone/src/test/assets/instance_extended.json +++ b/bigbone/src/test/assets/instance_extended.json @@ -1,36 +1,44 @@ { - "uri":"mastodon.social", - "title":"Mastodon", - "short_description":"The original server operated by the Mastodon gGmbH non-profit", - "description":"", - "email":"staff@mastodon.social", - "version":"3.5.3", - "urls":{ - "streaming_api":"wss://mastodon.social" + "domain": "mastodon.social", + "title": "Mastodon", + "version": "4.0.0rc1", + "source_url": "https://github.com/mastodon/mastodon", + "description": "The original server operated by the Mastodon gGmbH non-profit", + "usage": { + "users": { + "active_month": 123122 + } }, - "stats":{ - "user_count":812303, - "status_count":38151616, - "domain_count":25255 + "thumbnail": { + "url": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png", + "blurhash": "UeKUpFxuo~R%0nW;WCnhF6RjaJt757oJodS$", + "versions": { + "@1x": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png", + "@2x": "https://files.mastodon.social/site_uploads/files/000/000/001/@2x/57c12f441d083cde.png" + } }, - "thumbnail":"https://files.mastodon.social/site_uploads/files/000/000/001/original/vlcsnap-2018-08-27-16h43m11s127.png", - "languages":[ + "languages": [ "en" ], - "registrations":false, - "approval_required":false, - "invites_enabled":true, - "configuration":{ - "statuses":{ - "max_characters":500, - "max_media_attachments":4, - "characters_reserved_per_url":23 + "configuration": { + "urls": { + "streaming": "wss://mastodon.social" + }, + "accounts": { + "max_featured_tags": 10 + }, + "statuses": { + "max_characters": 500, + "max_media_attachments": 4, + "characters_reserved_per_url": 23 }, - "media_attachments":{ - "supported_mime_types":[ + "media_attachments": { + "supported_mime_types": [ "image/jpeg", "image/png", "image/gif", + "image/heic", + "image/heif", "image/webp", "video/webm", "video/mp4", @@ -54,74 +62,84 @@ "audio/3gpp", "video/x-ms-asf" ], - "image_size_limit":10485760, - "image_matrix_limit":16777216, - "video_size_limit":41943040, - "video_frame_rate_limit":60, - "video_matrix_limit":2304000 + "image_size_limit": 10485760, + "image_matrix_limit": 16777216, + "video_size_limit": 41943040, + "video_frame_rate_limit": 60, + "video_matrix_limit": 2304000 }, - "polls":{ - "max_options":4, - "max_characters_per_option":50, - "min_expiration":300, - "max_expiration":2629746 + "polls": { + "max_options": 4, + "max_characters_per_option": 50, + "min_expiration": 300, + "max_expiration": 2629746 + }, + "translation": { + "enabled": true } }, - "contact_account":{ - "id":"1", - "username":"Gargron", - "acct":"Gargron", - "display_name":"Eugen", - "locked":false, - "bot":false, - "discoverable":true, - "group":false, - "created_at":"2016-03-16T00:00:00.000Z", - "note":"\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e", - "url":"https://mastodon.social/@Gargron", - "avatar":"https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", - "avatar_static":"https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", - "header":"https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", - "header_static":"https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", - "followers_count":118944, - "following_count":305, - "statuses_count":72309, - "last_status_at":"2022-08-24", - "emojis":[ - - ], - "fields":[ - { - "name":"Patreon", - "value":"\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e", - "verified_at":null - } - ] + "registrations": { + "enabled": false, + "approval_required": false, + "message": null + }, + "contact": { + "email": "staff@mastodon.social", + "account": { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen 💀", + "locked": false, + "bot": false, + "discoverable": true, + "group": false, + "created_at": "2016-03-16T00:00:00.000Z", + "note": "

Founder, CEO and lead developer @Mastodon, Germany.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "followers_count": 133026, + "following_count": 311, + "statuses_count": 72605, + "last_status_at": "2022-10-31", + "noindex": false, + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + } + ] + } }, - "rules":[ + "rules": [ { - "id":"1", - "text":"Sexually explicit or violent media must be marked as sensitive when posting" + "id": "1", + "text": "Sexually explicit or violent media must be marked as sensitive when posting" }, { - "id":"2", - "text":"No racism, sexism, homophobia, transphobia, xenophobia, or casteism" + "id": "2", + "text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism" }, { - "id":"3", - "text":"No incitement of violence or promotion of violent ideologies" + "id": "3", + "text": "No incitement of violence or promotion of violent ideologies" }, { - "id":"4", - "text":"No harassment, dogpiling or doxxing of other users" + "id": "4", + "text": "No harassment, dogpiling or doxxing of other users" }, { - "id":"5", - "text":"No content illegal in Germany" + "id": "5", + "text": "No content illegal in Germany" }, { - "id":"7", - "text":"Do not share intentionally false or misleading information" + "id": "7", + "text": "Do not share intentionally false or misleading information" } ] } diff --git a/bigbone/src/test/assets/instance_extended_description_success.json b/bigbone/src/test/assets/instance_extended_description_success.json new file mode 100644 index 000000000..4cfa0d361 --- /dev/null +++ b/bigbone/src/test/assets/instance_extended_description_success.json @@ -0,0 +1,4 @@ +{ + "updated_at": "2022-11-03T04:09:07Z", + "content": "

For inquiries not related specifically to the operation of this server, such as press inquiries, please contact press@joinmastodon.org.

\n\n

Funding

\n\n

This server is crowdfunded by Patreon donations. For a list of sponsors, see joinmastodon.org.

\n\n

Reporting and moderation

\n\n

When reporting accounts, please make sure to include at least a few posts that show rule-breaking behaviour, when applicable. If there is any additional context that might help make a decision, please also include it in the comment. This is especially important when the content is in a language nobody on the moderation team speaks.

\n\n

We usually handle reports within 24 hours. Please mind that you are not notified when a report you have made has led to a punitive action, and that not all punitive actions are externally visible. For first time offenses, we may opt to delete offending content, escalating to harsher measures on repeat offenses.

\n\n

Impressum

\n\n

Mastodon gGmbH
\nMühlenstraße 8a
\n14167 Berlin
\nGermany

\n\n

E-Mail-Adresse: hello@joinmastodon.org

\n\n

Vertretungsberechtigt: Eugen Rochko (Geschäftsführer)

\n\n

Umsatzsteuer Identifikationsnummer (USt-ID): DE344258260

\n\n

Handelsregister
\nGeführt bei: Amtsgericht Charlottenburg
\nNummer: HRB 230086 B

\n" +} diff --git a/bigbone/src/test/assets/instance_peers_success.json b/bigbone/src/test/assets/instance_peers_success.json new file mode 100644 index 000000000..6a333ba53 --- /dev/null +++ b/bigbone/src/test/assets/instance_peers_success.json @@ -0,0 +1,5 @@ +[ + "tilde.zone", + "mspsocial.net", + "conf.tube" +] diff --git a/bigbone/src/test/assets/instance_rules_success.json b/bigbone/src/test/assets/instance_rules_success.json new file mode 100644 index 000000000..63f5ce35b --- /dev/null +++ b/bigbone/src/test/assets/instance_rules_success.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "text": "Sexually explicit or violent media must be marked as sensitive when posting" + }, + { + "id": "2", + "text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism" + }, + { + "id": "3", + "text": "No incitement of violence or promotion of violent ideologies" + }, + { + "id": "4", + "text": "No harassment, dogpiling or doxxing of other users" + }, + { + "id": "5", + "text": "No content illegal in Germany" + }, + { + "id": "7", + "text": "Do not share intentionally false or misleading information" + } +] diff --git a/bigbone/src/test/assets/instance_v1_extended.json b/bigbone/src/test/assets/instance_v1_extended.json new file mode 100644 index 000000000..6a922ad56 --- /dev/null +++ b/bigbone/src/test/assets/instance_v1_extended.json @@ -0,0 +1,126 @@ +{ + "uri": "mastodon.social", + "title": "Mastodon", + "short_description": "The original server operated by the Mastodon gGmbH non-profit", + "description": "", + "email": "staff@mastodon.social", + "version": "3.5.3", + "urls": { + "streaming_api": "wss://mastodon.social" + }, + "stats": { + "user_count": 812303, + "status_count": 38151616, + "domain_count": 25255 + }, + "thumbnail": "https://files.mastodon.social/site_uploads/files/000/000/001/original/vlcsnap-2018-08-27-16h43m11s127.png", + "languages": [ + "en" + ], + "registrations": false, + "approval_required": false, + "invites_enabled": true, + "configuration": { + "statuses": { + "max_characters": 500, + "max_media_attachments": 4, + "characters_reserved_per_url": 23 + }, + "media_attachments": { + "supported_mime_types": [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "video/webm", + "video/mp4", + "video/quicktime", + "video/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wave", + "audio/vnd.wave", + "audio/ogg", + "audio/vorbis", + "audio/mpeg", + "audio/mp3", + "audio/webm", + "audio/flac", + "audio/aac", + "audio/m4a", + "audio/x-m4a", + "audio/mp4", + "audio/3gpp", + "video/x-ms-asf" + ], + "image_size_limit": 10485760, + "image_matrix_limit": 16777216, + "video_size_limit": 41943040, + "video_frame_rate_limit": 60, + "video_matrix_limit": 2304000 + }, + "polls": { + "max_options": 4, + "max_characters_per_option": 50, + "min_expiration": 300, + "max_expiration": 2629746 + } + }, + "contact_account": { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "discoverable": true, + "group": false, + "created_at": "2016-03-16T00:00:00.000Z", + "note": "\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "followers_count": 118944, + "following_count": 305, + "statuses_count": 72309, + "last_status_at": "2022-08-24", + "emojis": [ + ], + "fields": [ + { + "name": "Patreon", + "value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e", + "verified_at": null + } + ] + }, + "rules": [ + { + "id": "1", + "text": "Sexually explicit or violent media must be marked as sensitive when posting" + }, + { + "id": "2", + "text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism" + }, + { + "id": "3", + "text": "No incitement of violence or promotion of violent ideologies" + }, + { + "id": "4", + "text": "No harassment, dogpiling or doxxing of other users" + }, + { + "id": "5", + "text": "No content illegal in Germany" + }, + { + "id": "7", + "text": "Do not share intentionally false or misleading information" + } + ] +} diff --git a/bigbone/src/test/kotlin/social/bigbone/api/method/InstanceMethodsTest.kt b/bigbone/src/test/kotlin/social/bigbone/api/method/InstanceMethodsTest.kt index a49c016c2..6b781a293 100644 --- a/bigbone/src/test/kotlin/social/bigbone/api/method/InstanceMethodsTest.kt +++ b/bigbone/src/test/kotlin/social/bigbone/api/method/InstanceMethodsTest.kt @@ -1,8 +1,19 @@ package social.bigbone.api.method +import io.mockk.verify +import org.amshove.kluent.invoking import org.amshove.kluent.shouldBeEqualTo -import org.junit.jupiter.api.Assertions +import org.amshove.kluent.shouldBeNull +import org.amshove.kluent.shouldHaveSize +import org.amshove.kluent.shouldNotBeNull +import org.amshove.kluent.shouldNotBeTrue +import org.amshove.kluent.shouldThrow +import org.amshove.kluent.withMessage import org.junit.jupiter.api.Test +import social.bigbone.api.entity.DomainBlock +import social.bigbone.api.entity.ExtendedDescription +import social.bigbone.api.entity.InstanceActivity +import social.bigbone.api.entity.Rule import social.bigbone.api.exception.BigBoneRequestException import social.bigbone.testtool.MockClient import social.bigbone.testtool.TestUtil @@ -14,11 +25,18 @@ class InstanceMethodsTest { val instanceMethods = InstanceMethods(client) val instance = instanceMethods.getInstance().execute() - instance.uri shouldBeEqualTo "test.com" + + instance.domain shouldBeEqualTo "test.com" instance.title shouldBeEqualTo "test.com" instance.description shouldBeEqualTo "description" - instance.email shouldBeEqualTo "owner@test.com" instance.version shouldBeEqualTo "1.3.2" + + verify { + client.get( + path = "/api/v2/instance", + query = null + ) + } } @Test @@ -27,29 +45,136 @@ class InstanceMethodsTest { val instanceMethods = InstanceMethods(client) val instance = instanceMethods.getInstance().execute() + instance.domain shouldBeEqualTo "mastodon.social" + instance.title shouldBeEqualTo "Mastodon" + instance.version shouldBeEqualTo "4.0.0rc1" + instance.sourceUrl shouldBeEqualTo "https://github.com/mastodon/mastodon" + instance.description shouldBeEqualTo "The original server operated by the Mastodon gGmbH non-profit" + instance.usage.users.activeMonth shouldBeEqualTo 123_122 + + with(instance.thumbnail) { + url shouldBeEqualTo "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png" + blurHash.shouldNotBeNull() + with(versions) { + this.shouldNotBeNull() + resolution1x.shouldNotBeNull() + resolution2x.shouldNotBeNull() + } + } + + with(instance.languages) { + size shouldBeEqualTo 1 + get(0) shouldBeEqualTo "en" + } + + val config = instance.configuration + config.urls.streaming shouldBeEqualTo "wss://mastodon.social" + config.accounts.maxFeaturedTags shouldBeEqualTo 10 + with(config.statuses) { + maxCharacters shouldBeEqualTo 500 + maxMediaAttachments shouldBeEqualTo 4 + charactersReservedPerUrl shouldBeEqualTo 23 + } + with(config.mediaAttachments) { + supportedMimeTypes shouldHaveSize 27 + imageSizeLimit shouldBeEqualTo 10_485_760 + imageMatrixLimit shouldBeEqualTo 16_777_216 + videoSizeLimit shouldBeEqualTo 41_943_040 + videoFrameRateLimit shouldBeEqualTo 60 + videoMatrixLimit shouldBeEqualTo 2_304_000 + } + with(config.polls) { + maxOptions shouldBeEqualTo 4 + maxCharactersPerOption shouldBeEqualTo 50 + minExpiration shouldBeEqualTo 300 + maxExpiration shouldBeEqualTo 2_629_746 + } + config.translation.enabled shouldBeEqualTo true + + with(instance.registrations) { + enabled shouldBeEqualTo false + approvalRequired shouldBeEqualTo false + message.shouldBeNull() + } + + with(instance.contact) { + email shouldBeEqualTo "staff@mastodon.social" + with(account) { + id shouldBeEqualTo "1" + emojis.isEmpty() shouldBeEqualTo true + } + } + + with(instance.rules) { + size shouldBeEqualTo 6 + get(0).id shouldBeEqualTo "1" + get(0).text shouldBeEqualTo "Sexually explicit or violent media must be marked as sensitive when posting" + } + + verify { + client.get( + path = "/api/v2/instance", + query = null + ) + } + } + + @Test + fun getInstanceV1Extended() { + val client = MockClient.mock("instance_v1_extended.json") + val instanceMethods = InstanceMethods(client) + + val instance = instanceMethods.getInstanceV1().execute() instance.uri shouldBeEqualTo "mastodon.social" instance.title shouldBeEqualTo "Mastodon" instance.description shouldBeEqualTo "" instance.email shouldBeEqualTo "staff@mastodon.social" instance.version shouldBeEqualTo "3.5.3" - val config = instance.configuration!! - config.statuses!!.maxCharacters shouldBeEqualTo 500 - config.statuses!!.maxMediaAttachments shouldBeEqualTo 4 - config.statuses!!.charactersReservedPerUrl shouldBeEqualTo 23 - config.mediaAttachments!!.supportedMimeTypes.size shouldBeEqualTo 25 - config.mediaAttachments!!.imageSizeLimit shouldBeEqualTo 10_485_760 - config.mediaAttachments!!.imageMatrixLimit shouldBeEqualTo 16_777_216 - config.mediaAttachments!!.videoSizeLimit shouldBeEqualTo 41_943_040 - config.mediaAttachments!!.videoFrameRateLimit shouldBeEqualTo 60 - config.mediaAttachments!!.videoMatrixLimit shouldBeEqualTo 2_304_000 - config.polls!!.maxOptions shouldBeEqualTo 4 - config.polls!!.maxCharactersPerOption shouldBeEqualTo 50 - config.polls!!.minExpiration shouldBeEqualTo 300 - config.polls!!.maxExpiration shouldBeEqualTo 2_629_746 - val rules = instance.rules!! - rules.size shouldBeEqualTo 6 - rules[0].id shouldBeEqualTo "1" - rules[0].text shouldBeEqualTo "Sexually explicit or violent media must be marked as sensitive when posting" + with(instance.configuration) { + shouldNotBeNull() + + with(statuses) { + shouldNotBeNull() + + maxCharacters shouldBeEqualTo 500 + maxMediaAttachments shouldBeEqualTo 4 + charactersReservedPerUrl shouldBeEqualTo 23 + } + + with(mediaAttachments) { + shouldNotBeNull() + + supportedMimeTypes.size shouldBeEqualTo 25 + imageSizeLimit shouldBeEqualTo 10_485_760 + imageMatrixLimit shouldBeEqualTo 16_777_216 + videoSizeLimit shouldBeEqualTo 41_943_040 + videoFrameRateLimit shouldBeEqualTo 60 + videoMatrixLimit shouldBeEqualTo 2_304_000 + } + + with(polls) { + shouldNotBeNull() + + maxOptions shouldBeEqualTo 4 + maxCharactersPerOption shouldBeEqualTo 50 + minExpiration shouldBeEqualTo 300 + maxExpiration shouldBeEqualTo 2_629_746 + } + } + + with(instance.rules) { + shouldNotBeNull() + shouldHaveSize(6) + first().id shouldBeEqualTo "1" + first().text shouldBeEqualTo "Sexually explicit or violent media must be marked as sensitive when posting" + } + + verify { + client.get( + path = "/api/v1/instance", + query = null + ) + } } @Test @@ -59,34 +184,217 @@ class InstanceMethodsTest { instanceMethods.getInstance() .doOnJson { val expected = """{ - "uri": "test.com", + "domain": "test.com", "title": "test.com", - "short_description": "short description", - "description": "description", - "email": "owner@test.com", "version": "1.3.2", - "stats": { - "user_count": 123456, - "status_count": 512023, - "domain_count": 13002 + "source_url": "https://github.com/mastodon/mastodon", + "description": "description", + "contact": { + "email": "owner@test.com" + }, + "usage": { + "users": { + "active_month": 123456 + } + }, + "thumbnail": { + "url": "https://www.server.com/testimage.svg" }, - "thumbnail": "https://www.server.com/testimage.svg", - "registrations": true, - "approval_required": false, - "invites_enabled": true + "registrations": { + "enabled": true, + "approval_required": false + } } """ - Assertions.assertEquals(TestUtil.normalizeLineBreaks(it), TestUtil.normalizeLineBreaks(expected)) + TestUtil.normalizeLineBreaks(it) shouldBeEqualTo TestUtil.normalizeLineBreaks(expected) } .execute() + + verify { + client.get( + path = "/api/v2/instance", + query = null + ) + } } @Test fun getInstanceWithException() { - Assertions.assertThrows(BigBoneRequestException::class.java) { - val client = MockClient.ioException() - val instanceMethods = InstanceMethods(client) + val client = MockClient.ioException() + val instanceMethods = InstanceMethods(client) + + invoking { instanceMethods.getInstance().execute() + } shouldThrow BigBoneRequestException::class + + verify { + client.get( + path = "/api/v2/instance", + query = null + ) + } + } + + @Test + fun `Given a client returning success, when getting peers, then call correct endpoint and expect parsed values`() { + val client = MockClient.mock("instance_peers_success.json") + + val instanceMethods = InstanceMethods(client) + val peers: List = instanceMethods.getPeers().execute() + + peers shouldHaveSize 3 + peers[0] shouldBeEqualTo "tilde.zone" + peers[1] shouldBeEqualTo "mspsocial.net" + peers[2] shouldBeEqualTo "conf.tube" + + verify { + client.get( + path = "/api/v1/instance/peers", + query = null + ) + } + } + + @Test + fun `Given a client returning unauthorized, when getting peers, then propagate error`() { + val client = MockClient.failWithResponse( + responseJsonAssetPath = "error_401_unauthorized.json", + responseCode = 401, + message = "Unauthorized" + ) + + invoking { + InstanceMethods(client).getPeers().execute() + } shouldThrow BigBoneRequestException::class withMessage "Unauthorized" + + verify { + client.get( + path = "/api/v1/instance/peers", + query = null + ) + } + } + + @Test + fun `Given a client returning success, when getting activity, then call correct endpoint and expect parsed values`() { + val client = MockClient.mock(jsonName = "instance_activity_success.json") + val instanceMethods = InstanceMethods(client) + + val activity: List = instanceMethods.getActivity().execute() + activity shouldHaveSize 12 + with(activity[0]) { + week shouldBeEqualTo "1574640000" + statuses shouldBeEqualTo "37125" + logins shouldBeEqualTo "14239" + registrations shouldBeEqualTo "542" + } + + verify { + client.get( + path = "/api/v1/instance/activity", + query = null + ) + } + } + + @Test + fun `Given a client returning unauthorized, when getting activity, then propagate error`() { + val client = MockClient.failWithResponse( + responseJsonAssetPath = "error_401_unauthorized.json", + responseCode = 401, + message = "Unauthorized" + ) + + invoking { + InstanceMethods(client).getActivity().execute() + } shouldThrow BigBoneRequestException::class withMessage "Unauthorized" + + verify { + client.get( + path = "/api/v1/instance/activity", + query = null + ) + } + } + + @Test + fun `Given a client returning success, when getting rules, then call correct endpoint and expect parsed values`() { + val client = MockClient.mock(jsonName = "instance_rules_success.json") + val instanceMethods = InstanceMethods(client) + + val rules: List = instanceMethods.getRules().execute() + rules shouldHaveSize 6 + with(rules[0]) { + id shouldBeEqualTo "1" + text shouldBeEqualTo "Sexually explicit or violent media must be marked as sensitive when posting" + } + + verify { + client.get( + path = "/api/v1/instance/rules", + query = null + ) + } + } + + @Test + fun `Given a client returning success, when getting blocked servers, then call correct endpoint and expect parsed values`() { + val client = MockClient.mock(jsonName = "instance_domain_blocks_success.json") + val instanceMethods = InstanceMethods(client) + + val domainBlocks: List = instanceMethods.getBlockedDomains().execute() + domainBlocks shouldHaveSize 2 + with(domainBlocks[0]) { + domain shouldBeEqualTo "birb.elfenban.de" + digest shouldBeEqualTo "5d2c6e02a0cced8fb05f32626437e3d23096480b47efbba659b6d9e80c85d280" + severity shouldBeEqualTo DomainBlock.Severity.SUSPEND + comment shouldBeEqualTo "Third-party bots" + } + + verify { + client.get( + path = "/api/v1/instance/domain_blocks", + query = null + ) + } + } + + @Test + fun `Given a client returning unauthorized, when getting blocked servers, then propagate error`() { + val client = MockClient.failWithResponse( + responseJsonAssetPath = "error_401_unauthorized.json", + responseCode = 401, + message = "Unauthorized" + ) + + invoking { + InstanceMethods(client).getBlockedDomains().execute() + } shouldThrow BigBoneRequestException::class withMessage "Unauthorized" + + verify { + client.get( + path = "/api/v1/instance/domain_blocks", + query = null + ) + } + } + + @Test + fun `Given a client returning success, when getting extended description, then call correct endpoint and expect parsed values`() { + val client = MockClient.mock(jsonName = "instance_extended_description_success.json") + val instanceMethods = InstanceMethods(client) + + val extendedDescription: ExtendedDescription = instanceMethods.getExtendedDescription().execute() + with(extendedDescription) { + updatedAt shouldBeEqualTo "2022-11-03T04:09:07Z" + content.isEmpty().shouldNotBeTrue() + } + + verify { + client.get( + path = "/api/v1/instance/extended_description", + query = null + ) } } }