From df7742bddcc77d703c71bcfa387fe26bf5b04b65 Mon Sep 17 00:00:00 2001 From: Patrick Geselbracht Date: Tue, 26 Dec 2023 21:01:53 +0100 Subject: [PATCH] Add Admin::EmailDomainBlock methods (#400) * Add Admin::EmailDomainBlock entity * Add Admin::EmailDomainBlock methods * Add unit tests * Update documentation --- .../admin/RxAdminEmailDomainBlockMethods.kt | 65 ++++++++ .../kotlin/social/bigbone/MastodonClient.kt | 12 +- .../api/entity/admin/AdminEmailDomainBlock.kt | 41 +++++ .../social/bigbone/api/entity/data/History.kt | 1 + .../admin/AdminEmailDomainBlockMethods.kt | 76 +++++++++ ...ail_domain_blocks_all_blocked_success.json | 44 +++++ ...min_email_domain_blocks_block_success.json | 42 +++++ ...in_email_domain_blocks_delete_success.json | 1 + ...in_email_domain_blocks_single_success.json | 42 +++++ .../admin/AdminEmailDomainBlockMethodsTest.kt | 151 ++++++++++++++++++ .../api-coverage/admin/email-domain-blocks.md | 12 +- 11 files changed, 479 insertions(+), 8 deletions(-) create mode 100644 bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminEmailDomainBlockMethods.kt create mode 100644 bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminEmailDomainBlock.kt create mode 100644 bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethods.kt create mode 100644 bigbone/src/test/assets/admin_email_domain_blocks_all_blocked_success.json create mode 100644 bigbone/src/test/assets/admin_email_domain_blocks_block_success.json create mode 100644 bigbone/src/test/assets/admin_email_domain_blocks_delete_success.json create mode 100644 bigbone/src/test/assets/admin_email_domain_blocks_single_success.json create mode 100644 bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethodsTest.kt diff --git a/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminEmailDomainBlockMethods.kt b/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminEmailDomainBlockMethods.kt new file mode 100644 index 000000000..721ca494d --- /dev/null +++ b/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminEmailDomainBlockMethods.kt @@ -0,0 +1,65 @@ +package social.bigbone.rx.admin + +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import social.bigbone.MastodonClient +import social.bigbone.api.Pageable +import social.bigbone.api.Range +import social.bigbone.api.entity.admin.AdminEmailDomainBlock +import social.bigbone.api.method.admin.AdminEmailDomainBlockMethods + +/** + * Reactive implementation of [AdminEmailDomainBlockMethods]. + * + * Disallow certain email domains from signing up. + * + * @see Mastodon admin/email_domain_blocks API methods + */ +class RxAdminEmailDomainBlockMethods(client: MastodonClient) { + + private val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + + /** + * Show information about all email domains blocked from signing up. + * + * @param range optional Range for the pageable return value + * + * @see Mastodon API documentation: admin/email_domain_blocks/#get + */ + @JvmOverloads + fun getAllEmailDomainBlocks(range: Range = Range()): Single> = Single.fromCallable { + adminEmailDomainBlockMethods.getAllEmailDomainBlocks(range).execute() + } + + /** + * Show information about a single email domain that is blocked from signups. + * + * @param id The ID of the EmailDomainBlock in the database. + * @see + * Mastodon API documentation: admin/email_domain_blocks/#get-one + */ + fun getEmailDomainBlock(id: String): Single = Single.fromCallable { + adminEmailDomainBlockMethods.getEmailDomainBlock(id).execute() + } + + /** + * Add a domain to the list of email domains blocked from signups. + * + * @param domain The domain to block federation with. + * @see Mastodon API documentation: admin/email_domain_blocks/#create + */ + fun blockEmailDomain(domain: String): Single = Single.fromCallable { + adminEmailDomainBlockMethods.blockEmailDomain(domain).execute() + } + + /** + * Lift a block against an email domain. + * + * @param id The ID of the EmailDomainBlock in the database. + * + * @see Mastodon API documentation: admin/email_domain_blocks/#delete + */ + fun removeEmailDomainBlock(id: String) = Completable.fromAction { + adminEmailDomainBlockMethods.removeEmailDomainBlock(id) + } +} diff --git a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt index f3218b035..1c3e6b08e 100644 --- a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt +++ b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt @@ -67,6 +67,7 @@ import social.bigbone.api.method.TimelineMethods import social.bigbone.api.method.TrendMethods import social.bigbone.api.method.admin.AdminDimensionMethods import social.bigbone.api.method.admin.AdminDomainBlockMethods +import social.bigbone.api.method.admin.AdminEmailDomainBlockMethods import social.bigbone.api.method.admin.AdminIpBlockMethods import social.bigbone.api.method.admin.AdminMeasureMethods import social.bigbone.api.method.admin.AdminRetentionMethods @@ -117,8 +118,15 @@ private constructor( * Access API methods under the "admin/domain_blocks" endpoint. */ @Suppress("unused") // public API - @get:JvmName("adminDomainBlock") - val adminDomainBlock: AdminDomainBlockMethods by lazy { AdminDomainBlockMethods(this) } + @get:JvmName("adminDomainBlocks") + val adminDomainBlocks: AdminDomainBlockMethods by lazy { AdminDomainBlockMethods(this) } + + /** + * Access API methods under the "admin/email_domain_blocks" endpoint. + */ + @Suppress("unused") // public API + @get:JvmName("adminEmailDomainBlocks") + val adminEmailDomainBlocks: AdminEmailDomainBlockMethods by lazy { AdminEmailDomainBlockMethods(this) } /** * Access API methods under the "admin/ip_blocks" endpoint. diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminEmailDomainBlock.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminEmailDomainBlock.kt new file mode 100644 index 000000000..bae4be029 --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminEmailDomainBlock.kt @@ -0,0 +1,41 @@ +package social.bigbone.api.entity.admin + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import social.bigbone.DateTimeSerializer +import social.bigbone.PrecisionDateTime +import social.bigbone.api.entity.data.History + +/** + * Represents an email domain that cannot be used to sign up. + * + * @see Mastodon API Admin::EmailDomainBlock documentation + */ +@Serializable +data class AdminEmailDomainBlock( + /** + * The ID of the EmailDomainBlock in the database. + * String cast from an Integer, but not guaranteed to be a number. + */ + @SerialName("id") + val id: String, + + /** + * The email domain that is not allowed to be used for signups. + */ + @SerialName("domain") + val domain: String, + + /** + * The timestamp of the notification. + */ + @SerialName("created_at") + @Serializable(with = DateTimeSerializer::class) + val createdAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable, + + /** + * Usage statistics for given days (typically the past week). + */ + @SerialName("history") + val history: List +) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/data/History.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/data/History.kt index e25db1e28..2d3cec6c9 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/entity/data/History.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/data/History.kt @@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable * Usage statistics for given days (typically the past week). * @see Mastodon API Tag history * @see Mastodon API PreviewCard history + * @see Mastodon API Admin::EmailDomainBlock history */ @Serializable data class History( diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethods.kt new file mode 100644 index 000000000..00973bff5 --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethods.kt @@ -0,0 +1,76 @@ +package social.bigbone.api.method.admin + +import social.bigbone.MastodonClient +import social.bigbone.MastodonRequest +import social.bigbone.Parameters +import social.bigbone.api.Pageable +import social.bigbone.api.Range +import social.bigbone.api.entity.admin.AdminEmailDomainBlock + +/** + * Disallow certain email domains from signing up. + * + * @see Mastodon admin/email_domain_blocks API methods + */ +class AdminEmailDomainBlockMethods(private val client: MastodonClient) { + + private val adminEmailDomainBlockEndpoint = "api/v1/admin/email_domain_blocks" + + /** + * Show information about all email domains blocked from signing up. + * + * @param range optional Range for the pageable return value + * + * @see Mastodon API documentation: admin/email_domain_blocks/#get + */ + @JvmOverloads + fun getAllEmailDomainBlocks(range: Range = Range()): MastodonRequest> { + return client.getPageableMastodonRequest( + endpoint = adminEmailDomainBlockEndpoint, + method = MastodonClient.Method.GET, + parameters = range.toParameters() + ) + } + + /** + * Show information about a single email domain that is blocked from signups. + * + * @param id The ID of the EmailDomainBlock in the database. + * @see + * Mastodon API documentation: admin/email_domain_blocks/#get-one + */ + fun getEmailDomainBlock(id: String): MastodonRequest { + return client.getMastodonRequest( + endpoint = "$adminEmailDomainBlockEndpoint/$id", + method = MastodonClient.Method.GET + ) + } + + /** + * Add a domain to the list of email domains blocked from signups. + * + * @param domain The domain to block federation with. + * @see Mastodon API documentation: admin/email_domain_blocks/#create + */ + fun blockEmailDomain(domain: String): MastodonRequest { + return client.getMastodonRequest( + endpoint = adminEmailDomainBlockEndpoint, + method = MastodonClient.Method.POST, + parameters = Parameters().append("domain", domain) + ) + } + + /** + * Lift a block against an email domain. + * + * @param id The ID of the EmailDomainBlock in the database. + * + * @see Mastodon API documentation: admin/email_domain_blocks/#delete + */ + fun removeEmailDomainBlock(id: String) { + client.performAction( + endpoint = "$adminEmailDomainBlockEndpoint/$id", + method = MastodonClient.Method.DELETE + ) + } +} diff --git a/bigbone/src/test/assets/admin_email_domain_blocks_all_blocked_success.json b/bigbone/src/test/assets/admin_email_domain_blocks_all_blocked_success.json new file mode 100644 index 000000000..3080f569b --- /dev/null +++ b/bigbone/src/test/assets/admin_email_domain_blocks_all_blocked_success.json @@ -0,0 +1,44 @@ +[ + { + "id": "1", + "domain": "foo", + "created_at": "2022-11-16T06:09:36.176Z", + "history": [ + { + "day": "1668556800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668470400", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668384000", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668297600", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668211200", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668124800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668038400", + "accounts": "0", + "uses": "0" + } + ] + } +] diff --git a/bigbone/src/test/assets/admin_email_domain_blocks_block_success.json b/bigbone/src/test/assets/admin_email_domain_blocks_block_success.json new file mode 100644 index 000000000..4b12867b7 --- /dev/null +++ b/bigbone/src/test/assets/admin_email_domain_blocks_block_success.json @@ -0,0 +1,42 @@ +{ + "id": "1", + "domain": "foo", + "created_at": "2022-11-16T06:09:36.176Z", + "history": [ + { + "day": "1668556800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668470400", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668384000", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668297600", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668211200", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668124800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668038400", + "accounts": "0", + "uses": "0" + } + ] +} diff --git a/bigbone/src/test/assets/admin_email_domain_blocks_delete_success.json b/bigbone/src/test/assets/admin_email_domain_blocks_delete_success.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/bigbone/src/test/assets/admin_email_domain_blocks_delete_success.json @@ -0,0 +1 @@ +{} diff --git a/bigbone/src/test/assets/admin_email_domain_blocks_single_success.json b/bigbone/src/test/assets/admin_email_domain_blocks_single_success.json new file mode 100644 index 000000000..4b12867b7 --- /dev/null +++ b/bigbone/src/test/assets/admin_email_domain_blocks_single_success.json @@ -0,0 +1,42 @@ +{ + "id": "1", + "domain": "foo", + "created_at": "2022-11-16T06:09:36.176Z", + "history": [ + { + "day": "1668556800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668470400", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668384000", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668297600", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668211200", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668124800", + "accounts": "0", + "uses": "0" + }, + { + "day": "1668038400", + "accounts": "0", + "uses": "0" + } + ] +} diff --git a/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethodsTest.kt b/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethodsTest.kt new file mode 100644 index 000000000..9deef63fc --- /dev/null +++ b/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminEmailDomainBlockMethodsTest.kt @@ -0,0 +1,151 @@ +package social.bigbone.api.method.admin + +import io.mockk.slot +import io.mockk.verify +import org.amshove.kluent.shouldBeEmpty +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldHaveSize +import org.junit.jupiter.api.Test +import social.bigbone.MastodonClient +import social.bigbone.Parameters +import social.bigbone.PrecisionDateTime.ValidPrecisionDateTime.ExactTime +import social.bigbone.api.Pageable +import social.bigbone.api.Range +import social.bigbone.api.entity.admin.AdminEmailDomainBlock +import social.bigbone.testtool.MockClient +import java.time.Instant + +class AdminEmailDomainBlockMethodsTest { + + @Test + fun `Given client returning success, when getting all blocked Email domains without Range, then call correct endpoint and parse response`() { + val client = MockClient.mock("admin_email_domain_blocks_all_blocked_success.json") + val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + + val emailDomainBlocks: Pageable = adminEmailDomainBlockMethods + .getAllEmailDomainBlocks() + .execute() + + with(emailDomainBlocks.part) { + shouldHaveSize(1) + + with(get(0)) { + id shouldBeEqualTo "1" + domain shouldBeEqualTo "foo" + createdAt shouldBeEqualTo ExactTime(Instant.parse("2022-11-16T06:09:36.176Z")) + + history shouldHaveSize 7 + history[0].day shouldBeEqualTo "1668556800" + history[0].accounts shouldBeEqualTo "0" + history[0].uses shouldBeEqualTo "0" + } + } + val parametersCapturingSlot = slot() + verify { + client.get( + path = "api/v1/admin/email_domain_blocks", + query = capture(parametersCapturingSlot) + ) + } + with(parametersCapturingSlot.captured) { + toQuery().shouldBeEmpty() + } + } + + @Test + fun `Given client returning success, when getting all blocked Email domains with Range, then use correct parameters`() { + val client = MockClient.mock("admin_email_domain_blocks_all_blocked_success.json") + val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + val range = Range(limit = 5, sinceId = "1234") + + adminEmailDomainBlockMethods.getAllEmailDomainBlocks(range).execute() + + val parametersCapturingSlot = slot() + verify { + client.get( + path = "api/v1/admin/email_domain_blocks", + query = capture(parametersCapturingSlot) + ) + } + with(parametersCapturingSlot.captured) { + toQuery() shouldBeEqualTo "since_id=${range.sinceId}&limit=${range.limit}" + } + } + + @Test + fun `Given client returning success, when getting single Email domain, then call correct endpoint and parse response`() { + val client = MockClient.mock("admin_email_domain_blocks_single_success.json") + val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + val requestedId = "1" + + val emailDomainBlock: AdminEmailDomainBlock = adminEmailDomainBlockMethods + .getEmailDomainBlock(requestedId) + .execute() + + with(emailDomainBlock) { + id shouldBeEqualTo "1" + domain shouldBeEqualTo "foo" + createdAt shouldBeEqualTo ExactTime(Instant.parse("2022-11-16T06:09:36.176Z")) + + history shouldHaveSize 7 + history[0].day shouldBeEqualTo "1668556800" + history[0].accounts shouldBeEqualTo "0" + history[0].uses shouldBeEqualTo "0" + } + verify { + client.get( + path = "api/v1/admin/email_domain_blocks/$requestedId", + query = null + ) + } + } + + @Test + fun `Given client returning success, when blocking an Email domain, then call correct endpoint and parse response`() { + val client = MockClient.mock("admin_email_domain_blocks_block_success.json") + val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + val requestedDomain = "foo" + + val emailDomainBlock: AdminEmailDomainBlock = adminEmailDomainBlockMethods + .blockEmailDomain(requestedDomain) + .execute() + + with(emailDomainBlock) { + id shouldBeEqualTo "1" + domain shouldBeEqualTo "foo" + createdAt shouldBeEqualTo ExactTime(Instant.parse("2022-11-16T06:09:36.176Z")) + + history shouldHaveSize 7 + history[0].day shouldBeEqualTo "1668556800" + history[0].accounts shouldBeEqualTo "0" + history[0].uses shouldBeEqualTo "0" + } + val parametersCapturingSlot = slot() + verify { + client.post( + path = "api/v1/admin/email_domain_blocks", + body = capture(parametersCapturingSlot) + ) + } + with(parametersCapturingSlot.captured) { + toQuery() shouldBeEqualTo "domain=$requestedDomain" + } + } + + @Test + fun `Given client returning success, when removing an Email domain block, then call correct endpoint`() { + val client = MockClient.mock("admin_email_domain_blocks_delete_success.json") + val adminEmailDomainBlockMethods = AdminEmailDomainBlockMethods(client) + val requestedId = "foo" + + adminEmailDomainBlockMethods.removeEmailDomainBlock(requestedId) + + verify { + client.performAction( + endpoint = "api/v1/admin/email_domain_blocks/$requestedId", + method = MastodonClient.Method.DELETE, + parameters = null + ) + } + } +} diff --git a/docs/api-coverage/admin/email-domain-blocks.md b/docs/api-coverage/admin/email-domain-blocks.md index bf4740c0d..1273e83a8 100644 --- a/docs/api-coverage/admin/email-domain-blocks.md +++ b/docs/api-coverage/admin/email-domain-blocks.md @@ -10,9 +10,9 @@ nav_order: 8 https://docs.joinmastodon.org/methods/admin/email_domain_blocks/ -| Method | Description | Status | Comments | -|------------------------------------------------|------------------------------------|--------|----------| -| `GET /api/v1/admin/email_domain_blocks` | List all blocked email domains | 🔴 | | -| `GET /api/v1/admin/email_domain_blocks/:id` | Get a single blocked email domain | 🔴 | | -| `POST /api/v1/admin/email_domain_blocks` | Block an email domain from signups | 🔴 | | -| `DELETE /api/v1/admin/email_domain_blocks/:id` | Delete an email domain block | 🔴 | | +| Method | Description | Status | Comments | +|------------------------------------------------|------------------------------------|---------------------------------|-----------------| +| `GET /api/v1/admin/email_domain_blocks` | List all blocked email domains | | Fully supported | +| `GET /api/v1/admin/email_domain_blocks/:id` | Get a single blocked email domain | | Fully supported | +| `POST /api/v1/admin/email_domain_blocks` | Block an email domain from signups | | Fully supported | +| `DELETE /api/v1/admin/email_domain_blocks/:id` | Delete an email domain block | | Fully supported |