Backend: Use @accord/ion for tests

This commit is contained in:
ADAMJR 2021-11-18 17:47:55 +00:00
parent 3f7a5e12e4
commit e58500de97
22 changed files with 589 additions and 5616 deletions

6011
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,13 @@
"dev": "ts-node-dev --transpile-only src/app.ts",
"debug": "nodemon --exec 'node --inspect=0.0.0.0:9229 --require ts-node/register src/app.ts' --ext 'ts,yml'",
"test": "ts-mocha --exit test/test.ts",
"test:full": "ts-mocha test/test.ts"
"test:unit": "ts-mocha test/**/**.test.ts"
},
"keywords": [],
"author": "ADAMJR",
"license": "ISC",
"dependencies": {
"@accord/ion": "github:accord-dot-app/ion",
"body-parser": "^1.19.0",
"chai-things": "^0.2.0",
"colors": "^1.4.0",
@ -45,7 +46,6 @@
"striptags": "^3.2.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"un": "^0.0.0",
"winston": "^3.3.3"
},
"devDependencies": {
@ -56,6 +56,7 @@
"@types/colors": "^1.2.1",
"@types/cors": "^2.8.7",
"@types/crypto-js": "^4.0.2",
"@types/deasync": "^0.1.2",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.11",
"@types/express-rate-limit": "^5.1.1",
@ -76,13 +77,9 @@
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-spies": "^1.0.0",
"i": "^0.3.6",
"mocha": "^8.2.1",
"nodemon": "^2.0.14",
"npm": "^7.6.3",
"sazerac": "^2.0.0",
"supertest": "^6.1.3",
"synchronous-promise": "^2.0.15",
"ts-mocha": "^8.0.0"
}
}

View File

@ -6,7 +6,7 @@ export default class Channels extends DBWrapper<string, ChannelDocument> {
public async get(id: string | undefined) {
const channel = await Channel.findById(id);
if (!channel)
throw new TypeError('Channel Not Found');
throw new TypeError('Channel not found');
return channel;
}

View File

@ -4,6 +4,7 @@ export class WSCooldowns {
public readonly active = new Map<string, EventLog[]>();
// TODO: handle(userId, eventName, guildId)
// required for bots
public handle(userId: string, eventName: keyof WS.To) {
this.prune(userId);
this.add(userId, eventName);
@ -14,16 +15,16 @@ export class WSCooldowns {
throw new TypeError('You are doing too many things at once!');
}
private get(clientId: string) {
return this.active.get(clientId)
private get(userId: string) {
return this.active.get(userId)
?? this.active
.set(clientId, [])
.get(clientId) as EventLog[];
.set(userId, [])
.get(userId) as EventLog[];
}
private add(clientId: string, eventName: keyof WS.To) {
private add(userId: string, eventName: keyof WS.To) {
this
.get(clientId)
.get(userId)
.push({ eventName, timestamp: new Date().getTime() });
}

View File

@ -36,7 +36,7 @@ export class WebSocket {
const Event = require(`./ws-events/${file}`).default;
try {
const event = new Event();
this.events.set(event.on, event);
this.events.set(event.on, (event));
} catch {}
}
@ -46,7 +46,11 @@ export class WebSocket {
for (const event of this.events.values())
client.on(event.on, async (data: any) => {
try {
await event.invoke.bind(event)(this, client, data);
const instructions = await event.invoke.call(event, this, client, data);
for (const { emit, send, to } of instructions)
this.io
.to(to)
.emit(emit, send);
} catch (error) {
client.emit('error', { message: (error as Error).message });
} finally {

View File

@ -12,7 +12,6 @@ export default class implements WSEvent<'CHANNEL_DELETE'> {
const channel = await deps.channels.getText(channelId);
await deps.wsGuard.validateCan(client, channel.guildId, 'MANAGE_CHANNELS');
// clean up the message
await User.updateMany(
{ voice: { channelId } },
{ voice: {} },

View File

@ -0,0 +1,20 @@
import ChannelDelete from '../../../src/ws/ws-events/channel-delete';
import { given, test } from '@accord/ion';
import { WebSocket } from '../../../src/ws/websocket';
import { WS } from '../../../src/types/ws';
test(channelDelete, () => {
before(() => console.log('before'));
given({}).rejectWith('Channel not found');
after(() => console.log('after'));
});
async function channelDelete(args: WS.To['CHANNEL_DELETE']) {
await import('../../../src/modules/deps');
global['log'] = console;
const event = new ChannelDelete();
return event.invoke(new WebSocket(), {} as any, args);
}

View File

@ -1,6 +1,6 @@
import { config } from 'dotenv';
import { execSync } from 'child_process';
import { expect, should, use } from 'chai';
import { should, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import chaiThings from 'chai-things';
@ -26,16 +26,12 @@ use(should);
try {
execSync(`kill -9 $(lsof -i :${process.env.PORT} | tail -n 1 | cut -d ' ' -f5) 2>> /dev/null`);
} catch {}
await import('./int/ws/channel-delete.tests');
})();
// import('./unit/models/app.tests');
// import('./unit/models/channel.tests');
// import('./unit/models/guild.tests');
// import('./unit/models/guild-member.tests');
// import('./unit/models/invite.tests');
// import('./unit/models/message.tests');
// import('./unit/models/role.tests');
// import('./unit/models/user.tests');
// import('./unit/other/snowflake-entity.tests');
// import('./unit/other/ws-cooldowns.tests');
import('./unit/ws/channel-delete.tests');
/**
* e2e: testing the final product (i.e. app)
* integration: testing full unit with dependencies
* unit: testing one unit (i.e. one class, function etc.) - mocks dependencies
*/

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longString, mongooseError } from '../../test-utils';
import { App } from '../../../src/data/models/app';

View File

@ -1,6 +1,6 @@
import { Channel } from '../../../src/data/models/channel';
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longString, mongooseError } from '../../test-utils';
test(createChannel, () => {

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { mongooseError } from '../../test-utils';
import { GuildMember } from '../../../src/data/models/guild-member';

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longString, mongooseError } from '../../test-utils';
import { Guild } from '../../../src/data/models/guild';

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { mongooseError } from '../../test-utils';
import { Invite } from '../../../src/data/models/invite';

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longString, mongooseError } from '../../test-utils';
import { Message } from '../../../src/data/models/message';

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longString, mongooseError } from '../../test-utils';
import { Role } from '../../../src/data/models/role';
import { PermissionTypes } from '../../../src/types/permission-types';

View File

@ -1,5 +1,5 @@
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
import { longArray, mongooseError } from '../../test-utils';
import { User } from '../../../src/data/models/user';
import { Mock } from '../../mock/mock';

View File

@ -0,0 +1,41 @@
import { expect } from 'chai';
import { WSCooldowns } from '../../../src/ws/modules/ws-cooldowns';
import { given, test } from '@accord/ion';
import { generateSnowflake } from '../../../src/data/snowflake-entity';
import assert from 'assert';
describe('ws-cooldowns', () => {
let cooldowns = new WSCooldowns();
let userId = generateSnowflake();
test(cooldowns.handle.bind(cooldowns), () => {
afterEach(() => {
cooldowns = new WSCooldowns();
userId = generateSnowflake();
});
given(userId, 'MESSAGE_CREATE')
.message('record with user id added')
.assert(() => cooldowns.active.has(userId));
given(userId, 'MESSAGE_CREATE')
.message('handle, exceeds max cooldowns, throws error')
.assert(() => {
const handle = () => cooldowns.handle(userId, 'MESSAGE_CREATE');
for (let i = 0; i < 60; i++) handle();
return expect(handle).to.throw('You are doing too many things at once!');
});
given(userId, 'MESSAGE_CREATE')
.message('handle, prunes old cooldowns')
.assert(() => {
cooldowns.active.set(userId, [
{ eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() },
{ eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() },
]);
return expect(cooldowns.active.size).to.equal(1);
});
});
});

View File

@ -1,42 +0,0 @@
import { expect } from 'chai';
import generateInvite from '../../../src/data/utils/generate-invite';
import { WSCooldowns } from '../../../src/ws/modules/ws-cooldowns';
describe('ws-cooldowns', () => {
let clientId: string;
let cooldowns: WSCooldowns;
beforeEach(() => {
cooldowns = new WSCooldowns();
clientId = generateInvite();
});
it('handle, adds cooldown', () => {
handle();
expect(cooldowns.active.size).to.equal(1);
});
it('handle, no cooldowns, no error', () => {
expect(handle).to.not.throw();
});
it('handle, exceeds max cooldowns, throws error', () => {
for (let i = 0; i < 60; i++) handle();
expect(handle).to.throw('You are doing too many things at once!');
});
it('handle, prunes old cooldowns', () => {
cooldowns.active.set(clientId, [
{ eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() },
{ eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() },
]);
handle();
expect(cooldowns.active.size).to.equal(1);
});
function handle() {
return cooldowns.handle(clientId, 'MESSAGE_CREATE');
}
});

View File

@ -1,14 +0,0 @@
import ChannelDelete from '../../../src/ws/ws-events/channel-delete';
import { given, test } from 'sazerac';
import { WebSocket } from '../../../src/ws/websocket';
import { WS } from '../../../src/types/ws';
import SynchronousPromise from 'synchronous-promise';
test(channelDelete, () => {
given().expectError('Channel not found');
});
function channelDelete(args: WS.To['CHANNEL_DELETE']) {
const event = new ChannelDelete();
return new SynchronousPromise(event.invoke.bind(event))(new WebSocket(), {} as any, args);
}

View File

@ -8,6 +8,7 @@
"lib": ["ES2020", "ES2019"],
"module": "CommonJS",
"noImplicitAny": false,
"declaration": true,
"outDir": "lib",
"removeComments": true,
"resolveJsonModule": true,
@ -22,5 +23,6 @@
"src",
"env",
"**/*.ts"
]
],
"exclude": ["test"]
}

View File

@ -1,4 +1,4 @@
import { given, test } from 'sazerac';
import { given, test } from '@accord/ion';
import { FormatService } from './format-service';
describe('format-service', () => {

View File

@ -1,5 +1,5 @@
import { MentionService } from './mention-service';
import { test, given } from 'sazerac';
import { test, given } from '@accord/ion';
describe.skip('mention-service', () => {
let service: MentionService;