api-surface #1
7 changed files with 458 additions and 6 deletions
feat(api): guilds + members + roles endpoint rollout
commit
6830abd7e3
|
|
@ -400,6 +400,9 @@ defmodule Dexcord.Api do
|
|||
@endpoint_groups [
|
||||
Dexcord.Api.Channels,
|
||||
Dexcord.Api.Messages,
|
||||
Dexcord.Api.Guilds,
|
||||
Dexcord.Api.Members,
|
||||
Dexcord.Api.Roles,
|
||||
Dexcord.Api.Users,
|
||||
Dexcord.Api.Interactions,
|
||||
Dexcord.Api.Commands,
|
||||
|
|
|
|||
96
lib/dexcord/api/guilds.ex
Normal file
96
lib/dexcord/api/guilds.ex
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
defmodule Dexcord.Api.Guilds do
|
||||
@moduledoc """
|
||||
Guild endpoints, declared with the `Dexcord.Api.Endpoint` DSL — mirrors the
|
||||
Discord "Guild" resource page (plus the guild widget / welcome-screen /
|
||||
onboarding / prune / audit-log sub-resources).
|
||||
|
||||
`get_guild/1,2` moved here from `Dexcord.Api.Misc`; the `Dexcord.Api` facade
|
||||
delegate follows the group automatically. `get_guild_preview/1` decodes into
|
||||
`Dexcord.Guild` (a lenient subset shape). Shapes with no model
|
||||
(`get_guild_widget_image`, `get_guild_vanity_url`, prune counts, incident
|
||||
actions, active-thread listings) stay `:raw`.
|
||||
"""
|
||||
use Dexcord.Api.Endpoint
|
||||
|
||||
endpoint :get_guild, :get, "/guilds/:guild_id", query: [:with_counts], returns: Dexcord.Guild
|
||||
endpoint :get_guild_preview, :get, "/guilds/:guild_id/preview", returns: Dexcord.Guild
|
||||
|
||||
endpoint :edit_guild, :patch, "/guilds/:guild_id",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.Guild
|
||||
|
||||
endpoint :get_guild_channels, :get, "/guilds/:guild_id/channels", returns: [Dexcord.Channel]
|
||||
|
||||
endpoint :create_guild_channel, :post, "/guilds/:guild_id/channels",
|
||||
body: true,
|
||||
binary_wrap: :name,
|
||||
reason: true,
|
||||
returns: Dexcord.Channel
|
||||
|
||||
endpoint :edit_guild_channel_positions, :patch, "/guilds/:guild_id/channels",
|
||||
body: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :list_active_guild_threads, :get, "/guilds/:guild_id/threads/active", returns: :raw
|
||||
endpoint :get_guild_invites, :get, "/guilds/:guild_id/invites", returns: [Dexcord.Invite]
|
||||
|
||||
endpoint :get_guild_integrations, :get, "/guilds/:guild_id/integrations",
|
||||
returns: [Dexcord.Integration]
|
||||
|
||||
endpoint :delete_guild_integration, :delete, "/guilds/:guild_id/integrations/:integration_id",
|
||||
reason: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :get_guild_widget_settings, :get, "/guilds/:guild_id/widget",
|
||||
returns: Dexcord.GuildWidgetSettings
|
||||
|
||||
endpoint :edit_guild_widget, :patch, "/guilds/:guild_id/widget",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.GuildWidgetSettings
|
||||
|
||||
endpoint :get_guild_widget, :get, "/guilds/:guild_id/widget.json", returns: Dexcord.GuildWidget
|
||||
|
||||
endpoint :get_guild_widget_image, :get, "/guilds/:guild_id/widget.png",
|
||||
query: [:style],
|
||||
returns: :raw
|
||||
|
||||
endpoint :get_guild_vanity_url, :get, "/guilds/:guild_id/vanity-url", returns: :raw
|
||||
|
||||
endpoint :get_guild_welcome_screen, :get, "/guilds/:guild_id/welcome-screen",
|
||||
returns: Dexcord.WelcomeScreen
|
||||
|
||||
endpoint :edit_guild_welcome_screen, :patch, "/guilds/:guild_id/welcome-screen",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.WelcomeScreen
|
||||
|
||||
endpoint :get_guild_onboarding, :get, "/guilds/:guild_id/onboarding",
|
||||
returns: Dexcord.GuildOnboarding
|
||||
|
||||
endpoint :edit_guild_onboarding, :put, "/guilds/:guild_id/onboarding",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.GuildOnboarding
|
||||
|
||||
endpoint :get_guild_prune_count, :get, "/guilds/:guild_id/prune",
|
||||
query: [:days, :include_roles],
|
||||
returns: :raw
|
||||
|
||||
endpoint :begin_guild_prune, :post, "/guilds/:guild_id/prune",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: :raw
|
||||
|
||||
endpoint :get_guild_voice_regions, :get, "/guilds/:guild_id/regions",
|
||||
returns: [Dexcord.VoiceRegion]
|
||||
|
||||
endpoint :edit_guild_incident_actions, :put, "/guilds/:guild_id/incident-actions",
|
||||
body: true,
|
||||
returns: :raw
|
||||
|
||||
endpoint :get_guild_audit_log, :get, "/guilds/:guild_id/audit-logs",
|
||||
query: [:user_id, :action_type, :before, :after, :limit],
|
||||
returns: Dexcord.AuditLog
|
||||
end
|
||||
81
lib/dexcord/api/members.ex
Normal file
81
lib/dexcord/api/members.ex
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
defmodule Dexcord.Api.Members do
|
||||
@moduledoc """
|
||||
Guild member, ban, and voice-state endpoints, declared with the
|
||||
`Dexcord.Api.Endpoint` DSL — mirrors the member/ban/voice sections of the
|
||||
Discord "Guild" resource page.
|
||||
|
||||
`bulk_guild_ban` returns the `{banned_users, failed_users}` envelope with no
|
||||
model (`:raw`). The `edit_*_voice_state` endpoints return `nil` (204).
|
||||
"""
|
||||
use Dexcord.Api.Endpoint
|
||||
|
||||
endpoint :get_guild_member, :get, "/guilds/:guild_id/members/:user_id", returns: Dexcord.Member
|
||||
|
||||
endpoint :list_guild_members, :get, "/guilds/:guild_id/members",
|
||||
query: [:limit, :after],
|
||||
returns: [Dexcord.Member]
|
||||
|
||||
endpoint :search_guild_members, :get, "/guilds/:guild_id/members/search",
|
||||
query: [:query, :limit],
|
||||
returns: [Dexcord.Member]
|
||||
|
||||
endpoint :add_guild_member, :put, "/guilds/:guild_id/members/:user_id",
|
||||
body: true,
|
||||
returns: Dexcord.Member
|
||||
|
||||
endpoint :edit_guild_member, :patch, "/guilds/:guild_id/members/:user_id",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.Member
|
||||
|
||||
endpoint :edit_current_member, :patch, "/guilds/:guild_id/members/@me",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.Member
|
||||
|
||||
endpoint :add_guild_member_role, :put, "/guilds/:guild_id/members/:user_id/roles/:role_id",
|
||||
reason: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :remove_guild_member_role,
|
||||
:delete,
|
||||
"/guilds/:guild_id/members/:user_id/roles/:role_id", reason: true, returns: nil
|
||||
|
||||
endpoint :remove_guild_member, :delete, "/guilds/:guild_id/members/:user_id",
|
||||
reason: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :get_guild_bans, :get, "/guilds/:guild_id/bans",
|
||||
query: [:limit, :before, :after],
|
||||
returns: [Dexcord.Ban]
|
||||
|
||||
endpoint :get_guild_ban, :get, "/guilds/:guild_id/bans/:user_id", returns: Dexcord.Ban
|
||||
|
||||
endpoint :create_guild_ban, :put, "/guilds/:guild_id/bans/:user_id",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :remove_guild_ban, :delete, "/guilds/:guild_id/bans/:user_id",
|
||||
reason: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :bulk_guild_ban, :post, "/guilds/:guild_id/bulk-ban",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: :raw
|
||||
|
||||
endpoint :get_current_user_voice_state, :get, "/guilds/:guild_id/voice-states/@me",
|
||||
returns: Dexcord.VoiceState
|
||||
|
||||
endpoint :get_user_voice_state, :get, "/guilds/:guild_id/voice-states/:user_id",
|
||||
returns: Dexcord.VoiceState
|
||||
|
||||
endpoint :edit_current_user_voice_state, :patch, "/guilds/:guild_id/voice-states/@me",
|
||||
body: true,
|
||||
returns: nil
|
||||
|
||||
endpoint :edit_user_voice_state, :patch, "/guilds/:guild_id/voice-states/:user_id",
|
||||
body: true,
|
||||
returns: nil
|
||||
end
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
defmodule Dexcord.Api.Misc do
|
||||
@moduledoc """
|
||||
Gateway / application-info / guild endpoints, declared with the
|
||||
Gateway / application-info endpoints, declared with the
|
||||
`Dexcord.Api.Endpoint` DSL.
|
||||
|
||||
`get_gateway_bot/0` stays `:raw` — its `url` / `shards` / `session_start_limit`
|
||||
shape has no model and the gateway reads it as a raw map.
|
||||
`get_current_application/0` returns a `Dexcord.ApplicationInfo`.
|
||||
|
||||
`get_guild` moved to `Dexcord.Api.Guilds` — the `Dexcord.Api` facade delegate
|
||||
follows the group automatically.
|
||||
"""
|
||||
use Dexcord.Api.Endpoint
|
||||
|
||||
|
|
@ -13,8 +16,4 @@ defmodule Dexcord.Api.Misc do
|
|||
|
||||
endpoint :get_current_application, :get, "/oauth2/applications/@me",
|
||||
returns: Dexcord.ApplicationInfo
|
||||
|
||||
endpoint :get_guild, :get, "/guilds/:guild_id",
|
||||
query: [:with_counts],
|
||||
returns: Dexcord.Guild
|
||||
end
|
||||
|
|
|
|||
37
lib/dexcord/api/roles.ex
Normal file
37
lib/dexcord/api/roles.ex
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
defmodule Dexcord.Api.Roles do
|
||||
@moduledoc """
|
||||
Guild role endpoints, declared with the `Dexcord.Api.Endpoint` DSL — mirrors
|
||||
the role section of the Discord "Guild" resource page.
|
||||
|
||||
`get_guild_role_member_counts` returns a `{role_id => count}` map with no
|
||||
model (`:raw`). `edit_guild_role_positions` takes a top-level list body and
|
||||
returns the full `[Dexcord.Role]` list.
|
||||
"""
|
||||
use Dexcord.Api.Endpoint
|
||||
|
||||
endpoint :get_guild_roles, :get, "/guilds/:guild_id/roles", returns: [Dexcord.Role]
|
||||
endpoint :get_guild_role, :get, "/guilds/:guild_id/roles/:role_id", returns: Dexcord.Role
|
||||
|
||||
endpoint :get_guild_role_member_counts, :get, "/guilds/:guild_id/roles/member-counts",
|
||||
returns: :raw
|
||||
|
||||
endpoint :create_guild_role, :post, "/guilds/:guild_id/roles",
|
||||
body: true,
|
||||
binary_wrap: :name,
|
||||
reason: true,
|
||||
returns: Dexcord.Role
|
||||
|
||||
endpoint :edit_guild_role_positions, :patch, "/guilds/:guild_id/roles",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: [Dexcord.Role]
|
||||
|
||||
endpoint :edit_guild_role, :patch, "/guilds/:guild_id/roles/:role_id",
|
||||
body: true,
|
||||
reason: true,
|
||||
returns: Dexcord.Role
|
||||
|
||||
endpoint :delete_guild_role, :delete, "/guilds/:guild_id/roles/:role_id",
|
||||
reason: true,
|
||||
returns: nil
|
||||
end
|
||||
|
|
@ -63,10 +63,13 @@ defmodule Dexcord.ApiFacadeTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "__endpoint_groups__/0 lists the six declared groups" do
|
||||
test "__endpoint_groups__/0 lists all declared groups" do
|
||||
assert Dexcord.Api.__endpoint_groups__() == [
|
||||
Dexcord.Api.Channels,
|
||||
Dexcord.Api.Messages,
|
||||
Dexcord.Api.Guilds,
|
||||
Dexcord.Api.Members,
|
||||
Dexcord.Api.Roles,
|
||||
Dexcord.Api.Users,
|
||||
Dexcord.Api.Interactions,
|
||||
Dexcord.Api.Commands,
|
||||
|
|
|
|||
233
test/dexcord/api_guilds_test.exs
Normal file
233
test/dexcord/api_guilds_test.exs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
defmodule Dexcord.ApiGuildsTest do
|
||||
@moduledoc """
|
||||
Representative wire tests for the `Dexcord.Api.Guilds`, `Dexcord.Api.Members`,
|
||||
and `Dexcord.Api.Roles` groups (Phase 5 Task 3): top-level-array bodies, the
|
||||
binary widget.png, audit-log decode, and the moved `get_guild`.
|
||||
"""
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias Dexcord.Api
|
||||
alias Dexcord.Api.Ratelimit
|
||||
alias Dexcord.FakeRest
|
||||
|
||||
@token "test.token.value"
|
||||
|
||||
setup do
|
||||
Dexcord.EnvSandbox.sandbox_env()
|
||||
Dexcord.Config.put(%{token: @token, handler: nil, intents: 0})
|
||||
|
||||
start_supervised!({Finch, name: Dexcord.Finch})
|
||||
start_supervised!(Ratelimit)
|
||||
start_supervised!(FakeRest)
|
||||
|
||||
Application.put_env(:dexcord, :api_base_url, FakeRest.base_url())
|
||||
FakeRest.subscribe(self())
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
# --- Guilds -------------------------------------------------------------
|
||||
|
||||
test "get_guild still works after moving out of Misc, honoring with_counts" do
|
||||
FakeRest.stub(:get, "/guilds/1", FakeRest.resp(200, body: ~s({"id":"1","name":"g"})))
|
||||
|
||||
assert {:ok, %Dexcord.Guild{id: 1, name: "g"}} = Api.get_guild(1, with_counts: true)
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert info.query_string == "with_counts=true"
|
||||
end
|
||||
|
||||
test "edit_guild PATCHes a body and decodes the Guild with a reason" do
|
||||
FakeRest.stub(:patch, "/guilds/1", FakeRest.resp(200, body: ~s({"id":"1","name":"new"})))
|
||||
|
||||
assert {:ok, %Dexcord.Guild{name: "new"}} =
|
||||
Api.edit_guild(1, %{"name" => "new"}, reason: "rebrand")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert Map.new(info.headers)["x-audit-log-reason"] == "rebrand"
|
||||
end
|
||||
|
||||
test "edit_guild_channel_positions sends a TOP-LEVEL array body and returns nil" do
|
||||
FakeRest.stub(:patch, "/guilds/1/channels", FakeRest.resp(204))
|
||||
|
||||
assert {:ok, nil} =
|
||||
Api.edit_guild_channel_positions(1, [
|
||||
%{"id" => "2", "position" => 1},
|
||||
%{"id" => "3", "position" => 2}
|
||||
])
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
|
||||
assert JSON.decode!(info.body) == [
|
||||
%{"id" => "2", "position" => 1},
|
||||
%{"id" => "3", "position" => 2}
|
||||
]
|
||||
end
|
||||
|
||||
test "get_guild_channels decodes a list of concrete channel structs" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/channels",
|
||||
FakeRest.resp(200, body: ~s([{"id":"2","type":0},{"id":"3","type":2}]))
|
||||
)
|
||||
|
||||
assert {:ok, [%Dexcord.TextChannel{id: 2}, %Dexcord.VoiceChannel{id: 3}]} =
|
||||
Api.get_guild_channels(1)
|
||||
end
|
||||
|
||||
test "get_guild_widget_settings decodes the settings struct" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/widget",
|
||||
FakeRest.resp(200, body: ~s({"enabled":true,"channel_id":"9"}))
|
||||
)
|
||||
|
||||
assert {:ok, %Dexcord.GuildWidgetSettings{enabled: true, channel_id: 9}} =
|
||||
Api.get_guild_widget_settings(1)
|
||||
end
|
||||
|
||||
test "get_guild_widget_image returns a non-JSON PNG body raw" do
|
||||
png = <<137, 80, 78, 71, 13, 10, 26, 10>>
|
||||
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/widget.png",
|
||||
FakeRest.resp(200, headers: [{"content-type", "image/png"}], body: png)
|
||||
)
|
||||
|
||||
assert {:ok, {:raw, ^png}} = Api.get_guild_widget_image(1, style: "banner1")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert info.query_string == "style=banner1"
|
||||
end
|
||||
|
||||
test "get_guild_audit_log encodes its query and decodes nested entries" do
|
||||
body = ~s({"audit_log_entries":[{"id":"5","action_type":1}],"users":[{"id":"7"}]})
|
||||
FakeRest.stub(:get, "/guilds/1/audit-logs", FakeRest.resp(200, body: body))
|
||||
|
||||
assert {:ok,
|
||||
%Dexcord.AuditLog{
|
||||
audit_log_entries: [%Dexcord.AuditLogEntry{id: 5}],
|
||||
users: [%Dexcord.User{id: 7}]
|
||||
}} = Api.get_guild_audit_log(1, user_id: "7", action_type: 1, limit: 50)
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
|
||||
assert URI.decode_query(info.query_string) == %{
|
||||
"user_id" => "7",
|
||||
"action_type" => "1",
|
||||
"limit" => "50"
|
||||
}
|
||||
end
|
||||
|
||||
# --- Members ------------------------------------------------------------
|
||||
|
||||
test "get_guild_member decodes a Member" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/members/2",
|
||||
FakeRest.resp(200, body: ~s({"nick":"bob","user":{"id":"2"}}))
|
||||
)
|
||||
|
||||
assert {:ok, %Dexcord.Member{nick: "bob", user: %Dexcord.User{id: 2}}} =
|
||||
Api.get_guild_member(1, 2)
|
||||
end
|
||||
|
||||
test "list_guild_members decodes a list and forwards paging query" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/members",
|
||||
FakeRest.resp(200, body: ~s([{"nick":"a"},{"nick":"b"}]))
|
||||
)
|
||||
|
||||
assert {:ok, [%Dexcord.Member{nick: "a"}, %Dexcord.Member{nick: "b"}]} =
|
||||
Api.list_guild_members(1, limit: 100, after: "0")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert URI.decode_query(info.query_string) == %{"limit" => "100", "after" => "0"}
|
||||
end
|
||||
|
||||
test "create_guild_ban PUTs a body, returns nil, carries a reason" do
|
||||
FakeRest.stub(:put, "/guilds/1/bans/2", FakeRest.resp(204))
|
||||
|
||||
assert {:ok, nil} =
|
||||
Api.create_guild_ban(1, 2, %{"delete_message_seconds" => 3600}, reason: "spam")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert info.method == "PUT"
|
||||
assert info.path == "/guilds/1/bans/2"
|
||||
assert Map.new(info.headers)["x-audit-log-reason"] == "spam"
|
||||
end
|
||||
|
||||
test "get_guild_ban decodes a Ban" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/bans/2",
|
||||
FakeRest.resp(200, body: ~s({"reason":"bad","user":{"id":"2"}}))
|
||||
)
|
||||
|
||||
assert {:ok, %Dexcord.Ban{reason: "bad", user: %Dexcord.User{id: 2}}} =
|
||||
Api.get_guild_ban(1, 2)
|
||||
end
|
||||
|
||||
test "get_current_user_voice_state decodes a VoiceState" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/voice-states/@me",
|
||||
FakeRest.resp(200, body: ~s({"channel_id":"9","session_id":"s"}))
|
||||
)
|
||||
|
||||
assert {:ok, %Dexcord.VoiceState{channel_id: 9, session_id: "s"}} =
|
||||
Api.get_current_user_voice_state(1)
|
||||
end
|
||||
|
||||
# --- Roles --------------------------------------------------------------
|
||||
|
||||
test "get_guild_roles decodes a list of Roles" do
|
||||
FakeRest.stub(
|
||||
:get,
|
||||
"/guilds/1/roles",
|
||||
FakeRest.resp(200, body: ~s([{"id":"2","name":"mod"},{"id":"3","name":"admin"}]))
|
||||
)
|
||||
|
||||
assert {:ok, [%Dexcord.Role{id: 2, name: "mod"}, %Dexcord.Role{id: 3, name: "admin"}]} =
|
||||
Api.get_guild_roles(1)
|
||||
end
|
||||
|
||||
test "create_guild_role wraps a binary as name and decodes a Role with a reason" do
|
||||
FakeRest.stub(:post, "/guilds/1/roles", FakeRest.resp(200, body: ~s({"id":"9","name":"vip"})))
|
||||
|
||||
assert {:ok, %Dexcord.Role{id: 9, name: "vip"}} =
|
||||
Api.create_guild_role(1, "vip", reason: "perk")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert JSON.decode!(info.body) == %{"name" => "vip"}
|
||||
assert Map.new(info.headers)["x-audit-log-reason"] == "perk"
|
||||
end
|
||||
|
||||
test "edit_guild_role_positions sends a list body and decodes a list of Roles" do
|
||||
FakeRest.stub(
|
||||
:patch,
|
||||
"/guilds/1/roles",
|
||||
FakeRest.resp(200, body: ~s([{"id":"2","position":1},{"id":"3","position":2}]))
|
||||
)
|
||||
|
||||
assert {:ok, [%Dexcord.Role{id: 2, position: 1}, %Dexcord.Role{id: 3, position: 2}]} =
|
||||
Api.edit_guild_role_positions(1, [
|
||||
%{"id" => "2", "position" => 1},
|
||||
%{"id" => "3", "position" => 2}
|
||||
])
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert is_list(JSON.decode!(info.body))
|
||||
end
|
||||
|
||||
test "delete_guild_role returns nil with a reason" do
|
||||
FakeRest.stub(:delete, "/guilds/1/roles/2", FakeRest.resp(204))
|
||||
|
||||
assert {:ok, nil} = Api.delete_guild_role(1, 2, reason: "obsolete")
|
||||
|
||||
assert_receive {:rest_hit, info}
|
||||
assert Map.new(info.headers)["x-audit-log-reason"] == "obsolete"
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue