Repository: andregasser/bigbone
Pull request #411 (2023-12-28 14:32:16)

Fully implement partially implemented AccountMethods
# Description

This PR is part 1 for #405 and implements missing properties and entity parameters. No new methods will be added in this PR—that’ll be the task for a follow-up PR.

# Type of Change

- New feature
- Documentation

# Breaking Changes

- `AccountMethods#verifyCredentials` now returns `CredentialAccount` instead of `Account`
- `AccountMethods#updateCredentials` now returns `CredentialAccount` instead of `Account`
- `AccountMethods#updateCredentials` got a few new parameters. As none of them are optional, this will break existing calls, but they can easily be added.
- `AccountMethods#getStatuses` gained two now parameters `excludeReblogs` and `filterByTaggedWith`. Both are optional, like many existing parameters, but they are intertwined with other parameters. So if you use Java, or used Kotlin without named parameters here, your calls will likely or may return wrong results if you supply a Boolean to the wrong field.

# How Has This Been Tested?

New unit tests and updates for existing unit tests

# Mandatory Checklist

- [x] I ran `gradle check` and there were no errors reported
- [x] I have performed a self-review of my code
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] All tests pass locally with my changes
- [x] I have added KDoc documentation to all public methods

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser) Report
Attention: `144 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`6960044`)](https://app.codecov.io/gh/andregasser/bigbone/commit/69600449efe62e891aff0c9fdaba56692dd147ce?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser) 50.01% compared to head [(`4a8c534`)](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser) 48.95%.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master     #411      +/-   ##
============================================
- Coverage     50.01%   48.95%   -1.06%     
- Complexity      620      637      +17     
============================================
  Files           155      156       +1     
  Lines          4243     4416     +173     
  Branches        269      293      +24     
============================================
+ Hits           2122     2162      +40     
- Misses         1885     2003     +118     
- Partials        236      251      +15     
```

| [Files](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser) | Coverage Δ | |
|---|---|---|
| [...c/main/kotlin/social/bigbone/api/entity/Account.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvYXBpL2VudGl0eS9BY2NvdW50Lmt0) | `85.48% <ø> (ø)` | |
| [...otlin/social/bigbone/api/entity/data/Visibility.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvYXBpL2VudGl0eS9kYXRhL1Zpc2liaWxpdHkua3Q=) | `100.00% <100.00%> (ø)` | |
| [.../kotlin/social/bigbone/api/method/StatusMethods.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvYXBpL21ldGhvZC9TdGF0dXNNZXRob2RzLmt0) | `75.25% <100.00%> (ø)` | |
| [...kotlin/social/bigbone/api/method/AccountMethods.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvYXBpL21ldGhvZC9BY2NvdW50TWV0aG9kcy5rdA==) | `71.21% <61.40%> (-4.10%)` | :arrow_down: |
| [.../main/kotlin/social/bigbone/rx/RxAccountMethods.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS1yeC9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvcngvUnhBY2NvdW50TWV0aG9kcy5rdA==) | `0.00% <0.00%> (ø)` | |
| [...lin/social/bigbone/api/entity/CredentialAccount.kt](https://app.codecov.io/gh/andregasser/bigbone/pull/411?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andr%C3%A9+Gasser#diff-YmlnYm9uZS9zcmMvbWFpbi9rb3RsaW4vc29jaWFsL2JpZ2JvbmUvYXBpL2VudGl0eS9DcmVkZW50aWFsQWNjb3VudC5rdA==) | `7.59% <7.59%> (ø)` | |

</details>

Patch:
diff --git a/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxAccountMethods.kt b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxAccountMethods.kt
index 59366a5..13875cb 100644
--- a/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxAccountMethods.kt
+++ b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxAccountMethods.kt
@@ -5,10 +5,13 @@ import social.bigbone.MastodonClient
 import social.bigbone.api.Pageable
 import social.bigbone.api.Range
 import social.bigbone.api.entity.Account
+import social.bigbone.api.entity.CredentialAccount
 import social.bigbone.api.entity.Relationship
 import social.bigbone.api.entity.Status
 import social.bigbone.api.entity.Token
+import social.bigbone.api.entity.data.Visibility
 import social.bigbone.api.method.AccountMethods
+import java.time.Duration
 
 /**
  * Reactive implementation of [AccountMethods].
@@ -54,22 +57,66 @@ class RxAccountMethods(client: MastodonClient) {
      * Test to make sure that the user token works.
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#verify_credentials">Mastodon API documentation: methods/accounts/#verify_credentials</a>
      */
-    fun verifyCredentials(): Single<Account> = Single.fromCallable {
+    fun verifyCredentials(): Single<CredentialAccount> = Single.fromCallable {
         accountMethods.verifyCredentials().execute()
     }
 
     /**
      * Update the user’s display and preferences.
+     *
+     * You should use [verifyCredentials] to first obtain plaintext representations from within the source parameter,
+     * then allow the user to edit these plaintext representations before submitting them through this API.
+     * The server will generate the corresponding HTML.
+     *
      * @param displayName The name to display in the user's profile
      * @param note A new biography for the user
      * @param avatar A String containing a base64-encoded image to display as the user's avatar
      *  (e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...)
      * @param header A String containing a base64-encoded image to display as the user's header image
      *  (e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...)
+     * @param locked Whether manual approval of follow requests is required
+     * @param bot Whether the account has a bot flag
+     * @param discoverable Whether the account should be shown in the profile directory
+     * @param hideCollections Whether to hide followers and followed accounts.
+     * @param indexable Whether public posts should be searchable to anyone
+     * @param profileFields The profile fields to be set
+     * @param defaultPostVisibility Default post privacy for authored statuses
+     * @param defaultSensitiveMark Whether to mark authored statuses as sensitive by default
+     * @param defaultLanguage Default language to use for authored statuses (ISO 6391)
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#update_credentials">Mastodon API documentation: methods/accounts/#update_credentials</a>
      */
-    fun updateCredentials(displayName: String?, note: String?, avatar: String?, header: String?): Single<Account> =
-        Single.fromCallable { accountMethods.updateCredentials(displayName, note, avatar, header).execute() }
+    fun updateCredentials(
+        displayName: String?,
+        note: String?,
+        avatar: String?,
+        header: String?,
+        locked: Boolean?,
+        bot: Boolean?,
+        discoverable: Boolean?,
+        hideCollections: Boolean?,
+        indexable: Boolean?,
+        profileFields: AccountMethods.ProfileFields?,
+        defaultPostVisibility: Visibility?,
+        defaultSensitiveMark: Boolean?,
+        defaultLanguage: String?
+    ): Single<CredentialAccount> = Single.fromCallable {
+        accountMethods.updateCredentials(
+            displayName = displayName,
+            note = note,
+            avatar = avatar,
+            header = header,
+            locked = locked,
+            bot = bot,
+            discoverable = discoverable,
+            hideCollections = hideCollections,
+            indexable = indexable,
+            profileFields = profileFields,
+            defaultPostVisibility = defaultPostVisibility,
+            defaultSensitiveMark = defaultSensitiveMark,
+            defaultLanguage = defaultLanguage
+        ).execute()
+    }
 
     /**
      * Accounts which follow the given account, if network is not hidden by the account owner.
@@ -95,11 +142,15 @@ class RxAccountMethods(client: MastodonClient) {
 
     /**
      * Statuses posted to the given account.
+     *
      * @param accountId ID of the account to look up
      * @param onlyMedia Filter out statuses without attachments.
      * @param excludeReplies Filter out statuses in reply to a different account.
+     * @param excludeReblogs Filter out boosts from the response.
      * @param pinned Filter for pinned statuses only.
+     * @param filterByTaggedWith Filter for statuses using a specific hashtag.
      * @param range optional Range for the pageable return value
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#statuses">Mastodon API documentation: methods/accounts/#statuses</a>
      */
     @JvmOverloads
@@ -107,19 +158,47 @@ class RxAccountMethods(client: MastodonClient) {
         accountId: String,
         onlyMedia: Boolean = false,
         excludeReplies: Boolean = false,
+        excludeReblogs: Boolean = false,
         pinned: Boolean = false,
+        filterByTaggedWith: String? = null,
         range: Range = Range()
     ): Single<Pageable<Status>> = Single.fromCallable {
-        accountMethods.getStatuses(accountId, onlyMedia, excludeReplies, pinned, range).execute()
+        accountMethods.getStatuses(
+            accountId = accountId,
+            onlyMedia = onlyMedia,
+            excludeReplies = excludeReplies,
+            excludeReblogs = excludeReblogs,
+            pinned = pinned,
+            filterByTaggedWith = filterByTaggedWith,
+            range = range
+        ).execute()
     }
 
     /**
      * Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
+     *
      * @param accountId ID of the account to follow
+     * @param includeReblogs Receive this account’s reblogs in home timeline? Defaults to true if null.
+     * @param notifyOnStatus Receive notifications when this account posts a status? Defaults to false if null.
+     * @param filterForLanguages Filter received statuses for these languages.
+     * If not provided, you will receive this account’s posts in all languages.
+     *
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#follow">Mastodon API documentation: methods/accounts/#follow</a>
      */
-    fun followAccount(accountId: String): Single<Relationship> = Single.fromCallable {
-        accountMethods.followAccount(accountId).execute()
+    @JvmOverloads
+    fun followAccount(
+        accountId: String,
+        includeReblogs: Boolean? = null,
+        notifyOnStatus: Boolean? = null,
+        filterForLanguages: List<String>? = null
+    ): Single<Relationship> = Single.fromCallable {
+        accountMethods.followAccount(
+            accountId = accountId,
+            includeReblogs = includeReblogs,
+            notifyOnStatus = notifyOnStatus,
+            filterForLanguages = filterForLanguages
+        ).execute()
     }
 
     /**
@@ -150,12 +229,26 @@ class RxAccountMethods(client: MastodonClient) {
     }
 
     /**
-     * Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).
+     * Mute the given account.
+     * Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).
+     *
      * @param accountId ID of the account to mute
+     * @param muteNotifications Mute notifications in addition to statuses? Defaults to true.
+     * @param muteDuration How long the mute should last, in seconds. Defaults to null (mute indefinitely)
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#mute">Mastodon API documentation: methods/accounts/#mute</a>
      */
-    fun muteAccount(accountId: String): Single<Relationship> = Single.fromCallable {
-        accountMethods.muteAccount(accountId).execute()
+    @JvmOverloads
+    fun muteAccount(
+        accountId: String,
+        muteNotifications: Boolean? = null,
+        muteDuration: Duration? = null
+    ): Single<Relationship> = Single.fromCallable {
+        accountMethods.muteAccount(
+            accountId = accountId,
+            muteNotifications = muteNotifications,
+            muteDuration = muteDuration
+        ).execute()
     }
 
     /**
@@ -169,21 +262,45 @@ class RxAccountMethods(client: MastodonClient) {
 
     /**
      * Find out whether a given account is followed, blocked, muted, etc.
+     *
      * @param accountIds List of IDs of the accounts to check
+     * @param includeSuspended Whether relationships should be returned for suspended users. Defaults to false if not supplied.
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#relationships">Mastodon API documentation: methods/accounts/#relationships</a>
      */
-    fun getRelationships(accountIds: List<String>): Single<List<Relationship>> = Single.fromCallable {
-        accountMethods.getRelationships(accountIds).execute()
+    @JvmOverloads
+    fun getRelationships(
+        accountIds: List<String>,
+        includeSuspended: Boolean? = null
+    ): Single<List<Relationship>> = Single.fromCallable {
+        accountMethods.getRelationships(accountIds, includeSuspended).execute()
     }
 
     /**
      * Search for matching accounts by username or display name.
+     *
      * @param query the search query
-     * @param limit the maximum number of matching accounts to return (default: 40)
+     * @param limit the maximum number of matching accounts to return (default: 40. max: 80)
+     * @param offset skips this amount of results
+     * @param limitToFollowing Limit the search to users you are following. Defaults to false if not supplied.
+     * @param attemptWebFingerLookup Use this when [query] is an exact address. Defaults to false if not supplied.
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#search">Mastodon API documentation: methods/accounts/#search</a>
      */
     @JvmOverloads
-    fun searchAccounts(query: String, limit: Int? = null): Single<List<Account>> = Single.fromCallable {
-        accountMethods.searchAccounts(query, limit).execute()
+    fun searchAccounts(
+        query: String,
+        limit: Int? = null,
+        offset: Int? = null,
+        limitToFollowing: Boolean? = null,
+        attemptWebFingerLookup: Boolean? = null
+    ): Single<List<Account>> = Single.fromCallable {
+        accountMethods.searchAccounts(
+            query = query,
+            limit = limit,
+            offset = offset,
+            limitToFollowing = limitToFollowing,
+            attemptWebFingerLookup = attemptWebFingerLookup
+        ).execute()
     }
 }
diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt
index 5daec56..45c0479 100644
--- a/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt
+++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/Account.kt
@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
 import social.bigbone.DateTimeSerializer
 import social.bigbone.PrecisionDateTime
 import social.bigbone.PrecisionDateTime.InvalidPrecisionDateTime
+import social.bigbone.PrecisionDateTime.ValidPrecisionDateTime.ExactTime
 
 /**
  * Represents a user of Mastodon and their associated profile.
@@ -14,6 +15,7 @@ import social.bigbone.PrecisionDateTime.InvalidPrecisionDateTime
 data class Account(
     /**
      * The account id.
+     * String cast from an Integer, but not guaranteed to be a number.
      */
     @SerialName("id")
     val id: String = "0",
@@ -25,7 +27,8 @@ data class Account(
     val username: String = "",
 
     /**
-     * The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
+     * The Webfinger account URI.
+     * Equal to [username] for local users, or username@domain for remote users.
      */
     @SerialName("acct")
     val acct: String = "",
@@ -44,30 +47,37 @@ data class Account(
 
     /**
      * The profile’s bio or description.
+     * String containing HTML.
      */
     @SerialName("note")
     val note: String = "",
 
     /**
      * An image icon that is shown next to statuses and in the profile.
+     * String containing a URL.
      */
     @SerialName("avatar")
     val avatar: String = "",
 
     /**
-     * A static version of the avatar. Equal to [avatar] if its value is a static image; different if avatar is an animated GIF.
+     * A static version of the avatar.
+     * Equal to [avatar] if its value is a static image; different if [avatar] is an animated GIF.
+     * String containing a URL.
      */
     @SerialName("avatar_static")
     val avatarStatic: String = "",
 
     /**
      * An image banner that is shown above the profile and in profile cards.
+     * String containing a URL.
      */
     @SerialName("header")
     val header: String = "",
 
     /**
-     * A static version of the header. Equal to [header] if its value is a static image; different if header is an animated GIF.
+     * A static version of the header.
+     * Equal to [header] if its value is a static image; different if [header] is an animated GIF.
+     * String containing a URL.
      */
     @SerialName("header_static")
     val headerStatic: String = "",
@@ -97,7 +107,7 @@ data class Account(
     val isBot: Boolean = false,
 
     /**
-     *  Indicates that the account represents a Group actor.
+     * Indicates that the account represents a Group actor.
      */
     @SerialName("group")
     val isGroup: Boolean = false,
@@ -135,6 +145,7 @@ data class Account(
 
     /**
      * When the account was created.
+     * Resolves to midnight of the creation date instead of an exact time even if [ExactTime] is returned.
      */
     @SerialName("created_at")
     @Serializable(with = DateTimeSerializer::class)
@@ -142,6 +153,7 @@ data class Account(
 
     /**
      * When the most recent status was posted.
+     * Will only have day-accuracy as no time is returned by the Mastodon API.
      */
     @SerialName("last_status_at")
     @Serializable(with = DateTimeSerializer::class)
diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/CredentialAccount.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/CredentialAccount.kt
new file mode 100644
index 0000000..59a4d11
--- /dev/null
+++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/CredentialAccount.kt
@@ -0,0 +1,267 @@
+package social.bigbone.api.entity
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import social.bigbone.DateTimeSerializer
+import social.bigbone.PrecisionDateTime
+import social.bigbone.PrecisionDateTime.InvalidPrecisionDateTime
+import social.bigbone.PrecisionDateTime.ValidPrecisionDateTime.ExactTime
+import social.bigbone.api.entity.data.Visibility
+import social.bigbone.api.method.AccountMethods
+
+/**
+ * Represents a user of Mastodon and their associated profile.
+ *
+ * @see <a href="https://docs.joinmastodon.org/entities/Account/#CredentialAccount">Mastodon API Account#CredentialAccount</a>
+ */
+@Serializable
+data class CredentialAccount(
+    /**
+     * The account id.
+     * String cast from an Integer, but not guaranteed to be a number.
+     */
+    @SerialName("id")
+    val id: String = "0",
+
+    /**
+     * The username of the account, not including domain.
+     */
+    @SerialName("username")
+    val username: String = "",
+
+    /**
+     * The Webfinger account URI.
+     * Equal to [username] for local users, or username@domain for remote users.
+     */
+    @SerialName("acct")
+    val acct: String = "",
+
+    /**
+     * The location of the user’s profile page.
+     */
+    @SerialName("url")
+    val url: String = "",
+
+    /**
+     * The profile’s display name.
+     */
+    @SerialName("display_name")
+    val displayName: String = "",
+
+    /**
+     * The profile’s bio or description.
+     * String containing HTML.
+     */
+    @SerialName("note")
+    val note: String = "",
+
+    /**
+     * An image icon that is shown next to statuses and in the profile.
+     * String containing a URL.
+     */
+    @SerialName("avatar")
+    val avatar: String = "",
+
+    /**
+     * A static version of the avatar.
+     * Equal to [avatar] if its value is a static image; different if [avatar] is an animated GIF.
+     * String containing a URL.
+     */
+    @SerialName("avatar_static")
+    val avatarStatic: String = "",
+
+    /**
+     * An image banner that is shown above the profile and in profile cards.
+     * String containing a URL.
+     */
+    @SerialName("header")
+    val header: String = "",
+
+    /**
+     * A static version of the header.
+     * Equal to [header] if its value is a static image; different if [header] is an animated GIF.
+     * String containing a URL.
+     */
+    @SerialName("header_static")
+    val headerStatic: String = "",
+
+    /**
+     * Whether the account manually approves follow requests.
+     */
+    @SerialName("locked")
+    val isLocked: Boolean = false,
+
+    /**
+     * Additional metadata attached to a profile as name-value pairs.
+     */
+    @SerialName("fields")
+    val fields: List<Field> = emptyList(),
+
+    /**
+     * Custom emoji entities to be used when rendering the profile.
+     */
+    @SerialName("emojis")
+    val emojis: List<CustomEmoji> = emptyList(),
+
+    /**
+     * Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot.
+     */
+    @SerialName("bot")
+    val isBot: Boolean = false,
+
+    /**
+     * Indicates that the account represents a Group actor.
+     */
+    @SerialName("group")
+    val isGroup: Boolean = false,
+
+    /**
+     * Whether the account has opted into discovery features such as the profile directory.
+     */
+    @SerialName("discoverable")
+    val isDiscoverable: Boolean? = null,
+
+    /**
+     * Whether the local user has opted out of being indexed by search engines.
+     */
+    @SerialName("noindex")
+    val isNotIndexed: Boolean? = null,
+
+    /**
+     * Indicates that the profile is currently inactive and that its user has moved to a new account.
+     */
+    @SerialName("moved")
+    val moved: CredentialAccount? = null,
+
+    /**
+     * An extra attribute returned only when an account is suspended.
+     */
+    @SerialName("suspended")
+    val isSuspended: Boolean? = null,
+
+    /**
+     * An extra attribute returned only when an account is silenced.
+     * If true, indicates that the account should be hidden behind a warning screen.
+     */
+    @SerialName("limited")
+    val isLimited: Boolean? = null,
+
+    /**
+     * When the account was created.
+     * Resolves to midnight of the creation date instead of an exact time even if [ExactTime] is returned.
+     */
+    @SerialName("created_at")
+    @Serializable(with = DateTimeSerializer::class)
+    val createdAt: PrecisionDateTime = InvalidPrecisionDateTime.Unavailable,
+
+    /**
+     * When the most recent status was posted.
+     * Will only have day-accuracy as no time is returned by the Mastodon API.
+     */
+    @SerialName("last_status_at")
+    @Serializable(with = DateTimeSerializer::class)
+    val lastStatusAt: PrecisionDateTime = InvalidPrecisionDateTime.Unavailable,
+
+    /**
+     * How many statuses are attached to this account.
+     */
+    @SerialName("statuses_count")
+    val statusesCount: Long = 0,
+
+    /**
+     * The reported followers of this profile.
+     */
+    @SerialName("followers_count")
+    val followersCount: Long = 0,
+
+    /**
+     * The reported follows of this profile.
+     */
+    @SerialName("following_count")
+    val followingCount: Long = 0,
+
+    /**
+     * An extra attribute that contains source values
+     * to be used with API methods that [AccountMethods.verifyCredentials] and [AccountMethods.updateCredentials].
+     */
+    @SerialName("source")
+    val source: Source,
+
+    /**
+     * The role assigned to the currently authorized user.
+     */
+    @SerialName("role")
+    val role: Role? = null
+) {
+    /**
+     * Specifies a name-value pair as used in [fields] of the [CredentialAccount] entity.
+     */
+    @Serializable
+    data class Field(
+        /**
+         * The key of a given field’s key-value pair.
+         */
+        @SerialName("name")
+        val name: String = "",
+
+        /**
+         * The value associated with the [name] key (HTML string).
+         */
+        @SerialName("value")
+        val value: String = "",
+
+        /**
+         * Timestamp of when the server verified a URL value for a rel=“me” link.
+         * [InvalidPrecisionDateTime.Unavailable] if [value] is a verified URL.
+         */
+        @SerialName("verified_at")
+        @Serializable(with = DateTimeSerializer::class)
+        val verifiedAt: PrecisionDateTime = InvalidPrecisionDateTime.Unavailable
+    )
+
+    /**
+     * An extra attribute that contains source values
+     * to be used with API methods that [AccountMethods.verifyCredentials] and [AccountMethods.updateCredentials].
+     * @see source
+     */
+    @Serializable
+    data class Source(
+        /**
+         * Profile bio.
+         * String containing plain-text content instead of HTML.
+         */
+        @SerialName("note")
+        val note: String,
+
+        /**
+         * Metadata about the account.
+         */
+        @SerialName("fields")
+        val fields: List<Field>,
+
+        /**
+         * The default post privacy to be used for new statuses.
+         */
+        @SerialName("privacy")
+        val privacy: Visibility,
+
+        /**
+         * Whether new statuses should be marked sensitive by default.
+         */
+        @SerialName("sensitive")
+        val sensitive: Boolean,
+
+        /**
+         * The default posting language for new statuses.
+         * String with ISO 639-1 language two-letter code or empty.
+         */
+        @SerialName("language")
+        val language: String = "",
+
+        /**
+         * The number of pending follow requests.
+         */
+        @SerialName("follow_requests_count")
+        val followRequestsCount: Int
+    )
+}
diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/data/Visibility.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/data/Visibility.kt
index acc5a18..e658aff 100644
--- a/bigbone/src/main/kotlin/social/bigbone/api/entity/data/Visibility.kt
+++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/data/Visibility.kt
@@ -1,5 +1,6 @@
 package social.bigbone.api.entity.data
 
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 
@@ -18,5 +19,8 @@ enum class Visibility {
     PRIVATE,
 
     @SerialName("direct")
-    DIRECT
+    DIRECT;
+
+    @OptIn(ExperimentalSerializationApi::class)
+    val apiName: String get() = serializer().descriptor.getElementName(ordinal)
 }
diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/AccountMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/AccountMethods.kt
index da0691f..0b5569c 100644
--- a/bigbone/src/main/kotlin/social/bigbone/api/method/AccountMethods.kt
+++ b/bigbone/src/main/kotlin/social/bigbone/api/method/AccountMethods.kt
@@ -6,9 +6,12 @@ import social.bigbone.Parameters
 import social.bigbone.api.Pageable
 import social.bigbone.api.Range
 import social.bigbone.api.entity.Account
+import social.bigbone.api.entity.CredentialAccount
 import social.bigbone.api.entity.Relationship
 import social.bigbone.api.entity.Status
 import social.bigbone.api.entity.Token
+import social.bigbone.api.entity.data.Visibility
+import java.time.Duration
 
 /**
  * Allows access to API methods with endpoints having an "api/vX/accounts" prefix.
@@ -64,37 +67,125 @@ class AccountMethods(private val client: MastodonClient) {
      * Test to make sure that the user token works.
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#verify_credentials">Mastodon API documentation: methods/accounts/#verify_credentials</a>
      */
-    fun verifyCredentials(): MastodonRequest<Account> {
+    fun verifyCredentials(): MastodonRequest<CredentialAccount> {
         return client.getMastodonRequest(
             endpoint = "api/v1/accounts/verify_credentials",
             method = MastodonClient.Method.GET
         )
     }
 
+    /**
+     * Name of a profile field used in [ProfileFields].
+     * Must not be longer than 255 characters.
+     */
+    @JvmInline
+    value class ProfileFieldName(val name: String) {
+        init {
+            require(name.length <= 255) {
+                "Name of profile field must not be longer than 255 characters but was: $name (${name.length} characters)."
+            }
+        }
+    }
+
+    /**
+     * Value of a profile field used in [ProfileFields].
+     * Must not be longer than 255 characters.
+     */
+    @JvmInline
+    value class ProfileFieldValue(val value: String) {
+        init {
+            require(value.length <= 255) {
+                "Value of profile field must not be longer than 255 characters but was: $value (${value.length} characters)."
+            }
+        }
+    }
+
+    /**
+     * Profile fields that can be set in [updateCredentials].
+     *
+     * At most four fields are allowed. Each of them has a max key and value length of 255 characters.
+     */
+    data class ProfileFields(
+        val first: Pair<ProfileFieldName, ProfileFieldValue>? = null,
+        val second: Pair<ProfileFieldName, ProfileFieldValue>? = null,
+        val third: Pair<ProfileFieldName, ProfileFieldValue>? = null,
+        val fourth: Pair<ProfileFieldName, ProfileFieldValue>? = null
+    ) {
+        fun toParameters(parameters: Parameters = Parameters()): Parameters {
+            fun appendField(index: Int, name: ProfileFieldName, value: ProfileFieldValue) {
+                parameters.append("fields_attributes[$index][name]", name.name)
+                parameters.append("fields_attributes[$index][value]", value.value)
+            }
+
+            return parameters.apply {
+                first?.let { (name, value) -> appendField(0, name, value) }
+                second?.let { (name, value) -> appendField(1, name, value) }
+                third?.let { (name, value) -> appendField(2, name, value) }
+                fourth?.let { (name, value) -> appendField(3, name, value) }
+            }
+        }
+    }
+
     /**
      * Update the user’s display and preferences.
+     *
+     * You should use [verifyCredentials] to first obtain plaintext representations from within the source parameter,
+     * then allow the user to edit these plaintext representations before submitting them through this API.
+     * The server will generate the corresponding HTML.
+     *
      * @param displayName The name to display in the user's profile
      * @param note A new biography for the user
      * @param avatar A String containing a base64-encoded image to display as the user's avatar
      *  (e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...)
      * @param header A String containing a base64-encoded image to display as the user's header image
      *  (e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...)
+     * @param locked Whether manual approval of follow requests is required
+     * @param bot Whether the account has a bot flag
+     * @param discoverable Whether the account should be shown in the profile directory
+     * @param hideCollections Whether to hide followers and followed accounts.
+     * @param indexable Whether public posts should be searchable to anyone
+     * @param profileFields The profile fields to be set
+     * @param defaultPostVisibility Default post privacy for authored statuses
+     * @param defaultSensitiveMark Whether to mark authored statuses as sensitive by default
+     * @param defaultLanguage Default language to use for authored statuses (ISO 6391)
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#update_credentials">Mastodon API documentation: methods/accounts/#update_credentials</a>
      */
     fun updateCredentials(
         displayName: String?,
         note: String?,
         avatar: String?,
-        header: String?
-    ): MastodonRequest<Account> {
+        header: String?,
+        locked: Boolean?,
+        bot: Boolean?,
+        discoverable: Boolean?,
+        hideCollections: Boolean?,
+        indexable: Boolean?,
+        profileFields: ProfileFields?,
+        defaultPostVisibility: Visibility?,
+        defaultSensitiveMark: Boolean?,
+        defaultLanguage: String?
+    ): MastodonRequest<CredentialAccount> {
         return client.getMastodonRequest(
             endpoint = "api/v1/accounts/update_credentials",
             method = MastodonClient.Method.PATCH,
             parameters = Parameters().apply {
-                displayName?.let { append("display_name", it) }
-                note?.let { append("note", it) }
-                avatar?.let { append("avatar", it) }
-                header?.let { append("header", it) }
+                displayName?.let { append("display_name", displayName) }
+                note?.let { append("note", note) }
+                avatar?.let { append("avatar", avatar) }
+                header?.let { append("header", header) }
+
+                locked?.let { append("locked", locked) }
+                bot?.let { append("bot", bot) }
+                discoverable?.let { append("discoverable", discoverable) }
+                hideCollections?.let { append("hide_collections", hideCollections) }
+                indexable?.let { append("indexable", indexable) }
+
+                profileFields?.toParameters(this)
+
+                defaultPostVisibility?.let { append("source[privacy]", defaultPostVisibility.apiName) }
+                defaultSensitiveMark?.let { append("source[sensitive]", defaultSensitiveMark) }
+                defaultLanguage?.let { append("source[language]", defaultLanguage) }
             }
         )
     }
@@ -131,11 +222,15 @@ class AccountMethods(private val client: MastodonClient) {
 
     /**
      * Statuses posted to the given account.
+     *
      * @param accountId ID of the account to look up
      * @param onlyMedia Filter out statuses without attachments.
      * @param excludeReplies Filter out statuses in reply to a different account.
+     * @param excludeReblogs Filter out boosts from the response.
      * @param pinned Filter for pinned statuses only.
+     * @param filterByTaggedWith Filter for statuses using a specific hashtag.
      * @param range optional Range for the pageable return value
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#statuses">Mastodon API documentation: methods/accounts/#statuses</a>
      */
     @JvmOverloads
@@ -143,7 +238,9 @@ class AccountMethods(private val client: MastodonClient) {
         accountId: String,
         onlyMedia: Boolean = false,
         excludeReplies: Boolean = false,
+        excludeReblogs: Boolean = false,
         pinned: Boolean = false,
+        filterByTaggedWith: String? = null,
         range: Range = Range()
     ): MastodonRequest<Pageable<Status>> {
         return client.getPageableMastodonRequest(
@@ -153,19 +250,38 @@ class AccountMethods(private val client: MastodonClient) {
                 if (onlyMedia) append("only_media", true)
                 if (pinned) append("pinned", true)
                 if (excludeReplies) append("exclude_replies", true)
+                if (excludeReblogs) append("exclude_reblogs", true)
+                filterByTaggedWith?.takeIf { it.isNotBlank() }?.let { append("tagged", filterByTaggedWith) }
             }
         )
     }
 
     /**
      * Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
+     *
      * @param accountId ID of the account to follow
+     * @param includeReblogs Receive this account’s reblogs in home timeline? Defaults to true if null.
+     * @param notifyOnStatus Receive notifications when this account posts a status? Defaults to false if null.
+     * @param filterForLanguages Filter received statuses for these languages. ISO 639-1 language two-letter codes.
+     * If not provided, you will receive this account’s posts in all languages.
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#follow">Mastodon API documentation: methods/accounts/#follow</a>
      */
-    fun followAccount(accountId: String): MastodonRequest<Relationship> {
+    @JvmOverloads
+    fun followAccount(
+        accountId: String,
+        includeReblogs: Boolean? = null,
+        notifyOnStatus: Boolean? = null,
+        filterForLanguages: List<String>? = null
+    ): MastodonRequest<Relationship> {
         return client.getMastodonRequest(
             endpoint = "api/v1/accounts/$accountId/follow",
-            method = MastodonClient.Method.POST
+            method = MastodonClient.Method.POST,
+            parameters = Parameters().apply {
+                includeReblogs?.let { append("reblogs", includeReblogs) }
+                notifyOnStatus?.let { append("notify", notifyOnStatus) }
+                filterForLanguages?.takeIf { it.isNotEmpty() }?.let { append("languages", filterForLanguages) }
+            }
         )
     }
 
@@ -206,14 +322,28 @@ class AccountMethods(private val client: MastodonClient) {
     }
 
     /**
-     * Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).
+     * Mute the given account.
+     * Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).
+     *
      * @param accountId ID of the account to mute
+     * @param muteNotifications Mute notifications in addition to statuses? Defaults to true.
+     * @param muteDuration How long the mute should last, in seconds. Defaults to null (mute indefinitely)
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#mute">Mastodon API documentation: methods/accounts/#mute</a>
      */
-    fun muteAccount(accountId: String): MastodonRequest<Relationship> {
+    @JvmOverloads
+    fun muteAccount(
+        accountId: String,
+        muteNotifications: Boolean? = null,
+        muteDuration: Duration? = null
+    ): MastodonRequest<Relationship> {
         return client.getMastodonRequest(
             endpoint = "api/v1/accounts/$accountId/mute",
-            method = MastodonClient.Method.POST
+            method = MastodonClient.Method.POST,
+            parameters = Parameters().apply {
+                muteNotifications?.let { append("notifications", muteNotifications) }
+                muteDuration?.let { append("duration", muteDuration.seconds) }
+            }
         )
     }
 
@@ -231,27 +361,45 @@ class AccountMethods(private val client: MastodonClient) {
 
     /**
      * Find out whether a given account is followed, blocked, muted, etc.
+     *
      * @param accountIds List of IDs of the accounts to check
+     * @param includeSuspended Whether relationships should be returned for suspended users. Defaults to false if not supplied.
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#relationships">Mastodon API documentation: methods/accounts/#relationships</a>
      */
-    fun getRelationships(accountIds: List<String>): MastodonRequest<List<Relationship>> {
+    @JvmOverloads
+    fun getRelationships(
+        accountIds: List<String>,
+        includeSuspended: Boolean? = null
+    ): MastodonRequest<List<Relationship>> {
         return client.getMastodonRequestForList(
             endpoint = "api/v1/accounts/relationships",
             method = MastodonClient.Method.GET,
-            parameters = Parameters().append("id", accountIds)
+            parameters = Parameters().apply {
+                append("id", accountIds)
+                includeSuspended?.let { append("with_suspended", includeSuspended) }
+            }
         )
     }
 
     /**
      * Search for matching accounts by username or display name.
+     *
      * @param query the search query
-     * @param limit the maximum number of matching accounts to return (default: 40)
+     * @param limit the maximum number of matching accounts to return (default: 40. max: 80)
+     * @param offset skips this amount of results
+     * @param limitToFollowing Limit the search to users you are following. Defaults to false if not supplied.
+     * @param attemptWebFingerLookup Use this when [query] is an exact address. Defaults to false if not supplied.
+     *
      * @see <a href="https://docs.joinmastodon.org/methods/accounts/#search">Mastodon API documentation: methods/accounts/#search</a>
      */
     @JvmOverloads
     fun searchAccounts(
         query: String,
-        limit: Int? = null
+        limit: Int? = null,
+        offset: Int? = null,
+        limitToFollowing: Boolean? = null,
+        attemptWebFingerLookup: Boolean? = null
     ): MastodonRequest<List<Account>> {
         return client.getMastodonRequestForList(
             endpoint = "api/v1/accounts/search",
@@ -259,6 +407,9 @@ class AccountMethods(private val client: MastodonClient) {
             parameters = Parameters().apply {
                 append("q", query)
                 limit?.let { append("limit", limit) }
+                offset?.let { append("offset", offset) }
+                limitToFollowing?.let { append("following", limitToFollowing) }
+                attemptWebFingerLookup?.let { append("resolve", attemptWebFingerLookup) }
             }
         )
     }
diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/StatusMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/StatusMethods.kt
index b4a4153..7774c6c 100644
--- a/bigbone/src/main/kotlin/social/bigbone/api/method/StatusMethods.kt
+++ b/bigbone/src/main/kotlin/social/bigbone/api/method/StatusMethods.kt
@@ -137,7 +137,7 @@ class StatusMethods(private val client: MastodonClient) {
             method = MastodonClient.Method.POST,
             parameters = Parameters().apply {
                 append("status", status)
-                append("visibility", visibility.name.lowercase())
+                append("visibility", visibility.apiName)
                 inReplyToId?.let { append("in_reply_to_id", it) }
                 mediaIds?.let { append("media_ids", it) }
                 append("sensitive", sensitive)
@@ -181,7 +181,7 @@ class StatusMethods(private val client: MastodonClient) {
                 append("status", status)
                 append("poll[options]", pollData.options)
                 append("poll[expires_in]", pollData.expiresIn)
-                append("visibility", visibility.name.lowercase())
+                append("visibility", visibility.apiName)
                 append("poll[multiple]", pollData.multiple ?: false)
                 append("poll[hide_totals]", pollData.hideTotals ?: false)
                 inReplyToId?.let { append("in_reply_to_id", it) }
@@ -232,7 +232,7 @@ class StatusMethods(private val client: MastodonClient) {
             parameters = Parameters().apply {
                 append("status", status)
                 append("scheduled_at", scheduledAt.toString())
-                append("visibility", visibility.name.lowercase())
+                append("visibility", visibility.apiName)
                 inReplyToId?.let { append("in_reply_to_id", it) }
                 mediaIds?.let { append("media_ids", it) }
                 append("sensitive", sensitive)
@@ -283,7 +283,7 @@ class StatusMethods(private val client: MastodonClient) {
                 append("scheduled_at", scheduledAt.toString())
                 append("poll[options]", pollData.options)
                 append("poll[expires_in]", pollData.expiresIn)
-                append("visibility", visibility.name.lowercase())
+                append("visibility", visibility.apiName)
                 append("poll[multiple]", pollData.multiple ?: false)
                 append("poll[hide_totals]", pollData.multiple ?: false)
                 inReplyToId?.let { append("in_reply_to_id", it) }
@@ -328,7 +328,7 @@ class StatusMethods(private val client: MastodonClient) {
             endpoint = "api/v1/statuses/$statusId/reblog",
             method = MastodonClient.Method.POST,
             parameters = Parameters().apply {
-                append("visibility", visibility.name.lowercase())
+                append("visibility", visibility.apiName)
             }
         )
     }
diff --git a/docs/api-coverage/accounts.md b/docs/api-coverage/accounts.md
index 09bff98..5600a5a 100644
--- a/docs/api-coverage/accounts.md
+++ b/docs/api-coverage/accounts.md
@@ -11,125 +11,28 @@ Methods concerning accounts and profiles.
 
 <a href="https://docs.joinmastodon.org/methods/accounts/" target="_blank">https://docs.joinmastodon.org/methods/accounts/</a>
 
-<table style="width:100%;table-layout:fixed;">
-  <tr>
-    <th style="width:45%;text-align:left;">Endpoint</th>
-    <th style="width:10%;text-align:center;">Status</th>
-    <th style="width:45%;text-align:left;">Comments</th>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts</code><br>Register an account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;">Error response with details not propagated. Otherwise, fully implemented.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/verify_credentials</code><br>Verify account credentials</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;">Should return <code>CredentialAccount</code> instead of <code>Account</code>.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>PATCH /api/v1/accounts/update_credentials</code><br>Update account credentials</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>locked</code>, <code>bot</code>, <code>discoverable</code>, <code>fields_attributes</code>, <code>source</code> query parameters are missing.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id</code><br>Get account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/statuses</code><br>Get account's statuses</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>exclude_reblogs</code>, <code>tagged</code> query parameters are missing.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/followers</code><br>Get account's followers</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/following</code><br>Get account's following</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/featured_tags</code><br>Get account's featured tags</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/lists</code><br>Get lists containing this account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/:id/follow</code><br>Follow account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>reblogs</code>, <code>notify</code>, <code>languages</code> query parameters are missing.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/unfollow</code><br>Unfollow account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/remove_from_followers</code><br>Remove account from followers</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/block</code><br>Block account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/unblock</code><br>Unblock account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/mute</code><br>Mute account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>notifications</code>, <code>duration</code> form data parameters are missing.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/unmute</code><br>Unmute account</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/green16.png"></td>
-    <td style="width:45%;text-align:left;">Fully supported.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/pin</code><br>Feature account on your profile</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/unpin</code><br>Unfeature account from profile</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>POST /api/v1/accounts/:id/note</code><br>Set private note on profile</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/relationships</code><br>Check relationships to other accounts</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>with_suspended</code> parameter missing from query parameters. Will default to false.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/familiar_followers</code><br>Find familiar followers</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/search</code><br>Search for matching accounts</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/orange16.png"></td>
-    <td style="width:45%;text-align:left;"><code>offset</code> (defaults to 0), <code>resolve</code> (defaults to false), <code>following</code> (defaults to false) query parameters are missing.</td>
-  </tr>
-  <tr>
-    <td style="width:45%;text-align:left;"><code>GET /api/v1/accounts/lookup</code><br>Lookup account ID from Webfinger address</td>
-    <td style="width:10%;text-align:center;"><img src="/assets/red16.png"></td>
-    <td style="width:45%;text-align:left;">Not implemented yet.</td>
-  </tr>
-</table>
+| Method                                            | Description                              |               Status               | Comments                                                                  | 
+|:--------------------------------------------------|------------------------------------------|:----------------------------------:|---------------------------------------------------------------------------|
+| `POST /api/v1/accounts`                           | Register an account                      | <img src="/assets/orange16.png" /> | Error response with details not propagated. Otherwise, fully implemented. |
+| `GET /api/v1/accounts/verify_credentials`         | Verify account credentials               | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `PATCH /api/v1/accounts/update_credentials`       | Update account credentials               | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/:id`                        | Get account                              | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/:id/statuses`               | Get account's statuses                   | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/:id/followers`              | Get account's followers                  | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/:id/following`              | Get account's following                  | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/:id/featured_tags`          | Get account's featured tags              |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `GET /api/v1/accounts/:id/lists`                  | Get lists containing this account        |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `GET /api/v1/accounts/:id/follow`                 | Follow account                           | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/unfollow`              | Unfollow account                         | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/remove_from_followers` | Remove account from followers            |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `POST /api/v1/accounts/:id/block`                 | Block account                            | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/unblock`               | Unblock account                          | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/mute`                  | Mute account                             | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/unmute`                | Unmute account                           | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `POST /api/v1/accounts/:id/pin`                   | Feature account on your profile          |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `POST /api/v1/accounts/:id/unpin`                 | Unfeature account from profile           |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `POST /api/v1/accounts/:id/note`                  | Set private note on profile              |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `GET /api/v1/accounts/relationships`              | Check relationships to other accounts    | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/familiar_followers`         | Find familiar followers                  |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |
+| `GET /api/v1/accounts/search`                     | Search for matching accounts             | <img src="/assets/green16.png" />  | Fully supported.                                                          |
+| `GET /api/v1/accounts/lookup`                     | Lookup account ID from Webfinger address |  <img src="/assets/red16.png" />   | Not implemented yet.                                                      |


Test patch:
diff --git a/bigbone/src/test/assets/accounts_register_422.json b/bigbone/src/test/assets/accounts_register_422.json
new file mode 100644
index 0000000..41d1fa8
--- /dev/null
+++ b/bigbone/src/test/assets/accounts_register_422.json
@@ -0,0 +1,23 @@
+{
+  "error": "Validation failed: Password can't be blank, Username must contain only letters, numbers and underscores, Agreement must be accepted",
+  "details": {
+    "password": [
+      {
+        "error": "ERR_BLANK",
+        "description": "can't be blank"
+      }
+    ],
+    "username": [
+      {
+        "error": "ERR_INVALID",
+        "description": "must contain only letters, numbers and underscores"
+      }
+    ],
+    "agreement": [
+      {
+        "error": "ERR_ACCEPTED",
+        "description": "must be accepted"
+      }
+    ]
+  }
+}
diff --git a/bigbone/src/test/assets/accounts_register_success.json b/bigbone/src/test/assets/accounts_register_success.json
new file mode 100644
index 0000000..08c8bb6
--- /dev/null
+++ b/bigbone/src/test/assets/accounts_register_success.json
@@ -0,0 +1,6 @@
+{
+  "access_token": "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0",
+  "token_type": "Bearer",
+  "scope": "read write follow push",
+  "created_at": 1573979017
+}
diff --git a/bigbone/src/test/assets/accounts_update_credentials_success.json b/bigbone/src/test/assets/accounts_update_credentials_success.json
new file mode 100644
index 0000000..9091279
--- /dev/null
+++ b/bigbone/src/test/assets/accounts_update_credentials_success.json
@@ -0,0 +1,78 @@
+{
+  "id": "14715",
+  "username": "trwnh",
+  "acct": "trwnh",
+  "display_name": "infinite love ⴳ",
+  "locked": false,
+  "bot": false,
+  "created_at": "2016-11-24T10:02:12.085Z",
+  "note": "<p>i have approximate knowledge of many things. perpetual student. (nb/ace/they)</p><p>xmpp/email: a@trwnh.com<br /><a href=\"https://trwnh.com\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">trwnh.com</span><span class=\"invisible\"></span></a><br />help me live: <a href=\"https://liberapay.com/at\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">liberapay.com/at</span><span class=\"invisible\"></span></a> or <a href=\"https://paypal.me/trwnh\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">paypal.me/trwnh</span><span class=\"invisible\"></span></a></p><p>- my triggers are moths and glitter<br />- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise<br />- dm me if i did something wrong, so i can improve<br />- purest person on fedi, do not lewd in my presence<br />- #1 ami cole fan account</p><p>:fatyoshi:</p>",
+  "url": "https://mastodon.social/@trwnh",
+  "avatar": "https://files.mastodon.social/accounts/avatars/000/014/715/original/34aa222f4ae2e0a9.png",
+  "avatar_static": "https://files.mastodon.social/accounts/avatars/000/014/715/original/34aa222f4ae2e0a9.png",
+  "header": "https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg",
+  "header_static": "https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg",
+  "followers_count": 834,
+  "following_count": 182,
+  "statuses_count": 33760,
+  "last_status_at": "2019-12-01T00:12:08.731Z",
+  "source": {
+    "privacy": "public",
+    "sensitive": false,
+    "language": "",
+    "note": "i have approximate knowledge of many things. perpetual student. (nb/ace/they)\r\n\r\nxmpp/email: a@trwnh.com\r\nhttps://trwnh.com\r\nhelp me live: https://liberapay.com/at or https://paypal.me/trwnh\r\n\r\n- my triggers are moths and glitter\r\n- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise\r\n- dm me if i did something wrong, so i can improve\r\n- purest person on fedi, do not lewd in my presence\r\n- #1 ami cole fan account\r\n\r\n:fatyoshi:",
+    "fields": [
+      {
+        "name": "Website",
+        "value": "https://trwnh.com",
+        "verified_at": "2019-08-29T04:14:55.571+00:00"
+      },
+      {
+        "name": "Sponsor",
+        "value": "https://liberapay.com/at",
+        "verified_at": "2019-11-15T10:06:15.557+00:00"
+      },
+      {
+        "name": "Fan of:",
+        "value": "Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo's Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)",
+        "verified_at": null
+      },
+      {
+        "name": "Main topics:",
+        "value": "systemic analysis, design patterns, anticapitalism, info/tech freedom, theory and philosophy, and otherwise being a genuine and decent wholesome poster. i'm just here to hang out and talk to cool people!",
+        "verified_at": null
+      }
+    ],
+    "follow_requests_count": 0
+  },
+  "emojis": [
+    {
+      "shortcode": "fatyoshi",
+      "url": "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+      "static_url": "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+      "visible_in_picker": true
+    }
+  ],
+  "fields": [
+    {
+      "name": "Website",
+      "value": "<a href=\"https://trwnh.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">trwnh.com</span><span class=\"invisible\"></span></a>",
+      "verified_at": "2019-08-29T04:14:55.571+00:00"
+    },
+    {
+      "name": "Sponsor",
+      "value": "<a href=\"https://liberapay.com/at\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">liberapay.com/at</span><span class=\"invisible\"></span></a>",
+      "verified_at": "2019-11-15T10:06:15.557+00:00"
+    },
+    {
+      "name": "Fan of:",
+      "value": "Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo&apos;s Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)",
+      "verified_at": null
+    },
+    {
+      "name": "Main topics:",
+      "value": "systemic analysis, design patterns, anticapitalism, info/tech freedom, theory and philosophy, and otherwise being a genuine and decent wholesome poster. i&apos;m just here to hang out and talk to cool people!",
+      "verified_at": null
+    }
+  ]
+}
diff --git a/bigbone/src/test/assets/accounts_verify_credentials_success.json b/bigbone/src/test/assets/accounts_verify_credentials_success.json
new file mode 100644
index 0000000..401caa1
--- /dev/null
+++ b/bigbone/src/test/assets/accounts_verify_credentials_success.json
@@ -0,0 +1,78 @@
+{
+  "id": "14715",
+  "username": "trwnh",
+  "acct": "trwnh",
+  "display_name": "infinite love ⴳ",
+  "locked": false,
+  "bot": false,
+  "created_at": "2016-11-24T10:02:12.085Z",
+  "note": "<p>i have approximate knowledge of many things. perpetual student. (nb/ace/they)</p><p>xmpp/email: a@trwnh.com<br /><a href=\"https://trwnh.com\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">trwnh.com</span><span class=\"invisible\"></span></a><br />help me live: <a href=\"https://liberapay.com/at\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">liberapay.com/at</span><span class=\"invisible\"></span></a> or <a href=\"https://paypal.me/trwnh\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">paypal.me/trwnh</span><span class=\"invisible\"></span></a></p><p>- my triggers are moths and glitter<br />- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise<br />- dm me if i did something wrong, so i can improve<br />- purest person on fedi, do not lewd in my presence<br />- #1 ami cole fan account</p><p>:fatyoshi:</p>",
+  "url": "https://mastodon.social/@trwnh",
+  "avatar": "https://files.mastodon.social/accounts/avatars/000/014/715/original/34aa222f4ae2e0a9.png",
+  "avatar_static": "https://files.mastodon.social/accounts/avatars/000/014/715/original/34aa222f4ae2e0a9.png",
+  "header": "https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg",
+  "header_static": "https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg",
+  "followers_count": 821,
+  "following_count": 178,
+  "statuses_count": 33120,
+  "last_status_at": "2019-11-24T15:49:42.251Z",
+  "source": {
+    "privacy": "public",
+    "sensitive": false,
+    "language": "",
+    "note": "i have approximate knowledge of many things. perpetual student. (nb/ace/they)\r\n\r\nxmpp/email: a@trwnh.com\r\nhttps://trwnh.com\r\nhelp me live: https://liberapay.com/at or https://paypal.me/trwnh\r\n\r\n- my triggers are moths and glitter\r\n- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise\r\n- dm me if i did something wrong, so i can improve\r\n- purest person on fedi, do not lewd in my presence\r\n- #1 ami cole fan account\r\n\r\n:fatyoshi:",
+    "fields": [
+      {
+        "name": "Website",
+        "value": "https://trwnh.com",
+        "verified_at": "2019-08-29T04:14:55.571+00:00"
+      },
+      {
+        "name": "Sponsor",
+        "value": "https://liberapay.com/at",
+        "verified_at": "2019-11-15T10:06:15.557+00:00"
+      },
+      {
+        "name": "Fan of:",
+        "value": "Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo's Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)",
+        "verified_at": null
+      },
+      {
+        "name": "Main topics:",
+        "value": "systemic analysis, design patterns, anticapitalism, info/tech freedom, theory and philosophy, and otherwise being a genuine and decent wholesome poster. i'm just here to hang out and talk to cool people!",
+        "verified_at": null
+      }
+    ],
+    "follow_requests_count": 0
+  },
+  "emojis": [
+    {
+      "shortcode": "fatyoshi",
+      "url": "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+      "static_url": "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+      "visible_in_picker": true
+    }
+  ],
+  "fields": [
+    {
+      "name": "Website",
+      "value": "<a href=\"https://trwnh.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">trwnh.com</span><span class=\"invisible\"></span></a>",
+      "verified_at": "2019-08-29T04:14:55.571+00:00"
+    },
+    {
+      "name": "Sponsor",
+      "value": "<a href=\"https://liberapay.com/at\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">liberapay.com/at</span><span class=\"invisible\"></span></a>",
+      "verified_at": "2019-11-15T10:06:15.557+00:00"
+    },
+    {
+      "name": "Fan of:",
+      "value": "Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo&apos;s Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)",
+      "verified_at": null
+    },
+    {
+      "name": "Main topics:",
+      "value": "systemic analysis, design patterns, anticapitalism, info/tech freedom, theory and philosophy, and otherwise being a genuine and decent wholesome poster. i&apos;m just here to hang out and talk to cool people!",
+      "verified_at": null
+    }
+  ]
+}
diff --git a/bigbone/src/test/kotlin/social/bigbone/api/method/AccountMethodsTest.kt b/bigbone/src/test/kotlin/social/bigbone/api/method/AccountMethodsTest.kt
index 4df19e8..4d4ed38 100644
--- a/bigbone/src/test/kotlin/social/bigbone/api/method/AccountMethodsTest.kt
+++ b/bigbone/src/test/kotlin/social/bigbone/api/method/AccountMethodsTest.kt
@@ -1,301 +1,713 @@
 package social.bigbone.api.method
 
+import io.mockk.slot
+import io.mockk.verify
+import org.amshove.kluent.invoking
+import org.amshove.kluent.shouldBeEmpty
 import org.amshove.kluent.shouldBeEqualTo
-import org.junit.jupiter.api.Assertions
+import org.amshove.kluent.shouldBeFalse
+import org.amshove.kluent.shouldBeTrue
+import org.amshove.kluent.shouldHaveSize
+import org.amshove.kluent.shouldThrow
+import org.amshove.kluent.withMessage
 import org.junit.jupiter.api.Test
+import social.bigbone.Parameters
+import social.bigbone.api.entity.data.Visibility
 import social.bigbone.api.exception.BigBoneRequestException
 import social.bigbone.testtool.MockClient
+import java.time.Duration
 
 class AccountMethodsTest {
+
+    @Test
+    fun `Given account returning success, when registering account, then call correct endpoint and parse response`() {
+        val client = MockClient.mock("accounts_register_success.json")
+        val accountMethods = AccountMethods(client)
+
+        val token = accountMethods.registerAccount(
+            username = "username",
+            email = "email",
+            password = "password",
+            agreement = true,
+            locale = "DE",
+            reason = "Please let me in!"
+        ).execute()
+
+        with(token) {
+            accessToken shouldBeEqualTo "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0"
+            tokenType shouldBeEqualTo "Bearer"
+            scope shouldBeEqualTo "read write follow push"
+            createdAt shouldBeEqualTo 1_573_979_017L
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.post(
+                path = "api/v1/accounts",
+                body = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "username=username" +
+                "&email=email" +
+                "&password=password" +
+                "&agreement=true" +
+                "&locale=DE" +
+                "&reason=Please+let+me+in%21"
+        }
+    }
+
+    @Test
+    fun `Given account failing with error response, when registering account, then propagate BigBoneRequestException`() {
+        val client = MockClient.failWithResponse(
+            responseJsonAssetPath = "accounts_register_422.json",
+            responseCode = 422,
+            message = "Unprocessable entity"
+        )
+        val accountMethods = AccountMethods(client)
+
+        invoking {
+            accountMethods.registerAccount(
+                username = "🤠--Buck",
+                email = "email",
+                password = "",
+                agreement = false,
+                locale = "DE",
+                reason = "Please let me in!"
+            ).execute()
+        }
+            .shouldThrow(BigBoneRequestException::class)
+            .withMessage("Unprocessable entity")
+    }
+
     @Test
     fun getAccount() {
         val client = MockClient.mock("account.json")
         val accountMethods = AccountMethods(client)
+
         val account = accountMethods.getAccount("1").execute()
-        account.acct shouldBeEqualTo "test@test.com"
-        account.displayName shouldBeEqualTo "test"
-        account.username shouldBeEqualTo "test"
+
+        with(account) {
+            acct shouldBeEqualTo "test@test.com"
+            displayName shouldBeEqualTo "test"
+            username shouldBeEqualTo "test"
+        }
+        verify {
+            client.get(
+                path = "api/v1/accounts/1",
+                query = null
+            )
+        }
     }
 
     @Test
     fun getAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking {
             accountMethods.getAccount("1").execute()
-        }
+        } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun verifyCredentials() {
-        val client = MockClient.mock("account.json")
+        val client = MockClient.mock("accounts_verify_credentials_success.json")
         val accountMethods = AccountMethods(client)
+
         val account = accountMethods.verifyCredentials().execute()
-        account.acct shouldBeEqualTo "test@test.com"
-        account.displayName shouldBeEqualTo "test"
-        account.username shouldBeEqualTo "test"
+
+        with(account) {
+            acct shouldBeEqualTo "trwnh"
+            displayName shouldBeEqualTo "infinite love ⴳ"
+            username shouldBeEqualTo "trwnh"
+        }
+        verify {
+            client.get(
+                path = "api/v1/accounts/verify_credentials",
+                query = null
+            )
+        }
     }
 
     @Test
     fun verifyCredentialsWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking {
             accountMethods.verifyCredentials().execute()
+        } shouldThrow BigBoneRequestException::class
+    }
+
+    @Test
+    fun `Given client failing with 401, when verifying credentials, then throw BigBoneRequestException`() {
+        val client = MockClient.failWithResponse(
+            responseJsonAssetPath = "error_401_unauthorized.json",
+            responseCode = 401,
+            message = "Unauthorized"
+        )
+        val accountMethods = AccountMethods(client)
+
+        invoking {
+            accountMethods.verifyCredentials().execute()
+        } shouldThrow BigBoneRequestException::class
+    }
+
+    @Test
+    fun `Given client returning success, when updating credentials with valid profile fields, then ensure correct endpoint parameters`() {
+        val client = MockClient.mock("accounts_update_credentials_success.json")
+        val accountMethods = AccountMethods(client)
+        val profileFields = AccountMethods.ProfileFields(
+            first = AccountMethods.ProfileFieldName("Location") to AccountMethods.ProfileFieldValue("Amsterdam, NL 🇳🇱"),
+            second = AccountMethods.ProfileFieldName("Pronouns") to AccountMethods.ProfileFieldValue("he/they"),
+            third = AccountMethods.ProfileFieldName("Website") to AccountMethods.ProfileFieldValue("https://example.com"),
+            fourth = null
+        )
+
+        accountMethods.updateCredentials(
+            displayName = null,
+            note = null,
+            avatar = null,
+            header = null,
+            locked = null,
+            bot = null,
+            discoverable = null,
+            hideCollections = null,
+            indexable = null,
+            profileFields = profileFields,
+            defaultPostVisibility = null,
+            defaultSensitiveMark = null,
+            defaultLanguage = null
+        ).execute()
+
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.patch(
+                path = "api/v1/accounts/update_credentials",
+                body = capture(parametersCapturingSlot)
+            )
         }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo
+                "fields_attributes[0][name]=Location" +
+                "&fields_attributes[0][value]=Amsterdam%2C+NL+%F0%9F%87%B3%F0%9F%87%B1" +
+                "&fields_attributes[1][name]=Pronouns" +
+                "&fields_attributes[1][value]=he%2Fthey" +
+                "&fields_attributes[2][name]=Website" +
+                "&fields_attributes[2][value]=https%3A%2F%2Fexample.com"
+        }
+    }
+
+    @Test
+    fun `Given string with more than 255 characters, when creating ProfileFieldName, then fail with exception`() {
+        val tooLongProfileFieldName = "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "0123456789" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "01234567890123456789" +
+            "01234567890" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "toomanycharacters"
+
+        invoking { AccountMethods.ProfileFieldName(tooLongProfileFieldName) }
+            .shouldThrow(IllegalArgumentException::class)
+            .withMessage(
+                "Name of profile field must not be longer than 255 characters but was: " +
+                    "$tooLongProfileFieldName (${tooLongProfileFieldName.length} characters)."
+            )
     }
 
-    // TODO getVerifyCredentialsWith401
+    @Test
+    fun `Given string with more than 255 characters, when creating ProfileFieldValue, then fail with exception`() {
+        val tooLongProfileFieldValue = "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "0123456789" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "01234567890123456789" +
+            "01234567890" +
+            "abcdefghijklmnopqrstuvwxyz" +
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+            "toomanycharacters"
+
+        invoking { AccountMethods.ProfileFieldValue(tooLongProfileFieldValue) }
+            .shouldThrow(IllegalArgumentException::class)
+            .withMessage(
+                "Value of profile field must not be longer than 255 characters but was: " +
+                    "$tooLongProfileFieldValue (${tooLongProfileFieldValue.length} characters)."
+            )
+    }
 
     @Test
     fun updateCredentials() {
-        val client = MockClient.mock("account.json")
+        val client = MockClient.mock("accounts_update_credentials_success.json")
         val accountMethods = AccountMethods(client)
-        val account = accountMethods.updateCredentials("test", "test", "test", "test").execute()
-        account.acct shouldBeEqualTo "test@test.com"
-        account.displayName shouldBeEqualTo "test"
-        account.username shouldBeEqualTo "test"
+
+        val account = accountMethods.updateCredentials(
+            displayName = "test",
+            note = "test",
+            avatar = "test",
+            header = "test",
+            locked = false,
+            bot = false,
+            discoverable = false,
+            hideCollections = false,
+            indexable = true,
+            profileFields = null,
+            defaultPostVisibility = Visibility.UNLISTED,
+            defaultSensitiveMark = true,
+            defaultLanguage = "EN"
+        ).execute()
+
+        with(account) {
+            acct shouldBeEqualTo "trwnh"
+            displayName shouldBeEqualTo "infinite love ⴳ"
+            username shouldBeEqualTo "trwnh"
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.patch(
+                path = "api/v1/accounts/update_credentials",
+                body = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "display_name=test" +
+                "&note=test" +
+                "&avatar=test" +
+                "&header=test" +
+                "&locked=false" +
+                "&bot=false" +
+                "&discoverable=false" +
+                "&hide_collections=false" +
+                "&indexable=true" +
+                "&source[privacy]=unlisted" +
+                "&source[sensitive]=true" +
+                "&source[language]=EN"
+        }
     }
 
     @Test
     fun updateCredentialsWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.updateCredentials("test", "test", "test", "test").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking {
+            accountMethods.updateCredentials(
+                displayName = "test",
+                note = "test",
+                avatar = "test",
+                header = "test",
+                locked = false,
+                bot = false,
+                discoverable = false,
+                hideCollections = false,
+                indexable = true,
+                profileFields = null,
+                defaultPostVisibility = null,
+                defaultSensitiveMark = null,
+                defaultLanguage = null
+            ).execute()
+        } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun getFollowers() {
         val client = MockClient.mock("accounts.json")
         val accountMethods = AccountMethods(client)
+
         val pageable = accountMethods.getFollowers("1").execute()
-        val account = pageable.part.first()
-        account.acct shouldBeEqualTo "test@test.com"
-        account.displayName shouldBeEqualTo "test"
-        account.username shouldBeEqualTo "test"
+
+        with(pageable.part) {
+            shouldHaveSize(1)
+
+            with(first()) {
+                acct shouldBeEqualTo "test@test.com"
+                displayName shouldBeEqualTo "test"
+                username shouldBeEqualTo "test"
+            }
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.get(
+                path = "api/v1/accounts/1/followers",
+                query = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery().shouldBeEmpty()
+        }
     }
 
     @Test
     fun getFollowersWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.getFollowers("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.getFollowers("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun getFollowing() {
         val client = MockClient.mock("accounts.json")
         val accountMethods = AccountMethods(client)
+
         val pageable = accountMethods.getFollowing("1").execute()
-        val account = pageable.part.first()
-        account.acct shouldBeEqualTo "test@test.com"
-        account.displayName shouldBeEqualTo "test"
-        account.username shouldBeEqualTo "test"
+
+        with(pageable.part) {
+            shouldHaveSize(1)
+
+            with(first()) {
+                acct shouldBeEqualTo "test@test.com"
+                displayName shouldBeEqualTo "test"
+                username shouldBeEqualTo "test"
+            }
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.get(
+                path = "api/v1/accounts/1/following",
+                query = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery().shouldBeEmpty()
+        }
     }
 
     @Test
     fun getFollowingWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.getFollowing("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.getFollowing("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun getStatuses() {
         val client = MockClient.mock("statuses.json")
         val accountMethods = AccountMethods(client)
-        val pageable = accountMethods.getStatuses("1", false).execute()
-        val status = pageable.part.first()
-        status.id shouldBeEqualTo "11111"
+
+        val pageable = accountMethods.getStatuses(
+            accountId = "1",
+            onlyMedia = false,
+            excludeReplies = true,
+            excludeReblogs = true,
+            pinned = false,
+            filterByTaggedWith = ""
+        ).execute()
+
+        with(pageable.part) {
+            shouldHaveSize(1)
+
+            with(first()) {
+                id shouldBeEqualTo "11111"
+            }
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.get(
+                path = "api/v1/accounts/1/statuses",
+                query = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "exclude_replies=true&exclude_reblogs=true"
+        }
     }
 
     @Test
     fun getStatusesWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking {
             accountMethods.getStatuses("1", false).execute()
-        }
+        } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun followAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
-        val relationship = accountMethods.followAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        val relationship = accountMethods.followAccount(
+            accountId = "1",
+            includeReblogs = true,
+            notifyOnStatus = false,
+            filterForLanguages = listOf("de", "en", "uk")
+        ).execute()
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing.shouldBeTrue()
+            isFollowedBy.shouldBeFalse()
+            isBlocking.shouldBeFalse()
+            isMuting.shouldBeFalse()
+            isRequested.shouldBeFalse()
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.post(
+                path = "api/v1/accounts/1/follow",
+                body = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "reblogs=true" +
+                "&notify=false" +
+                "&languages[]=de" +
+                "&languages[]=en" +
+                "&languages[]=uk"
+        }
     }
 
     @Test
     fun followAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.followAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.followAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun unfollowAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
-        val relationship = accountMethods.unfollowAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        val relationship = accountMethods.unfollowAccount("3361").execute()
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing shouldBeEqualTo true
+            isFollowedBy shouldBeEqualTo false
+            isBlocking shouldBeEqualTo false
+            isMuting shouldBeEqualTo false
+            isRequested shouldBeEqualTo false
+        }
+        verify {
+            client.post(
+                path = "api/v1/accounts/3361/unfollow",
+                body = null
+            )
+        }
     }
 
     @Test
     fun unfollowAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.unfollowAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.unfollowAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun blockAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
+
         val relationship = accountMethods.blockAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing shouldBeEqualTo true
+            isFollowedBy shouldBeEqualTo false
+            isBlocking shouldBeEqualTo false
+            isMuting shouldBeEqualTo false
+            isRequested shouldBeEqualTo false
+        }
+        verify {
+            client.post(
+                path = "api/v1/accounts/1/block",
+                body = null
+            )
+        }
     }
 
     @Test
     fun blockAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.blockAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.blockAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun unblockAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
+
         val relationship = accountMethods.unblockAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing shouldBeEqualTo true
+            isFollowedBy shouldBeEqualTo false
+            isBlocking shouldBeEqualTo false
+            isMuting shouldBeEqualTo false
+            isRequested shouldBeEqualTo false
+        }
+        verify {
+            client.post(
+                path = "api/v1/accounts/1/unblock",
+                body = null
+            )
+        }
     }
 
     @Test
     fun unblockAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.unblockAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.unblockAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun muteAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
-        val relationship = accountMethods.muteAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        val relationship = accountMethods.muteAccount(
+            accountId = "1",
+            muteNotifications = false,
+            muteDuration = Duration.ofDays(3)
+        ).execute()
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing shouldBeEqualTo true
+            isFollowedBy shouldBeEqualTo false
+            isBlocking shouldBeEqualTo false
+            isMuting shouldBeEqualTo false
+            isRequested shouldBeEqualTo false
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.post(
+                path = "api/v1/accounts/1/mute",
+                body = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "notifications=false&duration=259200"
+        }
     }
 
     @Test
     fun muteAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.muteAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.muteAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun unmuteAccount() {
         val client = MockClient.mock("relationship.json")
         val accountMethods = AccountMethods(client)
+
         val relationship = accountMethods.unmuteAccount("1").execute()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        with(relationship) {
+            id shouldBeEqualTo "3361"
+            isFollowing shouldBeEqualTo true
+            isFollowedBy shouldBeEqualTo false
+            isBlocking shouldBeEqualTo false
+            isMuting shouldBeEqualTo false
+            isRequested shouldBeEqualTo false
+        }
+        verify {
+            client.post(
+                path = "api/v1/accounts/1/unmute",
+                body = null
+            )
+        }
     }
 
     @Test
     fun unmuteAccountWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.unmuteAccount("1").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.unmuteAccount("1").execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun getRelationships() {
         val client = MockClient.mock("relationships.json")
         val accountMethods = AccountMethods(client)
-        val relationships = accountMethods.getRelationships(listOf("1")).execute()
-        val relationship = relationships.first()
-        relationship.id shouldBeEqualTo "3361"
-        relationship.isFollowing shouldBeEqualTo true
-        relationship.isFollowedBy shouldBeEqualTo false
-        relationship.isBlocking shouldBeEqualTo false
-        relationship.isMuting shouldBeEqualTo false
-        relationship.isRequested shouldBeEqualTo false
+
+        val relationships = accountMethods.getRelationships(
+            accountIds = listOf("1", "2", "23", "42"),
+            includeSuspended = true
+        ).execute()
+
+        with(relationships) {
+            shouldHaveSize(1)
+
+            with(first()) {
+                id shouldBeEqualTo "3361"
+                isFollowing shouldBeEqualTo true
+                isFollowedBy shouldBeEqualTo false
+                isBlocking shouldBeEqualTo false
+                isMuting shouldBeEqualTo false
+                isRequested shouldBeEqualTo false
+            }
+        }
+        val parametersCapturingSlot = slot<Parameters>()
+        verify {
+            client.get(
+                path = "api/v1/accounts/relationships",
+                query = capture(parametersCapturingSlot)
+            )
+        }
+        with(parametersCapturingSlot.captured) {
+            toQuery() shouldBeEqualTo "id[]=1" +
+                "&id[]=2" +
+                "&id[]=23" +
+                "&id[]=42" +
+                "&with_suspended=true"
+        }
     }
 
     @Test
     fun getRelationshipsWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.getRelationships(listOf("1")).execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.getRelationships(listOf("1")).execute() } shouldThrow BigBoneRequestException::class
     }
 
     @Test
     fun searchAccounts() {
         val client = MockClient.mock("account_search.json")
         val accountMethods = AccountMethods(client)
-        val result = accountMethods.searchAccounts("test").execute()
-        val account = result.first()
-        account.acct shouldBeEqualTo "A"
-        account.displayName shouldBeEqualTo ""
-        account.username shouldBeEqualTo "A"
+
+        val accounts = accountMethods.searchAccounts("test").execute()
+
+        with(accounts) {
+            shouldHaveSize(40)
+
+            with(first()) {
+                acct shouldBeEqualTo "A"
+                displayName shouldBeEqualTo ""
+                username shouldBeEqualTo "A"
+            }
+        }
     }
 
     @Test
     fun searchAccountsWithException() {
-        Assertions.assertThrows(BigBoneRequestException::class.java) {
-            val client = MockClient.ioException()
-            val accountMethods = AccountMethods(client)
-            accountMethods.searchAccounts("test").execute()
-        }
+        val client = MockClient.ioException()
+        val accountMethods = AccountMethods(client)
+
+        invoking { accountMethods.searchAccounts("test").execute() } shouldThrow BigBoneRequestException::class
     }
 }