mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +00:00
every primary path API route is refactored!
This commit is contained in:
parent
f2508fbea4
commit
d407a015c9
9 changed files with 682 additions and 12 deletions
|
@ -1,10 +1,10 @@
|
|||
// @ts-ignore
|
||||
import { requireEditor, requireMember } from '@roleypoly/api/src/guilds/middleware';
|
||||
import { authBot } from '@roleypoly/api/src/routes/auth/bot';
|
||||
import { authCallback } from '@roleypoly/api/src/routes/auth/callback';
|
||||
import { authSessionDelete } from '@roleypoly/api/src/routes/auth/delete-session';
|
||||
import { authSession } from '@roleypoly/api/src/routes/auth/session';
|
||||
import { guildsGuild } from '@roleypoly/api/src/routes/guilds/guild';
|
||||
import { guildsCacheDelete } from '@roleypoly/api/src/routes/guilds/guild-cache-delete';
|
||||
import { guildsRolesPut } from '@roleypoly/api/src/routes/guilds/guild-roles-put';
|
||||
import { guildsGuildPatch } from '@roleypoly/api/src/routes/guilds/guilds-patch';
|
||||
import { guildsSlug } from '@roleypoly/api/src/routes/guilds/slug';
|
||||
|
@ -34,7 +34,12 @@ router.delete('/auth/session', withSession, requireSession, authSessionDelete);
|
|||
const guildsCommon = [injectParams, withSession, requireSession, requireMember];
|
||||
router.get('/guilds/:guildId', ...guildsCommon, guildsGuild);
|
||||
router.patch('/guilds/:guildId', ...guildsCommon, requireEditor, guildsGuildPatch);
|
||||
router.delete('/guilds/:guildId/cache', ...guildsCommon, requireEditor, notImplemented);
|
||||
router.delete(
|
||||
'/guilds/:guildId/cache',
|
||||
...guildsCommon,
|
||||
requireEditor,
|
||||
guildsCacheDelete
|
||||
);
|
||||
router.put('/guilds/:guildId/roles', ...guildsCommon, guildsRolesPut);
|
||||
|
||||
// Slug is unauthenticated...
|
||||
|
|
32
packages/api/src/routes/guilds/guild-cache-delete.spec.ts
Normal file
32
packages/api/src/routes/guilds/guild-cache-delete.spec.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
jest.mock('../../guilds/getters');
|
||||
|
||||
import { UserGuildPermissions } from '@roleypoly/types';
|
||||
import { getGuild } from '../../guilds/getters';
|
||||
import { configContext, makeRequest, makeSession } from '../../utils/testHelpers';
|
||||
|
||||
const mockGetGuild = getGuild as jest.Mock;
|
||||
|
||||
describe('DELETE /guilds/:id/cache', () => {
|
||||
it('calls getGuilds and returns No Content', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: UserGuildPermissions.Admin,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const response = await makeRequest('DELETE', `/guilds/123/cache`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
expect(mockGetGuild).toHaveBeenCalledWith(expect.any(Object), '123', true);
|
||||
});
|
||||
});
|
12
packages/api/src/routes/guilds/guild-cache-delete.ts
Normal file
12
packages/api/src/routes/guilds/guild-cache-delete.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { getGuild } from '@roleypoly/api/src/guilds/getters';
|
||||
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
|
||||
import { noContent } from '@roleypoly/api/src/utils/response';
|
||||
|
||||
export const guildsCacheDelete: RoleypolyHandler = async (
|
||||
request: Request,
|
||||
context: Context
|
||||
) => {
|
||||
await getGuild(context.config, context.params.guildId!, true);
|
||||
|
||||
return noContent();
|
||||
};
|
|
@ -1,5 +1,370 @@
|
|||
jest.mock('../../guilds/getters');
|
||||
jest.mock('../../utils/discord');
|
||||
|
||||
import {
|
||||
CategoryType,
|
||||
Features,
|
||||
Guild,
|
||||
GuildData,
|
||||
Member,
|
||||
OwnRoleInfo,
|
||||
RoleSafety,
|
||||
RoleUpdate,
|
||||
TransactionType,
|
||||
} from '@roleypoly/types';
|
||||
import { getGuild, getGuildData, getGuildMember } from '../../guilds/getters';
|
||||
import { AuthType, discordFetch } from '../../utils/discord';
|
||||
import { json } from '../../utils/response';
|
||||
import { configContext, makeRequest, makeSession } from '../../utils/testHelpers';
|
||||
|
||||
const mockDiscordFetch = discordFetch as jest.Mock;
|
||||
const mockGetGuild = getGuild as jest.Mock;
|
||||
const mockGetGuildMember = getGuildMember as jest.Mock;
|
||||
const mockGetGuildData = getGuildData as jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
doMock();
|
||||
});
|
||||
|
||||
describe('PUT /guilds/:id/roles', () => {
|
||||
it('returns Not Implemented when called', () => {
|
||||
expect(true).toBe(true);
|
||||
it('adds member roles when called with valid roles', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const update: RoleUpdate = {
|
||||
knownState: ['role-1'],
|
||||
transactions: [{ id: 'role-2', action: TransactionType.Add }],
|
||||
};
|
||||
|
||||
mockDiscordFetch.mockReturnValueOnce(
|
||||
json({
|
||||
roles: ['role-1', 'role-2'],
|
||||
})
|
||||
);
|
||||
|
||||
const response = await makeRequest(
|
||||
'PUT',
|
||||
`/guilds/123/roles`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
body: JSON.stringify(update),
|
||||
},
|
||||
{
|
||||
BOT_TOKEN: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockDiscordFetch).toHaveBeenCalledWith(
|
||||
`/guilds/123/members/${session.user.id}`,
|
||||
'test',
|
||||
AuthType.Bot,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
roles: ['role-1', 'role-2'],
|
||||
}),
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-audit-log-reason': `Picked their roles via ${config.uiPublicURI}`,
|
||||
},
|
||||
method: 'PATCH',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('removes member roles when called with valid roles', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const update: RoleUpdate = {
|
||||
knownState: ['role-1'],
|
||||
transactions: [{ id: 'role-1', action: TransactionType.Remove }],
|
||||
};
|
||||
|
||||
mockDiscordFetch.mockReturnValueOnce(
|
||||
json({
|
||||
roles: [],
|
||||
})
|
||||
);
|
||||
|
||||
const response = await makeRequest(
|
||||
'PUT',
|
||||
`/guilds/123/roles`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
body: JSON.stringify(update),
|
||||
},
|
||||
{
|
||||
BOT_TOKEN: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockDiscordFetch).toHaveBeenCalledWith(
|
||||
`/guilds/123/members/${session.user.id}`,
|
||||
'test',
|
||||
AuthType.Bot,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
roles: [],
|
||||
}),
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-audit-log-reason': `Picked their roles via ${config.uiPublicURI}`,
|
||||
},
|
||||
method: 'PATCH',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('does not update roles when called only with invalid roles', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const update: RoleUpdate = {
|
||||
knownState: ['role-1'],
|
||||
transactions: [
|
||||
{ id: 'role-3', action: TransactionType.Add }, // role is in a hidden category
|
||||
{ id: 'role-5-unsafe', action: TransactionType.Add }, // role is marked unsafe
|
||||
],
|
||||
};
|
||||
|
||||
const response = await makeRequest(
|
||||
'PUT',
|
||||
`/guilds/123/roles`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
body: JSON.stringify(update),
|
||||
},
|
||||
{
|
||||
BOT_TOKEN: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(mockDiscordFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('filters roles that are invalid while accepting ones that are valid', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const update: RoleUpdate = {
|
||||
knownState: ['role-1'],
|
||||
transactions: [
|
||||
{ id: 'role-3', action: TransactionType.Add }, // role is in a hidden category
|
||||
{ id: 'role-2', action: TransactionType.Add }, // role is in a hidden category
|
||||
],
|
||||
};
|
||||
|
||||
const response = await makeRequest(
|
||||
'PUT',
|
||||
`/guilds/123/roles`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
body: JSON.stringify(update),
|
||||
},
|
||||
{
|
||||
BOT_TOKEN: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockDiscordFetch).toHaveBeenCalledWith(
|
||||
`/guilds/123/members/${session.user.id}`,
|
||||
'test',
|
||||
AuthType.Bot,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
roles: ['role-1', 'role-2'],
|
||||
}),
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-audit-log-reason': `Picked their roles via ${config.uiPublicURI}`,
|
||||
},
|
||||
method: 'PATCH',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('400s when no transactions are present', async () => {
|
||||
const [config] = configContext();
|
||||
const session = await makeSession(config, {
|
||||
guilds: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
permissionLevel: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const update: RoleUpdate = {
|
||||
knownState: ['role-1'],
|
||||
transactions: [],
|
||||
};
|
||||
|
||||
const response = await makeRequest(
|
||||
'PUT',
|
||||
`/guilds/123/roles`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.sessionID}`,
|
||||
},
|
||||
body: JSON.stringify(update),
|
||||
},
|
||||
{
|
||||
BOT_TOKEN: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(mockDiscordFetch).not.toHaveBeenCalled();
|
||||
expect(mockGetGuild).not.toHaveBeenCalled();
|
||||
expect(mockGetGuildData).not.toHaveBeenCalled();
|
||||
expect(mockGetGuildMember).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const doMock = () => {
|
||||
const guild: Guild & OwnRoleInfo = {
|
||||
id: '123',
|
||||
name: 'test',
|
||||
icon: 'test',
|
||||
highestRolePosition: 0,
|
||||
roles: [
|
||||
{
|
||||
id: 'role-1',
|
||||
name: 'Role 1',
|
||||
color: 0,
|
||||
position: 17,
|
||||
permissions: '',
|
||||
managed: false,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'role-2',
|
||||
name: 'Role 2',
|
||||
color: 0,
|
||||
position: 16,
|
||||
permissions: '',
|
||||
managed: false,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'role-3',
|
||||
name: 'Role 3',
|
||||
color: 0,
|
||||
position: 15,
|
||||
permissions: '',
|
||||
managed: false,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'role-4',
|
||||
name: 'Role 4',
|
||||
color: 0,
|
||||
position: 14,
|
||||
permissions: '',
|
||||
managed: false,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'role-5-unsafe',
|
||||
name: 'Role 5 (Unsafe)',
|
||||
color: 0,
|
||||
position: 14,
|
||||
permissions: '',
|
||||
managed: false,
|
||||
safety: RoleSafety.DangerousPermissions,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const member: Member = {
|
||||
roles: ['role-1'],
|
||||
pending: false,
|
||||
nick: '',
|
||||
};
|
||||
|
||||
const guildData: GuildData = {
|
||||
id: '123',
|
||||
message: 'test',
|
||||
categories: [
|
||||
{
|
||||
id: 'category-1',
|
||||
name: 'Category 1',
|
||||
position: 0,
|
||||
hidden: false,
|
||||
type: CategoryType.Multi,
|
||||
roles: ['role-1', 'role-2'],
|
||||
},
|
||||
{
|
||||
id: 'category-2',
|
||||
name: 'Category 2',
|
||||
position: 1,
|
||||
hidden: true,
|
||||
type: CategoryType.Multi,
|
||||
roles: ['role-3'],
|
||||
},
|
||||
],
|
||||
features: Features.None,
|
||||
auditLogWebhook: null,
|
||||
accessControl: {
|
||||
allowList: [],
|
||||
blockList: [],
|
||||
blockPending: false,
|
||||
},
|
||||
};
|
||||
|
||||
mockGetGuild.mockReturnValue(guild);
|
||||
mockGetGuildMember.mockReturnValue(member);
|
||||
mockGetGuildData.mockReturnValue(guildData);
|
||||
mockDiscordFetch.mockReturnValue(json({}));
|
||||
};
|
||||
|
|
|
@ -1,9 +1,154 @@
|
|||
import {
|
||||
getGuild,
|
||||
getGuildData,
|
||||
getGuildMember,
|
||||
} from '@roleypoly/api/src/guilds/getters';
|
||||
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
|
||||
import { notImplemented } from '@roleypoly/api/src/utils/response';
|
||||
import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
|
||||
import {
|
||||
engineeringProblem,
|
||||
invalid,
|
||||
json,
|
||||
notFound,
|
||||
serverError,
|
||||
} from '@roleypoly/api/src/utils/response';
|
||||
import {
|
||||
difference,
|
||||
isIdenticalArray,
|
||||
keyBy,
|
||||
union,
|
||||
} from '@roleypoly/misc-utils/collection-tools';
|
||||
import {
|
||||
GuildData,
|
||||
Member,
|
||||
Role,
|
||||
RoleSafety,
|
||||
RoleTransaction,
|
||||
RoleUpdate,
|
||||
TransactionType,
|
||||
} from '@roleypoly/types';
|
||||
|
||||
export const guildsRolesPut: RoleypolyHandler = async (
|
||||
request: Request,
|
||||
context: Context
|
||||
) => {
|
||||
return notImplemented();
|
||||
if (!request.body) {
|
||||
return invalid();
|
||||
}
|
||||
|
||||
const updateRequest: RoleUpdate = await request.json();
|
||||
|
||||
if (updateRequest.transactions.length === 0) {
|
||||
return invalid();
|
||||
}
|
||||
|
||||
const guildID = context.params.guildId;
|
||||
if (!guildID) {
|
||||
return engineeringProblem('params not set up correctly');
|
||||
}
|
||||
|
||||
const userID = context.session!.user.id;
|
||||
|
||||
const [member, guildData, guild] = await Promise.all([
|
||||
getGuildMember(context.config, guildID, userID),
|
||||
getGuildData(context.config, guildID),
|
||||
getGuild(context.config, guildID),
|
||||
]);
|
||||
|
||||
if (!guild || !member) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const newRoles = calculateNewRoles({
|
||||
currentRoles: member.roles,
|
||||
guildRoles: guild.roles,
|
||||
guildData,
|
||||
updateRequest,
|
||||
});
|
||||
|
||||
if (isIdenticalArray(member.roles, newRoles)) {
|
||||
return invalid();
|
||||
}
|
||||
|
||||
const patchMemberRoles = await discordFetch<Member>(
|
||||
`/guilds/${guildID}/members/${userID}`,
|
||||
context.config.botToken,
|
||||
AuthType.Bot,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-audit-log-reason': `Picked their roles via ${context.config.uiPublicURI}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
roles: newRoles,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!patchMemberRoles) {
|
||||
return serverError(new Error('discord rejected the request'));
|
||||
}
|
||||
|
||||
context.fetchContext.waitUntil(getGuildMember(context.config, guildID, userID, true));
|
||||
|
||||
const updatedMember: Member = {
|
||||
roles: patchMemberRoles.roles,
|
||||
};
|
||||
|
||||
return json(updatedMember);
|
||||
};
|
||||
|
||||
export const calculateNewRoles = ({
|
||||
currentRoles,
|
||||
guildData,
|
||||
guildRoles,
|
||||
updateRequest,
|
||||
}: {
|
||||
currentRoles: string[];
|
||||
guildRoles: Role[];
|
||||
guildData: GuildData;
|
||||
updateRequest: RoleUpdate;
|
||||
}): string[] => {
|
||||
const roleMap = keyBy(guildRoles, 'id');
|
||||
|
||||
// These roles were ones changed between knownState (role picker page load/cache) and current (fresh from discord).
|
||||
// We could cause issues, so we'll re-add them later.
|
||||
// const diffRoles = difference(updateRequest.knownState, currentRoles);
|
||||
|
||||
// Only these are safe
|
||||
const allSafeRoles = guildData.categories.reduce<string[]>(
|
||||
(categorizedRoles, category) =>
|
||||
!category.hidden
|
||||
? [
|
||||
...categorizedRoles,
|
||||
...category.roles.filter(
|
||||
(roleID) => roleMap[roleID]?.safety === RoleSafety.Safe
|
||||
),
|
||||
]
|
||||
: categorizedRoles,
|
||||
[]
|
||||
);
|
||||
|
||||
const safeTransactions = updateRequest.transactions.filter((tx: RoleTransaction) =>
|
||||
allSafeRoles.includes(tx.id)
|
||||
);
|
||||
|
||||
const changesByAction = safeTransactions.reduce<
|
||||
Record<TransactionType, RoleTransaction[]>
|
||||
>((group, value, _1, _2, key = value.action) => (group[key].push(value), group), {
|
||||
[TransactionType.Add]: [],
|
||||
[TransactionType.Remove]: [],
|
||||
});
|
||||
|
||||
const rolesToAdd = (changesByAction[TransactionType.Add] ?? []).map(
|
||||
(tx: RoleTransaction) => tx.id
|
||||
);
|
||||
const rolesToRemove = (changesByAction[TransactionType.Remove] ?? []).map(
|
||||
(tx: RoleTransaction) => tx.id
|
||||
);
|
||||
|
||||
const final = union(difference(currentRoles, rolesToRemove), rolesToAdd);
|
||||
|
||||
return final;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
jest.mock('../../../utils/discord');
|
||||
|
||||
import { discordFetch } from '../../../utils/discord';
|
||||
import { configContext } from '../../../utils/testHelpers';
|
||||
import {
|
||||
extractInteractionResponse,
|
||||
isDeferred,
|
||||
isEphemeral,
|
||||
makeInteractionsRequest,
|
||||
mockUpdateCall,
|
||||
} from '../testHelpers';
|
||||
|
||||
const mockDiscordFetch = discordFetch as jest.Mock;
|
||||
it('responds with the username when member.nick is missing', async () => {
|
||||
const [, context] = configContext();
|
||||
const response = await makeInteractionsRequest(
|
||||
context,
|
||||
{
|
||||
name: 'hello-world',
|
||||
},
|
||||
false,
|
||||
{
|
||||
member: {
|
||||
nick: undefined,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const interaction = await extractInteractionResponse(response);
|
||||
|
||||
expect(isDeferred(interaction)).toBe(true);
|
||||
expect(isEphemeral(interaction)).toBe(true);
|
||||
expect(mockDiscordFetch).toBeCalledWith(
|
||||
...mockUpdateCall(expect, {
|
||||
content: 'Hey there, test-user',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('responds with the nickname when member.nick is set', async () => {
|
||||
const [, context] = configContext();
|
||||
const response = await makeInteractionsRequest(context, {
|
||||
name: 'hello-world',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const interaction = await extractInteractionResponse(response);
|
||||
|
||||
expect(isDeferred(interaction)).toBe(true);
|
||||
expect(isEphemeral(interaction)).toBe(true);
|
||||
expect(mockDiscordFetch).toBeCalledWith(
|
||||
...mockUpdateCall(expect, {
|
||||
content: 'Hey there, test-user-nick',
|
||||
})
|
||||
);
|
||||
});
|
|
@ -59,7 +59,7 @@ export const runAsync = async (
|
|||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE,
|
||||
data: {
|
||||
flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0,
|
||||
...response.data,
|
||||
|
@ -82,7 +82,7 @@ export const runAsync = async (
|
|||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE,
|
||||
data: {
|
||||
content: "I'm sorry, I'm having trouble processing this request.",
|
||||
flags: InteractionFlags.EPHEMERAL,
|
||||
|
|
|
@ -24,10 +24,10 @@ it('responds with a simple hello-world!', async () => {
|
|||
});
|
||||
expect(mockDiscordFetch).toBeCalledWith(expect.any(String), '', AuthType.None, {
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE,
|
||||
data: {
|
||||
flags: InteractionFlags.EPHEMERAL,
|
||||
content: 'Hey there, test-user',
|
||||
content: 'Hey there, test-user-nick',
|
||||
},
|
||||
}),
|
||||
headers: expect.any(Object),
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { handleInteraction } from '@roleypoly/api/src/routes/interactions/interactions';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import { AuthType } from '@roleypoly/api/src/utils/discord';
|
||||
import { getID } from '@roleypoly/api/src/utils/id';
|
||||
import {
|
||||
InteractionCallbackData,
|
||||
InteractionCallbackType,
|
||||
InteractionData,
|
||||
InteractionFlags,
|
||||
InteractionRequest,
|
||||
InteractionResponse,
|
||||
InteractionType,
|
||||
|
@ -32,7 +36,8 @@ export const getSignatureHeaders = (
|
|||
export const makeInteractionsRequest = async (
|
||||
context: Context,
|
||||
interactionData: Partial<InteractionData>,
|
||||
forceInvalid?: boolean
|
||||
forceInvalid?: boolean,
|
||||
topLevelMixin?: Partial<InteractionRequest>
|
||||
): Promise<Response> => {
|
||||
context.config.publicKey = hexPublicKey;
|
||||
|
||||
|
@ -55,9 +60,10 @@ export const makeInteractionsRequest = async (
|
|||
avatar: '',
|
||||
},
|
||||
member: {
|
||||
nick: 'test-user',
|
||||
nick: 'test-user-nick',
|
||||
roles: [],
|
||||
},
|
||||
...topLevelMixin,
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/interactions', {
|
||||
|
@ -81,3 +87,48 @@ export const extractInteractionResponse = async (
|
|||
const body = await response.json();
|
||||
return body as InteractionResponse;
|
||||
};
|
||||
|
||||
export const isDeferred = (response: InteractionResponse): boolean => {
|
||||
return response.type === InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE;
|
||||
};
|
||||
|
||||
export const isEphemeral = (response: InteractionResponse): boolean => {
|
||||
return (
|
||||
(response.data?.flags || 0 & InteractionFlags.EPHEMERAL) ===
|
||||
InteractionFlags.EPHEMERAL
|
||||
);
|
||||
};
|
||||
|
||||
export const interactionData = (
|
||||
response: InteractionResponse
|
||||
): Omit<InteractionCallbackData, 'flags'> | undefined => {
|
||||
const { data } = response;
|
||||
if (!data) return undefined;
|
||||
|
||||
delete data.flags;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const mockUpdateCall = (
|
||||
expect: any,
|
||||
data: Omit<InteractionCallbackData, 'flags'>
|
||||
) => {
|
||||
return [
|
||||
expect.any(String),
|
||||
'',
|
||||
AuthType.None,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE,
|
||||
data: {
|
||||
flags: InteractionFlags.EPHEMERAL,
|
||||
...data,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'PATCH',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue