diff --git a/src/discord/interactions/invite.test.js b/src/discord/interactions/invite.test.js index 8290718..571623d 100644 --- a/src/discord/interactions/invite.test.js +++ b/src/discord/interactions/invite.test.js @@ -59,7 +59,7 @@ test("invite: checks if guild exists", async t => { // it might not exist if the }) test("invite: checks if channel exists or is autocreatable", async t => { - db.prepare("UPDATE guild_active SET autocreate = 0").run() + db.prepare("UPDATE guild_active SET autocreate = 0 WHERE guild_id = '112760669178241024'").run() const msgs = await fromAsync(_interact({ data: { options: [{ @@ -72,7 +72,7 @@ test("invite: checks if channel exists or is autocreatable", async t => { guild_id: "112760669178241024" }, {})) t.equal(msgs[0].createInteractionResponse.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() + db.prepare("UPDATE guild_active SET autocreate = 1 WHERE guild_id = '112760669178241024'").run() }) test("invite: checks if user is already invited to space", async t => { diff --git a/src/web/pug-sync.js b/src/web/pug-sync.js index fb83caa..a966f06 100644 --- a/src/web/pug-sync.js +++ b/src/web/pug-sync.js @@ -7,6 +7,7 @@ const getRelativePath = require("get-relative-path") const h3 = require("h3") const {defineEventHandler, defaultContentType, setResponseStatus, useSession, getQuery} = h3 const {compileFile} = require("@cloudrac3r/pug") +const pretty = process.argv.join(" ").includes("test") const {reg} = require("../matrix/read-registration") @@ -31,7 +32,7 @@ function render(event, filename, locals) { function compile() { try { - const template = compileFile(path, {}) + const template = compileFile(path, {pretty}) pugCache.set(path, async (event, locals) => { defaultContentType(event, "text/html; charset=utf-8") const session = await useSession(event, {password: reg.as_token}) diff --git a/src/web/pug/guild_access_denied.pug b/src/web/pug/guild_access_denied.pug index 319d4de..6e88e81 100644 --- a/src/web/pug/guild_access_denied.pug +++ b/src/web/pug/guild_access_denied.pug @@ -23,3 +23,13 @@ block body != icons.Spots.SpotAlertXL p Either the selected server doesn't exist, or you don't have the Manage Server permission on Discord. p If you've checked your permissions, try #[a(href=rel("/oauth")) logging in again.] + + else if !row + .s-empty-state.wmx4.p48 + != icons.Spots.SpotAlertXL + p Please add the bot to your server using the buttons on the home page. + + else + .s-empty-state.wmx4.p48 + != icons.Spots.SpotAlertXL + p Access denied. diff --git a/src/web/pug/guild_not_linked.pug b/src/web/pug/guild_not_linked.pug index 2eade34..0e2a2d4 100644 --- a/src/web/pug/guild_not_linked.pug +++ b/src/web/pug/guild_not_linked.pug @@ -11,7 +11,7 @@ mixin space(space) strong= space.name if space.topic ul.s-user-card--awards - li space.topic + li= space.topic block body .s-notice.s-notice__info.d-flex.g16 diff --git a/src/web/routes/guild.js b/src/web/routes/guild.js index ff645a8..a1bffef 100644 --- a/src/web/routes/guild.js +++ b/src/web/routes/guild.js @@ -115,12 +115,12 @@ as.router.get("/guild", defineEventHandler(async event => { // Permission problems if (!guild_id || !guild || !(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guild_id) || !row) { - return pugSync.render(event, "guild_access_denied.pug", {guild_id}) + return pugSync.render(event, "guild_access_denied.pug", {guild_id, row}) } // Self-service guild that hasn't been linked yet - needs a special page encouraging the link flow if (!row.space_id && row.autocreate === 0) { - const spaces = db.prepare("SELECT room_id, type, name, avatar FROM invite LEFT JOIN guild_space ON invite.room_id = guild_space.space_id WHERE mxid = ? AND space_id IS NULL and type = 'm.space'").all(session.data.mxid) + const spaces = db.prepare("SELECT room_id, type, name, topic, avatar FROM invite LEFT JOIN guild_space ON invite.room_id = guild_space.space_id WHERE mxid = ? AND space_id IS NULL and type = 'm.space'").all(session.data.mxid) return pugSync.render(event, "guild_not_linked.pug", {guild, guild_id, spaces}) } diff --git a/src/web/routes/guild.test.js b/src/web/routes/guild.test.js index 3ef177e..671baed 100644 --- a/src/web/routes/guild.test.js +++ b/src/web/routes/guild.test.js @@ -1,70 +1,96 @@ // @ts-check const tryToCatch = require("try-to-catch") -const {test} = require("supertape") -const {router} = require("../../../test/web") +const {router, test} = require("../../../test/web") const {MatrixServerError} = require("../../matrix/mreq") let nonce test("web guild: access denied when not logged in", async t => { - const content = await router.test("get", "/guild?guild_id=112760669178241024", { + const html = await router.test("get", "/guild?guild_id=112760669178241024", { sessionData: { }, }) - t.match(content, /You need to log in to manage your servers./) + t.match(html, /You need to log in to manage your servers./) }) test("web guild: asks to select guild if not selected", async t => { - const content = await router.test("get", "/guild", { + const html = await router.test("get", "/guild", { sessionData: { user_id: "1", managedGuilds: [] }, }) - t.match(content, /Select a server from the top right corner to continue./) + t.match(html, /Select a server from the top right corner to continue./) }) test("web guild: access denied when guild id messed up", async t => { - const content = await router.test("get", "/guild?guild_id=1", { + const html = await router.test("get", "/guild?guild_id=1", { sessionData: { user_id: "1", managedGuilds: [] }, }) - t.match(content, /the selected server doesn't exist/) + t.match(html, /the selected server doesn't exist/) }) test("web invite: access denied with invalid nonce", async t => { - const content = await router.test("get", "/invite?nonce=1") - t.match(content, /This QR code has expired./) + const html = await router.test("get", "/invite?nonce=1") + t.match(html, /This QR code has expired./) }) test("web guild: can view unbridged guild", async t => { - const content = await router.test("get", "/guild?guild_id=66192955777486848", { + const html = await router.test("get", "/guild?guild_id=66192955777486848", { sessionData: { user_id: "1", managedGuilds: ["66192955777486848"] - }, - api: { - async getStateEvent(roomID, type, key) { - return {} - }, - async getMembers(roomID, membership) { - return {chunk: []} - }, - async getFullHierarchy(roomID) { - return [] - } } }) - t.match(content, /Function & Arg`) }) +test("web guild: unbridged self-service guild prompts log in to matrix", async t => { + const html = await router.test("get", "/guild?guild_id=665289423482519565", { + sessionData: { + user_id: "1", + managedGuilds: ["665289423482519565"] + } + }) + t.has(html, `You picked self-service mode`) + t.has(html, `You need to log in with Matrix first`) +}) + +test("web guild: unbridged self-service guild asks to be invited", async t => { + const html = await router.test("get", "/guild?guild_id=665289423482519565", { + sessionData: { + mxid: "@user:example.org", + user_id: "1", + managedGuilds: ["665289423482519565"] + } + }) + t.has(html, `On Matrix, invite <`) +}) + +test("web guild: unbridged self-service guild shows available spaces", async t => { + const html = await router.test("get", "/guild?guild_id=665289423482519565", { + sessionData: { + mxid: "@cadence:cadence.moe", + user_id: "1", + managedGuilds: ["665289423482519565"] + } + }) + t.has(html, `Data Horde`) + t.has(html, `
  • here is the space topic
  • `) + // t.match(html, //) + // t.notMatch(html, /some room<\/strong>/) + // t.notMatch(html, /somebody else's space<\/strong>/) +}) + + test("web guild: can view bridged guild", async t => { - const content = await router.test("get", "/guild?guild_id=112760669178241024", { + const html = await router.test("get", "/guild?guild_id=112760669178241024", { sessionData: { managedGuilds: ["112760669178241024"] }, @@ -80,14 +106,14 @@ test("web guild: can view bridged guild", async t => { } } }) - t.match(content, / { - const content = await router.test("get", `/invite?nonce=${nonce}`) - t.match(content, /Invite a Matrix user/) + const html = await router.test("get", `/invite?nonce=${nonce}`) + t.match(html, /Invite a Matrix user/) }) diff --git a/test/data.js b/test/data.js index b92ae1b..c18f173 100644 --- a/test/data.js +++ b/test/data.js @@ -18,6 +18,20 @@ module.exports = { id: "112760669178241024", default_thread_rate_limit_per_user: 0, guild_id: "112760669178241024" + }, + saving_the_world: { + type: 0, + topic: "Anything and everything archiving/preservation related", + rate_limit_per_user: 0, + position: 0, + permission_overwrites: [], + parent_id: "665289423482519566", + name: "saving-the-world", + last_pin_timestamp: "2021-04-14T18:39:41+00:00", + last_message_id: "1335828749479837750", + id: "665310973967597573", + flags: 0, + guild_id: "665289423482519565" } }, room: { @@ -252,6 +266,111 @@ module.exports = { nsfw: false, safety_alerts_channel_id: null, lazy: true + }, + data_horde: { + preferred_locale: "en-US", + afk_channel_id: null, + profile: null, + owner_id: "222343226990788609", + soundboard_sounds: [], + hub_type: null, + mfa_level: 0, + activity_instances: [], + inventory_settings: null, + voice_states: [], + system_channel_id: "675397790204952636", + id: "665289423482519565", + member_count: 138, + clan: null, + default_message_notifications: 1, + name: "Data Horde", + banner: null, + premium_subscription_count: 0, + max_stage_video_channel_users: 50, + max_members: 500000, + incidents_data: null, + joined_at: "2020-05-10T02:00:10.646000+00:00", + unavailable: false, + discovery_splash: null, + threads: [], + system_channel_flags: 0, + safety_alerts_channel_id: null, + nsfw: false, + nsfw_level: 0, + stage_instances: [], + large: false, + icon: "d7c4bdb35c10f21e475a50fb205d5c32", + roles: [ + { + version: 1683238686112, + unicode_emoji: null, + tags: {}, + position: 0, + permissions: "2221982107557441", + name: "@everyone", + mentionable: false, + managed: false, + id: "665289423482519565", + icon: null, + hoist: false, + flags: 0, + color: 0 + }, + { + version: 1683791258594, + unicode_emoji: null, + tags: {}, + position: 22, + permissions: "7515668211", + name: "Founder", + mentionable: true, + managed: false, + id: "665290147377578005", + icon: null, + hoist: false, + flags: 0, + color: 1752220 + }, + { + version: 1683791258580, + unicode_emoji: null, + tags: {}, + position: 19, + permissions: "6546775617", + name: "Gaming Alexandria", + mentionable: false, + managed: false, + id: "684524730274807911", + icon: null, + hoist: false, + flags: 0, + color: 15844367 + } + ], + description: null, + afk_timeout: 300, + verification_level: 1, + latest_onboarding_question_id: null, + guild_scheduled_events: [], + rules_channel_id: null, + embedded_activities: [], + region: "deprecated", + vanity_url_code: null, + application_id: null, + premium_tier: 0, + explicit_content_filter: 0, + stickers: [], + public_updates_channel_id: null, + splash: null, + premium_progress_bar_enabled: false, + features: [], + lazy: true, + max_video_channel_users: 25, + application_command_counts: {}, + home_header: null, + version: 1717720047590, + emojis: [], + presences: [] } }, user: { diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 4a7d2f4..ab6c73e 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -1,7 +1,9 @@ BEGIN TRANSACTION; INSERT INTO guild_active (guild_id, autocreate) VALUES -('112760669178241024', 1); +('112760669178241024', 1), +('66192955777486848', 1), +('665289423482519565', 0); INSERT INTO guild_space (guild_id, space_id, privacy_level) VALUES ('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe', 0); @@ -171,4 +173,9 @@ INSERT INTO media_proxy (permitted_hash) VALUES (-429802515645771439), (4558604729745184757); +INSERT INTO invite (mxid, room_id, type, name, avatar, topic) VALUES +('@cadence:cadence.moe', '!zTMspHVUBhFLLSdmnS:cadence.moe', 'm.space', 'Data Horde', 'mxc://cadence.moe/TLqQOsTSrZkVKwBSWYTZNTrw', 'here is the space topic'), +('@cadence:cadence.moe', '!room:cadence.moe', NULL, 'some room', NULL, NULL), +('@rnl:cadence.moe', '!space:cadence.moe', NULL, 'somebody else''s space', NULL, NULL); + COMMIT; diff --git a/test/test.js b/test/test.js index 69e3d48..eabab9f 100644 --- a/test/test.js +++ b/test/test.js @@ -6,7 +6,7 @@ const stp = require("stream").promises const sqlite = require("better-sqlite3") const migrate = require("../src/db/migrate") const HeatSync = require("heatsync") -const {test} = require("supertape") +const {test, extend} = require("supertape") const data = require("./data") /** @type {import("node-fetch").default} */ // @ts-ignore @@ -31,10 +31,12 @@ const discord = { guilds: new Map([ [data.guild.general.id, data.guild.general], [data.guild.fna.id, data.guild.fna], + [data.guild.data_horde.id, data.guild.data_horde] ]), guildChannelMap: new Map([ [data.guild.general.id, [data.channel.general.id]], [data.guild.fna.id, []], + [data.guild.data_horde.id, [data.channel.saving_the_world.id]] ]), application: { id: "684280192553844747" @@ -47,7 +49,8 @@ const discord = { ["498323546729086986", { guild_id: "497159726455455754", name: "bad-boots-prison" - }] + }], + [data.channel.saving_the_world.id, data.channel.saving_the_world] ]) } diff --git a/test/web.js b/test/web.js index e22a97c..f13cbf0 100644 --- a/test/web.js +++ b/test/web.js @@ -2,6 +2,35 @@ const passthrough = require("../src/passthrough") const h3 = require("h3") const http = require("http") const {SnowTransfer} = require("snowtransfer") +const assert = require("assert").strict +const domino = require("domino") +const {extend} = require("supertape") + +/** + * @param {string} html + */ +function getContent(html) { + const doc = domino.createDocument(html) + doc.querySelectorAll("svg").cache.forEach(e => e.remove()) + const content = doc.getElementById("content") + assert(content) + return content.innerHTML.trim() +} + +const test = extend({ + has: operator => /** @param {string | RegExp} expected */ (html, expected, message = "should have substring in html content") => { + const content = getContent(html) + const is = expected instanceof RegExp ? content.match(expected) : content.includes(expected) + const {output, result} = operator.equal(content, expected.toString()) + return { + expected: expected.toString(), + message, + is, + result: result, + output: output + } + } +}) class Router { constructor() { @@ -67,3 +96,4 @@ const router = new Router() passthrough.as = {router} module.exports.router = router +module.exports.test = test