From f77602afa6171f0841432c16f4e4a9c874db91a5 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 30 Sep 2024 16:26:12 +1300 Subject: [PATCH] Add tests for privacy interaction --- src/discord/interactions/privacy.js | 49 ++++++++++---- src/discord/interactions/privacy.test.js | 86 ++++++++++++++++++++++++ test/test.js | 1 + 3 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 src/discord/interactions/privacy.test.js diff --git a/src/discord/interactions/privacy.js b/src/discord/interactions/privacy.js index bb8c6c9c..8227b5a8 100644 --- a/src/discord/interactions/privacy.js +++ b/src/discord/interactions/privacy.js @@ -3,32 +3,38 @@ const DiscordTypes = require("discord-api-types/v10") const {discord, sync, db, select} = require("../../passthrough") const {id: botID} = require("../../../addbot") +const {InteractionMethods} = require("snowtransfer") /** @type {import("../../d2m/actions/create-space")} */ const createSpace = sync.require("../../d2m/actions/create-space") /** * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction + * @param {{createSpace: typeof createSpace}} di + * @returns {AsyncGenerator<{[k in keyof InteractionMethods]?: Parameters[2]}>} */ -async function interact({id, token, data, guild_id}) { +async function* _interact({data, guild_id}, {createSpace}) { // Check guild is bridged const current = select("guild_space", "privacy_level", {guild_id}).pluck().get() - if (current == null) return { - type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, - data: { - content: "This server isn't bridged to Matrix, so you can't set the Matrix privacy level.", - flags: DiscordTypes.MessageFlags.Ephemeral - } + InteractionMethods.prototype.createInteractionResponse + if (current == null) { + return yield {createInteractionResponse: { + type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, + data: { + content: "This server isn't bridged to Matrix, so you can't set the Matrix privacy level.", + flags: DiscordTypes.MessageFlags.Ephemeral + } + }} } // Get input level /** @type {DiscordTypes.APIApplicationCommandInteractionDataStringOption[] | undefined} */ // @ts-ignore const options = data.options - const input = options?.[0].value || "" + const input = options?.[0]?.value || "" const levels = ["invite", "link", "directory"] const level = levels.findIndex(x => input === x) if (level === -1) { - return discord.snow.interaction.createInteractionResponse(id, token, { + return yield {createInteractionResponse: { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: "**Usage: `/privacy `**. This will set who can join the space on Matrix-side. There are three levels:" @@ -38,22 +44,37 @@ async function interact({id, token, data, guild_id}) { + `\n**Current privacy level: \`${levels[current]}\`**`, flags: DiscordTypes.MessageFlags.Ephemeral } - }) + }} } - await discord.snow.interaction.createInteractionResponse(id, token, { + yield {createInteractionResponse: { type: DiscordTypes.InteractionResponseType.DeferredChannelMessageWithSource, data: { flags: DiscordTypes.MessageFlags.Ephemeral } - }) + }} db.prepare("UPDATE guild_space SET privacy_level = ? WHERE guild_id = ?").run(level, guild_id) await createSpace.syncSpaceFully(guild_id) // this is inefficient but OK to call infrequently on user request - await discord.snow.interaction.editOriginalInteractionResponse(botID, token, { + yield {editOriginalInteractionResponse: { content: `Privacy level updated to \`${levels[level]}\`.` - }) + }} +} + +/* c8 ignore start */ + +/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */ +async function interact(interaction) { + for await (const response of _interact(interaction, {createSpace})) { + if (response.createInteractionResponse) { + // TODO: Test if it is reasonable to remove `await` from these calls. Or zip these calls with the next interaction iteration and use Promise.all. + await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, response.createInteractionResponse) + } else if (response.editOriginalInteractionResponse) { + await discord.snow.interaction.editOriginalInteractionResponse(botID, interaction.token, response.editOriginalInteractionResponse) + } + } } module.exports.interact = interact +module.exports._interact = _interact diff --git a/src/discord/interactions/privacy.test.js b/src/discord/interactions/privacy.test.js new file mode 100644 index 00000000..a94bbc73 --- /dev/null +++ b/src/discord/interactions/privacy.test.js @@ -0,0 +1,86 @@ +const {test} = require("supertape") +const DiscordTypes = require("discord-api-types/v10") +const {select, db} = require("../../passthrough") +const {_interact} = require("./privacy") + +/** + * @template T + * @param {AsyncIterable} ai + * @returns {Promise} + */ +async function fromAsync(ai) { + const result = [] + for await (const value of ai) { + result.push(value) + } + return result +} + +test("privacy: checks if guild is bridged", async t => { + const msgs = await fromAsync(_interact({ + data: { + options: [] + }, + guild_id: "0" + }, {})) + t.equal(msgs.length, 1) + t.equal(msgs[0].createInteractionResponse.data.content, "This server isn't bridged to Matrix, so you can't set the Matrix privacy level.") +}) + +test("privacy: reports usage if there is no parameter", async t => { + const msgs = await fromAsync(_interact({ + data: { + options: [] + }, + guild_id: "112760669178241024" + }, {})) + t.equal(msgs.length, 1) + t.match(msgs[0].createInteractionResponse.data.content, /Usage: `\/privacy/) +}) + +test("privacy: reports usage for invalid parameter", async t => { + const msgs = await fromAsync(_interact({ + data: { + options: [ + { + name: "level", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "info" + } + ] + }, + guild_id: "112760669178241024" + }, {})) + t.equal(msgs.length, 1) + t.match(msgs[0].createInteractionResponse.data.content, /Usage: `\/privacy/) +}) + +test("privacy: updates setting and calls syncSpace for valid parameter", async t => { + let called = 0 + const msgs = await fromAsync(_interact({ + data: { + options: [ + { + name: "level", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "directory" + } + ] + }, + guild_id: "112760669178241024" + }, { + createSpace: { + async syncSpaceFully(guildID) { + called++ + t.equal(guildID, "112760669178241024") + } + } + })) + t.equal(msgs.length, 2) + t.equal(msgs[0].createInteractionResponse.type, DiscordTypes.InteractionResponseType.DeferredChannelMessageWithSource) + t.equal(msgs[1].editOriginalInteractionResponse.content, "Privacy level updated to `directory`.") + t.equal(called, 1) + t.equal(select("guild_space", "privacy_level", {guild_id: "112760669178241024"}).pluck().get(), 2) + // Undo database changes + db.prepare("UPDATE guild_space SET privacy_level = 0 WHERE guild_id = ?").run("112760669178241024") +}) diff --git a/test/test.js b/test/test.js index 66954944..c6ab1bbb 100644 --- a/test/test.js +++ b/test/test.js @@ -140,5 +140,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/m2d/converters/emoji-sheet.test") require("../src/discord/interactions/invite.test") require("../src/discord/interactions/matrix-info.test") + require("../src/discord/interactions/privacy.test") require("../src/discord/interactions/reactions.test") })()