Files
fluxer/fluxer_api/src/api/oauth/tests/OAuth2ApplicationDelete.test.ts
T

148 lines
5.8 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils';
import {
createOAuth2Application,
createUniqueApplicationName,
deleteOAuth2Application,
getOAuth2Application,
listOAuth2Applications,
} from '@app/api/oauth/tests/OAuth2TestUtils';
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
import {HTTP_STATUS} from '@app/api/test/TestConstants';
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {beforeEach, describe, expect, test} from 'vitest';
describe('OAuth2 Application Delete', () => {
let harness: ApiTestHarness;
beforeEach(async () => {
harness = await createApiTestHarness();
});
test('deletes application and invalidates bot token', async () => {
const account = await createTestAccount(harness);
const appName = createUniqueApplicationName();
const createResult = await createOAuth2Application(harness, account.token, {
name: appName,
redirect_uris: ['https://example.com/callback'],
});
await createBuilder(harness, `Bot ${createResult.botToken}`).get('/users/@me').expect(HTTP_STATUS.OK).execute();
await deleteOAuth2Application(harness, account.token, createResult.application.id, account.password);
await createBuilder(harness, account.token)
.get(`/oauth2/applications/${createResult.application.id}`)
.expect(HTTP_STATUS.NOT_FOUND)
.execute();
const applications = await listOAuth2Applications(harness, account.token);
const found = applications.find((app) => app.id === createResult.application.id);
expect(found).toBeUndefined();
await createBuilder(harness, `Bot ${createResult.botToken}`)
.get('/users/@me')
.expect(HTTP_STATUS.UNAUTHORIZED)
.execute();
const botUser = await createBuilder<{
id: string;
username: string;
discriminator: string;
avatar: string | null;
}>(harness, account.token)
.get(`/users/${createResult.botUserId}`)
.expect(HTTP_STATUS.OK)
.execute();
expect(botUser.username).toBe('DeletedUser');
expect(botUser.discriminator).toBe('0000');
expect(botUser.avatar).toBeNull();
});
test('keeps bot-authored messages readable after deleting the application', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
const guild = await createBuilder<{
id: string;
system_channel_id?: string;
}>(harness, account.token)
.post('/guilds')
.body({name: `Bot Message Retention ${Date.now()}`})
.expect(HTTP_STATUS.OK)
.execute();
const channelId = guild.system_channel_id;
if (!channelId) {
throw new Error('Guild response missing system channel');
}
const seeded = await createBuilder<{
messages: Array<{
message_id: string;
}>;
}>(harness, '')
.post('/test/messages/seed')
.body({
channel_id: channelId,
author_id: createResult.botUserId,
clear_existing: true,
messages: [{content: 'bot message before deletion'}],
})
.expect(HTTP_STATUS.OK)
.execute();
const botMessageId = seeded.messages[0]?.message_id;
if (!botMessageId) {
throw new Error('Seeded message response missing message id');
}
await deleteOAuth2Application(harness, account.token, createResult.application.id, account.password);
const messages = await createBuilder<Array<MessageResponse>>(harness, account.token)
.get(`/channels/${channelId}/messages`)
.expect(HTTP_STATUS.OK)
.execute();
const foundMessage = messages.find((message) => message.id === botMessageId);
expect(foundMessage).toBeDefined();
expect(foundMessage?.author.id).toBe(createResult.botUserId);
expect(foundMessage?.author.bot).toBe(true);
expect(foundMessage?.author.username).toBe('DeletedUser');
expect(foundMessage?.author.discriminator).toBe('0000');
const authorCount = await createBuilderWithoutAuth<{
count: number;
}>(harness)
.get(`/test/users/${createResult.botUserId}/messages/count`)
.expect(HTTP_STATUS.OK)
.execute();
expect(authorCount.count).toBe(1);
});
test('enforces access control', async () => {
const owner = await createTestAccount(harness);
const otherUser = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, owner.token, {
name: createUniqueApplicationName(),
});
await createBuilder(harness, otherUser.token)
.delete(`/oauth2/applications/${createResult.application.id}`)
.body({password: otherUser.password})
.expect(HTTP_STATUS.FORBIDDEN)
.execute();
const application = await getOAuth2Application(harness, owner.token, createResult.application.id);
expect(application.id).toBe(createResult.application.id);
});
test('requires sudo verification', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
await createBuilder(harness, account.token)
.delete(`/oauth2/applications/${createResult.application.id}`)
.body({})
.expect(HTTP_STATUS.FORBIDDEN, 'SUDO_MODE_REQUIRED')
.execute();
const application = await getOAuth2Application(harness, account.token, createResult.application.id);
expect(application.id).toBe(createResult.application.id);
});
test('rejects wrong password', async () => {
const account = await createTestAccount(harness);
const createResult = await createOAuth2Application(harness, account.token, {
name: createUniqueApplicationName(),
});
await createBuilder(harness, account.token)
.delete(`/oauth2/applications/${createResult.application.id}`)
.body({password: 'wrong-password'})
.expect(HTTP_STATUS.BAD_REQUEST, 'INVALID_FORM_BODY')
.execute();
});
});