From bad8c5b8c249f9060aea44d903f1ca47cbadf8b0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 30 Sep 2024 00:51:55 +1300 Subject: [PATCH] Test invite interaction & code coverage --- scripts/setup.js | 6 +- src/db/orm.test.js | 10 ++ src/discord/interactions/bridge.js | 115 ------------ src/discord/interactions/invite.js | 14 +- src/discord/interactions/invite.test.js | 228 ++++++++++++++++++++++++ src/discord/register-interactions.js | 17 -- src/discord/utils.js | 2 +- src/discord/utils.test.js | 73 ++++++++ src/matrix/power.test.js | 12 -- src/matrix/read-registration.js | 6 +- src/matrix/read-registration.test.js | 21 ++- src/types.d.ts | 7 +- test/data.js | 49 ++++- test/ooye-test-data.sql | 3 + test/test.js | 5 +- 15 files changed, 407 insertions(+), 161 deletions(-) delete mode 100644 src/discord/interactions/bridge.js create mode 100644 src/discord/interactions/invite.test.js delete mode 100644 src/matrix/power.test.js diff --git a/scripts/setup.js b/scripts/setup.js index a3902be2..13c24921 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -7,6 +7,7 @@ const sqlite = require("better-sqlite3") const {scheduler} = require("timers/promises") const {isDeepStrictEqual} = require("util") const {createServer} = require("http") +const {join} = require("path") const {prompt} = require("enquirer") const Input = require("enquirer/lib/prompts/input") @@ -208,7 +209,6 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) { url: bridgeOriginResponse.bridge_origin, ooye: { ...template.ooye, - ...serverNameResponse, ...bridgeOriginResponse, server_origin: serverOrigin, ...discordTokenResponse, @@ -335,8 +335,8 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) { } // Upload those emojis to the chosen location db.prepare("REPLACE INTO auto_emoji (name, emoji_id, guild_id) VALUES ('_', '_', ?)").run(guild.id) - await uploadAutoEmoji(discord.snow, guild, "L1", "docs/img/L1.png") - await uploadAutoEmoji(discord.snow, guild, "L2", "docs/img/L2.png") + await uploadAutoEmoji(discord.snow, guild, "L1", join(__dirname, "../docs/img/L1.png")) + await uploadAutoEmoji(discord.snow, guild, "L2", join(__dirname, "../docs/img/L2.png")) } console.log("✅ Emojis are ready...") diff --git a/src/db/orm.test.js b/src/db/orm.test.js index 278723a2..a8f10f45 100644 --- a/src/db/orm.test.js +++ b/src/db/orm.test.js @@ -58,3 +58,13 @@ test("orm: from: join direction works", t => { const hasNoOwnerInner = from("sim").join("sim_proxy", "user_id", "inner").select("user_id", "proxy_owner_id").where({sim_name: "crunch_god"}).get() t.deepEqual(hasNoOwnerInner, undefined) }) + +test("orm: select unsafe works (to select complex column names that can't be type verified)", t => { + const results = from("member_cache") + .join("member_power", "mxid") + .join("channel_room", "room_id") // only include rooms that are bridged + .and("where member_power.room_id = '*' and member_cache.power_level != member_power.power_level") + .selectUnsafe("mxid", "member_cache.room_id", "member_power.power_level") + .all() + t.equal(results[0].power_level, 100) +}) diff --git a/src/discord/interactions/bridge.js b/src/discord/interactions/bridge.js deleted file mode 100644 index 1fbc57e0..00000000 --- a/src/discord/interactions/bridge.js +++ /dev/null @@ -1,115 +0,0 @@ -// @ts-check - -const DiscordTypes = require("discord-api-types/v10") -const Ty = require("../../types") -const {discord, sync, db, select, from, as} = require("../../passthrough") -const assert = require("assert/strict") - -/** @type {import("../../matrix/api")} */ -const api = sync.require("../../matrix/api") - -/** @type {Map>} spaceID -> list of rooms */ -const cache = new Map() -/** @type {Map} roomID -> spaceID */ -const reverseCache = new Map() - -// Manage clearing the cache -sync.addTemporaryListener(as, "type:m.room.name", /** @param {Ty.Event.StateOuter} event */ async event => { - if (event.state_key !== "") return - const roomID = event.room_id - const spaceID = reverseCache.get(roomID) - if (!spaceID) return - const childRooms = await cache.get(spaceID) - if (!childRooms) return - if (event.content.name) { - const found = childRooms.find(r => r.value === roomID) - if (!found) return - found.name = event.content.name - } else { - cache.set(spaceID, Promise.resolve(childRooms.filter(r => r.value !== roomID))) - reverseCache.delete(roomID) - } -}) - -// Manage adding to the cache -async function getCachedHierarchy(spaceID) { - return cache.get(spaceID) || (() => { - const entry = (async () => { - const result = await api.getFullHierarchy(spaceID) - /** @type {{name: string, value: string}[]} */ - const childRooms = [] - for (const room of result) { - if (room.name && !room.name.match(/^\[[⛓️🔊]\]/) && room.room_type !== "m.space") { - childRooms.push({name: room.name, value: room.room_id}) - reverseCache.set(room.room_id, spaceID) - } - } - return childRooms - })() - cache.set(spaceID, entry) - return entry - })() -} - -/** @param {DiscordTypes.APIApplicationCommandAutocompleteGuildInteraction} interaction */ -async function interactAutocomplete({id, token, data, guild_id}) { - const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get() - if (!spaceID) { - return discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { - choices: [ - { - name: `Error: This server needs to be bridged somewhere first...`, - value: "baby" - } - ] - } - }) - } - - let rooms = await getCachedHierarchy(spaceID) - // @ts-ignore - rooms = rooms.filter(r => r.name.includes(data.options[0].value)) - - await discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { - choices: rooms - } - }) -} - -/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */ -async function interactSubmit({id, token, data, guild_id}) { - const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get() - if (!spaceID) { - return discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, - data: { - content: "Error: This server needs to be bridged somewhere first...", - flags: DiscordTypes.MessageFlags.Ephemeral - } - }) - } - - return discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, - data: { - content: "Valid input. This would do something but it isn't implemented yet.", - flags: DiscordTypes.MessageFlags.Ephemeral - } - }) -} - -/** @param {DiscordTypes.APIGuildInteraction} interaction */ -async function interact(interaction) { - if (interaction.type === DiscordTypes.InteractionType.ApplicationCommandAutocomplete) { - return interactAutocomplete(interaction) - } else if (interaction.type === DiscordTypes.InteractionType.ApplicationCommand) { - // @ts-ignore - return interactSubmit(interaction) - } -} - -module.exports.interact = interact diff --git a/src/discord/interactions/invite.js b/src/discord/interactions/invite.js index 3b39d9d7..bacf465a 100644 --- a/src/discord/interactions/invite.js +++ b/src/discord/interactions/invite.js @@ -14,13 +14,14 @@ const api = sync.require("../../matrix/api") /** * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction + * @param {{api: typeof api}} di * @returns {Promise} */ -async function _interact({data, channel, guild_id}) { +async function _interact({data, channel, guild_id}, {api}) { // Get named MXID /** @type {DiscordTypes.APIApplicationCommandInteractionDataStringOption[] | undefined} */ // @ts-ignore const options = data.options - const input = options?.[0].value || "" + const input = options?.[0]?.value || "" const mxid = input.match(/@([^:]+):([a-z0-9:-]+\.[a-z0-9.:-]+)/)?.[0] if (!mxid) return { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, @@ -110,9 +111,10 @@ async function _interact({data, channel, guild_id}) { /** * @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction + * @param {{api: typeof api}} di * @returns {Promise} */ -async function _interactButton({channel, message}) { +async function _interactButton({channel, message}, {api}) { const mxid = message.content.match(/`(@(?:[^:]+):(?:[a-z0-9:-]+\.[a-z0-9.:-]+))`/)?.[1] assert(mxid) const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() @@ -127,14 +129,16 @@ async function _interactButton({channel, message}) { } } +/* c8 ignore start */ + /** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction */ async function interact(interaction) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction)) + await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction, {api})) } /** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */ async function interactButton(interaction) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interactButton(interaction)) + await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interactButton(interaction, {api})) } module.exports.interact = interact diff --git a/src/discord/interactions/invite.test.js b/src/discord/interactions/invite.test.js new file mode 100644 index 00000000..431ff60a --- /dev/null +++ b/src/discord/interactions/invite.test.js @@ -0,0 +1,228 @@ +const {test} = require("supertape") +const DiscordTypes = require("discord-api-types/v10") +const {db, discord} = require("../../passthrough") +const {MatrixServerError} = require("../../matrix/mreq") +const {_interact, _interactButton} = require("./invite") + +test("invite: checks for missing matrix ID", async t => { + const msg = await _interact({ + data: { + options: [] + }, + channel: discord.channels.get("0"), + guild_id: "112760669178241024" + }, {}) + t.equal(msg.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`") +}) + +test("invite: checks for invalid matrix ID", async t => { + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence" + }] + }, + channel: discord.channels.get("0"), + guild_id: "112760669178241024" + }, {}) + t.equal(msg.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`") +}) + +test("invite: checks if channel exists or is autocreatable", async t => { + db.prepare("UPDATE guild_active SET autocreate = 0").run() + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("498323546729086986"), + guild_id: "112760669178241024" + }, {}) + t.equal(msg.data.content, "This channel isn't bridged, so you can't invite Matrix users yet. Try turning on automatic room-creation or link a Matrix room in the website.") + db.prepare("UPDATE guild_active SET autocreate = 1").run() +}) + +test("invite: checks if user is already invited to space", async t => { + let called = 0 + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("112760669178241024"), + guild_id: "112760669178241024" + }, { + api: { + getStateEvent: async (roomID, type, stateKey) => { + called++ + t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID + t.equal(type, "m.room.member") + t.equal(stateKey, "@cadence:cadence.moe") + return { + displayname: "cadence", + membership: "invite" + } + } + } + }) + t.equal(msg.data.content, "`@cadence:cadence.moe` already has an invite, which they haven't accepted yet.") + t.equal(called, 1) +}) + +test("invite: invites if user is not in space", async t => { + let called = 0 + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("112760669178241024"), + guild_id: "112760669178241024" + }, { + api: { + getStateEvent: async (roomID, type, stateKey) => { + called++ + t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID + t.equal(type, "m.room.member") + t.equal(stateKey, "@cadence:cadence.moe") + throw new MatrixServerError("State event doesn't exist or something") + }, + inviteToRoom: async (roomID, mxid) => { + called++ + t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID + t.equal(mxid, "@cadence:cadence.moe") + } + } + }) + t.equal(msg.data.content, "You invited `@cadence:cadence.moe` to the server.") + t.equal(called, 2) +}) + +test("invite: prompts to invite to room (if never joined)", async t => { + let called = 0 + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("112760669178241024"), + guild_id: "112760669178241024" + }, { + api: { + getStateEvent: async (roomID, type, stateKey) => { + called++ + t.equal(type, "m.room.member") + t.equal(stateKey, "@cadence:cadence.moe") + if (roomID === "!jjWAGMeQdNrVZSSfvz:cadence.moe") { // space ID + return { + displayname: "cadence", + membership: "join" + } + } else { + throw new MatrixServerError("State event doesn't exist or something") + } + } + } + }) + t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?") + t.equal(called, 2) +}) + +test("invite: prompts to invite to room (if left)", async t => { + let called = 0 + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("112760669178241024"), + guild_id: "112760669178241024" + }, { + api: { + getStateEvent: async (roomID, type, stateKey) => { + called++ + t.equal(type, "m.room.member") + t.equal(stateKey, "@cadence:cadence.moe") + if (roomID === "!jjWAGMeQdNrVZSSfvz:cadence.moe") { // space ID + return { + displayname: "cadence", + membership: "join" + } + } else { + return { + displayname: "cadence", + membership: "leave" + } + } + } + } + }) + t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?") + t.equal(called, 2) +}) + +test("invite button: invites to room when button clicked", async t => { + let called = 0 + const msg = await _interactButton({ + channel: discord.channels.get("112760669178241024"), + message: { + content: "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?" + } + }, { + api: { + inviteToRoom: async (roomID, mxid) => { + called++ + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") // room ID + t.equal(mxid, "@cadence:cadence.moe") + } + } + }) + t.equal(msg.data.content, "You invited `@cadence:cadence.moe` to the channel.") + t.equal(called, 1) +}) + +test("invite: no-op if in room and space", async t => { + let called = 0 + const msg = await _interact({ + data: { + options: [{ + name: "user", + type: DiscordTypes.ApplicationCommandOptionType.String, + value: "@cadence:cadence.moe" + }] + }, + channel: discord.channels.get("112760669178241024"), + guild_id: "112760669178241024" + }, { + api: { + getStateEvent: async (roomID, type, stateKey) => { + called++ + t.equal(type, "m.room.member") + t.equal(stateKey, "@cadence:cadence.moe") + return { + displayname: "cadence", + membership: "join" + } + } + } + }) + t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server and this channel.") + t.equal(called, 2) +}) diff --git a/src/discord/register-interactions.js b/src/discord/register-interactions.js index cd9203fa..7b1e52d5 100644 --- a/src/discord/register-interactions.js +++ b/src/discord/register-interactions.js @@ -7,7 +7,6 @@ const {id} = require("../../addbot") const matrixInfo = sync.require("./interactions/matrix-info.js") const invite = sync.require("./interactions/invite.js") const permissions = sync.require("./interactions/permissions.js") -const bridge = sync.require("./interactions/bridge.js") const reactions = sync.require("./interactions/reactions.js") const privacy = sync.require("./interactions/privacy.js") @@ -39,20 +38,6 @@ discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{ name: "user" } ] -}, { - name: "bridge", - contexts: [DiscordTypes.InteractionContextType.Guild], - type: DiscordTypes.ApplicationCommandType.ChatInput, - description: "Start bridging this channel to a Matrix room", - default_member_permissions: String(DiscordTypes.PermissionFlagsBits.ManageChannels), - options: [ - { - type: DiscordTypes.ApplicationCommandOptionType.String, - description: "Destination room to bridge to", - name: "room", - autocomplete: true - } - ] }, { name: "privacy", contexts: [DiscordTypes.InteractionContextType.Guild], @@ -94,8 +79,6 @@ async function dispatchInteraction(interaction) { await permissions.interact(interaction) } else if (interactionId === "permissions_edit") { await permissions.interactEdit(interaction) - } else if (interactionId === "bridge") { - await bridge.interact(interaction) } else if (interactionId === "Reactions") { await reactions.interact(interaction) } else if (interactionId === "privacy") { diff --git a/src/discord/utils.js b/src/discord/utils.js index dc96ff8a..dea05ae2 100644 --- a/src/discord/utils.js +++ b/src/discord/utils.js @@ -113,7 +113,7 @@ function isWebhookMessage(message) { * @param {Pick} message */ function isEphemeralMessage(message) { - return message.flags && (message.flags & DiscordTypes.MessageFlags.Ephemeral) + return Boolean(message.flags && (message.flags & DiscordTypes.MessageFlags.Ephemeral)) } /** @param {string} snowflake */ diff --git a/src/discord/utils.test.js b/src/discord/utils.test.js index 7c5f0c89..79004409 100644 --- a/src/discord/utils.test.js +++ b/src/discord/utils.test.js @@ -84,6 +84,67 @@ test("getPermissions: channel overwrite to allow role works", t => { t.equal((permissions & want), want) }) +test("getPermissions: channel overwrite to allow user works", t => { + const guildRoles = [ + { + version: 1695412489043, + unicode_emoji: null, + tags: {}, + position: 0, + permissions: "559623605571137", + name: "@everyone", + mentionable: false, + managed: false, + id: "1154868424724463687", + icon: null, + hoist: false, + flags: 0, + color: 0 + }, + { + version: 1695412604262, + unicode_emoji: null, + tags: { bot_id: "466378653216014359" }, + position: 1, + permissions: "536995904", + name: "PluralKit", + mentionable: false, + managed: true, + id: "1154868908336099444", + icon: null, + hoist: false, + flags: 0, + color: 0 + }, + { + version: 1698778936921, + unicode_emoji: null, + tags: {}, + position: 1, + permissions: "536870912", + name: "web hookers", + mentionable: false, + managed: false, + id: "1168988246680801360", + icon: null, + hoist: false, + flags: 0, + color: 0 + } + ] + const userRoles = [] + const userID = "353373325575323648" + const overwrites = [ + { type: 0, id: "1154868908336099444", deny: "0", allow: "1024" }, + { type: 0, id: "1154868424724463687", deny: "1024", allow: "0" }, + { type: 0, id: "1168988246680801360", deny: "0", allow: "1024" }, + { type: 1, id: "353373325575323648", deny: "0", allow: "1024" } + ] + const permissions = utils.getPermissions(userRoles, guildRoles, userID, overwrites) + const want = BigInt(1 << 10 | 1 << 16) + t.equal((permissions & want), want) +}) + test("hasSomePermissions: detects the permission", t => { const userPermissions = DiscordTypes.PermissionFlagsBits.MentionEveryone | DiscordTypes.PermissionFlagsBits.BanMembers const canRemoveMembers = utils.hasSomePermissions(userPermissions, ["KickMembers", "BanMembers"]) @@ -107,3 +168,15 @@ test("hasAllPermissions: doesn't detect not the permissions", t => { const canRemoveMembers = utils.hasAllPermissions(userPermissions, ["KickMembers", "BanMembers"]) t.equal(canRemoveMembers, false) }) + +test("isEphemeralMessage: detects ephemeral message", t => { + t.equal(utils.isEphemeralMessage(data.special_message.ephemeral_message), true) +}) + +test("isEphemeralMessage: doesn't detect normal message", t => { + t.equal(utils.isEphemeralMessage(data.message.simple_plaintext), false) +}) + +test("getPublicUrlForCdn: no-op on non-discord URL", t => { + t.equal(utils.getPublicUrlForCdn("https://cadence.moe"), "https://cadence.moe") +}) diff --git a/src/matrix/power.test.js b/src/matrix/power.test.js deleted file mode 100644 index 5423c4fa..00000000 --- a/src/matrix/power.test.js +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-check - -const {test} = require("supertape") -const power = require("./power") - -test("power: get affected rooms", t => { - t.deepEqual(power._getAffectedRooms(), [{ - mxid: "@test_auto_invite:example.org", - power_level: 100, - room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", - }]) -}) diff --git a/src/matrix/read-registration.js b/src/matrix/read-registration.js index 9fb05359..d126851c 100644 --- a/src/matrix/read-registration.js +++ b/src/matrix/read-registration.js @@ -9,7 +9,7 @@ const registrationFilePath = path.join(process.cwd(), "registration.yaml") /** @param {import("../types").AppServiceRegistrationConfig} reg */ function checkRegistration(reg) { - reg["ooye"].invite = (reg.ooye.invite || []).filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line + reg["ooye"].invite = reg.ooye.invite.filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line assert(reg.ooye?.max_file_size) assert(reg.ooye?.namespace_prefix) assert(reg.ooye?.server_name) @@ -19,6 +19,7 @@ function checkRegistration(reg) { assert.match(reg.url, /^https?:/, "url must start with http:// or https://") } +/* c8 ignore next 4 */ /** @param {import("../types").AppServiceRegistrationConfig} reg */ function writeRegistration(reg) { fs.writeFileSync(registrationFilePath, JSON.stringify(reg, null, 2)) @@ -52,6 +53,7 @@ function getTemplateRegistration(serverName) { socket: 6693, ooye: { namespace_prefix, + server_name: serverName, max_file_size: 5000000, content_length_workaround: false, include_user_id_in_mxid: false, @@ -66,6 +68,8 @@ function readRegistration() { try { const content = fs.readFileSync(registrationFilePath, "utf8") result = JSON.parse(content) + result.ooye.invite ||= [] + /* c8 ignore next */ } catch (e) {} return result } diff --git a/src/matrix/read-registration.test.js b/src/matrix/read-registration.test.js index 80ac09f1..5fb3b55c 100644 --- a/src/matrix/read-registration.test.js +++ b/src/matrix/read-registration.test.js @@ -1,5 +1,8 @@ +// @ts-check + +const tryToCatch = require("try-to-catch") const {test} = require("supertape") -const {reg} = require("./read-registration") +const {reg, checkRegistration, getTemplateRegistration} = require("./read-registration") test("reg: has necessary parameters", t => { const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"] @@ -8,3 +11,19 @@ test("reg: has necessary parameters", t => { propertiesToCheck ) }) + +test("check: passes on sample", t => { + checkRegistration(reg) + t.pass("all assertions passed") +}) + +test("check: fails on template as template is missing some required values that are gathered during setup", t => { + let err + try { + // @ts-ignore + checkRegistration(getTemplateRegistration("cadence.moe")) + } catch (e) { + err = e + } + t.ok(err, "one of the assertions failed as expected") +}) diff --git a/src/types.d.ts b/src/types.d.ts index 62d9b30f..576bb59f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -55,9 +55,10 @@ export type InitialAppServiceRegistrationConfig = { socket?: string | number, ooye: { namespace_prefix: string - max_file_size: number, - content_length_workaround: boolean, - invite: string[], + server_name: string + max_file_size: number + content_length_workaround: boolean + invite: string[] include_user_id_in_mxid: boolean } } diff --git a/test/data.js b/test/data.js index eb2c42a8..9dc2d723 100644 --- a/test/data.js +++ b/test/data.js @@ -4128,7 +4128,54 @@ module.exports = { guild_id: "112760669178241024" }, position: 0 - } + }, + ephemeral_message: { + webhook_id: "684280192553844747", + type: 20, + tts: false, + timestamp: "2024-09-29T11:22:04.865000+00:00", + position: 0, + pinned: false, + nonce: "1289910062243905536", + mentions: [], + mention_roles: [], + mention_everyone: false, + interaction_metadata: { + user: {baby: true}, + type: 2, + name: "invite", + id: "1289910063691206717", + command_type: 1, + authorizing_integration_owners: {baby: true} + }, + interaction: { + user: {baby: true}, + type: 2, + name: "invite", + id: "1289910063691206717" + }, + id: "1289910064995504182", + flags: 64, + embeds: [], + edited_timestamp: null, + content: "`@cadence:cadence.moe` is already in this server and this channel.", + components: [], + channel_id: "1100319550446252084", + author: { + username: "Matrix Bridge", + public_flags: 0, + id: "684280192553844747", + global_name: null, + discriminator: "5728", + clan: null, + bot: true, + avatar_decoration_data: null, + avatar: "48ae3c24f2a6ec5c60c41bdabd904018" + }, + attachments: [], + application_id: "684280192553844747" + }, + shard_id: 0 }, interaction_message: { thinking_interaction_without_bot_user: { diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 2c235613..b6f0951c 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -3,6 +3,9 @@ BEGIN TRANSACTION; INSERT INTO guild_space (guild_id, space_id, privacy_level) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe', 0); +INSERT INTO guild_active (guild_id, autocreate) VALUES +('112760669178241024', 1); + INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom_avatar) VALUES ('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, NULL), ('497161350934560778', '!CzvdIdUQXgUjDVKxeU:cadence.moe', 'amanda-spam', NULL, NULL, NULL), diff --git a/test/test.js b/test/test.js index 281df29d..07146ab2 100644 --- a/test/test.js +++ b/test/test.js @@ -23,8 +23,8 @@ reg.id = "baby" // don't actually take authenticated actions on the server reg.as_token = "baby" reg.hs_token = "baby" reg.ooye.bridge_origin = "https://bridge.example.org" -reg.ooye.invite = [] +/** @type {import("heatsync").default} */ // @ts-ignore const sync = new HeatSync({watchFS: false}) const discord = { @@ -35,6 +35,7 @@ const discord = { id: "684280192553844747" }, channels: new Map([ + [data.channel.general.id, data.channel.general], ["497161350934560778", { guild_id: "497159726455455754" }], @@ -117,7 +118,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/matrix/kstate.test") require("../src/matrix/api.test") require("../src/matrix/file.test") - require("../src/matrix/power.test") require("../src/matrix/read-registration.test") require("../src/matrix/txnid.test") require("../src/d2m/actions/create-room.test") @@ -136,4 +136,5 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/m2d/converters/event-to-message.test") require("../src/m2d/converters/utils.test") require("../src/m2d/converters/emoji-sheet.test") + require("../src/discord/interactions/invite.test") })()