More code coverage
This commit is contained in:
@@ -83,3 +83,5 @@ function defineMediaProxyHandler(domain) {
|
||||
|
||||
as.router.get(`/download/discordcdn/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("cdn.discordapp.com"))
|
||||
as.router.get(`/download/discordmedia/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("media.discordapp.net"))
|
||||
|
||||
module.exports._cache = cache
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// @ts-check
|
||||
|
||||
const assert = require("assert").strict
|
||||
const tryToCatch = require("try-to-catch")
|
||||
const {test} = require("supertape")
|
||||
const {router} = require("../../../test/web")
|
||||
const {MatrixServerError} = require("../../matrix/mreq")
|
||||
const {_cache} = require("./download-discord")
|
||||
|
||||
test("web download discord: access denied if not a known attachment", async t => {
|
||||
const [error] = await tryToCatch(() =>
|
||||
@@ -12,19 +13,6 @@ test("web download discord: access denied if not a known attachment", async t =>
|
||||
channel_id: "1",
|
||||
attachment_id: "2",
|
||||
file_name: "image.png"
|
||||
},
|
||||
snow: {
|
||||
channel: {
|
||||
async refreshAttachmentURLs(attachments) {
|
||||
if (typeof attachments === "string") attachments = [attachments]
|
||||
return {
|
||||
refreshed_urls: attachments.map(a => ({
|
||||
original: a,
|
||||
refreshed: a + `?ex=${Math.floor(Date.now() / 1000 + 3600).toString(16)}`
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -43,7 +31,7 @@ test("web download discord: works if a known attachment", async t => {
|
||||
snow: {
|
||||
channel: {
|
||||
async refreshAttachmentURLs(attachments) {
|
||||
if (typeof attachments === "string") attachments = [attachments]
|
||||
assert(Array.isArray(attachments))
|
||||
return {
|
||||
refreshed_urls: attachments.map(a => ({
|
||||
original: a,
|
||||
@@ -70,7 +58,7 @@ test("web download discord: uses cache", async t => {
|
||||
event,
|
||||
snow: {
|
||||
channel: {
|
||||
// @ts-ignore
|
||||
/* c8 ignore next 4 */
|
||||
async refreshAttachmentURLs(attachments) {
|
||||
notCalled = false
|
||||
throw new Error("tried to refresh when it should be in cache")
|
||||
@@ -80,3 +68,30 @@ test("web download discord: uses cache", async t => {
|
||||
})
|
||||
t.ok(notCalled)
|
||||
})
|
||||
|
||||
test("web download discord: refreshes when cache has expired", async t => {
|
||||
_cache.set(`https://cdn.discordapp.com/attachments/655216173696286746/1314358913482621010/image.png`, Promise.resolve(`https://cdn.discordapp.com/blah?ex=${Math.floor(new Date("2026-01-01").getTime() / 1000 + 3600).toString(16)}`))
|
||||
let called = 0
|
||||
await router.test("get", "/download/discordcdn/:channel_id/:attachment_id/:file_name", {
|
||||
params: {
|
||||
channel_id: "655216173696286746",
|
||||
attachment_id: "1314358913482621010",
|
||||
file_name: "image.png"
|
||||
},
|
||||
snow: {
|
||||
channel: {
|
||||
async refreshAttachmentURLs(attachments) {
|
||||
called++
|
||||
assert(Array.isArray(attachments))
|
||||
return {
|
||||
refreshed_urls: attachments.map(a => ({
|
||||
original: a,
|
||||
refreshed: a + `?ex=${Math.floor(Date.now() / 1000 + 3600).toString(16)}`
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(called, 1)
|
||||
})
|
||||
|
||||
+40
-13
@@ -73,23 +73,47 @@ function filterTo(xs, fn) {
|
||||
return filtered
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{type: number, parent_id?: string, position?: number}} channel
|
||||
* @param {Map<string, {type: number, parent_id?: string, position?: number}>} channels
|
||||
*/
|
||||
function getPosition(channel, channels) {
|
||||
let position = 0
|
||||
|
||||
// Categories always appear below un-categorised channels. Their contents can be ordered.
|
||||
// So categories, and things in them, will have their position multiplied by a big number. The category's big number. The regular position small number sorts within the category.
|
||||
// Categories are size 2000.
|
||||
let foundCategory = channel
|
||||
while (foundCategory.parent_id) {
|
||||
foundCategory = channels.get(foundCategory.parent_id)
|
||||
}
|
||||
if (foundCategory.type === DiscordTypes.ChannelType.GuildCategory) position = (foundCategory.position + 1) * 2000
|
||||
|
||||
// Categories always appear above what they contain.
|
||||
if (channel.type === DiscordTypes.ChannelType.GuildCategory) position -= 0.5
|
||||
|
||||
// Within a category, voice channels are always sorted to the bottom. The text/voice split is size 1000 each.
|
||||
if ([DiscordTypes.ChannelType.GuildVoice, DiscordTypes.ChannelType.GuildStageVoice].includes(channel.type)) position += 1000
|
||||
|
||||
// Channels are manually ordered within the text/voice split.
|
||||
if (typeof channel.position === "number") position += channel.position
|
||||
|
||||
// Threads appear below their channel.
|
||||
if ([DiscordTypes.ChannelType.PublicThread, DiscordTypes.ChannelType.PrivateThread, DiscordTypes.ChannelType.AnnouncementThread].includes(channel.type)) {
|
||||
position += 0.5
|
||||
let parent = channels.get(channel.parent_id)
|
||||
if (parent && parent["position"]) position += parent["position"]
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIGuild} guild
|
||||
* @param {Ty.R.Hierarchy[]} rooms
|
||||
* @param {string[]} roles
|
||||
*/
|
||||
function getChannelRoomsLinks(guild, rooms, roles) {
|
||||
function getPosition(channel) {
|
||||
let position = 0
|
||||
let looking = channel
|
||||
while (looking.parent_id) {
|
||||
looking = discord.channels.get(looking.parent_id)
|
||||
position = looking.position * 1000
|
||||
}
|
||||
if (channel.position) position += channel.position
|
||||
return position
|
||||
}
|
||||
|
||||
let channelIDs = discord.guildChannelMap.get(guild.id)
|
||||
assert(channelIDs)
|
||||
|
||||
@@ -97,7 +121,7 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
||||
let linkedChannelsWithDetails = linkedChannels.map(c => ({channel: discord.channels.get(c.channel_id), ...c}))
|
||||
let removedUncachedChannels = filterTo(linkedChannelsWithDetails, c => c.channel)
|
||||
let linkedChannelIDs = linkedChannelsWithDetails.map(c => c.channel_id)
|
||||
linkedChannelsWithDetails.sort((a, b) => getPosition(a.channel) - getPosition(b.channel))
|
||||
linkedChannelsWithDetails.sort((a, b) => getPosition(a.channel, discord.channels) - getPosition(b.channel, discord.channels))
|
||||
|
||||
let unlinkedChannelIDs = channelIDs.filter(c => !linkedChannelIDs.includes(c))
|
||||
/** @type {DiscordTypes.APIGuildChannel[]} */ // @ts-ignore
|
||||
@@ -107,7 +131,7 @@ function getChannelRoomsLinks(guild, rooms, roles) {
|
||||
const permissions = dUtils.getPermissions(roles, guild.roles, botID, c["permission_overwrites"])
|
||||
return dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
|
||||
})
|
||||
unlinkedChannels.sort((a, b) => getPosition(a) - getPosition(b))
|
||||
unlinkedChannels.sort((a, b) => getPosition(a, discord.channels) - getPosition(b, discord.channels))
|
||||
|
||||
let linkedRoomIDs = linkedChannels.map(c => c.room_id)
|
||||
let unlinkedRooms = [...rooms]
|
||||
@@ -239,3 +263,6 @@ as.router.post("/api/invite", defineEventHandler(async event => {
|
||||
return sendRedirect(event, "/ok?msg=User has been invited.", 302)
|
||||
}
|
||||
}))
|
||||
|
||||
module.exports._getPosition = getPosition
|
||||
module.exports._filterTo = filterTo
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// @ts-check
|
||||
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const tryToCatch = require("try-to-catch")
|
||||
const {router, test} = require("../../../test/web")
|
||||
const {MatrixServerError} = require("../../matrix/mreq")
|
||||
const {_getPosition, _filterTo} = require("./guild")
|
||||
|
||||
let nonce
|
||||
|
||||
@@ -101,12 +103,6 @@ test("web guild: can view bridged guild when logged in with discord", async t =>
|
||||
managedGuilds: ["112760669178241024"]
|
||||
},
|
||||
api: {
|
||||
async getStateEvent(roomID, type, key) {
|
||||
return {}
|
||||
},
|
||||
async getMembers(roomID, membership) {
|
||||
return {chunk: []}
|
||||
},
|
||||
async getFullHierarchy(roomID) {
|
||||
return []
|
||||
}
|
||||
@@ -121,12 +117,6 @@ test("web guild: can view bridged guild when logged in with matrix", async t =>
|
||||
mxid: "@cadence:cadence.moe"
|
||||
},
|
||||
api: {
|
||||
async getStateEvent(roomID, type, key) {
|
||||
return {}
|
||||
},
|
||||
async getMembers(roomID, membership) {
|
||||
return {chunk: []}
|
||||
},
|
||||
async getFullHierarchy(roomID) {
|
||||
return []
|
||||
}
|
||||
@@ -191,12 +181,12 @@ test("api invite: can invite with valid nonce", async t => {
|
||||
async getStateEvent(roomID, type, key) {
|
||||
called++
|
||||
if (type === "m.room.member" && key === "@cadence:cadence.moe") {
|
||||
return {membership: "leave"}
|
||||
throw new Error("event not found")
|
||||
} else if (type === "m.room.power_levels" && key === "") {
|
||||
return {}
|
||||
} else {
|
||||
t.fail(`unexpected getStateEvent call. roomID: ${roomID}, type: ${type}, key: ${key}`)
|
||||
}
|
||||
/* c8 ignore next */
|
||||
t.fail(`unexpected getStateEvent call. roomID: ${roomID}, type: ${type}, key: ${key}`)
|
||||
},
|
||||
async getStateEventOuter(roomID, type, key) {
|
||||
called++
|
||||
@@ -284,9 +274,9 @@ test("api invite: can invite to a moderated guild", async t => {
|
||||
return {membership: "leave"}
|
||||
} else if (type === "m.room.power_levels" && key === "") {
|
||||
return {}
|
||||
} else {
|
||||
t.fail(`unexpected getStateEvent call. roomID: ${roomID}, type: ${type}, key: ${key}`)
|
||||
}
|
||||
/* c8 ignore next */
|
||||
t.fail(`unexpected getStateEvent call. roomID: ${roomID}, type: ${type}, key: ${key}`)
|
||||
},
|
||||
async getStateEventOuter(roomID, type, key) {
|
||||
called++
|
||||
@@ -362,3 +352,52 @@ test("api invite: does not reinvite joined users", async t => {
|
||||
t.notOk(error)
|
||||
t.equal(called, 1)
|
||||
})
|
||||
|
||||
|
||||
test("position sorting: sorts like discord does", t => {
|
||||
const channelsList = [{
|
||||
type: DiscordTypes.ChannelType.GuildText,
|
||||
id: "first",
|
||||
position: 0
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.PublicThread,
|
||||
id: "thread",
|
||||
parent_id: "first",
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.GuildText,
|
||||
id: "second",
|
||||
position: 1
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.GuildVoice,
|
||||
id: "voice",
|
||||
position: 0
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.GuildCategory,
|
||||
id: "category",
|
||||
position: 0
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.GuildText,
|
||||
id: "category-first",
|
||||
parent_id: "category",
|
||||
position: 0
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.GuildText,
|
||||
id: "category-second",
|
||||
parent_id: "category",
|
||||
position: 1
|
||||
}, {
|
||||
type: DiscordTypes.ChannelType.PublicThread,
|
||||
id: "category-second-thread",
|
||||
parent_id: "category-second",
|
||||
}].reverse()
|
||||
const channels = new Map(channelsList.map(c => [c.id, c]))
|
||||
const sortedChannelIDs = [...channels.values()].sort((a, b) => _getPosition(a, channels) - _getPosition(b, channels)).map(c => c.id)
|
||||
t.deepEqual(sortedChannelIDs, ["first", "thread", "second", "voice", "category", "category-first", "category-second", "category-second-thread"])
|
||||
})
|
||||
|
||||
test("filterTo: works", t => {
|
||||
const fruit = ["apple", "banana", "apricot"]
|
||||
const rest = _filterTo(fruit, f => f[0] === "b")
|
||||
t.deepEqual(fruit, ["banana"])
|
||||
t.deepEqual(rest, ["apple", "apricot"])
|
||||
})
|
||||
|
||||
@@ -70,16 +70,14 @@ as.router.post("/api/link-space", defineEventHandler(async event => {
|
||||
// Check space ID
|
||||
if (!session.data.mxid) throw createError({status: 403, message: "Forbidden", data: "Can't link with your Matrix space if you aren't logged in to Matrix"})
|
||||
const spaceID = parsedBody.space_id
|
||||
const inviteType = select("invite", "type", {mxid: session.data.mxid, room_id: spaceID}).pluck().get()
|
||||
if (inviteType !== "m.space") throw createError({status: 403, message: "Forbidden", data: "You personally must invite OOYE to that space on Matrix"})
|
||||
const inviteRow = select("invite", ["mxid", "type"], {mxid: session.data.mxid, room_id: spaceID}).get()
|
||||
if (!inviteRow || inviteRow.type !== "m.space") throw createError({status: 403, message: "Forbidden", data: "You personally must invite OOYE to that space on Matrix"})
|
||||
|
||||
// Check they are not already bridged
|
||||
const existing = select("guild_space", "guild_id", {}, "WHERE guild_id = ? OR space_id = ?").get(guildID, spaceID)
|
||||
if (existing) throw createError({status: 400, message: "Bad Request", data: `Guild ID ${guildID} or space ID ${spaceID} are already bridged and cannot be reused`})
|
||||
|
||||
const inviteSender = select("invite", "mxid", {mxid: session.data.mxid, room_id: spaceID}).pluck().get()
|
||||
const inviteSenderServer = inviteSender?.match(/:(.*)/)?.[1]
|
||||
const via = [inviteSenderServer || ""]
|
||||
const via = [inviteRow.mxid.match(/:(.*)/)[1]]
|
||||
|
||||
// Check space exists and bridge is joined
|
||||
try {
|
||||
|
||||
@@ -39,7 +39,8 @@ test("log in with matrix: sends message to log in", async t => {
|
||||
let called = 0
|
||||
await router.test("post", "/api/log-in-with-matrix", {
|
||||
body: {
|
||||
mxid: "@cadence:cadence.moe"
|
||||
mxid: "@cadence:cadence.moe",
|
||||
next: "https://bridge.cadence.moe/guild?guild_id=123"
|
||||
},
|
||||
api: {
|
||||
async usePrivateChat(mxid) {
|
||||
@@ -51,7 +52,7 @@ test("log in with matrix: sends message to log in", async t => {
|
||||
called++
|
||||
t.equal(roomID, "!created:cadence.moe")
|
||||
t.equal(type, "m.room.message")
|
||||
token = content.body.match(/log-in-with-matrix\?token=([a-f0-9-]+)/)[1]
|
||||
token = content.body.match(/log-in-with-matrix\?token=([a-f0-9-]+)&next=/)[1]
|
||||
t.ok(token, "log in token not issued")
|
||||
return ""
|
||||
}
|
||||
|
||||
+28
-16
@@ -2,12 +2,12 @@
|
||||
|
||||
const {z} = require("zod")
|
||||
const {randomUUID} = require("crypto")
|
||||
const {defineEventHandler, getValidatedQuery, sendRedirect, createError} = require("h3")
|
||||
const {defineEventHandler, getValidatedQuery, sendRedirect, createError, H3Event} = require("h3")
|
||||
const {SnowTransfer, tokenless} = require("snowtransfer")
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const getRelativePath = require("get-relative-path")
|
||||
|
||||
const {discord, as, db, sync} = require("../../passthrough")
|
||||
const {as, db, sync} = require("../../passthrough")
|
||||
const {id, permissions} = require("../../../addbot")
|
||||
/** @type {import("../auth")} */
|
||||
const auth = sync.require("../auth")
|
||||
@@ -33,6 +33,24 @@ const schema = {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {H3Event} event
|
||||
* @returns {(string) => {user: {getGuilds: () => Promise<DiscordTypes.RESTGetAPICurrentUserGuildsResult>}}}
|
||||
*/
|
||||
function getClient(event) {
|
||||
/* c8 ignore next */
|
||||
return event.context.getClient || (accessToken => new SnowTransfer(`Bearer ${accessToken}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {H3Event} event
|
||||
* @returns {typeof tokenless.getOauth2Token}
|
||||
*/
|
||||
function getOauth2Token(event) {
|
||||
/* c8 ignore next */
|
||||
return event.context.getOauth2Token || tokenless.getOauth2Token
|
||||
}
|
||||
|
||||
as.router.get("/oauth", defineEventHandler(async event => {
|
||||
const session = await auth.useSession(event)
|
||||
let scope = "guilds"
|
||||
@@ -61,21 +79,15 @@ as.router.get("/oauth", defineEventHandler(async event => {
|
||||
if (!savedState) throw createError({status: 400, message: "Missing state", data: "Missing saved state parameter. Please try again, and make sure you have cookies enabled."})
|
||||
if (savedState != parsedQuery.data.state) return tryAgain()
|
||||
|
||||
const oauthResult = await tokenless.getOauth2Token(id, redirect_uri, reg.ooye.discord_client_secret, parsedQuery.data.code)
|
||||
const parsedToken = schema.token.safeParse(oauthResult)
|
||||
if (!parsedToken.success) {
|
||||
throw createError({status: 502, message: "Invalid token response", data: `Discord completed OAuth, but returned this instead of an OAuth access token: ${JSON.stringify(oauthResult)}`})
|
||||
}
|
||||
const oauthResult = await getOauth2Token(event)(id, redirect_uri, reg.ooye.discord_client_secret, parsedQuery.data.code)
|
||||
const parsedToken = schema.token.parse(oauthResult)
|
||||
|
||||
const userID = Buffer.from(parsedToken.data.access_token.split(".")[0], "base64").toString()
|
||||
const client = new SnowTransfer(`Bearer ${parsedToken.data.access_token}`)
|
||||
try {
|
||||
const guilds = await client.user.getGuilds()
|
||||
var managedGuilds = guilds.filter(g => BigInt(g.permissions) & DiscordTypes.PermissionFlagsBits.ManageGuild).map(g => g.id)
|
||||
await session.update({managedGuilds, userID, state: undefined})
|
||||
} catch (e) {
|
||||
throw createError({status: 502, message: "API call failed", data: e.message})
|
||||
}
|
||||
const userID = Buffer.from(parsedToken.access_token.split(".")[0], "base64").toString()
|
||||
const client = getClient(event)(parsedToken.access_token)
|
||||
|
||||
const guilds = await client.user.getGuilds()
|
||||
var managedGuilds = guilds.filter(g => BigInt(g.permissions) & DiscordTypes.PermissionFlagsBits.ManageGuild).map(g => g.id)
|
||||
await session.update({managedGuilds, userID, state: undefined})
|
||||
|
||||
// Set auto-create for the guild
|
||||
// @ts-ignore
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// @ts-check
|
||||
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const tryToCatch = require("try-to-catch")
|
||||
const assert = require("assert/strict")
|
||||
const {router, test} = require("../../../test/web")
|
||||
|
||||
test("web oauth: redirects to Discord on first visit (add easy)", async t => {
|
||||
let event = {}
|
||||
await router.test("get", "/oauth?action=add", {
|
||||
event,
|
||||
sessionData: {
|
||||
password: "password123"
|
||||
}
|
||||
})
|
||||
t.equal(event.node.res.statusCode, 302)
|
||||
t.match(event.node.res.getHeader("location"), /^https:\/\/discord.com\/oauth2\/authorize\?client_id=684280192553844747&scope=bot\+guilds&permissions=2251801424568320&response_type=code&redirect_uri=https:\/\/bridge\.example\.org\/oauth&state=/)
|
||||
})
|
||||
|
||||
test("web oauth: redirects to Discord on first visit (add self service)", async t => {
|
||||
let event = {}
|
||||
await router.test("get", "/oauth?action=add-self-service", {
|
||||
event,
|
||||
sessionData: {
|
||||
password: "password123"
|
||||
}
|
||||
})
|
||||
t.equal(event.node.res.statusCode, 302)
|
||||
t.match(event.node.res.getHeader("location"), /^https:\/\/discord.com\/oauth2\/authorize\?client_id=684280192553844747&scope=bot\+guilds&permissions=2251801424568320&response_type=code&redirect_uri=https:\/\/bridge\.example\.org\/oauth&state=/)
|
||||
})
|
||||
|
||||
test("web oauth: advises user about cookies if state is missing", async t => {
|
||||
let event = {}
|
||||
const [e] = await tryToCatch(() => router.test("get", "/oauth?state=693551d5-47c5-49e2-a433-3600abe3c15c&code=DISCORD_CODE&guild_id=9", {
|
||||
event
|
||||
}))
|
||||
t.equal(e.message, "Missing state")
|
||||
})
|
||||
|
||||
test("web oauth: redirects to Discord again if state doesn't match", async t => {
|
||||
let event = {}
|
||||
await router.test("get", "/oauth?state=693551d5-47c5-49e2-a433-3600abe3c15c&code=DISCORD_CODE", {
|
||||
event,
|
||||
sessionData: {
|
||||
state: "438aa253-1311-4483-9aa2-c251e29e72c9",
|
||||
password: "password123"
|
||||
}
|
||||
})
|
||||
t.equal(event.node.res.statusCode, 302)
|
||||
t.match(event.node.res.getHeader("location"), /^https:\/\/discord\.com\/oauth2\/authorize/)
|
||||
})
|
||||
|
||||
test("web oauth: uses returned state, logs in", async t => {
|
||||
let event = {}
|
||||
await router.test("get", "/oauth?state=693551d5-47c5-49e2-a433-3600abe3c15c&code=DISCORD_CODE", {
|
||||
event,
|
||||
sessionData: {
|
||||
state: "693551d5-47c5-49e2-a433-3600abe3c15c",
|
||||
selfService: false,
|
||||
password: "password123"
|
||||
},
|
||||
getOauth2Token() {
|
||||
return {
|
||||
token_type: "Bearer",
|
||||
access_token: "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
|
||||
expires_in: 604800,
|
||||
refresh_token: "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
|
||||
scope: "bot+guilds"
|
||||
}
|
||||
},
|
||||
getClient(accessToken) {
|
||||
return {
|
||||
user: {
|
||||
async getGuilds() {
|
||||
return [{
|
||||
id: "9",
|
||||
permissions: DiscordTypes.PermissionFlagsBits.ManageGuild
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(event.node.res.statusCode, 302)
|
||||
t.equal(event.node.res.getHeader("location"), "./")
|
||||
})
|
||||
|
||||
test("web oauth: uses returned state, adds managed guild", async t => {
|
||||
let event = {}
|
||||
await router.test("get", "/oauth?state=693551d5-47c5-49e2-a433-3600abe3c15c&code=DISCORD_CODE&guild_id=9", {
|
||||
event,
|
||||
sessionData: {
|
||||
state: "693551d5-47c5-49e2-a433-3600abe3c15c",
|
||||
selfService: false,
|
||||
password: "password123"
|
||||
},
|
||||
getOauth2Token() {
|
||||
return {
|
||||
token_type: "Bearer",
|
||||
access_token: "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
|
||||
expires_in: 604800,
|
||||
refresh_token: "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
|
||||
scope: "bot+guilds"
|
||||
}
|
||||
},
|
||||
getClient(accessToken) {
|
||||
return {
|
||||
user: {
|
||||
async getGuilds() {
|
||||
return [{
|
||||
id: "9",
|
||||
permissions: DiscordTypes.PermissionFlagsBits.ManageGuild
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(event.node.res.statusCode, 302)
|
||||
t.equal(event.node.res.getHeader("location"), "guild?guild_id=9")
|
||||
})
|
||||
Reference in New Issue
Block a user