Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions bases/bot_detector/discord_bot/cogs/rsn_linking_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ async def install_plugin_msg(self) -> discord.Embed:
)
return embed

async def set_primary_msg(self, name: str) -> discord.Embed:
embed = discord.Embed(title=f"Setting '{name}' as Primary:", color=0x00FF00)
embed.add_field(
name="STATUS",
inline=False,
value=cleandoc(
f"""
'{name}' is now your primary account.
"""
),
)
embed.add_field(
name="INFO",
inline=False,
value=cleandoc(
"""
You can change your primary at any time by typing '/set_primary <RSN>'.
"""
),
)
return embed

async def unlink_msg(self, name: str, role_removed: bool) -> discord.Embed:
embed = discord.Embed(title=f"Unlinking '{name}':", color=0xFFA500)
embed.add_field(
Expand Down Expand Up @@ -349,3 +371,59 @@ async def unlink(self, ctx: Context, *, name: str):

embed = await self.unlink_msg(name=name, role_removed=role_removed)
await ctx.reply(embed=embed)

@commands.hybrid_command(name="set_primary")
async def set_primary(self, ctx: Context, *, name: str):
logger.debug(
f"{ctx.author.name=}, {ctx.author.id=}, Requesting set_primary, {name=}"
)

if not name:
await ctx.reply(
"Please specify the RSN of the account you'd wish to set as primary. /set_primary <RSN>"
)
return

if not string_processing.is_valid_rsn(name):
await ctx.reply(f"{name} isn't a valid Runescape user name.")
return

assert self.deps.legacy_api is not None
player = await self.deps.legacy_api.get_player(player_name=name)
player_id = player.get("id") if player else None
if player_id is None:
await ctx.reply(f"No player found for '{name}'.")
return

session_factory = self.deps.get_session_factory()
discord_id = str(ctx.author.id)
async with session_factory() as session:
linked = await self.verification_repo.get_linked_accounts(
async_session=session,
discord_id=discord_id,
)
match = [
account for account in linked if account.Player_id == int(player_id)
]

if not match:
await ctx.reply(f"'{name}' is not linked to your Discord account.")
return

if match[0].primary_rsn == 1:
await ctx.reply(f"'{name}' is already your primary account.")
return

updated = await self.verification_repo.set_primary_rsn(
async_session=session,
discord_id=discord_id,
player_id=int(player_id),
is_primary=True,
)

if not updated:
await ctx.reply(f"Failed to set '{name}' as your primary account.")
return

embed = await self.set_primary_msg(name=name)
await ctx.reply(embed=embed)
126 changes: 126 additions & 0 deletions test/bases/bot_detector/discord_bot/test_rsn_linking_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ async def _invoke(cog: rsnLinkingCommands, ctx: AsyncMock, name: str):
return await type(cog).unlink.callback(cog, ctx, name=name)


async def _invoke_set_primary(cog: rsnLinkingCommands, ctx: AsyncMock, name: str):
return await type(cog).set_primary.callback(cog, ctx, name=name)


def _verified_role() -> MagicMock:
role = MagicMock(spec=discord.Role)
role.id = VERIFIED_ROLE_ID
Expand Down Expand Up @@ -193,3 +197,125 @@ async def test_unlink_skips_role_lookup_outside_guild():

cog.verification_repo.get_linked_accounts.assert_awaited_once()
ctx.author.remove_roles.assert_not_awaited()


def _linked_account(player_id: int, primary_rsn: int = 0) -> MagicMock:
account = MagicMock()
account.Player_id = player_id
account.primary_rsn = primary_rsn
return account


@pytest.mark.asyncio
async def test_set_primary_success_replies_embed():
cog = _make_cog()
ctx = _make_ctx()

cog.deps.legacy_api.get_player = AsyncMock(
return_value={"id": 42, "name": "Zezima"}
)
cog.deps.get_session_factory = MagicMock(return_value=_make_session_factory())
cog.verification_repo.get_linked_accounts = AsyncMock(
return_value=[_linked_account(player_id=42)]
)
cog.verification_repo.set_primary_rsn = AsyncMock(return_value=True)

await _invoke_set_primary(cog, ctx, "Zezima")

cog.verification_repo.set_primary_rsn.assert_awaited_once()
kwargs = cog.verification_repo.set_primary_rsn.call_args.kwargs
assert kwargs["discord_id"] == "12345"
assert kwargs["player_id"] == 42
assert kwargs["is_primary"] is True
ctx.reply.assert_awaited_once()
embed = ctx.reply.call_args.kwargs["embed"]
assert embed.title == "Setting 'Zezima' as Primary:"
fields = {f.name for f in embed.fields}
assert "STATUS" in fields


@pytest.mark.asyncio
async def test_set_primary_invalid_rsn_rejected():
cog = _make_cog()
ctx = _make_ctx()

await _invoke_set_primary(cog, ctx, "this name is way too long")

ctx.reply.assert_awaited_once_with(
"this name is way too long isn't a valid Runescape user name."
)
cog.deps.legacy_api.get_player.assert_not_called()


@pytest.mark.asyncio
async def test_set_primary_player_not_found():
cog = _make_cog()
ctx = _make_ctx()

cog.deps.legacy_api.get_player = AsyncMock(return_value=None)

await _invoke_set_primary(cog, ctx, "Zezima")

ctx.reply.assert_awaited_once_with("No player found for 'Zezima'.")
cog.verification_repo.set_primary_rsn.assert_not_called()


@pytest.mark.asyncio
async def test_set_primary_not_linked_rejected():
cog = _make_cog()
ctx = _make_ctx()

cog.deps.legacy_api.get_player = AsyncMock(
return_value={"id": 42, "name": "Zezima"}
)
cog.deps.get_session_factory = MagicMock(return_value=_make_session_factory())
cog.verification_repo.get_linked_accounts = AsyncMock(
return_value=[_linked_account(player_id=7)]
)

await _invoke_set_primary(cog, ctx, "Zezima")

ctx.reply.assert_awaited_once_with(
"'Zezima' is not linked to your Discord account."
)
cog.verification_repo.set_primary_rsn.assert_not_called()


@pytest.mark.asyncio
async def test_set_primary_already_primary_rejected():
cog = _make_cog()
ctx = _make_ctx()

cog.deps.legacy_api.get_player = AsyncMock(
return_value={"id": 42, "name": "Zezima"}
)
cog.deps.get_session_factory = MagicMock(return_value=_make_session_factory())
cog.verification_repo.get_linked_accounts = AsyncMock(
return_value=[_linked_account(player_id=42, primary_rsn=1)]
)

await _invoke_set_primary(cog, ctx, "Zezima")

ctx.reply.assert_awaited_once_with("'Zezima' is already your primary account.")
cog.verification_repo.set_primary_rsn.assert_not_called()


@pytest.mark.asyncio
async def test_set_primary_update_failure_replies_error():
cog = _make_cog()
ctx = _make_ctx()

cog.deps.legacy_api.get_player = AsyncMock(
return_value={"id": 42, "name": "Zezima"}
)
cog.deps.get_session_factory = MagicMock(return_value=_make_session_factory())
cog.verification_repo.get_linked_accounts = AsyncMock(
return_value=[_linked_account(player_id=42)]
)
cog.verification_repo.set_primary_rsn = AsyncMock(return_value=False)

await _invoke_set_primary(cog, ctx, "Zezima")

ctx.reply.assert_awaited_once_with(
"Failed to set 'Zezima' as your primary account."
)
Loading