13 Commits

Author SHA1 Message Date
bf4f55c91b Upload files to ".gitea/workflows"
All checks were successful
nodeJS remote worker / build (push) Successful in 46s
2026-02-26 14:36:36 +11:00
b3566010a2 Update .gitea/workflows/build-main.yml
All checks were successful
nodeJS remote worker / build (push) Successful in 47s
2026-02-26 00:28:41 +11:00
0eee2988ce update build-main with SSH compose deploy
Some checks failed
nodeJS remote worker / build (push) Failing after 44s
2026-02-26 00:20:49 +11:00
a86260cc4a Update .gitea/workflows/build-main.yml
All checks were successful
nodeJS remote worker / build (push) Successful in 45s
2026-02-26 00:15:15 +11:00
506e3ef9dd Update .gitea/workflows/build-main.yml
Some checks failed
nodeJS remote worker / build (push) Failing after 18s
2026-02-26 00:12:08 +11:00
6a33fb592a Update .gitea/workflows/build-main.yml
Some checks failed
nodeJS remote worker / build (push) Has been cancelled
2026-02-26 00:11:21 +11:00
2f255cefd1 Update .gitea/workflows/build-main.yml
Some checks failed
nodeJS remote worker / build (push) Failing after 18s
2026-02-26 00:09:48 +11:00
c54016de77 Delete .gitea/workflows/test-workflow.yaml
Some checks failed
nodeJS remote worker / build (push) Failing after 19s
2026-02-26 00:08:03 +11:00
31da15eaeb Update .gitea/workflows/build.yml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
nodeJS remote worker / build (push) Failing after 19s
2026-02-26 00:07:40 +11:00
43f4302dbc add build workflow
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
nodeJS remote worker / build (push) Failing after 33s
2026-02-25 23:55:42 +11:00
af3da44946 test
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 41s
2026-02-25 23:34:29 +11:00
af57e2e6a3 add workflows 2026-02-25 23:21:13 +11:00
77191566e3 initial setup 2026-02-25 23:18:48 +11:00
30 changed files with 1723 additions and 2854 deletions

View File

@@ -0,0 +1,45 @@
name: nodeJS remote worker
on:
push:
branches: ["develop"]
pull_request:
branches: ["develop"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: login to gitea registry
uses: docker/login-action@v3
with:
registry: ${{ gitea.server_url }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
engineering.sanya.gay/pluralflux/pluralflux:latest
- name: Deploy bot
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
port: 22
script: |
cd /root/pluralflux-dev/PluralFlux
docker compose up -d

View File

@@ -0,0 +1,46 @@
name: nodeJS remote worker
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: login to gitea registry
uses: docker/login-action@v3
with:
registry: ${{ gitea.server_url }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
engineering.sanya.gay/pluralflux/pluralflux:latest
- name: Deploy bot
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd /root/pluralflux-prod/PluralFlux
docker compose up -d

9
.gitignore vendored
View File

@@ -1,13 +1,8 @@
node_modules/ node_modules
build/
tmp/
temp/
.idea .idea
secrets/ secrets/
coverage
config.json config.json
coverage
log.txt log.txt
.env .env
oya.png oya.png
variables.env
.env.production

View File

@@ -7,4 +7,4 @@ FROM node:20-alpine
WORKDIR /app WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/node_modules ./node_modules
COPY . . COPY . .
CMD ["npm", "start"] CMD ["node", "src/bot.js"]

View File

@@ -1,25 +1,35 @@
services: services:
main: main:
image: engineering.sanya.gay/pluralflux/pluralflux image: engineering.sanya.gay/pluralflux/pluralflux-dev
container_name: pluralflux container_name: pluralflux
restart: unless-stopped restart: unless-stopped
env_file: "variables.env" networks:
- pluralflux-net
env_file: "secrets.env"
postgres: postgres:
image: postgres:latest image: postgres:latest
env_file: "variables.env" container_name: pluralflux-postgres
env_file: "secrets.env"
volumes: volumes:
- pgdata:/var/lib/postgresql - pgdata:/var/lib/postgresql
- ./pgBackup:/mnt/pgBackup
ports: ports:
- "5432:5432" - "5432:5432"
networks:
- pluralflux-net
pgadmin: pgadmin:
image: dpage/pgadmin4:latest image: dpage/pgadmin4:latest
container_name: pluralflux-pgadmin
ports: ports:
- "5050:80" - "5050:80"
env_file: "variables.env" env_file: "secrets.env"
depends_on: depends_on:
- postgres - postgres
volumes: networks:
- pgadmindata:/var/lib/pgadmin - pluralflux-net
networks:
pluralflux-net:
volumes: volumes:
pgdata: pgdata:
secrets:

View File

@@ -1,26 +0,0 @@
import "reflect-metadata"
import { DataSource } from "typeorm"
import * as env from 'dotenv';
import * as path from "path";
env.config();
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: process.env.POSTGRES_PASSWORD,
database: "postgres",
synchronize: false,
logging: false,
entities: [path.join(__dirname, "./entity/*.{ts,js}")],
migrations: [path.join(__dirname, "./migrations/*.{ts,js}")],
migrationsRun: true,
migrationsTableName: 'migrations',
migrationsTransactionMode: 'all',
invalidWhereValuesBehavior: {
null: "sql-null",
undefined: "throw",
},
});

View File

@@ -1,39 +0,0 @@
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn} from "typeorm"
@Entity({name: "Member", synchronize: true})
export class Member {
@PrimaryGeneratedColumn()
id: number
@Column()
userid: string
@Column({
length: 100
})
name: string
@Column({
type: "varchar",
nullable: true,
length: 100
})
displayname: string
@Column({
nullable: true,
})
proxy: string
@Column({
nullable: true,
})
propic: string
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date
}

View File

@@ -1,14 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Update1772417745487 implements MigrationInterface {
name = 'Update1772417745487'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "Member" ("id" SERIAL NOT NULL, "userid" character varying NOT NULL, "name" character varying(100) NOT NULL, "displayname" character varying(100), "proxy" character varying, "propic" character varying, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_235428a1d87c5f639ef7b7cf170" PRIMARY KEY ("id"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "Member"`);
}
}

View File

@@ -1,12 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddData1772419448503 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`INSERT INTO "Member"(id, userid, name,displayname, proxy, propic, "createdAt", "updatedAt") SELECT id,userid, name,displayname, proxy, propic, "createdAt", "updatedAt" FROM "Members";`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`TRUNCATE TABLE "Member"`);
}
}

2020
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,36 +7,27 @@
"type": "git", "type": "git",
"url": "https://github.com/pieartsy/PluralFlux.git" "url": "https://github.com/pieartsy/PluralFlux.git"
}, },
"type": "commonjs",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fluxerjs/core": "^1.2.2", "@fluxerjs/core": "^1.2.2",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"pg": "^8.19.0", "pg": "^8.18.0",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"psql": "^0.0.1", "sequelize": "^6.37.7",
"reflect-metadata": "^0.2.2", "tmp": "^0.2.5"
"tmp": "^0.2.5",
"typeorm": "^0.3.28"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.29.0", "@babel/core": "^7.29.0",
"@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-commonjs": "^7.28.6",
"@babel/preset-env": "^7.29.0", "@babel/preset-env": "^7.29.0",
"@fetch-mock/jest": "^0.2.20", "@fetch-mock/jest": "^0.2.20",
"@types/node": "^25.3.3",
"babel-jest": "^30.2.0", "babel-jest": "^30.2.0",
"fetch-mock": "^12.6.0", "fetch-mock": "^12.6.0",
"jest": "^30.2.0", "jest": "^30.2.0",
"ts-node": "^10.9.2", "jest-fetch-mock": "^3.0.3"
"typescript": "^5.9.3"
}, },
"scripts": { "scripts": {
"test": "jest", "test": "jest"
"start": "ts-node src/bot.js",
"build-db": "tsc",
"generate-db": "typeorm-ts-node-commonjs migration:generate -d database/data-source.ts database/migrations/update",
"run-migration": "typeorm-ts-node-commonjs migration:run -d database/data-source.ts"
} }
} }

6
secrets.env Normal file
View File

@@ -0,0 +1,6 @@
FLUXER_BOT_TOKEN=<your bot token here>
POSTGRES_PASSWORD=<your postgres password here>
PGADMIN_DEFAULT_EMAIL: <default postgres admin login>
PGADMIN_DEFAULT_PASSWORD: <your postgres password here>
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'

View File

@@ -1,27 +1,24 @@
const {Client, Events, Message} = require('@fluxerjs/core'); import { Client, Events, Message } from '@fluxerjs/core';
const {messageHelper} = require("./helpers/messageHelper.js"); import { messageHelper } from "./helpers/messageHelper.js";
const {enums} = require("./enums.js"); import {enums} from "./enums.js";
const {commands} = require("./commands.js"); import {commands} from "./commands.js";
const {webhookHelper} = require("./helpers/webhookHelper.js"); import {webhookHelper} from "./helpers/webhookHelper.js";
const env = require('dotenv'); import env from 'dotenv';
const {utils} = require("./helpers/utils.js"); import {utils} from "./helpers/utils.js";
const { AppDataSource } = require("../database/data-source");
env.config(); env.config({path: './.env'});
const token = process.env.FLUXER_BOT_TOKEN; export const token = process.env.FLUXER_BOT_TOKEN;
if (!token) { if (!token) {
console.error("Missing FLUXER_BOT_TOKEN environment variable."); console.error("Missing FLUXER_BOT_TOKEN environment variable.");
process.exit(1); process.exit(1);
} }
client = new Client({ intents: 0 }); export const client = new Client({ intents: 0 });
module.exports.client = client;
client.on(Events.MessageCreate, async (message) => { client.on(Events.MessageCreate, async (message) => {
await module.exports.handleMessageCreate(message); await handleMessageCreate(message);
}); });
/** /**
@@ -31,15 +28,19 @@ client.on(Events.MessageCreate, async (message) => {
* @param {Message} message - The message object * @param {Message} message - The message object
* *
**/ **/
module.exports.handleMessageCreate = async function(message) { export const handleMessageCreate = async function(message) {
try { try {
// Ignore bots
if (message.author.bot) return;
// Parse command and arguments // Parse command and arguments
const content = message.content.trim(); const content = message.content.trim();
// Ignore bots and messages without content
if (message.author.bot || content.length === 0) return;
// If message doesn't start with the bot prefix, it could still be a message with a proxy tag. If it's not, return. // If message doesn't start with the bot prefix, it could still be a message with a proxy tag. If it's not, return.
if (!content.startsWith(messageHelper.prefix)) { if (!content.startsWith(messageHelper.prefix)) {
return await webhookHelper.sendMessageAsMember(client, message); await webhookHelper.sendMessageAsMember(client, message).catch((e) => {
throw e
});
return;
} }
const commandName = content.slice(messageHelper.prefix.length).split(" ")[0]; const commandName = content.slice(messageHelper.prefix.length).split(" ")[0];
@@ -56,7 +57,9 @@ module.exports.handleMessageCreate = async function(message) {
} }
if (command) { if (command) {
await command.execute(message, args); await command.execute(message, args).catch(e => {
throw e
});
} }
else { else {
await message.reply(enums.err.COMMAND_NOT_RECOGNIZED); await message.reply(enums.err.COMMAND_NOT_RECOGNIZED);
@@ -64,6 +67,7 @@ module.exports.handleMessageCreate = async function(message) {
} }
catch(error) { catch(error) {
console.error(error); console.error(error);
// return await message.reply(error.message);
} }
} }
@@ -82,23 +86,15 @@ function printGuilds() {
} }
const debouncePrintGuilds = utils.debounce(printGuilds, 2000); const debouncePrintGuilds = utils.debounce(printGuilds, 2000);
// export const debounceLogin = utils.debounce(client.login, 60000); export const debounceLogin = utils.debounce(client.login, 60000);
module.exports.login = async function() { (async () => {
try { try {
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
}
await client.login(token); await client.login(token);
// await db.check_connection();
} catch (err) { } catch (err) {
console.error('Login failed:', err); console.error('Login failed:', err);
process.exit(1); process.exit(1);
} }
} })();
function main()
{
exports.login();
}
main();

View File

@@ -1,20 +1,20 @@
const {messageHelper} = require("./helpers/messageHelper.js"); import {messageHelper} from "./helpers/messageHelper.js";
const {enums} = require("./enums.js"); import {enums} from "./enums.js";
const {memberHelper} = require("./helpers/memberHelper.js"); import {memberHelper} from "./helpers/memberHelper.js";
const {EmbedBuilder} = require("@fluxerjs/core"); import {EmbedBuilder} from "@fluxerjs/core";
const {importHelper} = require("./helpers/importHelper.js"); import {importHelper} from "./helpers/importHelper.js";
const commands = { const cmds = {
commandsMap: new Map(), commandsMap: new Map(),
aliasesMap: new Map() aliasesMap: new Map()
}; };
commands.aliasesMap.set('m', {command: 'member'}) cmds.aliasesMap.set('m', {command: 'member'})
commands.commandsMap.set('member', { cmds.commandsMap.set('member', {
description: enums.help.SHORT_DESC_MEMBER, description: enums.help.SHORT_DESC_MEMBER,
async execute(message, args) { async execute(message, args) {
await commands.memberCommand(message, args) await cmds.memberCommand(message, args)
} }
}) })
@@ -26,37 +26,31 @@ commands.commandsMap.set('member', {
* @param {string[]} args - The parsed arguments * @param {string[]} args - The parsed arguments
* *
**/ **/
commands.memberCommand = async function (message, args) { cmds.memberCommand = async function(message, args) {
const authorFull = `${message.author.username}#${message.author.discriminator}` const authorFull = `${message.author.username}#${message.author.discriminator}`
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null; const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
const attachmentExpires = message.attachments.size > 0 ? message.attachments.first().expires_at : null; const attachmentExpires = message.attachments.size > 0 ? message.attachments.first().expires_at : null;
let reply;
try { const reply = await memberHelper.parseMemberCommand(message.author.id, authorFull, args, attachmentUrl, attachmentExpires).catch(async (e) =>{console.error(e); await message.reply(e.message);});
reply = await memberHelper.parseMemberCommand(message.author.id, authorFull, args, attachmentUrl, attachmentExpires)
} catch (e) {
return await message.reply(e.message);
}
if (typeof reply === 'string') { if (typeof reply === 'string') {
await message.reply(reply); await message.reply(reply);
} else if (reply instanceof EmbedBuilder) { }
else if (reply instanceof EmbedBuilder) {
await message.reply({embeds: [reply]}) await message.reply({embeds: [reply]})
} else if (typeof reply === 'object') { }
// The little dash is so that the errors print out in bullet points in Fluxer else if (typeof reply === 'object') {
const errorsText = reply.errors.length > 0 ? '- ' + reply.errors.join('\n- ') : null; const errorsText = reply.errors.length > 0 ? reply.errors.join('\n- ') : null;
return await message.reply({ return await message.reply({content: `${reply.success} ${errorsText ? `\n\n${enums.err.ERRORS_OCCURRED}\n` + errorsText : ""}`, embeds: [reply.embed]})
content: `${reply.success} ${errorsText ? `\n\n${enums.err.ERRORS_OCCURRED}\n` + errorsText : ""}`,
embeds: [reply.embed]
})
} }
} }
commands.commandsMap.set('help', { cmds.commandsMap.set('help', {
description: enums.help.SHORT_DESC_HELP, description: enums.help.SHORT_DESC_HELP,
async execute(message) { async execute(message) {
const fields = [...commands.commandsMap.entries()].map(([name, cmd]) => ({ const fields = [...cmds.commandsMap.entries()].map(([name, cmd]) => ({
name: `${messageHelper.prefix}${name}`, name: `${messageHelper.prefix}${name}`,
value: cmd.description, value: cmd.description,
inline: true, inline: true,
@@ -73,10 +67,10 @@ commands.commandsMap.set('help', {
}, },
}) })
commands.commandsMap.set('import', { cmds.commandsMap.set('import', {
description: enums.help.SHORT_DESC_IMPORT, description: enums.help.SHORT_DESC_IMPORT,
async execute(message, args) { async execute(message, args) {
await commands.importCommand(message, args); await cmds.importCommand(message, args);
} }
}) })
@@ -88,35 +82,29 @@ commands.commandsMap.set('import', {
* @param {string[]} args - The parsed arguments * @param {string[]} args - The parsed arguments
* *
**/ **/
commands.importCommand = async function (message, args) { cmds.importCommand = async function(message, args) {
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null; const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
if ((message.content.includes('--help') || (args[0] === '' && args.length === 1)) && !attachmentUrl ) { if ((message.content.includes('--help') || (args[0] === '' && args.length === 1)) && !attachmentUrl ) {
return await message.reply(enums.help.IMPORT); return await message.reply(enums.help.IMPORT);
} }
let errorsText; return await importHelper.pluralKitImport(message.author.id, attachmentUrl).then(async (successfullyAdded) => {
try { await message.reply(successfullyAdded);
const successfullyAdded = await importHelper.pluralKitImport(message.author.id, attachmentUrl) }).catch(async (error) => {
return await message.reply(successfullyAdded);
} catch (error) {
if (error instanceof AggregateError) { if (error instanceof AggregateError) {
// errors.message can be a list of successfully added members, or say that none were successful. // errors.message can be a list of successfully added members, or say that none were successful.
errorsText = `${error.message}.\n\n${enums.err.ERRORS_OCCURRED}\n\n${error.errors.join('\n')}`; let errorsText = `${error.message}.\n\n${enums.err.ERRORS_OCCURRED}\n${error.errors.join('\n')}`;
await message.reply(errorsText).catch(async () => {
const returnedBuffer = messageHelper.returnBufferFromText(errorsText);
await message.reply({content: returnedBuffer.text, files: [{ name: 'text.txt', data: returnedBuffer.file }]
})
});
} }
// If just one error was returned. // If just one error was returned.
else { else {
console.error(error); return await message.reply(error.message);
errorsText = error.message;
} }
}
if (errorsText.length > 2000) {
const returnedBuffer = messageHelper.returnBufferFromText(errorsText);
await message.reply({
content: returnedBuffer.text, files: [{name: 'text.txt', data: returnedBuffer.file}]
}) })
} else {
await message.reply(errorsText)
} }
} export const commands = cmds;
module.exports.commands = commands;

84
src/database.js Normal file
View File

@@ -0,0 +1,84 @@
import {DataTypes, Sequelize} from 'sequelize';
import * as env from 'dotenv';
env.config();
const password = process.env.POSTGRES_PASSWORD;
if (!password) {
console.error("Missing POSTGRES_PWD environment variable.");
process.exit(1);
}
const db = {};
const sequelize = new Sequelize('postgres', 'postgres', password, {
host: 'localhost',
logging: false,
dialect: 'postgres'
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.members = sequelize.define('Member', {
userid: {
type: DataTypes.STRING,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
displayname: {
type: DataTypes.STRING,
},
propic: {
type: DataTypes.STRING,
},
proxy: {
type: DataTypes.STRING,
}
});
db.systems = sequelize.define('System', {
userid: {
type: DataTypes.STRING,
},
fronter: {
type: DataTypes.STRING
},
grouptag: {
type: DataTypes.STRING
},
autoproxy: {
type: DataTypes.BOOLEAN,
}
})
/**
* Checks Sequelize database connection.
*/
db.check_connection = async function() {
await sequelize.authenticate().then(async () => {
console.log('Connection has been established successfully.');
await syncModels();
}).catch(err => {
console.error('Unable to connect to the database:', err);
process.exit(1);
});
}
/**
* Syncs Sequelize models.
*/
async function syncModels() {
await sequelize.sync().then(() => {
console.log('Models synced successfully.');
}).catch((err) => {
console.error('Syncing models did not work', err);
process.exit(1);
});
}
export const database = db;

View File

@@ -1,6 +1,6 @@
const enums = {}; const helperEnums = {};
enums.err = { helperEnums.err = {
NO_MEMBER: "No such member was found.", NO_MEMBER: "No such member was found.",
NO_NAME_PROVIDED: "No member name was provided for", NO_NAME_PROVIDED: "No member name was provided for",
NO_VALUE: "has not been set for this member.", NO_VALUE: "has not been set for this member.",
@@ -22,11 +22,10 @@ enums.err = {
NO_MEMBERS_IMPORTED: 'No members were imported.', NO_MEMBERS_IMPORTED: 'No members were imported.',
ERRORS_OCCURRED: "These errors occurred:", ERRORS_OCCURRED: "These errors occurred:",
COMMAND_NOT_RECOGNIZED: "Command not recognized. Try typing `pf;help` for command list.", COMMAND_NOT_RECOGNIZED: "Command not recognized. Try typing `pf;help` for command list.",
SET_TO_NULL: "It has been set to null instead.", SET_TO_NULL: "It has been set to null instead."
CANNOT_FETCH_RESOURCE: "Could not download the file at this time."
} }
enums.help = { helperEnums.help = {
SHORT_DESC_HELP: "Lists available commands.", SHORT_DESC_HELP: "Lists available commands.",
SHORT_DESC_MEMBER: "Accesses subcommands related to proxy members.", SHORT_DESC_MEMBER: "Accesses subcommands related to proxy members.",
SHORT_DESC_IMPORT: "Imports from PluralKit.", SHORT_DESC_IMPORT: "Imports from PluralKit.",
@@ -43,11 +42,8 @@ enums.help = {
IMPORT: "Imports from PluralKit using the JSON file provided by their export command. Importing from other proxy bots is TBD. `pf;import` and attach your JSON file to the message. This will only save the fields that are present in the bot currently, not anything else like birthdays or system handles (yet?). **Only one proxy can be set per member currently.**\n\n**PRO TIP**: For privacy reasons, try DMing the bot with this command and your JSON file--it should still work the same." IMPORT: "Imports from PluralKit using the JSON file provided by their export command. Importing from other proxy bots is TBD. `pf;import` and attach your JSON file to the message. This will only save the fields that are present in the bot currently, not anything else like birthdays or system handles (yet?). **Only one proxy can be set per member currently.**\n\n**PRO TIP**: For privacy reasons, try DMing the bot with this command and your JSON file--it should still work the same."
} }
enums.misc = { helperEnums.misc = {
ATTACHMENT_SENT_BY: "Attachment sent by:", ATTACHMENT_SENT_BY: "Attachment sent by:"
ATTACHMENT_EXPIRATION_WARNING: "**NOTE:** Because this profile picture is hosted on Fluxer, it will expire. To avoid this, upload the picture to another website like <https://imgbb.com/> and link to it directly.",
FLUXER_ATTACHMENT_URL: "https://fluxerusercontent.com/attachments/"
} }
module.exports.enums = enums; export const enums = helperEnums;

View File

@@ -1,7 +1,7 @@
const {enums} = require("../enums.js"); import {enums} from "../enums.js";
const {memberHelper} = require("./memberHelper.js"); import {memberHelper} from "./memberHelper.js";
const importHelper = {}; const ih = {};
/** /**
* Tries to import from Pluralkit. * Tries to import from Pluralkit.
@@ -9,50 +9,35 @@ const importHelper = {};
* @async * @async
* @param {string} authorId - The author of the message * @param {string} authorId - The author of the message
* @param {string | null} [attachmentUrl] - The attached JSON url. * @param {string | null} [attachmentUrl] - The attached JSON url.
* @returns {Promise<string>} A successful addition of all members. * @returns {string} A successful addition of all members.
* @throws {Error} When the member exists, or creating a member doesn't work. * @throws {Error} When the member exists, or creating a member doesn't work.
*/ */
importHelper.pluralKitImport = async function (authorId, attachmentUrl= null) { ih.pluralKitImport = async function (authorId, attachmentUrl= null) {
let fetchResult, pkData;
if (!attachmentUrl) { if (!attachmentUrl) {
throw new Error(enums.err.NOT_JSON_FILE); throw new Error(enums.err.NOT_JSON_FILE);
} }
try { return fetch(attachmentUrl).then((res) => res.json()).then(async(pkData) => {
fetchResult = await fetch(attachmentUrl);
}
catch(e) {
throw new Error(enums.err.CANNOT_FETCH_RESOURCE, { cause: e });
}
try {
pkData = await fetchResult.json();
}
catch(e) {
throw new Error(enums.err.NOT_JSON_FILE, { cause: e })
}
const pkMembers = pkData.members; const pkMembers = pkData.members;
let errors = []; let errors = [];
let addedMembers = []; const addedMembers = [];
for (let pkMember of pkMembers) { for (let pkMember of pkMembers) {
const proxy = pkMember.proxy_tags[0] ? `${pkMember.proxy_tags[0].prefix ?? ''}text${pkMember.proxy_tags[0].suffix ?? ''}` : null; const proxy = pkMember.proxy_tags[0] ? `${pkMember.proxy_tags[0].prefix ?? ''}text${pkMember.proxy_tags[0].suffix ?? ''}` : null;
try { await memberHelper.addFullMember(authorId, pkMember.name, pkMember.display_name, proxy, pkMember.avatar_url).then((memberObj) => {
const memberObj = await memberHelper.addFullMember(authorId, pkMember.name, pkMember.display_name, proxy, pkMember.avatar_url);
addedMembers.push(memberObj.member.name); addedMembers.push(memberObj.member.name);
if (memberObj.errors.length > 0) { if (memberObj.errors.length > 0) {
errors.push(`\n**${pkMember.name}:** `); errors.push(`\n**${pkMember.name}:** `)
errors = errors.concat(memberObj.errors); errors = errors.concat(memberObj.errors);
} }
} }).catch(e => {
catch(e) {
errors.push(e.message); errors.push(e.message);
} });
} }
const aggregatedText = addedMembers.length > 0 ? `Successfully added members: ${addedMembers.join(', ')}` : `${enums.err.NO_MEMBERS_IMPORTED}`; const aggregatedText = addedMembers.length > 0 ? `Successfully added members: ${addedMembers.join(', ')}` : `${enums.err.NO_MEMBERS_IMPORTED}`;
if (errors.length > 0) { if (errors.length > 0) {
throw new AggregateError(errors, aggregatedText); throw new AggregateError(errors, aggregatedText);
} }
return aggregatedText; return aggregatedText;
});
} }
exports.importHelper = importHelper; export const importHelper = ih;

View File

@@ -1,9 +1,10 @@
const {enums} = require("../enums.js"); import {database} from '../database.js';
const {EmbedBuilder} = require("@fluxerjs/core"); import {enums} from "../enums.js";
const {utils} = require("./utils.js"); import {Op} from "sequelize";
const {memberRepo} = require("../repositories/memberRepo.js"); import {EmbedBuilder} from "@fluxerjs/core";
import {utils} from "./utils.js";
const memberHelper = {}; const mh = {};
const commandList = ['new', 'remove', 'name', 'list', 'displayname', 'proxy', 'propic']; const commandList = ['new', 'remove', 'name', 'list', 'displayname', 'proxy', 'propic'];
const newAndRemoveCommands = ['new', 'remove']; const newAndRemoveCommands = ['new', 'remove'];
@@ -21,15 +22,16 @@ const newAndRemoveCommands = ['new', 'remove'];
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed. * @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions. * @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
* @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors. * @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors.
* @throws {Error}
*/ */
memberHelper.parseMemberCommand = async function (authorId, authorFull, args, attachmentUrl = null, attachmentExpiration = null) { mh.parseMemberCommand = async function (authorId, authorFull, args, attachmentUrl = null, attachmentExpiration = null) {
let memberName, command, isHelp = false; let memberName, command, isHelp = false;
// checks whether command is in list, otherwise assumes it's a name // checks whether command is in list, otherwise assumes it's a name
// ex: pf;member remove, pf;member remove --help // ex: pf;member remove, pf;member remove --help
// ex: pf;member, pf;member --help // ex: pf;member, pf;member --help
if (args.length === 0 || args[0] === '--help' || args[0] === '') { if (args.length === 0 || args[0] === '--help' || args[0] === '') {
return memberHelper.getMemberCommandInfo(); return mh.getMemberCommandInfo();
} }
// ex: pf;member remove somePerson // ex: pf;member remove somePerson
if (commandList.includes(args[0])) { if (commandList.includes(args[0])) {
@@ -51,7 +53,7 @@ memberHelper.parseMemberCommand = async function (authorId, authorFull, args, at
isHelp = true; isHelp = true;
} }
return await memberHelper.memberArgumentHandler(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration); return await mh.memberArgumentHandler(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration)
} }
/** /**
@@ -70,18 +72,17 @@ memberHelper.parseMemberCommand = async function (authorId, authorFull, args, at
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed. * @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions. * @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
* @returns {Promise<{EmbedBuilder, [string], string}>} A member info embed + info/errors. * @returns {Promise<{EmbedBuilder, [string], string}>} A member info embed + info/errors.
* @returns {Promise<string>} - A help message * @throws {Error}
* @throws {Error} When there's no member or a command is not recognized.
*/ */
memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp, command = null, memberName = null, args = [], attachmentUrl = null, attachmentExpiration = null) { mh.memberArgumentHandler = async function(authorId, authorFull, isHelp, command = null, memberName = null, args = [], attachmentUrl = null, attachmentExpiration = null) {
if (!command && !memberName && !isHelp) { if (!command && !memberName && !isHelp) {
throw new Error(enums.err.COMMAND_NOT_RECOGNIZED); throw new Error(enums.err.COMMAND_NOT_RECOGNIZED);
} }
else if (isHelp) { else if (isHelp) {
return memberHelper.sendHelpEnum(command); return mh.sendHelpEnum(command);
} }
else if (command === "list") { else if (command === "list") {
return await memberHelper.getAllMembersInfo(authorId, authorFull); return await mh.getAllMembersInfo(authorId, authorFull);
} }
else if (!memberName && !isHelp) { else if (!memberName && !isHelp) {
throw new Error(enums.err.NO_MEMBER); throw new Error(enums.err.NO_MEMBER);
@@ -92,10 +93,10 @@ memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp
// ex: pf;member blah blah // ex: pf;member blah blah
if (command && memberName && (values.length > 0 || newAndRemoveCommands.includes(command) || attachmentUrl)) { if (command && memberName && (values.length > 0 || newAndRemoveCommands.includes(command) || attachmentUrl)) {
return await memberHelper.memberCommandHandler(authorId, command, memberName, values, attachmentUrl, attachmentExpiration); return await mh.memberCommandHandler(authorId, command, memberName, values, attachmentUrl, attachmentExpiration).catch((e) => {throw e});
} }
else if (memberName && values.length === 0) { else if (memberName && values.length === 0) {
return await memberHelper.sendCurrentValue(authorId, memberName, command); return await mh.sendCurrentValue(authorId, memberName, command).catch((e) => {throw e});
} }
} }
@@ -110,14 +111,15 @@ memberHelper.memberArgumentHandler = async function(authorId, authorFull, isHelp
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed. * @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions. * @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
* @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors. * @returns {Promise<{EmbedBuilder, string[], string}>} A member info embed + info/errors.
* @throws {Error} When there's no member
*/ */
memberHelper.sendCurrentValue = async function(authorId, memberName, command= null) { mh.sendCurrentValue = async function(authorId, memberName, command= null) {
const member = await memberRepo.getMemberByName(authorId, memberName); const member = await mh.getMemberByName(authorId, memberName).then((m) => {
if (!member) throw new Error(enums.err.NO_MEMBER); if (!m) throw new Error(enums.err.NO_MEMBER);
return m;
});
if (!command) { if (!command) {
return memberHelper.getMemberInfo(member); return mh.getMemberInfo(member);
} }
switch (command) { switch (command) {
@@ -138,7 +140,7 @@ memberHelper.sendCurrentValue = async function(authorId, memberName, command= nu
* @param {string} command - The command being called. * @param {string} command - The command being called.
* @returns {string} - The help text associated with a command. * @returns {string} - The help text associated with a command.
*/ */
memberHelper.sendHelpEnum = function(command) { mh.sendHelpEnum = function(command) {
switch (command) { switch (command) {
case 'new': case 'new':
return enums.help.NEW; return enums.help.NEW;
@@ -167,24 +169,26 @@ memberHelper.sendHelpEnum = function(command) {
* @param {string[]} values - The values to be passed in. Only includes the values after member name and command name. * @param {string[]} values - The values to be passed in. Only includes the values after member name and command name.
* @param {string | null} attachmentUrl - The attachment URL, if any * @param {string | null} attachmentUrl - The attachment URL, if any
* @param {string | null} attachmentExpiration - The attachment expiry date, if any * @param {string | null} attachmentExpiration - The attachment expiry date, if any
* @returns {Promise<string> | Promise <EmbedBuilder> | Promise<{EmbedBuilder, [string], string}>} * @returns {Promise<string>} A success message.
* @returns {Promise <EmbedBuilder>} A list of 25 members as an embed.
* @returns {Promise <EmbedBuilder>} A list of member commands and descriptions.
* @returns {Promise<{EmbedBuilder, [string], string}>} A member info embed + info/errors.
* @throws {Error}
*/ */
memberHelper.memberCommandHandler = async function(authorId, command, memberName, values, attachmentUrl = null, attachmentExpiration = null) { mh.memberCommandHandler = async function(authorId, command, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
switch (command) { switch (command) {
case 'new': case 'new':
return await memberHelper.addNewMember(authorId, memberName, values, attachmentUrl, attachmentExpiration); return await mh.addNewMember(authorId, memberName, values, attachmentUrl, attachmentExpiration).catch((e) => {throw e});
case 'remove': case 'remove':
return await memberHelper.removeMember(authorId, memberName); return await mh.removeMember(authorId, memberName).catch((e) => {throw e});
case 'name': case 'name':
return await memberHelper.updateName(authorId, memberName, values[0]); return await mh.updateName(authorId, memberName, values[0]).catch((e) => {throw e});
case 'displayname': case 'displayname':
return await memberHelper.updateDisplayName(authorId, memberName, values[0]); return await mh.updateDisplayName(authorId, memberName, values[0]).catch((e) => {throw e});
case 'proxy': case 'proxy':
return await memberHelper.updateProxy(authorId, memberName, values[0]); return await mh.updateProxy(authorId, memberName, values[0]).catch((e) => {throw e});
case 'propic': case 'propic':
return await memberHelper.updatePropic(authorId, memberName, values[0], attachmentUrl, attachmentExpiration); return await mh.updatePropic(authorId, memberName, values[0], attachmentUrl, attachmentExpiration).catch((e) => {throw e});
default:
throw new Error(enums.err.COMMAND_NOT_RECOGNIZED);
} }
} }
@@ -198,15 +202,20 @@ memberHelper.memberCommandHandler = async function(authorId, command, memberName
* @param {string | null} [attachmentUrl] - The attachment URL, if any * @param {string | null} [attachmentUrl] - The attachment URL, if any
* @param {string | null} [attachmentExpiration] - The attachment expiry date, if any * @param {string | null} [attachmentExpiration] - The attachment expiry date, if any
* @returns {Promise<{EmbedBuilder, string[], string}>} A successful addition. * @returns {Promise<{EmbedBuilder, string[], string}>} A successful addition.
* @throws {Error} When creating a member doesn't work.
*/ */
memberHelper.addNewMember = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) { mh.addNewMember = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
const displayName = values[0]; const displayName = values[0];
const proxy = values[1]; const proxy = values[1];
const propic = values[2] ?? attachmentUrl; const propic = values[2] ?? attachmentUrl;
const memberObj = await memberHelper.addFullMember(authorId, memberName, displayName, proxy, propic, attachmentExpiration); return await mh.addFullMember(authorId, memberName, displayName, proxy, propic, attachmentExpiration).then((response) => {
const memberInfoEmbed = memberHelper.getMemberInfo(memberObj.member); const memberInfoEmbed = mh.getMemberInfo(response.member);
return {embed: memberInfoEmbed, errors: memberObj.errors, success: `${memberName} has been added successfully.`} return {embed: memberInfoEmbed, errors: response.errors, success: `${memberName} has been added successfully.`};
}).catch(e => {
console.error(e);
throw e;
})
} }
/** /**
@@ -219,12 +228,14 @@ memberHelper.addNewMember = async function (authorId, memberName, values, attach
* @returns {Promise<string>} A successful update. * @returns {Promise<string>} A successful update.
* @throws {RangeError} When the name doesn't exist. * @throws {RangeError} When the name doesn't exist.
*/ */
memberHelper.updateName = async function (authorId, memberName, name) { mh.updateName = async function (authorId, memberName, name) {
const trimmedName = name.trim(); const trimmedName = name.trim();
if (trimmedName === '') { if (trimmedName === '') {
throw new RangeError(`Name ${enums.err.NO_VALUE}`); throw new RangeError(`Name ${enums.err.NO_VALUE}`);
} }
return await memberHelper.updateMemberField(authorId, memberName, "name", trimmedName); return await mh.updateMemberField(authorId, memberName, "name", trimmedName).catch((e) => {
throw e
});
} }
/** /**
@@ -237,7 +248,7 @@ memberHelper.updateName = async function (authorId, memberName, name) {
* @returns {Promise<string>} A successful update. * @returns {Promise<string>} A successful update.
* @throws {RangeError} When the display name is too long or doesn't exist. * @throws {RangeError} When the display name is too long or doesn't exist.
*/ */
memberHelper.updateDisplayName = async function (authorId, membername, displayname) { mh.updateDisplayName = async function (authorId, membername, displayname) {
const trimmedName = displayname.trim(); const trimmedName = displayname.trim();
if (trimmedName.length > 32) { if (trimmedName.length > 32) {
@@ -246,7 +257,9 @@ memberHelper.updateDisplayName = async function (authorId, membername, displayna
else if (trimmedName === '') { else if (trimmedName === '') {
throw new RangeError(`Display name ${enums.err.NO_VALUE}`); throw new RangeError(`Display name ${enums.err.NO_VALUE}`);
} }
return await memberHelper.updateMemberField(authorId, membername, "displayname", trimmedName); return await mh.updateMemberField(authorId, membername, "displayname", trimmedName).catch((e) => {
throw e
});
} }
/** /**
@@ -257,12 +270,13 @@ memberHelper.updateDisplayName = async function (authorId, membername, displayna
* @param {string} memberName - The member to update * @param {string} memberName - The member to update
* @param {string} proxy - The proxy to set * @param {string} proxy - The proxy to set
* @returns {Promise<string> } A successful update. * @returns {Promise<string> } A successful update.
* @throws {Error} When an empty proxy was provided, or a proxy exists.
*/ */
memberHelper.updateProxy = async function (authorId, memberName, proxy) { mh.updateProxy = async function (authorId, memberName, proxy) {
// Throws error if exists // Throws error if exists
await memberHelper.checkIfProxyExists(authorId, proxy); await mh.checkIfProxyExists(authorId, proxy).catch((e) => { throw e; });
return await memberHelper.updateMemberField(authorId, memberName, "proxy", proxy); return await mh.updateMemberField(authorId, memberName, "proxy", proxy).catch((e) => { throw e;});
} }
/** /**
@@ -275,13 +289,14 @@ memberHelper.updateProxy = async function (authorId, memberName, proxy) {
* @param {string | null} attachmentUrl - The attachment URL, if any * @param {string | null} attachmentUrl - The attachment URL, if any
* @param {string | null} attachmentExpiration - The attachment expiry date, if any * @param {string | null} attachmentExpiration - The attachment expiry date, if any
* @returns {Promise<string>} A successful update. * @returns {Promise<string>} A successful update.
* @throws {Error} When loading the profile picture from a URL doesn't work.
*/ */
memberHelper.updatePropic = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) { mh.updatePropic = async function (authorId, memberName, values, attachmentUrl = null, attachmentExpiration = null) {
const imgUrl = values ?? attachmentUrl; const imgUrl = values ?? attachmentUrl;
// Throws error if invalid // Throws error if invalid
await utils.checkImageFormatValidity(imgUrl); await utils.checkImageFormatValidity(imgUrl).catch((e) => { throw e });
const expirationWarning = utils.setExpirationWarning(imgUrl, attachmentExpiration);
return await memberHelper.updateMemberField(authorId, memberName, "propic", imgUrl, expirationWarning); return await mh.updateMemberField(authorId, memberName, "propic", imgUrl, attachmentExpiration).catch((e) => { throw e });
} }
/** /**
@@ -293,13 +308,18 @@ memberHelper.updatePropic = async function (authorId, memberName, values, attach
* @returns {Promise<string>} A successful removal. * @returns {Promise<string>} A successful removal.
* @throws {Error} When there is no member to remove. * @throws {Error} When there is no member to remove.
*/ */
memberHelper.removeMember = async function (authorId, memberName) { mh.removeMember = async function (authorId, memberName) {
const destroyed = await memberRepo.removeMember(authorId, memberName); return await database.members.destroy({
if (destroyed > 0) { where: {
return `Member "${memberName}" has been deleted.`; name: {[Op.iLike]: memberName},
} else { userid: authorId
throw new Error(`${enums.err.NO_MEMBER}`);
} }
}).then((result) => {
if (result) {
return `Member "${memberName}" has been deleted.`;
}
throw new Error(`${enums.err.NO_MEMBER}`);
})
} }
/*======Non-Subcommands======*/ /*======Non-Subcommands======*/
@@ -314,14 +334,15 @@ memberHelper.removeMember = async function (authorId, memberName) {
* @param {string | null} [proxy] - The proxy tag of the member. * @param {string | null} [proxy] - The proxy tag of the member.
* @param {string | null} [propic] - The profile picture URL of the member. * @param {string | null} [propic] - The profile picture URL of the member.
* @param {string | null} [attachmentExpiration] - The expiration date of an uploaded profile picture. * @param {string | null} [attachmentExpiration] - The expiration date of an uploaded profile picture.
* @returns {Promise<{Members, string[]}>} A successful addition object, including errors if there are any. * @returns {Promise<{model, string[]}>} A successful addition object, including errors if there are any.
* @throws {Error} When the member already exists, there are validation errors, or adding a member doesn't work. * @throws {Error} When the member already exists, there are validation errors, or adding a member doesn't work.
*/ */
memberHelper.addFullMember = async function (authorId, memberName, displayName = null, proxy = null, propic = null, attachmentExpiration = null) { mh.addFullMember = async function (authorId, memberName, displayName = null, proxy = null, propic = null, attachmentExpiration = null) {
const existingMember = await memberRepo.getMemberByName(authorId, memberName); await mh.getMemberByName(authorId, memberName).then((member) => {
if (existingMember) { if (member) {
throw new Error(`Can't add ${memberName}. ${enums.err.MEMBER_EXISTS}`); throw new Error(`Can't add ${memberName}. ${enums.err.MEMBER_EXISTS}`);
} }
});
const errors = []; const errors = [];
const trimmedName = memberName.trim(); const trimmedName = memberName.trim();
@@ -345,34 +366,30 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
} }
} }
let isValidProxy; let isValidProxy;
if (proxy && proxy.length > 0) { if (proxy && proxy.length > 0) {
try { await mh.checkIfProxyExists(authorId, proxy).then(() => {
const proxyExists = await memberHelper.checkIfProxyExists(authorId, proxy); isValidProxy = true;
isValidProxy = !proxyExists; }).catch((e) => {
}
catch(e) {
errors.push(`Tried to set proxy to \"${proxy}\". ${e.message}. ${enums.err.SET_TO_NULL}`); errors.push(`Tried to set proxy to \"${proxy}\". ${e.message}. ${enums.err.SET_TO_NULL}`);
isValidProxy = false; isValidProxy = false;
} });
} }
let isValidPropic, expirationWarning; let isValidPropic;
if (propic && propic.length > 0) { if (propic && propic.length > 0) {
try { await utils.checkImageFormatValidity(propic).then(() => {
isValidPropic = await utils.checkImageFormatValidity(propic); isValidPropic = true;
expirationWarning = utils.setExpirationWarning(propic, attachmentExpiration); }).catch((e) => {
if (expirationWarning) {
errors.push(expirationWarning);
}
}
catch(e) {
errors.push(`Tried to set profile picture to \"${propic}\". ${e.message}. ${enums.err.SET_TO_NULL}`); errors.push(`Tried to set profile picture to \"${propic}\". ${e.message}. ${enums.err.SET_TO_NULL}`);
isValidPropic = false; isValidPropic = false;
});
} }
if (isValidPropic && attachmentExpiration) {
errors.push(mh.setExpirationWarning(attachmentExpiration));
} }
const member = await database.members.create({
const member = await memberRepo.createMember({
name: memberName, userid: authorId, displayname: isValidDisplayName ? displayName : null, proxy: isValidProxy ? proxy : null, propic: isValidPropic ? propic : null name: memberName, userid: authorId, displayname: isValidDisplayName ? displayName : null, proxy: isValidProxy ? proxy : null, propic: isValidPropic ? propic : null
}); });
@@ -387,26 +404,52 @@ memberHelper.addFullMember = async function (authorId, memberName, displayName =
* @param {string} memberName - The member to update * @param {string} memberName - The member to update
* @param {string} columnName - The column name to update. * @param {string} columnName - The column name to update.
* @param {string} value - The value to update to. * @param {string} value - The value to update to.
* @param {string | null} [expirationWarning] - The attachment expiration warning (if any) * @param {string | null} [attachmentExpiration] - The attachment expiration date (if any)
* @returns {Promise<string>} A successful update. * @returns {Promise<string>} A successful update.
* @throws {Error} When no member row was updated. * @throws {Error} When no member row was updated.
*/ */
memberHelper.updateMemberField = async function (authorId, memberName, columnName, value, expirationWarning = null) { mh.updateMemberField = async function (authorId, memberName, columnName, value, attachmentExpiration = null) {
const res = await memberRepo.updateMemberField(authorId, memberName, columnName, value); let fluxerPropicWarning;
if (res === 0) {
// indicates that an attachment was uploaded on Fluxer directly
if (columnName === "propic" && attachmentExpiration) {
fluxerPropicWarning = mh.setExpirationWarning(value);
}
return await database.members.update({[columnName]: value}, {
where: {
name: {[Op.iLike]: memberName},
userid: authorId
}
}).then((res) => {
if (res[0] === 0) {
throw new Error(`Can't update ${memberName}. ${enums.err.NO_MEMBER}.`); throw new Error(`Can't update ${memberName}. ${enums.err.NO_MEMBER}.`);
} else { } else {
return `Updated ${columnName} for ${memberName} to ${value}${expirationWarning ? `. ${expirationWarning}.` : '.'}`; return `Updated ${columnName} for ${memberName} to ${value}${fluxerPropicWarning ?? ''}.`;
}
})
}
/**
* Sets the warning for an expiration date.
*
* @param {string} expirationString - An expiration date string.
* @returns {string} A description of the expiration, interpolating the expiration string.
*/
mh.setExpirationWarning = function (expirationString) {
let expirationDate = new Date(expirationString);
if (!isNaN(expirationDate.valueOf())) {
expirationDate = expirationDate.toDateString();
return `\n**NOTE:** Because this profile picture was uploaded via Fluxer, it will currently expire on *${expirationDate}*. To avoid this, upload the picture to another website like <https://imgbb.com/> and link to it directly`
} }
} }
/** /**
* Gets the details for a member. * Gets the details for a member.
* *
* @param {{Members, string[]}} member - The member object * @param {model} member - The member object
* @returns {EmbedBuilder} The member's info. * @returns {EmbedBuilder} The member's info.
*/ */
memberHelper.getMemberInfo = function (member) { mh.getMemberInfo = function (member) {
return new EmbedBuilder() return new EmbedBuilder()
.setTitle(member.name) .setTitle(member.name)
.setDescription(`Details for ${member.name}`) .setDescription(`Details for ${member.name}`)
@@ -427,17 +470,40 @@ memberHelper.getMemberInfo = function (member) {
* @returns {Promise<EmbedBuilder>} The info for all members. * @returns {Promise<EmbedBuilder>} The info for all members.
* @throws {Error} When there are no members for an author. * @throws {Error} When there are no members for an author.
*/ */
memberHelper.getAllMembersInfo = async function (authorId, authorName) { mh.getAllMembersInfo = async function (authorId, authorName) {
const members = await memberRepo.getMembersByAuthor(authorId); const members = await mh.getMembersByAuthor(authorId);
if (members.length === 0) throw Error(enums.err.USER_NO_MEMBERS); if (members == null) throw Error(enums.err.USER_NO_MEMBERS);
const fields = [...members.entries()].map(([index, member]) => ({ const fields = [...members.entries()].map(([name, member]) => ({
name: member.name, value: `(Proxy: \`${member.proxy ?? "unset"}\`)`, inline: true, name: member.name, value: `(Proxy: \`${member.proxy ?? "unset"}\`)`, inline: true,
})); }));
return new EmbedBuilder() return new EmbedBuilder()
.setTitle(`${fields.length > 25 ? "First 25 m" : "M"}embers for ${authorName}`) .setTitle(`${fields > 25 ? "First 25 m" : "M"}embers for ${authorName}`)
.addFields(...fields); .addFields(...fields);
} }
/**
* Gets a member based on the author and proxy tag.
*
* @async
* @param {string} authorId - The author of the message.
* @param {string} memberName - The member's name.
* @returns {Promise<model>} The member object.
*/
mh.getMemberByName = async function (authorId, memberName) {
return await database.members.findOne({where: {userid: authorId, name: {[Op.iLike]: memberName}}});
}
/**
* Gets all members belonging to the author.
*
* @async
* @param {string} authorId - The author of the message
* @returns {Promise<model[] | null>} The member object array.
*/
mh.getMembersByAuthor = async function (authorId) {
return await database.members.findAll({where: {userid: authorId}});
}
/** /**
* Checks if proxy exists for a member. * Checks if proxy exists for a member.
* *
@@ -446,16 +512,19 @@ memberHelper.getAllMembersInfo = async function (authorId, authorName) {
* @returns {Promise<boolean> } Whether the proxy exists. * @returns {Promise<boolean> } Whether the proxy exists.
* @throws {Error} When an empty proxy was provided, or no proxy exists. * @throws {Error} When an empty proxy was provided, or no proxy exists.
*/ */
memberHelper.checkIfProxyExists = async function (authorId, proxy) { mh.checkIfProxyExists = async function (authorId, proxy) {
const splitProxy = proxy.trim().split("text"); const splitProxy = proxy.trim().split("text");
if (splitProxy.length < 2) throw new Error(enums.err.NO_TEXT_FOR_PROXY); if (splitProxy.length < 2) throw new Error(enums.err.NO_TEXT_FOR_PROXY);
if (!splitProxy[0] && !splitProxy[1]) throw new Error(enums.err.NO_PROXY_WRAPPER); if (!splitProxy[0] && !splitProxy[1]) throw new Error(enums.err.NO_PROXY_WRAPPER);
const memberList = await memberRepo.getMembersByAuthor(authorId); await mh.getMembersByAuthor(authorId).then((memberList) => {
const proxyExists = memberList.some(member => member.proxy === proxy); const proxyExists = memberList.some(member => member.proxy === proxy);
if (proxyExists) { if (proxyExists) {
throw new Error(enums.err.PROXY_EXISTS); throw new Error(enums.err.PROXY_EXISTS);
} }
}).catch(e => {
throw e
});
return false; return false;
} }
@@ -464,7 +533,7 @@ memberHelper.checkIfProxyExists = async function (authorId, proxy) {
* *
* @returns {EmbedBuilder } An embed of member commands. * @returns {EmbedBuilder } An embed of member commands.
*/ */
memberHelper.getMemberCommandInfo = function() { mh.getMemberCommandInfo = function() {
const fields = [ const fields = [
{name: `**new**`, value: enums.help.NEW, inline: false}, {name: `**new**`, value: enums.help.NEW, inline: false},
{name: `**remove**`, value: enums.help.REMOVE, inline: false}, {name: `**remove**`, value: enums.help.REMOVE, inline: false},
@@ -481,4 +550,4 @@ memberHelper.getMemberCommandInfo = function() {
} }
module.exports.memberHelper = memberHelper; export const memberHelper = mh;

View File

@@ -1,4 +1,4 @@
const {memberRepo} = require('../repositories/memberRepo.js'); import {memberHelper} from "./memberHelper.js";
const msgh = {}; const msgh = {};
@@ -36,10 +36,11 @@ msgh.parseCommandArgs = function(content, commandName) {
* @param {string} authorId - The author of the message. * @param {string} authorId - The author of the message.
* @param {string} content - The full message content * @param {string} content - The full message content
* @param {string | null} [attachmentUrl] - The url for an attachment to the message, if any exists. * @param {string | null} [attachmentUrl] - The url for an attachment to the message, if any exists.
* @returns {Promise<{model, string, bool}>} The proxy message object. * @returns {{model, string, bool}} The proxy message object.
* @throws {Error} If a proxy message is sent with no message or attachment within it.
*/ */
msgh.parseProxyTags = async function (authorId, content, attachmentUrl = null){ msgh.parseProxyTags = async function (authorId, content, attachmentUrl = null){
const members = await memberRepo.getMembersByAuthor(authorId); const members = await memberHelper.getMembersByAuthor(authorId);
// If an author has no members, no sense in searching for proxy // If an author has no members, no sense in searching for proxy
if (members.length === 0) { if (members.length === 0) {
return; return;
@@ -80,4 +81,4 @@ msgh.returnBufferFromText = function (text) {
return {text: text, file: undefined} return {text: text, file: undefined}
} }
module.exports.messageHelper = msgh; export const messageHelper = msgh;

View File

@@ -1,8 +1,8 @@
const {enums} = require('../enums'); import {enums} from '../enums.js'
const utils = {}; const u = {};
utils.debounce = function(func, delay) { u.debounce = function(func, delay) {
let timeout = null; let timeout = null;
return function (...args) { return function (...args) {
clearTimeout(timeout); clearTimeout(timeout);
@@ -15,43 +15,15 @@ utils.debounce = function(func, delay) {
* *
* @async * @async
* @param {string} imageUrl - The url of the image * @param {string} imageUrl - The url of the image
* @returns {bool} - Whether the image is in a valid format
* @throws {Error} When loading the profile picture from a URL doesn't work, or it fails requirements. * @throws {Error} When loading the profile picture from a URL doesn't work, or it fails requirements.
*/ */
utils.checkImageFormatValidity = async function (imageUrl) { u.checkImageFormatValidity = async function (imageUrl) {
const acceptableImages = ['image/png', 'image/jpg', 'image/jpeg', 'image/webp']; const acceptableImages = ['image/png', 'image/jpg', 'image/jpeg', 'image/webp'];
let response, blobFile; await fetch(imageUrl).then(r => r.blob()).then(blobFile => {
try { if (blobFile.size > 1000000 || !acceptableImages.includes(blobFile.type)) throw new Error(enums.err.PROPIC_FAILS_REQUIREMENTS);
response = await fetch(imageUrl); }).catch((error) => {
} throw new Error(`${enums.err.PROPIC_CANNOT_LOAD}: ${error.message}`);
catch(e) { });
throw new Error(`${enums.err.PROPIC_CANNOT_LOAD}: ${e.message}`);
} }
blobFile = await response.blob(); export const utils = u;
if (blobFile.size > 10000000 || !acceptableImages.includes(blobFile.type)) throw new Error(enums.err.PROPIC_FAILS_REQUIREMENTS);
return true;
}
/**
* Sets the warning that a Fluxer-uploaded image will expire.
*
* @param {string | null} [imgUrl] - An image URL.
* @param {string | null} [expirationString] - An expiration date string.
* @returns {string | null} A description of the expiration, or null.
*/
utils.setExpirationWarning = function (imgUrl = null, expirationString = null) {
if (imgUrl && imgUrl.startsWith(enums.misc.FLUXER_ATTACHMENT_URL)) {
return enums.misc.ATTACHMENT_EXPIRATION_WARNING;
}
else if (expirationString) {
let expirationDate = new Date(expirationString);
if (!isNaN(expirationDate.valueOf())) {
return `${enums.misc.ATTACHMENT_EXPIRATION_WARNING}. Expiration date: *${expirationString}*.`;
}
}
return null;
}
module.exports.utils = utils;

View File

@@ -1,8 +1,8 @@
const {messageHelper} = require("./messageHelper.js"); import {messageHelper} from "./messageHelper.js";
const {Webhook, Channel, Message, Client} = require('@fluxerjs/core'); import {Webhook, Channel, Message, Client} from '@fluxerjs/core';
const {enums} = require("../enums.js"); import {enums} from "../enums.js";
const webhookHelper = {}; const wh = {};
const name = 'PluralFlux Proxy Webhook'; const name = 'PluralFlux Proxy Webhook';
@@ -13,9 +13,9 @@ const name = 'PluralFlux Proxy Webhook';
* @param {Message} message - The full message object. * @param {Message} message - The full message object.
* @throws {Error} When the proxy message is not in a server. * @throws {Error} When the proxy message is not in a server.
*/ */
webhookHelper.sendMessageAsMember = async function(client, message) { wh.sendMessageAsMember = async function(client, message) {
const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null; const attachmentUrl = message.attachments.size > 0 ? message.attachments.first().url : null;
const proxyMatch = await messageHelper.parseProxyTags(message.author.id, message.content, attachmentUrl); const proxyMatch = await messageHelper.parseProxyTags(message.author.id, message.content, attachmentUrl).catch(e =>{throw e});
// If the message doesn't match a proxy, just return. // If the message doesn't match a proxy, just return.
if (!proxyMatch || !proxyMatch.member || (proxyMatch.message.length === 0 && !proxyMatch.hasAttachment) ) { if (!proxyMatch || !proxyMatch.member || (proxyMatch.message.length === 0 && !proxyMatch.hasAttachment) ) {
return; return;
@@ -27,7 +27,7 @@ webhookHelper.sendMessageAsMember = async function(client, message) {
if (proxyMatch.hasAttachment) { if (proxyMatch.hasAttachment) {
return await message.reply(`${enums.misc.ATTACHMENT_SENT_BY} ${proxyMatch.member.displayname ?? proxyMatch.member.name}`) return await message.reply(`${enums.misc.ATTACHMENT_SENT_BY} ${proxyMatch.member.displayname ?? proxyMatch.member.name}`)
} }
await webhookHelper.replaceMessage(client, message, proxyMatch.message, proxyMatch.member); await wh.replaceMessage(client, message, proxyMatch.message, proxyMatch.member).catch(e =>{throw e});
} }
/** /**
@@ -39,23 +39,24 @@ webhookHelper.sendMessageAsMember = async function(client, message) {
* @param {model} member - A member object from the database. * @param {model} member - A member object from the database.
* @throws {Error} When there's no message to send. * @throws {Error} When there's no message to send.
*/ */
webhookHelper.replaceMessage = async function(client, message, text, member) { wh.replaceMessage = async function(client, message, text, member) {
// attachment logic is not relevant yet, text length will always be over 0 right now // attachment logic is not relevant yet, text length will always be over 0 right now
if (text.length > 0 || message.attachments.size > 0) { if (text.length > 0 || message.attachments.size > 0) {
const channel = client.channels.get(message.channelId); const channel = client.channels.get(message.channelId);
const webhook = await webhookHelper.getOrCreateWebhook(client, channel); const webhook = await wh.getOrCreateWebhook(client, channel).catch((e) =>{throw e});
const username = member.displayname ?? member.name; const username = member.displayname ?? member.name;
if (text.length <= 2000) { if (text.length > 0) {
await webhook.send({content: text, username: username, avatar_url: member.propic}) await webhook.send({content: text, username: username, avatar_url: member.propic}).catch(async(e) => {
}
else if (text.length > 2000) {
const returnedBuffer = messageHelper.returnBufferFromText(text); const returnedBuffer = messageHelper.returnBufferFromText(text);
await webhook.send({content: returnedBuffer.text, username: username, avatar_url: member.propic, files: [{ name: 'text.txt', data: returnedBuffer.file }] await webhook.send({content: returnedBuffer.text, username: username, avatar_url: member.propic, files: [{ name: 'text.txt', data: returnedBuffer.file }]
}) })
console.error(e);
});
} }
if (message.attachments.size > 0) { if (message.attachments.size > 0) {
// Not implemented yet // Not implemented yet
} }
await message.delete(); await message.delete();
} }
} }
@@ -68,10 +69,10 @@ webhookHelper.replaceMessage = async function(client, message, text, member) {
* @returns {Webhook} A webhook object. * @returns {Webhook} A webhook object.
* @throws {Error} When no webhooks are allowed in the channel. * @throws {Error} When no webhooks are allowed in the channel.
*/ */
webhookHelper.getOrCreateWebhook = async function(client, channel) { wh.getOrCreateWebhook = async function(client, channel) {
// If channel doesn't allow webhooks // If channel doesn't allow webhooks
if (!channel?.createWebhook) throw new Error(enums.err.NO_WEBHOOKS_ALLOWED); if (!channel?.createWebhook) throw new Error(enums.err.NO_WEBHOOKS_ALLOWED);
let webhook = await webhookHelper.getWebhook(client, channel) let webhook = await wh.getWebhook(client, channel).catch((e) =>{throw e});
if (!webhook) { if (!webhook) {
webhook = await channel.createWebhook({name: name}); webhook = await channel.createWebhook({name: name});
} }
@@ -85,12 +86,18 @@ webhookHelper.getOrCreateWebhook = async function(client, channel) {
* @param {Channel} channel - The channel the message was sent in. * @param {Channel} channel - The channel the message was sent in.
* @returns {Webhook} A webhook object. * @returns {Webhook} A webhook object.
*/ */
webhookHelper.getWebhook = async function(client, channel) { wh.getWebhook = async function(client, channel) {
const channelWebhooks = await channel?.fetchWebhooks() ?? []; const channelWebhooks = await channel?.fetchWebhooks() ?? [];
if (channelWebhooks.length === 0) { if (channelWebhooks.length === 0) {
return; return;
} }
return channelWebhooks.find((webhook) => webhook.name === name); let pf_webhook;
channelWebhooks.forEach((webhook) => {
if (webhook.name === name) {
pf_webhook = webhook;
}
})
return pf_webhook;
} }
module.exports.webhookHelper = webhookHelper; export const webhookHelper = wh;

View File

@@ -1,81 +0,0 @@
const Member = require("../../database/entity/Member");
const { AppDataSource } = require("../../database/data-source");
const {ILike} = require("typeorm");
const members = AppDataSource.getRepository(Member.Member)
const memberRepo = {};
/**
* Gets a member based on the author and proxy tag.
*
* @async
* @param {string} authorId - The author of the message.
* @param {string} memberName - The member's name.
* @returns {Promise<Member | null>} The member object or null if not found.
*/
memberRepo.getMemberByName = async function (authorId, memberName) {
return await members.findOne({where: {userid: authorId, name: ILike(memberName)}});
}
/**
* Gets all members belonging to the author.
*
* @async
* @param {string} authorId - The author of the message
* @returns {Promise<Member[]>} The member object array.
*/
memberRepo.getMembersByAuthor = async function (authorId) {
return await members.findBy({userid: authorId});
}
/**
* Removes a member.
*
* @async
* @param {string} authorId - The author of the message
* @param {string} memberName - The name of the member to remove
* @returns {Promise<number>} Number of results removed.
*/
memberRepo.removeMember = async function (authorId, memberName) {
const deleted = await members.delete({
where: {
name: ILike(memberName),
userid: authorId
}
})
return deleted.affected;
}
/**
* Adds a member with full details.
*
* @async
* @param {{name: string, userid: string, displayname: (string|null), proxy: (string|null), propic: (string|null)}} createObj - Object with parameters in it
* @returns {Promise<Member>} A successful inserted object.
*/
memberRepo.createMember = async function (createObj) {
return members.insert({
name: createObj.name, userid: createObj.authorId, displayname: createObj.displayName, proxy: createObj.proxy, propic: createObj.propic
});
}
/**
* Updates one fields for a member in the database.
*
* @async
* @param {string} authorId - The author of the message
* @param {string} memberName - The member to update
* @param {string} columnName - The column name to update.
* @param {string} value - The value to update to.
* @returns {Promise<number>} A successful update.
*/
memberRepo.updateMemberField = async function (authorId, memberName, columnName, value) {
const updated = await members.update({[columnName]: value}, {
where: {
name: ILike(memberName),
userid: authorId
}
})
return updated.affected;
}
module.exports.memberRepo = memberRepo;

View File

@@ -56,15 +56,6 @@ jest.mock("../src/commands.js", () => {
} }
}) })
jest.mock('../database/data-source.ts', () => {
return {
AppDataSource: {
isInitialized: false,
initialize: jest.fn().mockResolvedValue()
}
}
})
const {Client, Events} = require('@fluxerjs/core'); const {Client, Events} = require('@fluxerjs/core');
const {messageHelper} = require("../src/helpers/messageHelper.js"); const {messageHelper} = require("../src/helpers/messageHelper.js");
@@ -74,7 +65,6 @@ const {webhookHelper} = require("../src/helpers/webhookHelper.js");
const {utils} = require("../src/helpers/utils.js"); const {utils} = require("../src/helpers/utils.js");
let {handleMessageCreate, client} = require("../src/bot.js"); let {handleMessageCreate, client} = require("../src/bot.js");
const {login} = require("../src/bot");
describe('bot', () => { describe('bot', () => {
beforeEach(() => { beforeEach(() => {
@@ -84,7 +74,7 @@ describe('bot', () => {
describe('handleMessageCreate', () => { describe('handleMessageCreate', () => {
test('on message creation, if message is from bot, return', async () => { test('on message creation, if message is from bot, return', () => {
// Arrange // Arrange
const message = { const message = {
author: { author: {
@@ -92,11 +82,27 @@ describe('bot', () => {
} }
} }
// Act // Act
const res = await handleMessageCreate(message); return handleMessageCreate(message).then((res) => {
expect(res).toBeUndefined(); expect(res).toBe(undefined);
});
}) })
test("if message doesn't start with bot prefix, call sendMessageAsMember", async () => { test('on message creation, if message is empty, return', () => {
// Arrange
const message = {
content: " ",
author: {
bot: false
}
}
// Act
return handleMessageCreate(message).then((res) => {
// Assert
expect(res).toBe(undefined);
});
})
test("if message doesn't start with bot prefix, call sendMessageAsMember", () => {
// Arrange // Arrange
webhookHelper.sendMessageAsMember.mockResolvedValue(); webhookHelper.sendMessageAsMember.mockResolvedValue();
const message = { const message = {
@@ -106,32 +112,38 @@ describe('bot', () => {
} }
} }
// Act // Act
const res = await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledTimes(1); expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledTimes(1);
expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledWith(client, message) expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledWith(client, message)
});
}) })
test("if sendMessageAsMember returns error, catch and log error", async () => { test("if sendMessageAsMember returns error, log error", () => {
// Arrange // Arrange
webhookHelper.sendMessageAsMember.mockRejectedValue(new Error("error")); webhookHelper.sendMessageAsMember.mockImplementation(() => {
throw Error("error")
});
const message = { const message = {
content: "hello", content: "hello",
author: { author: {
bot: false bot: false
} }
} }
jest.spyOn(global.console, 'error').mockImplementation(() => {}); jest.mock('console', () => {
return {error: jest.fn()}
})
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).catch(() => {
// Assert // Assert
expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledTimes(1); expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledTimes(1);
expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledWith(client, message) expect(webhookHelper.sendMessageAsMember).toHaveBeenCalledWith(client, message)
expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(new Error('error')); expect(console.error).toHaveBeenCalledWith(new Error('error'))
});
}) })
test("if no command after prefix, return correct enum", async () => { test("if no command after prefix, return correct enum", () => {
// Arrange // Arrange
const message = { const message = {
content: "pf;", content: "pf;",
@@ -141,14 +153,15 @@ describe('bot', () => {
reply: jest.fn() reply: jest.fn()
} }
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(enums.help.SHORT_DESC_PLURALFLUX); expect(message.reply).toHaveBeenCalledWith(enums.help.SHORT_DESC_PLURALFLUX);
expect(webhookHelper.sendMessageAsMember).not.toHaveBeenCalled(); expect(webhookHelper.sendMessageAsMember).not.toHaveBeenCalled();
});
}) })
test("if command after prefix, call parseCommandArgs and commandsMap.get", async () => { test("if command after prefix, call parseCommandArgs and commandsMap.get", () => {
// Arrange // Arrange
const message = { const message = {
content: "pf;help", content: "pf;help",
@@ -162,16 +175,17 @@ describe('bot', () => {
} }
commands.commandsMap.get = jest.fn().mockReturnValue(command); commands.commandsMap.get = jest.fn().mockReturnValue(command);
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(messageHelper.parseCommandArgs).toHaveBeenCalledTimes(1); expect(messageHelper.parseCommandArgs).toHaveBeenCalledTimes(1);
expect(messageHelper.parseCommandArgs).toHaveBeenCalledWith('pf;help', 'help'); expect(messageHelper.parseCommandArgs).toHaveBeenCalledWith('pf;help', 'help');
expect(commands.commandsMap.get).toHaveBeenCalledTimes(1); expect(commands.commandsMap.get).toHaveBeenCalledTimes(1);
expect(commands.commandsMap.get).toHaveBeenCalledWith('help'); expect(commands.commandsMap.get).toHaveBeenCalledWith('help');
expect(webhookHelper.sendMessageAsMember).not.toHaveBeenCalled(); expect(webhookHelper.sendMessageAsMember).not.toHaveBeenCalled();
});
}) })
test('if commands.commandsMap.get returns undefined, call aliasesMap.get and commandsMap.get again with that value', async () => { test('if commands.commandsMap.get returns undefined, call aliasesMap.get and commandsMap.get again with that value', () => {
// Arrange // Arrange
const message = { const message = {
content: "pf;m", content: "pf;m",
@@ -186,17 +200,18 @@ describe('bot', () => {
commands.commandsMap.get = jest.fn().mockReturnValueOnce(); commands.commandsMap.get = jest.fn().mockReturnValueOnce();
commands.aliasesMap.get = jest.fn().mockReturnValueOnce(mockAlias); commands.aliasesMap.get = jest.fn().mockReturnValueOnce(mockAlias);
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(commands.commandsMap.get).toHaveBeenCalledTimes(2); expect(commands.commandsMap.get).toHaveBeenCalledTimes(2);
expect(commands.commandsMap.get).toHaveBeenNthCalledWith(1, 'm'); expect(commands.commandsMap.get).toHaveBeenNthCalledWith(1, 'm');
expect(commands.commandsMap.get).toHaveBeenNthCalledWith(2, 'member'); expect(commands.commandsMap.get).toHaveBeenNthCalledWith(2, 'member');
expect(commands.aliasesMap.get).toHaveBeenCalledTimes(1); expect(commands.aliasesMap.get).toHaveBeenCalledTimes(1);
expect(commands.aliasesMap.get).toHaveBeenCalledWith('m'); expect(commands.aliasesMap.get).toHaveBeenCalledWith('m');
});
}) })
test('if aliasesMap.get returns undefined, do not call commandsMap again', async () => { test('if aliasesMap.get returns undefined, do not call commandsMap again', () => {
// Arrange // Arrange
const message = { const message = {
content: "pf;m", content: "pf;m",
@@ -211,13 +226,16 @@ describe('bot', () => {
commands.commandsMap.get = jest.fn().mockReturnValueOnce(); commands.commandsMap.get = jest.fn().mockReturnValueOnce();
commands.aliasesMap.get = jest.fn().mockReturnValueOnce(); commands.aliasesMap.get = jest.fn().mockReturnValueOnce();
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(commands.commandsMap.get).toHaveBeenCalledTimes(1);
expect(commands.commandsMap.get).toHaveBeenNthCalledWith(1, 'm');
expect(commands.aliasesMap.get).toHaveBeenCalledTimes(1); expect(commands.aliasesMap.get).toHaveBeenCalledTimes(1);
expect(commands.aliasesMap.get).toHaveBeenCalledWith('m'); expect(commands.aliasesMap.get).toHaveBeenCalledWith('m');
});
}) })
test("if command exists, call command.execute", async () => { test("if command exists, call command.execute", () => {
// Arrange // Arrange
const message = { const message = {
content: "pf;member test", content: "pf;member test",
@@ -234,7 +252,7 @@ describe('bot', () => {
command.execute = jest.fn().mockResolvedValue(); command.execute = jest.fn().mockResolvedValue();
// Act // Act
await handleMessageCreate(message) return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(command.execute).toHaveBeenCalledTimes(1); expect(command.execute).toHaveBeenCalledTimes(1);
expect(command.execute).toHaveBeenCalledWith(message, ['test']); expect(command.execute).toHaveBeenCalledWith(message, ['test']);
@@ -242,13 +260,15 @@ describe('bot', () => {
}); });
}) })
test("if command.execute returns error, log error", async () => { test("if command.execute returns error, log error", () => {
// Arrange // Arrange
const command = { const command = {
execute: jest.fn() execute: jest.fn()
} }
commands.commandsMap.get = jest.fn().mockReturnValue(command); commands.get = jest.fn().mockReturnValue(command);
command.execute.mockRejectedValue(new Error("error")); command.execute.mockImplementation(() => {
throw Error("error")
});
const message = { const message = {
content: "pf;member test", content: "pf;member test",
author: { author: {
@@ -256,16 +276,18 @@ describe('bot', () => {
}, },
reply: jest.fn() reply: jest.fn()
} }
jest.spyOn(global.console, 'error').mockImplementation(() => { jest.mock('console', () => {
return {error: jest.fn()}
}) })
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).catch(() => {
// Assert // Assert
expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(new Error('error')) expect(console.error).toHaveBeenCalledWith(new Error('error'))
});
}) })
test("if command does not exist, return correct enum", async () => { test("if command does not exist, return correct enum", () => {
// Arrange // Arrange
commands.commandsMap.get = jest.fn().mockReturnValue(); commands.commandsMap.get = jest.fn().mockReturnValue();
commands.aliasesMap.get = jest.fn().mockReturnValue(); commands.aliasesMap.get = jest.fn().mockReturnValue();
@@ -277,17 +299,17 @@ describe('bot', () => {
reply: jest.fn() reply: jest.fn()
} }
// Act // Act
await handleMessageCreate(message); return handleMessageCreate(message).then(() => {
// Assert // Assert
expect(message.reply).toHaveBeenCalledWith(enums.err.COMMAND_NOT_RECOGNIZED); expect(message.reply).toHaveBeenCalledWith(enums.err.COMMAND_NOT_RECOGNIZED);
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
});
})
}) })
test('login calls client.login with correct argument', async () => { test('calls client.login with correct argument', () => {
// Arrange
client.login = jest.fn().mockResolvedValue();
// Act // Act
await login(); client.login = jest.fn().mockResolvedValue();
// Assert // Assert
expect(client.login).toHaveBeenCalledTimes(1); expect(client.login).toHaveBeenCalledTimes(1);
expect(client.login).toHaveBeenCalledWith(process.env.FLUXER_BOT_TOKEN) expect(client.login).toHaveBeenCalledWith(process.env.FLUXER_BOT_TOKEN)

View File

@@ -24,8 +24,11 @@ jest.mock('../src/helpers/importHelper.js', () => {
} }
} }
}) })
jest.mock('console', () => {
return {error: jest.fn()}
})
import {messageHelper} from "../src/helpers/messageHelper.js"; import {messageHelper, prefix} from "../src/helpers/messageHelper.js";
import {memberHelper} from "../src/helpers/memberHelper.js"; import {memberHelper} from "../src/helpers/memberHelper.js";
import {EmbedBuilder} from "@fluxerjs/core"; import {EmbedBuilder} from "@fluxerjs/core";
@@ -37,16 +40,9 @@ describe('commands', () => {
const authorId = '123'; const authorId = '123';
const discriminator = '123'; const discriminator = '123';
const username = 'somePerson' const username = 'somePerson'
const attachmentUrl = 'oya.json'; const attachmentUrl = 'oya.png';
const attachmentExpiration = new Date('2026-01-01').toDateString(); const attachmentExpiration = new Date('2026-01-01').toDateString();
let message; const message = {
const args = ['new']
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
message = {
author: { author: {
username: username, username: username,
id: authorId, id: authorId,
@@ -54,152 +50,129 @@ describe('commands', () => {
}, },
attachments: { attachments: {
size: 1, size: 1,
first: jest.fn().mockImplementation(() => { first: jest.fn().mockImplementation(() => ({
return { expires_at: attachmentExpiration,
url: attachmentUrl, url: attachmentUrl
expires_at: attachmentExpiration }))
}
})
}, },
reply: jest.fn().mockResolvedValue(), reply: jest.fn().mockResolvedValue(),
content: 'pf;import'
} }
const args = ['new']
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
}) })
describe('memberCommand', () => { describe('memberCommand', () => {
test('calls parseMemberCommand with the correct arguments', async () => { test('calls parseMemberCommand with the correct arguments', () => {
// Arrange // Arrange
memberHelper.parseMemberCommand = jest.fn().mockResolvedValue("parsed command"); memberHelper.parseMemberCommand = jest.fn().mockResolvedValue("parsed command");
// Act // Act
await commands.memberCommand(message, args) return commands.memberCommand(message, args).then(() => {
// Assert
expect(memberHelper.parseMemberCommand).toHaveBeenCalledTimes(1); expect(memberHelper.parseMemberCommand).toHaveBeenCalledTimes(1);
expect(memberHelper.parseMemberCommand).toHaveBeenCalledWith(authorId, `${username}#${discriminator}`, args, attachmentUrl, attachmentExpiration); expect(memberHelper.parseMemberCommand).toHaveBeenCalledWith(authorId, `${username}#${discriminator}`, args, attachmentUrl, attachmentExpiration);
}); });
}) })
test('if parseMemberCommand returns error, log error and reply with error', async () => { test('if parseMemberCommand returns error, log error and reply with error', () => {
// Arrange // Arrange
memberHelper.parseMemberCommand = jest.fn().mockRejectedValue(new Error('error')); memberHelper.parseMemberCommand = jest.fn().mockImplementation(() => {throw new Error('error')});
// Act // Act
await commands.memberCommand(message, args) return commands.memberCommand(message, args).catch(() => {
// Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith('error'); expect(message.reply).toHaveBeenCalledWith('error');
expect(console.error).toHaveBeenCalledWith(new Error('error'));
}); });
test('if parseMemberCommand returns embed, reply with embed', async () => {
// Arrange
const embed = new EmbedBuilder();
memberHelper.parseMemberCommand = jest.fn().mockResolvedValue(embed);
// Act
await commands.memberCommand(message, args);
expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith({embeds: [embed]})
}) })
test('if parseMemberCommand returns object, reply with embed and content', async () => { test('if parseMemberCommand returns embed, reply with embed', () => {
// Arrange
const embed = new EmbedBuilder();
memberHelper.parseMemberCommand = jest.fn().mockResolvedValue();
// Act
return commands.memberCommand(message, args).catch(() => {
// Assert
expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith({embeds: [embed]})
});
})
test('if parseMemberCommand returns object, reply with embed and content', () => {
// Arrange // Arrange
const reply = { const reply = {
errors: ['error', 'error2'], errors: ['error', 'error2'],
success: 'success', success: 'success',
embed: {title: 'hi'} embed: {}
} }
const expected = {
content: `success \n\n${enums.err.ERRORS_OCCURRED}\n- error\n- error2`,
embeds: [reply.embed]
}
console.log(expected)
memberHelper.parseMemberCommand = jest.fn().mockResolvedValue(reply); memberHelper.parseMemberCommand = jest.fn().mockResolvedValue(reply);
// Act // Act
await commands.memberCommand(message, args); return commands.memberCommand(message, args).catch(() => {
// Assert // Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(expected) expect(message.reply).toHaveBeenCalledWith({content: `success\n\n${enums.err.ERRORS_OCCURRED}\n\nerror\nerror2}`, embeds: [reply.embed]})
});
})
}) })
describe('importCommand', () => { describe('importCommand', () => {
test('if message includes --help and no attachmentURL, return help message', async () => { test('if message includes --help and no attachmentURL, return help message', () => {
// Arrange
const args = ["--help"]; const args = ["--help"];
message.content = "pf;import --help"; message.content = "pf;import --help";
message.attachments.size = 0; message.attachments.size = 0;
// Act return commands.importCommand(message, args).then(() => {
await commands.importCommand(message, args)
// Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(enums.help.IMPORT); expect(message.reply).toHaveBeenCalledWith(enums.help.IMPORT);
expect(importHelper.pluralKitImport).not.toHaveBeenCalled(); expect(importHelper.pluralKitImport).not.toHaveBeenCalled();
}) })
})
test('if no args and no attachmentURL, return help message', async () => { test('if no args and no attachmentURL, return help message', () => {
// Arrange
const args = [""]; const args = [""];
message.content = 'pf;import' message.content = 'pf;import'
message.attachments.size = 0; message.attachments.size = 0;
// Act return commands.importCommand(message, args).then(() => {
await commands.importCommand(message, args)
// Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(enums.help.IMPORT); expect(message.reply).toHaveBeenCalledWith(enums.help.IMPORT);
expect(importHelper.pluralKitImport).not.toHaveBeenCalled(); expect(importHelper.pluralKitImport).not.toHaveBeenCalled();
}) })
})
test('if attachment URL, call pluralKitImport with correct arguments', async () => { test('if attachment URL, call pluralKitImport with correct arguments', () => {
// Arrange
const args = [""]; const args = [""];
message.content = 'pf;import'; message.content = 'pf;import'
importHelper.pluralKitImport = jest.fn().mockResolvedValue('success'); importHelper.pluralKitImport = jest.fn().mockResolvedValue('success');
// Act return commands.importCommand(message, args).then(() => {
await commands.importCommand(message, args);
// Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith('success'); expect(message.reply).toHaveBeenCalledWith('success');
expect(importHelper.pluralKitImport).toHaveBeenCalledTimes(1); expect(importHelper.pluralKitImport).toHaveBeenCalledTimes(1);
expect(importHelper.pluralKitImport).toHaveBeenCalledWith(authorId, attachmentUrl); expect(importHelper.pluralKitImport).toHaveBeenCalledWith(authorId, attachmentUrl);
}) })
})
test('if pluralKitImport returns aggregate errors with length <= 2000, send errors.', async () => { test('if pluralKitImport returns aggregate errors, send errors.', () => {
// Arrange
const args = [""]; const args = [""];
message.content = 'pf;import' message.content = 'pf;import'
importHelper.pluralKitImport = jest.fn().mockRejectedValue(new AggregateError(['error1', 'error2'], 'errors')); importHelper.pluralKitImport = jest.fn().mockImplementation(() => {throw new AggregateError(['error1', 'error2'], 'errors')});
// Act return commands.importCommand(message, args).catch(() => {
await commands.importCommand(message, args);
// Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(`errors. \n\n${enums.err.ERRORS_OCCURRED}\n\nerror1\nerror2`); expect(message.reply).toHaveBeenCalledWith(`errors. \n\n${enums.err.ERRORS_OCCURRED}\n\nerror1\nerror2`);
}) })
test('if pluralKitImport returns aggregate errors with length > 2000, call returnBufferFromText and message.reply.', async () => {
// Arrange
const args = [""];
const text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbb";
const file = Buffer.from(text, 'utf-8');
const returnedBuffer = {text: 'bbbb', file: file};
const expected = {content: returnedBuffer.text, files: [{name: 'text.txt', data: returnedBuffer.file}]};
importHelper.pluralKitImport = jest.fn().mockRejectedValue(new AggregateError([text, 'error2'], 'errors'));
messageHelper.returnBufferFromText = jest.fn().mockReturnValue(returnedBuffer);
// Act
await commands.importCommand(message, args);
// Assert
expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(expected);
}) })
test('if pluralKitImport returns one error, reply with error and log it', async () => { test('if message.reply throws error, call returnBufferFromText and message.reply again.', () => {
// Arrange // Arrange
importHelper.pluralKitImport = jest.fn().mockRejectedValue(new Error('error')); const args = [""];
jest.spyOn(global.console, 'error').mockImplementation(() => {}) message.content = 'pf;import'
// Act message.reply = jest.fn().mockImplementationOnce(() => {throw e})
await commands.importCommand(message, args); messageHelper.returnBufferFromText = jest.fn().mockResolvedValue({file: 'test.txt', text: 'normal content'});
expect(message.reply).toHaveBeenCalledTimes(1); return commands.importCommand(message, args).catch(() => {
expect(message.reply).toHaveBeenCalledWith('error'); expect(message.reply).toHaveBeenCalledTimes(2);
expect(console.error).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenNthCalledWith(1, {content: 'normal content', files: [{name: 'test.txt', data: 'test.txt' }],});
expect(console.error).toHaveBeenCalledWith(new Error('error')); })
}) })
}) })

View File

@@ -1,4 +1,5 @@
const {enums} = require('../../src/enums.js'); const {enums} = require('../../src/enums.js');
const fetchMock = require('jest-fetch-mock');
jest.mock('../../src/helpers/memberHelper.js', () => { jest.mock('../../src/helpers/memberHelper.js', () => {
return { return {
@@ -8,6 +9,7 @@ jest.mock('../../src/helpers/memberHelper.js', () => {
} }
}) })
fetchMock.enableMocks();
const {memberHelper} = require("../../src/helpers/memberHelper.js"); const {memberHelper} = require("../../src/helpers/memberHelper.js");
const {importHelper} = require('../../src/helpers/importHelper.js'); const {importHelper} = require('../../src/helpers/importHelper.js');
@@ -38,63 +40,58 @@ describe('importHelper', () => {
} }
beforeEach(() => { beforeEach(() => {
jest.resetModules(); global.fetch = jest.fn();
jest.clearAllMocks();
global.fetch = jest.fn().mockResolvedValue({ global.fetch = jest.fn().mockResolvedValue({
ok: true, ok: true,
json: () => Promise.resolve(mockData) json: () => Promise.resolve(mockData)
}) })
}) })
describe('pluralKitImport', () => { describe('pluralKitImport', () => {
test('if no attachment URL, throws error', async () => { test('if no attachment URL, throws error', () => {
await expect(importHelper.pluralKitImport(authorId)).rejects.toThrow(enums.err.NOT_JSON_FILE); return importHelper.pluralKitImport(authorId).catch((e) => {
expect(e).toEqual(new Error(enums.err.NOT_JSON_FILE));
})
}) })
test('if attachment URL, calls fetch and addFullMember and returns value', async () => { test('if attachment URL, calls fetch and addFullMember and returns value', () => {
memberHelper.addFullMember.mockResolvedValue(mockAddReturn); memberHelper.addFullMember.mockResolvedValue(mockAddReturn);
const result = await importHelper.pluralKitImport(authorId, attachmentUrl); return importHelper.pluralKitImport(authorId, attachmentUrl).then((res) => {
expect(fetch).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith(attachmentUrl); expect(fetch).toHaveBeenCalledWith(attachmentUrl);
expect(memberHelper.addFullMember).toHaveBeenCalledWith(authorId, mockImportedMember.name, mockImportedMember.display_name, 'SP{text}', mockImportedMember.avatar_url); expect(memberHelper.addFullMember).toHaveBeenCalledWith(authorId, mockImportedMember.name, mockImportedMember.display_name, 'SP{text}', mockImportedMember.avatar_url);
expect(result).toEqual(`Successfully added members: ${mockAddReturnMember.name}`) expect(res).toEqual(`Successfully added members: ${mockAddReturnMember.name}`)
})
}) })
test('if addFullMember returns nothing, return correct enum', () => {
test('if fetch fails, throws error', async () => {
global.fetch = jest.fn().mockRejectedValue("can't get");
await expect(importHelper.pluralKitImport(authorId, attachmentUrl)).rejects.toThrow(enums.err.CANNOT_FETCH_RESOURCE, "can't get file");
})
test('if json conversion fails, throws error', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.reject("not json")
})
await expect(importHelper.pluralKitImport(authorId, attachmentUrl)).rejects.toThrow(enums.err.NOT_JSON_FILE, "not json");
})
test('if addFullMember returns nothing, return correct enum', async () => {
memberHelper.addFullMember.mockResolvedValue(); memberHelper.addFullMember.mockResolvedValue();
const promise = importHelper.pluralKitImport(authorId, attachmentUrl); return importHelper.pluralKitImport(authorId, attachmentUrl).catch((res) => {
await expect(promise).rejects.toBeInstanceOf(AggregateError); expect(res).toEqual(new AggregateError([], enums.err.NO_MEMBERS_IMPORTED));
await expect(promise).rejects.toMatchObject(AggregateError([], enums.err.NO_MEMBERS_IMPORTED)); })
}) })
test('if addFullMember throws error, catch and return error', async () => { test('if addFullMember returns nothing and throws error, catch and return error', () => {
memberHelper.addFullMember.mockRejectedValue(new Error('error')); memberHelper.addFullMember.mockResolvedValue(new Error('error'));
await expect(importHelper.pluralKitImport(authorId, attachmentUrl)).rejects.toMatchObject(new AggregateError(['error'], enums.err.NO_MEMBERS_IMPORTED)); return importHelper.pluralKitImport(authorId, attachmentUrl).catch((res) => {
}); expect(res).toEqual(new AggregateError([new Error('error')], enums.err.NO_MEMBERS_IMPORTED))
})
})
test('if addFullMember returns member but also contains error, return member and error', async () => { test('if addFullMember returns member but also contains error, return member and error', () => {
// Arrange // Arrange
const memberObj = {errors: ['error'], member: mockAddReturnMember}; const memberObj = {errors: ['error'], member: mockAddReturnMember};
memberHelper.addFullMember.mockResolvedValue(memberObj); memberHelper.addFullMember.mockResolvedValue(memberObj);
await expect(importHelper.pluralKitImport(authorId, attachmentUrl)).rejects.toMatchObject(new AggregateError(['error'], `Successfully added members: ${mockAddReturnMember.name}`)); // Act
}); return importHelper.pluralKitImport(authorId, attachmentUrl).catch((res) => {
}); // Assert
expect(res).toEqual(new AggregateError(['error'], `Successfully added members: ${mockAddReturnMember.name}`))
})
})
})
afterEach(() => { afterEach(() => {
// restore the spy created with spyOn // restore the spy created with spyOn

View File

@@ -2,14 +2,16 @@ const {enums} = require('../../src/enums.js');
const {utils} = require("../../src/helpers/utils.js"); const {utils} = require("../../src/helpers/utils.js");
jest.mock('@fluxerjs/core', () => jest.fn()); jest.mock('@fluxerjs/core', () => jest.fn());
jest.mock('../../src/repositories/memberRepo.js', () => { jest.mock('../../src/database.js', () => {
return { return {
memberRepo: { database: {
getMemberByName: jest.fn().mockResolvedValue(), members: {
getMembersByAuthor: jest.fn().mockResolvedValue(), create: jest.fn().mockResolvedValue(),
removeMember: jest.fn().mockResolvedValue(), update: jest.fn().mockResolvedValue(),
createMember: jest.fn().mockResolvedValue(), destroy: jest.fn().mockResolvedValue(),
updateMemberField: jest.fn().mockResolvedValue(), findOne: jest.fn().mockResolvedValue(),
findAll: jest.fn().mockResolvedValue(),
}
} }
} }
}); });
@@ -23,8 +25,10 @@ jest.mock("../../src/helpers/utils.js", () => {
} }
}); });
const {Op} = require('sequelize');
const {memberHelper} = require("../../src/helpers/memberHelper.js"); const {memberHelper} = require("../../src/helpers/memberHelper.js");
const {memberRepo} = require("../../src/repositories/memberRepo.js"); const {database} = require("../../src/database");
describe('MemberHelper', () => { describe('MemberHelper', () => {
const authorId = "0001"; const authorId = "0001";
@@ -35,7 +39,7 @@ describe('MemberHelper', () => {
name: "somePerson", name: "somePerson",
displayname: "Some Person", displayname: "Some Person",
proxy: "--text", proxy: "--text",
propic: 'ono.png' propic: attachmentUrl
} }
beforeEach(() => { beforeEach(() => {
@@ -59,12 +63,13 @@ describe('MemberHelper', () => {
[[]] [[]]
])('%s calls getMemberCommandInfo and returns expected result', async (args) => { ])('%s calls getMemberCommandInfo and returns expected result', async (args) => {
// Act // Act
const result = await memberHelper.parseMemberCommand(authorId, authorFull, args); return memberHelper.parseMemberCommand(authorId, authorFull, args).then((result) => {
// Assert // Assert
expect(result).toEqual("member command info"); expect(result).toEqual("member command info");
expect(memberHelper.getMemberCommandInfo).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberCommandInfo).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberCommandInfo).toHaveBeenCalledWith(); expect(memberHelper.getMemberCommandInfo).toHaveBeenCalledWith();
}); });
});
test.each([ test.each([
[[mockMember.name, '--help'], null, null, undefined, true, undefined], [[mockMember.name, '--help'], null, null, undefined, true, undefined],
@@ -121,14 +126,15 @@ describe('MemberHelper', () => {
[['propic', mockMember.name, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic], null, null, 'propic', false, mockMember.name], [['propic', mockMember.name, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic], null, null, 'propic', false, mockMember.name],
[['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, null, 'propic', false, mockMember.name], [['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, null, 'propic', false, mockMember.name],
[['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, attachmentExpiration, 'propic', false, mockMember.name] [['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, attachmentExpiration, 'propic', false, mockMember.name]
])('%s args with attachmentURL %s and attachment expiration %s calls memberCommandHandler with correct values', async (args, attachmentUrl, attachmentExpiration, command, isHelp, memberName) => { ])('%s args with attachmentURL %s and attachment expiration %s calls memberCommandHandler with correct values', (args, attachmentUrl, attachmentExpiration, command, isHelp, memberName) => {
console.log(args, command, isHelp) console.log(args, command, isHelp)
// Act // Act
const result = await memberHelper.parseMemberCommand(authorId, authorFull, args, attachmentUrl, attachmentExpiration); return memberHelper.parseMemberCommand(authorId, authorFull, args, attachmentUrl, attachmentExpiration).then((result) => {
// Assert // Assert
expect(result).toEqual("handled argument"); expect(result).toEqual("handled argument");
expect(memberHelper.memberArgumentHandler).toHaveBeenCalledTimes(1); expect(memberHelper.memberArgumentHandler).toHaveBeenCalledTimes(1);
expect(memberHelper.memberArgumentHandler).toHaveBeenCalledWith(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration); expect(memberHelper.memberArgumentHandler).toHaveBeenCalledWith(authorId, authorFull, isHelp, command, memberName, args, attachmentUrl, attachmentExpiration);
});
}) })
}); });
@@ -140,10 +146,12 @@ describe('MemberHelper', () => {
jest.spyOn(memberHelper, 'sendHelpEnum').mockReturnValue("help enum"); jest.spyOn(memberHelper, 'sendHelpEnum').mockReturnValue("help enum");
}) })
test('when all values are null should throw command not recognized enum', async () => { test('when all values are null should return command not recognized enum', () => {
// Arrange // Arrange
await expect(memberHelper.memberArgumentHandler(authorId, authorFull, false, null, null, [])).rejects.toThrow(enums.err.COMMAND_NOT_RECOGNIZED); return memberHelper.memberArgumentHandler(authorId, authorFull, false, null, null, []).catch((result) => {
// Assert
expect(result).toEqual(new Error(enums.err.COMMAND_NOT_RECOGNIZED));
});
}) })
test.each([ test.each([
@@ -153,9 +161,12 @@ describe('MemberHelper', () => {
['displayname'], ['displayname'],
['proxy'], ['proxy'],
['propic'], ['propic'],
])('when %s is present but other values are null, should throw no member enum', async (command) => { ])('when %s is present but other values are null, should return no member enum', (command) => {
// Arrange // Arrange
await expect(memberHelper.memberArgumentHandler(authorId, authorFull, false, command, null, [])).rejects.toThrow(enums.err.NO_MEMBER); return memberHelper.memberArgumentHandler(authorId, authorFull, false, command, null, []).catch((result) => {
// Assert
expect(result).toEqual(new Error(enums.err.NO_MEMBER));
});
}) })
@@ -167,22 +178,24 @@ describe('MemberHelper', () => {
['displayname'], ['displayname'],
['proxy'], ['proxy'],
['propic'], ['propic'],
])('%s calls sendHelpEnum', async (command) => { ])('%s calls sendHelpEnum', (command) => {
// Arrange // Arrange
const result = await memberHelper.memberArgumentHandler(authorId, authorFull, true, command, mockMember.name, []); return memberHelper.memberArgumentHandler(authorId, authorFull, true, command, mockMember.name, []).then((result) => {
// Assert // Assert
expect(result).toEqual("help enum"); expect(result).toEqual("help enum");
expect(memberHelper.sendHelpEnum).toHaveBeenCalledTimes(1); expect(memberHelper.sendHelpEnum).toHaveBeenCalledTimes(1);
expect(memberHelper.sendHelpEnum).toHaveBeenCalledWith(command); expect(memberHelper.sendHelpEnum).toHaveBeenCalledWith(command);
});
}) })
test('list should call getAllMembersInfo', async () => { test('list should call getAllMembersInfo', () => {
// Arrange // Arrange
const result = await memberHelper.memberArgumentHandler(authorId, authorFull, false, 'list', mockMember.name, []); return memberHelper.memberArgumentHandler(authorId, authorFull, false, 'list', mockMember.name, []).then((result) => {
// Assert // Assert
expect(result).toEqual("all member info"); expect(result).toEqual("all member info");
expect(memberHelper.getAllMembersInfo).toHaveBeenCalledTimes(1); expect(memberHelper.getAllMembersInfo).toHaveBeenCalledTimes(1);
expect(memberHelper.getAllMembersInfo).toHaveBeenCalledWith(authorId, authorFull); expect(memberHelper.getAllMembersInfo).toHaveBeenCalledWith(authorId, authorFull);
});
}) })
test.each([ test.each([
@@ -229,16 +242,16 @@ describe('MemberHelper', () => {
[['propic', mockMember.name, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic], null, null, 'propic'], [['propic', mockMember.name, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic], null, null, 'propic'],
[['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, null, 'propic'], [['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, null, 'propic'],
[['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, attachmentExpiration, 'propic'] [['propic', mockMember.name, undefined, mockMember.name, mockMember.displayname, mockMember.proxy, undefined], mockMember.propic, attachmentExpiration, 'propic']
])('%s args with attachmentURL %s and attachment expiration %s calls memberCommandHandler', async (args, attachmentUrl, attachmentExpiration, command) => { ])('%s args with attachmentURL %s and attachment expiration %s calls memberCommandHandler', (args, attachmentUrl, attachmentExpiration, command) => {
// Arrange // Arrange
let values = args.slice(2); let values = args.slice(2);
const result = await memberHelper.memberArgumentHandler(authorId, authorFull, false, command, mockMember.name, args, attachmentUrl, attachmentExpiration); return memberHelper.memberArgumentHandler(authorId, authorFull, false, command, mockMember.name, args, attachmentUrl, attachmentExpiration).then((result) => {
// Assert // Assert
expect(result).toEqual("handled command"); expect(result).toEqual("handled command");
expect(memberHelper.memberCommandHandler).toHaveBeenCalledTimes(1); expect(memberHelper.memberCommandHandler).toHaveBeenCalledTimes(1);
expect(memberHelper.memberCommandHandler).toHaveBeenCalledWith(authorId, command, mockMember.name, values, attachmentUrl, attachmentExpiration); expect(memberHelper.memberCommandHandler).toHaveBeenCalledWith(authorId, command, mockMember.name, values, attachmentUrl, attachmentExpiration);
});
}) })
test.each([ test.each([
@@ -247,14 +260,16 @@ describe('MemberHelper', () => {
['displayname'], ['displayname'],
['proxy'], ['proxy'],
['propic'], ['propic'],
])('%s calls sendCurrentValue', async (command) => { ])('%s calls sendCurrentValue', (command) => {
const result = await memberHelper.memberArgumentHandler(authorId, authorFull, false, command, mockMember.name, []); return memberHelper.memberArgumentHandler(authorId, authorFull, false, command, mockMember.name, []).then((result) => {
// Assert // Assert
expect(result).toEqual("current value"); expect(result).toEqual("current value");
expect(memberHelper.sendCurrentValue).toHaveBeenCalledTimes(1); expect(memberHelper.sendCurrentValue).toHaveBeenCalledTimes(1);
expect(memberHelper.sendCurrentValue).toHaveBeenCalledWith(authorId,mockMember.name, command); expect(memberHelper.sendCurrentValue).toHaveBeenCalledWith(authorId,mockMember.name, command);
});
}) })
}); });
describe('sendCurrentValue', () => { describe('sendCurrentValue', () => {
@@ -264,54 +279,58 @@ describe('MemberHelper', () => {
['displayname', `The display name for ${mockMember.name} is \"${mockMember.displayname}\".`], ['displayname', `The display name for ${mockMember.name} is \"${mockMember.displayname}\".`],
['proxy', `The proxy for ${mockMember.name} is \"${mockMember.proxy}\".`], ['proxy', `The proxy for ${mockMember.name} is \"${mockMember.proxy}\".`],
['propic', `The profile picture for ${mockMember.name} is \"${mockMember.propic}\".`], ['propic', `The profile picture for ${mockMember.name} is \"${mockMember.propic}\".`],
])('%s calls getMemberByName and returns value', async (command, expected) => { ])('%s calls getMemberByName and returns value', (command, expected) => {
// Arrange // Arrange
memberRepo.getMemberByName.mockResolvedValue(mockMember); jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
// Act // Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command); return memberHelper.sendCurrentValue(authorId, mockMember.name, command).then((result) => {
// Assert // Assert
expect(result).toEqual(expected); expect(result).toEqual(expected);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name); expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId,mockMember.name);
});
}) })
test('returns error if no member found', async () => { test('returns error if no member found', () => {
// Arrange // Arrange
memberRepo.getMemberByName.mockResolvedValue(null); jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(null);
// Act // Act
await expect(memberHelper.sendCurrentValue(authorId, mockMember.name, 'name')).rejects.toThrow(enums.err.NO_MEMBER); return memberHelper.sendCurrentValue(authorId, mockMember.name, 'name').catch((result) => {
// Assert // Assert
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1); expect(result).toEqual(new Error(enums.err.NO_MEMBER));
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name); expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId,mockMember.name);
}); });
})
test('calls getMemberInfo with member if no command present', async () => { test('calls getMemberInfo with member if no command present', () => {
// Arrange // Arrange
memberRepo.getMemberByName.mockResolvedValue(mockMember); jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(mockMember);
jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue('member info'); jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue('member info');
// Act // Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, null); return memberHelper.sendCurrentValue(authorId, mockMember.name, null).then((result) => {
// Assert // Assert
expect(result).toEqual('member info'); expect(result).toEqual('member info');
expect(memberHelper.getMemberInfo).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberInfo).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberInfo).toHaveBeenCalledWith(mockMember); expect(memberHelper.getMemberInfo).toHaveBeenCalledWith(mockMember);
});
}) })
test.each([ test.each([
['displayname', `Display name ${enums.err.NO_VALUE}`], ['displayname', `Display name ${enums.err.NO_VALUE}`],
['proxy', `Proxy ${enums.err.NO_VALUE}`], ['proxy', `Proxy ${enums.err.NO_VALUE}`],
['propic', `Propic ${enums.err.NO_VALUE}`], ['propic', `Propic ${enums.err.NO_VALUE}`],
])('returns null message if no value found', async (command, expected) => { ])('returns null message if no member found', (command, expected) => {
// Arrange // Arrange
const empty = {name: mockMember.name, displayname: null, proxy: null, propic: null} const empty = {name: mockMember.name, displayname: null, proxy: null, propic: null}
memberRepo.getMemberByName.mockResolvedValue(empty); jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue(empty);
// Act // Act
const result = await memberHelper.sendCurrentValue(authorId, mockMember.name, command); return memberHelper.sendCurrentValue(authorId, mockMember.name, command).then((result) => {
// Assert // Assert
expect(result).toEqual(expected); expect(result).toEqual(expected);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name); expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId,mockMember.name);
});
}) })
}) })
@@ -322,49 +341,56 @@ describe('MemberHelper', () => {
jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(mockMember); jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(mockMember);
jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue(); jest.spyOn(memberHelper, 'getMemberInfo').mockResolvedValue();
// Act // Act
const result = await memberHelper.addNewMember(authorId, mockMember.name, args, attachmentUrl, attachmentExpiration); return memberHelper.addNewMember(authorId, mockMember.name, args, attachmentUrl, attachmentExpiration).then(() => {
// Assert
expect(memberHelper.addFullMember).toHaveBeenCalledTimes(1); expect(memberHelper.addFullMember).toHaveBeenCalledTimes(1);
expect(memberHelper.addFullMember).toHaveBeenCalledWith(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic, attachmentExpiration); expect(memberHelper.addFullMember).toHaveBeenCalledWith(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic, attachmentExpiration);
}) })
})
test('calls getMemberInfo when successful and returns result', async () => { test('calls getMemberInfo when successful and returns result', async () => {
// Arrange // Arrange
const args = [mockMember.displayname, mockMember.proxy, mockMember.propic]; const args = [mockMember.displayname, mockMember.proxy, mockMember.propic];
const fullMemberResponse = {member: mockMember, errors: []} const fullMemberResponse = {member: mockMember, errors: []}
const expected = { const expected = {embed: mockMember, errors: [], success: `${mockMember.name} has been added successfully.`};
embed: mockMember,
errors: [],
success: `${mockMember.name} has been added successfully.`
};
jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(fullMemberResponse); jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(fullMemberResponse);
jest.spyOn(memberHelper, 'getMemberInfo').mockReturnValue(mockMember); jest.spyOn(memberHelper, 'getMemberInfo').mockReturnValue(mockMember);
//Act //Act
const result = await memberHelper.addNewMember(authorId, mockMember.name, args, attachmentUrl, attachmentExpiration); return memberHelper.addNewMember(authorId, mockMember.name, args, attachmentUrl, attachmentExpiration).then((result) => {
// Assert // Assert
expect(result).toEqual(expected); expect(result).toEqual(expected);
expect(memberHelper.getMemberInfo).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberInfo).toHaveBeenCalledTimes(1);
expect(memberHelper.getMemberInfo).toHaveBeenCalledWith(mockMember); expect(memberHelper.getMemberInfo).toHaveBeenCalledWith(mockMember);
}) })
})
test('throws expected error when getMemberInfo throws error', async () => { test('throws expected error when getMemberInfo throws error', async () => {
// Arrange // Arrange
const args = []; const args = [];
const memberObject = {name: args[1]} const memberObject = {name: args[1]}
jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(memberObject); jest.spyOn(memberHelper, 'addFullMember').mockResolvedValue(memberObject);
jest.spyOn(memberHelper, 'getMemberInfo').mockImplementation(() => {throw new Error('getMemberInfo error')}); jest.spyOn(memberHelper, 'getMemberInfo').mockImplementation(() => {
throw new Error('getMemberInfo error')
});
//Act //Act
await expect(memberHelper.addNewMember(authorId, mockMember.name, args)).rejects.toThrow('getMemberInfo error'); return memberHelper.addNewMember(authorId, mockMember.name, args).catch((result) => {
// Assert
expect(result).toEqual(new Error('getMemberInfo error'));
})
}) })
test('throws expected error when addFullMember throws error', async () => { test('throws expected error when addFullMember throws error', async () => {
// Arrange // Arrange
const args = []; const args = [];
const expected = 'add full member error'; const expected = 'add full member error';
jest.spyOn(memberHelper, 'addFullMember').mockRejectedValue(new Error(expected)); jest.spyOn(memberHelper, 'addFullMember').mockImplementation(() => {
throw new Error(expected)
});
//Act //Act
await expect(memberHelper.addNewMember(authorId, mockMember.name, args)).rejects.toThrow(expected) return memberHelper.addNewMember(authorId, mockMember.name, args).catch((result) => {
// Assert
expect(result).toEqual(new Error(expected));
})
}) })
}) })
@@ -374,55 +400,63 @@ describe('MemberHelper', () => {
// Arrange; // Arrange;
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated"); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
// Act // Act
const result = await memberHelper.updateName(authorId, mockMember.name, " somePerson ") return memberHelper.updateName(authorId, mockMember.name, " somePerson ").then((result) => {
// Assert // Assert
expect(result).toEqual("Updated"); expect(result).toEqual("Updated");
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1); expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "name", "somePerson"); expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "name", "somePerson");
}) })
})
test('throws error when name is blank', async () => { test('throws error when name is blank', async () => {
// Arrange; // Arrange;
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated"); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
// Act & Assert // Act
await expect(memberHelper.updateName(authorId, mockMember.name, " ")).rejects.toThrow('Name ' + enums.err.NO_VALUE); return memberHelper.updateName(authorId, mockMember.name, " ").catch((result) => {
// Assert // Assert
expect(result).toEqual(new RangeError("Name " + enums.err.NO_VALUE));
expect(memberHelper.updateMemberField).not.toHaveBeenCalled(); expect(memberHelper.updateMemberField).not.toHaveBeenCalled();
}) })
}) })
})
describe('updateDisplayName', () => { describe('updateDisplayName', () => {
test('throws error when displayname is blank', async () => { test('throws error when displayname is blank', async () => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue(); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue();
// Act & Assert // Act
await expect(memberHelper.updateDisplayName(authorId, mockMember.name, " ")).rejects.toThrow("Display name " + enums.err.NO_VALUE); return memberHelper.updateDisplayName(authorId, mockMember.name, mockMember.displayname).catch((result) => {
// Assert // Assert
expect(result).toEqual(new Error(`Display name ${enums.err.NO_VALUE}`));
expect(memberHelper.updateMemberField).not.toHaveBeenCalled(); expect(memberHelper.updateMemberField).not.toHaveBeenCalled();
}) })
})
test('Sends error when display name is too long', async () => { test('Sends error when display name is too long', async () => {
// Arrange // Arrange
const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue(); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue();
// Act & Assert // Act
await expect(memberHelper.updateDisplayName(authorId, mockMember.name, tooLongDisplayName)).rejects.toThrow(enums.err.DISPLAY_NAME_TOO_LONG); return memberHelper.updateDisplayName(authorId, mockMember.name, tooLongDisplayName).catch((result) => {
// Assert // Assert
expect(result).toEqual(new RangeError(enums.err.DISPLAY_NAME_TOO_LONG));
expect(memberHelper.updateMemberField).not.toHaveBeenCalled(); expect(memberHelper.updateMemberField).not.toHaveBeenCalled();
}) })
})
test('call updateMemberField with correct arguments when displayname passed in correctly and returns string', async () => { test('call updateMemberField with correct arguments when displayname passed in correctly and returns string', async () => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated"); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
// Act // Act
const result = await memberHelper.updateDisplayName(authorId, mockMember.name, " Some Person "); return memberHelper.updateDisplayName(authorId, mockMember.name, " Some Person ").then((result) => {
// Assert // Assert
expect(result).toEqual("Updated"); expect(result).toEqual("Updated");
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "displayname", mockMember.displayname); expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "displayname", mockMember.displayname);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1); expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
}) })
}) })
})
describe('updateProxy', () => { describe('updateProxy', () => {
test('calls checkIfProxyExists and updateMemberField and returns string', async() => { test('calls checkIfProxyExists and updateMemberField and returns string', async() => {
@@ -430,83 +464,75 @@ describe('MemberHelper', () => {
jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue(); jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue();
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated"); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
// Act // Act
const result = await memberHelper.updateProxy(authorId, mockMember.name, "--text"); return memberHelper.updateProxy(authorId, mockMember.name, "--text").then((result) => {
// Assert
expect(result).toEqual("Updated"); expect(result).toEqual("Updated");
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1); expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1);
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy); expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1); expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "proxy", mockMember.proxy); expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "proxy", mockMember.proxy);
});
}) })
}) })
describe('updatePropic', () => { describe('updatePropic', () => {
test.each([ test.each([
[null, attachmentUrl, undefined, attachmentUrl], [null, attachmentUrl, null, attachmentUrl],
[mockMember.propic, null, undefined, mockMember.propic], [mockMember.propic, null, null, mockMember.propic],
[mockMember.propic, attachmentUrl, undefined, mockMember.propic], [mockMember.propic, attachmentUrl, null, attachmentUrl],
[null, attachmentUrl, attachmentExpiration, attachmentUrl]
])('calls checkImageFormatValidity and updateMemberField and returns string', async(imgUrl, attachmentUrl, attachmentExpiration, expected) => { ])('calls checkImageFormatValidity and updateMemberField and returns string', async(imgUrl, attachmentUrl, attachmentExpiration, expected) => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated"); jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
utils.setExpirationWarning = jest.fn().mockReturnValue(undefined);
// Act // Act
const result = await memberHelper.updatePropic(authorId, mockMember.name, imgUrl, attachmentUrl, attachmentExpiration); return memberHelper.updatePropic(authorId, mockMember.name, imgUrl, attachmentUrl, attachmentExpiration).then((result) => {
// Assert
expect(result).toEqual("Updated"); expect(result).toEqual("Updated");
expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1); expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1);
expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(expected); expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(expected);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1); expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", expected, undefined); expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", expected, attachmentExpiration);
}) });
test('calls setExpirationWarning', async() => {
// Arrange
jest.spyOn(memberHelper, 'updateMemberField').mockResolvedValue("Updated");
utils.setExpirationWarning = jest.fn().mockReturnValue(enums.misc.ATTACHMENT_EXPIRATION_WARNING);
// Act
const result = await memberHelper.updatePropic(authorId, mockMember.name, null, attachmentUrl, attachmentExpiration);
// Assert
expect(result).toEqual("Updated");
expect(utils.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(utils.setExpirationWarning).toHaveBeenCalledWith(attachmentUrl, attachmentExpiration);
expect(memberHelper.updateMemberField).toHaveBeenCalledTimes(1);
expect(memberHelper.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, "propic", attachmentUrl, enums.misc.ATTACHMENT_EXPIRATION_WARNING);
}) })
}) })
describe('addFullMember', () => { describe('addFullMember', () => {
const { database} = require('../../src/database.js');
beforeEach(() => {
jest.spyOn(memberHelper, 'getMemberByName').mockResolvedValue();
})
test('calls getMemberByName', async () => { test('calls getMemberByName', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue();
// Act // Act
await memberHelper.addFullMember(authorId, mockMember.name) return await memberHelper.addFullMember(authorId, mockMember.name).then(() => {
// Assert // Assert
expect(memberRepo.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name); expect(memberHelper.getMemberByName).toHaveBeenCalledWith(authorId, mockMember.name);
expect(memberRepo.getMemberByName).toHaveBeenCalledTimes(1); expect(memberHelper.getMemberByName).toHaveBeenCalledTimes(1);
})
}) })
test('if getMemberByName returns member, throw error', async () => { test('if getMemberByName returns member, throw error', async () => {
// Arrange memberHelper.getMemberByName.mockResolvedValue({name: mockMember.name});
memberRepo.getMemberByName.mockResolvedValue({name: mockMember.name}); // Act
// Act & Assert return await memberHelper.addFullMember(authorId, mockMember.name).catch((e) => {
await expect(memberHelper.addFullMember(authorId, mockMember.name)).rejects.toThrow(`Can't add ${mockMember.name}. ${enums.err.MEMBER_EXISTS}`)
// Assert // Assert
expect(memberRepo.createMember).not.toHaveBeenCalled(); expect(e).toEqual(new Error(`Can't add ${mockMember.name}. ${enums.err.MEMBER_EXISTS}`))
expect(database.members.create).not.toHaveBeenCalled();
})
}) })
test('if name is not filled out, throw error', async () => { test('if name is not filled out, throw error', async () => {
// Arrange
memberRepo.getMemberByName.mockResolvedValue();
// Act // Act
await expect(memberHelper.addFullMember(authorId, " ")).rejects.toThrow(`Name ${enums.err.NO_VALUE}. ${enums.err.NAME_REQUIRED}`); return await memberHelper.addFullMember(authorId, " ").catch((e) => {
// Assert // Assert
expect(memberRepo.createMember).not.toHaveBeenCalled(); expect(e).toEqual(new Error(`Name ${enums.err.NO_VALUE}. ${enums.err.NAME_REQUIRED}`))
expect(database.members.create).not.toHaveBeenCalled();
})
}) })
test('if displayname is over 32 characters, call memberRepo.createMember with null value', async () => { test('if displayname is over 32 characters, call database.member.create with null value', async () => {
// Arrange // Arrange
memberRepo.getMemberByName.mockResolvedValue(); memberHelper.getMemberByName.mockResolvedValue();
const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const tooLongDisplayName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const expectedMemberArgs = { const expectedMemberArgs = {
name: mockMember.name, name: mockMember.name,
@@ -515,46 +541,50 @@ describe('MemberHelper', () => {
proxy: null, proxy: null,
propic: null propic: null
} }
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = { const expectedReturn = {
member: expectedMemberArgs, member: expectedMemberArgs,
errors: [`Tried to set displayname to \"${tooLongDisplayName}\". ${enums.err.DISPLAY_NAME_TOO_LONG}. ${enums.err.SET_TO_NULL}`] errors: [`Tried to set displayname to \"${tooLongDisplayName}\". ${enums.err.DISPLAY_NAME_TOO_LONG}. ${enums.err.SET_TO_NULL}`]
} }
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, tooLongDisplayName, null, null); return await memberHelper.addFullMember(authorId, mockMember.name, tooLongDisplayName, null, null).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
})
}) })
test('if proxy, call checkIfProxyExists', async () => { test('if proxy, call checkIfProxyExists', async () => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue(true); jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue();
const expectedMemberArgs = { const expectedMemberArgs = {
name: mockMember.name, name: mockMember.name,
userid: authorId, userid: authorId,
displayname: null, displayname: null,
proxy: null, proxy: mockMember.proxy,
propic: null propic: null
} }
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {member: expectedMemberArgs, errors: []} const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy) return await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy); expect(memberHelper.checkIfProxyExists).toHaveBeenCalledWith(authorId, mockMember.proxy);
expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1); expect(memberHelper.checkIfProxyExists).toHaveBeenCalledTimes(1);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
})
}) })
test('if checkProxyExists throws error, call database.member.create with null value', async () => { test('if checkProxyExists throws error, call database.member.create with null value', async () => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'checkIfProxyExists').mockRejectedValue(new Error('error')); jest.spyOn(memberHelper, 'checkIfProxyExists').mockImplementation(() => {
throw new Error('error')
});
const expectedMemberArgs = { const expectedMemberArgs = {
name: mockMember.name, name: mockMember.name,
userid: authorId, userid: authorId,
@@ -562,18 +592,19 @@ describe('MemberHelper', () => {
proxy: null, proxy: null,
propic: null propic: null
} }
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = { const expectedReturn = {
member: expectedMemberArgs, member: expectedMemberArgs,
errors: [`Tried to set proxy to \"${mockMember.proxy}\". error. ${enums.err.SET_TO_NULL}`] errors: [`Tried to set proxy to \"${mockMember.proxy}\". error. ${enums.err.SET_TO_NULL}`]
} }
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy, null) return await memberHelper.addFullMember(authorId, mockMember.name, null, mockMember.proxy, null).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
})
}) })
test('if propic, call checkImageFormatValidity', async () => { test('if propic, call checkImageFormatValidity', async () => {
@@ -583,24 +614,24 @@ describe('MemberHelper', () => {
userid: authorId, userid: authorId,
displayname: null, displayname: null,
proxy: null, proxy: null,
propic: null propic: mockMember.propic
} }
utils.setExpirationWarning = jest.fn().mockReturnValue(); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = {member: expectedMemberArgs, errors: []} const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic); return await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(mockMember.propic); expect(utils.checkImageFormatValidity).toHaveBeenCalledWith(mockMember.propic);
expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1); expect(utils.checkImageFormatValidity).toHaveBeenCalledTimes(1);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
})
}) })
test('if checkImageFormatValidity throws error, call database.member.create with null value', async () => { test('if checkImageFormatValidity throws error, call database.member.create with null value', async () => {
// Arrange // Arrange
utils.checkImageFormatValidity = jest.fn().mockRejectedValue(new Error("error")); utils.checkImageFormatValidity = jest.fn().mockImplementation(() => {throw new Error("error")})
const expectedMemberArgs = { const expectedMemberArgs = {
name: mockMember.name, name: mockMember.name,
userid: authorId, userid: authorId,
@@ -608,33 +639,23 @@ describe('MemberHelper', () => {
proxy: null, proxy: null,
propic: null propic: null
} }
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
const expectedReturn = { const expectedReturn = {
member: expectedMemberArgs, member: expectedMemberArgs,
errors: [`Tried to set profile picture to \"${mockMember.propic}\". error. ${enums.err.SET_TO_NULL}`] errors: [`Tried to set profile picture to \"${mockMember.propic}\". error. ${enums.err.SET_TO_NULL}`]
} }
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic); return await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
}) })
test('calls setExpirationWarning if attachmentExpiration exists', async () => {
// Arrange
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true);
utils.setExpirationWarning = jest.fn().mockReturnValue(`${enums.misc.ATTACHMENT_EXPIRATION_WARNING}`);
// Act
await memberHelper.addFullMember(authorId, mockMember.name, null, null, mockMember.propic, attachmentExpiration)
// Assert
expect(utils.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(utils.setExpirationWarning).toHaveBeenCalledWith(mockMember.propic, attachmentExpiration);
}) })
test('if all values are valid, call database.members.create', async () => { test('if all values are valid, call database.members.create', async () => {
// Arrange // Arrange
jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue(false); jest.spyOn(memberHelper, 'checkIfProxyExists').mockResolvedValue();
const expectedMemberArgs = { const expectedMemberArgs = {
name: mockMember.name, name: mockMember.name,
userid: authorId, userid: authorId,
@@ -642,54 +663,72 @@ describe('MemberHelper', () => {
proxy: mockMember.proxy, proxy: mockMember.proxy,
propic: mockMember.propic propic: mockMember.propic
} }
memberRepo.createMember = jest.fn().mockResolvedValue(expectedMemberArgs); database.members.create = jest.fn().mockResolvedValue(expectedMemberArgs);
utils.checkImageFormatValidity = jest.fn().mockResolvedValue(true); utils.checkImageFormatValidity = jest.fn().mockResolvedValue();
utils.setExpirationWarning = jest.fn().mockReturnValue();
const expectedReturn = {member: expectedMemberArgs, errors: []} const expectedReturn = {member: expectedMemberArgs, errors: []}
// Act // Act
const res = await memberHelper.addFullMember(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic); return await memberHelper.addFullMember(authorId, mockMember.name, mockMember.displayname, mockMember.proxy, mockMember.propic).then((res) => {
// Assert // Assert
expect(res).toEqual(expectedReturn); expect(res).toEqual(expectedReturn);
expect(memberRepo.createMember).toHaveBeenCalledWith(expectedMemberArgs); expect(database.members.create).toHaveBeenCalledWith(expectedMemberArgs);
expect(memberRepo.createMember).toHaveBeenCalledTimes(1); expect(database.members.create).toHaveBeenCalledTimes(1);
})
}) })
}) })
describe('updateMemberField', () => { describe('updateMemberField', () => {
const {database} = require('../../src/database.js');
beforeEach(() => { beforeEach(() => {
utils.setExpirationWarning = jest.fn().mockReturnValue(`warning`); jest.spyOn(memberHelper, "setExpirationWarning").mockReturnValue(' warning');
memberRepo.updateMemberField = jest.fn().mockResolvedValue([1]); database.members = {
update: jest.fn().mockResolvedValue([1])
};
})
test('calls setExpirationWarning if attachmentExpiration', async () => {
return memberHelper.updateMemberField(authorId, mockMember.name, "propic", mockMember.propic, attachmentExpiration).then((res) => {
expect(memberHelper.setExpirationWarning).toHaveBeenCalledTimes(1);
expect(memberHelper.setExpirationWarning).toHaveBeenCalledWith(mockMember.propic);
})
}) })
test.each([ test.each([
['name', mockMember.name, undefined, `Updated name for ${mockMember.name} to ${mockMember.name}.`], ['name', mockMember.name, null, `Updated name for ${mockMember.name} to ${mockMember.name}`],
['displayname', mockMember.displayname, undefined, `Updated displayname for ${mockMember.name} to ${mockMember.displayname}.`], ['displayname', mockMember.displayname, null, `Updated name for ${mockMember.name} to ${mockMember.displayname}`],
['proxy', mockMember.proxy, undefined, `Updated proxy for ${mockMember.name} to ${mockMember.proxy}.`], ['proxy', mockMember.proxy, null, `Updated name for ${mockMember.name} to ${mockMember.proxy}`],
['propic', mockMember.propic, undefined, `Updated propic for ${mockMember.name} to ${mockMember.propic}.`], ['propic', mockMember.propic, null, `Updated name for ${mockMember.name} to ${mockMember.propic}`],
['propic', mockMember.propic, ['propic', mockMember.propic, attachmentExpiration, `Updated name for ${mockMember.name} to ${mockMember.propic} warning}`]
'warning', `Updated propic for ${mockMember.name} to ${mockMember.propic}. warning.`] ])('calls database.members.update with correct column and value and return string', async (columnName, value, attachmentExpiration) => {
])('calls database.members.update with correct column and value and return string', async (columnName, value, attachmentExpiration, expected) => { // Arrange
return memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration).then((res) => {
// Act // Act
const res = await memberHelper.updateMemberField(authorId, mockMember.name, columnName, value, attachmentExpiration) expect(database.members.update).toHaveBeenCalledTimes(1);
// Assert expect(database.members.update).toHaveBeenCalledWith({[columnName]: value}, {
expect(res).toEqual(expected); where: {
expect(memberRepo.updateMemberField).toHaveBeenCalledTimes(1); name: {[Op.iLike]: mockMember.name},
expect(memberRepo.updateMemberField).toHaveBeenCalledWith(authorId, mockMember.name, columnName, value) userid: authorId
}
})
})
}) })
test('if database.members.update returns 0 rows changed, throw error', async () => { test('if database.members.update returns 0 rows changed, throw error', () => {
// Arrange // Arrange
memberRepo.updateMemberField = jest.fn().mockResolvedValue(0); database.members = {
update: jest.fn().mockResolvedValue([0])
};
// Act // Act
await expect(memberHelper.updateMemberField(authorId, mockMember.name, "displayname", mockMember.displayname)).rejects.toThrow(`Can't update ${mockMember.name}. ${enums.err.NO_MEMBER}.`); return memberHelper.updateMemberField(authorId, mockMember.name, "displayname", mockMember.displayname).catch((res) => {
expect(res).toEqual(new Error(`Can't update ${mockMember.name}. ${enums.err.NO_MEMBER}.`))
})
}) })
}) })
describe('checkIfProxyExists', () => { describe('checkIfProxyExists', () => {
beforeEach(() => { beforeEach(() => {
memberRepo.getMembersByAuthor.mockResolvedValue([mockMember]); jest.spyOn(memberHelper, "getMembersByAuthor").mockResolvedValue([mockMember]);
}) })
test.each([ test.each([
@@ -703,30 +742,32 @@ describe('MemberHelper', () => {
['SP: text'], ['SP: text'],
['text --SP'], ['text --SP'],
])('%s should call getMembersByAuthor and return false', async (proxy) => { ])('%s should call getMembersByAuthor and return false', async (proxy) => {
// Act return memberHelper.checkIfProxyExists(authorId, proxy).then((res) => {
const res = await memberHelper.checkIfProxyExists(authorId, proxy)
// Assert
expect(res).toEqual(false) expect(res).toEqual(false)
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1); expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId); expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
})
}) })
test.each([ test.each([
['--', enums.err.NO_TEXT_FOR_PROXY, false], ['--', enums.err.NO_TEXT_FOR_PROXY, false],
[' ', enums.err.NO_TEXT_FOR_PROXY, false], [' ', enums.err.NO_TEXT_FOR_PROXY, false],
['text', enums.err.NO_PROXY_WRAPPER, false], ['text', enums.err.NO_PROXY_WRAPPER, false],
])('%s returns correct error and does not call getMemberByAuthor', async (proxy, error, shouldCall) => { ['--text', enums.err.PROXY_EXISTS, true]
// Act & Assert ])('%s returns correct error and calls getMembersByAuthor if appropriate', async (proxy, error, shouldCall) => {
await expect(memberHelper.checkIfProxyExists(authorId, proxy)).rejects.toThrow(error); return memberHelper.checkIfProxyExists(authorId, proxy).catch((res) => {
expect(res).toEqual(new Error(error))
expect(memberRepo.getMembersByAuthor).not.toHaveBeenCalled(); if (shouldCall) {
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberHelper.getMembersByAuthor).toHaveBeenCalledWith(authorId);
}
else {
expect(memberHelper.getMembersByAuthor).not.toHaveBeenCalled();
}
})
}) })
test('--text returns correct error and calls getMemberByAuthor', async () => {
await expect(memberHelper.checkIfProxyExists(authorId, "--text")).rejects.toThrow(enums.err.PROXY_EXISTS);
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledTimes(1);
expect(memberRepo.getMembersByAuthor).toHaveBeenCalledWith(authorId);
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -2,16 +2,14 @@ const env = require('dotenv');
env.config(); env.config();
jest.mock('../../src/repositories/memberRepo.js', () => { jest.mock('../../src/helpers/memberHelper.js', () => {
return { return {memberHelper: {
memberRepo: {
getMembersByAuthor: jest.fn() getMembersByAuthor: jest.fn()
} }}
}
}) })
const {memberHelper} = require("../../src/helpers/memberHelper.js");
const {messageHelper} = require("../../src/helpers/messageHelper.js"); const {messageHelper} = require("../../src/helpers/messageHelper.js");
const {memberRepo} = require("../../src/repositories/memberRepo");
describe('messageHelper', () => { describe('messageHelper', () => {
@@ -22,11 +20,11 @@ describe('messageHelper', () => {
describe('parseCommandArgs', () => { describe('parseCommandArgs', () => {
test.each([ test.each([
['pf;member', ['']], ['pk;member', ['']],
['pf;member add somePerson "Some Person"', ['add', 'somePerson', 'Some Person']], ['pk;member add somePerson "Some Person"', ['add', 'somePerson', 'Some Person']],
['pf;member add \"Some Person\"', ['add', 'Some Person']], ['pk;member add \"Some Person\"', ['add', 'Some Person']],
['pf;member add somePerson \'Some Person\'', ['add', 'somePerson', 'Some Person']], ['pk;member add somePerson \'Some Person\'', ['add', 'somePerson', 'Some Person']],
['pf;member add somePerson \"\'Some\' Person\"', ['add', 'somePerson', 'Some Person']], ['pk;member add somePerson \"\'Some\' Person\"', ['add', 'somePerson', 'Some Person']],
])('%s returns correct arguments', (content, expected) => { ])('%s returns correct arguments', (content, expected) => {
// Arrange // Arrange
const command = "member"; const command = "member";
@@ -54,7 +52,7 @@ describe('messageHelper', () => {
const attachmentUrl = "../oya.png" const attachmentUrl = "../oya.png"
beforeEach(() => { beforeEach(() => {
memberRepo.getMembersByAuthor = jest.fn().mockImplementation((specificAuthorId) => { memberHelper.getMembersByAuthor = jest.fn().mockImplementation((specificAuthorId) => {
if (specificAuthorId === "1") return membersFor1; if (specificAuthorId === "1") return membersFor1;
if (specificAuthorId === "2") return membersFor2; if (specificAuthorId === "2") return membersFor2;
if (specificAuthorId === "3") return membersFor3; if (specificAuthorId === "3") return membersFor3;
@@ -81,9 +79,10 @@ describe('messageHelper', () => {
['3', '--hello', attachmentUrl,{}], ['3', '--hello', attachmentUrl,{}],
])('ID %s with string %s returns correct proxy', async(specificAuthorId, content, attachmentUrl, expected) => { ])('ID %s with string %s returns correct proxy', async(specificAuthorId, content, attachmentUrl, expected) => {
// Act // Act
const res = await messageHelper.parseProxyTags(specificAuthorId, content, attachmentUrl); return messageHelper.parseProxyTags(specificAuthorId, content, attachmentUrl).then((res) => {
// Assert // Assert
expect(res).toEqual(expected); expect(res).toEqual(expected);
})
}); });
}) })

View File

@@ -1,86 +1,15 @@
const {enums} = require("../../src/enums"); const {enums} = require("../../src/enums");
const fetchMock = require('jest-fetch-mock');
fetchMock.enableMocks();
const {utils} = require("../../src/helpers/utils.js"); const {utils} = require("../../src/helpers/utils.js");
describe('utils', () => { describe('utils', () => {
const attachmentUrl = 'oya.png';
const expirationString = new Date("2026-01-01").toDateString();
let blob;
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
jest.clearAllMocks(); jest.clearAllMocks();
blob = new Blob([JSON.stringify({attachmentUrl: attachmentUrl})], {type: 'image/png'});
global.fetch = jest.fn(() =>
Promise.resolve({
blob: () => Promise.resolve(blob),
})
);
})
describe('checkImageFormatValidity', () => {
test('calls fetch with imageUrl and returns true if no errors', async() => {
// Act
const res = await utils.checkImageFormatValidity(attachmentUrl);
// Assert
expect(res).toBe(true);
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith(attachmentUrl);
})
test('throws error if fetch returns error', async() => {
// Arrange
global.fetch = jest.fn().mockRejectedValue(new Error('error'));
// Act & Assert
await expect(utils.checkImageFormatValidity(attachmentUrl)).rejects.toThrow(`${enums.err.PROPIC_CANNOT_LOAD}: error`);
})
test('throws error if blob returns error', async() => {
// Arrange
global.fetch = jest.fn(() =>
Promise.resolve({
blob: () => Promise.reject(new Error('error'))
}))
// Act & Assert
await expect(utils.checkImageFormatValidity(attachmentUrl)).rejects.toThrow('error');
})
test('throws error if blob in wrong format', async() => {
// Arrange
blob = new Blob([JSON.stringify({attachmentUrl})], {type: 'text/html'});
global.fetch = jest.fn(() =>
Promise.resolve({
blob: () => Promise.resolve(blob),
})
);
// Act & Assert
await expect(utils.checkImageFormatValidity(attachmentUrl)).rejects.toThrow(enums.err.PROPIC_FAILS_REQUIREMENTS);
})
})
describe('setExpirationWarning', () => {
test('sets warning if image Url starts with Fluxer host', () => {
// Act
const result = utils.setExpirationWarning(`${enums.misc.FLUXER_ATTACHMENT_URL}${attachmentUrl}`);
// Assert
expect(result).toEqual(enums.misc.ATTACHMENT_EXPIRATION_WARNING);
})
test('sets warning if expiration string exists', () => {
const result = utils.setExpirationWarning(null, expirationString);
// Assert
expect(result).toEqual(`${enums.misc.ATTACHMENT_EXPIRATION_WARNING}. Expiration date: *${expirationString}*.`);
})
test('returns null if img url does not start iwth fluxer host and no expiration', () => {
// Act
const result = utils.setExpirationWarning(attachmentUrl);
// Assert
expect(result).toBeNull();
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -14,44 +14,10 @@ const {webhookHelper} = require("../../src/helpers/webhookHelper.js");
const {enums} = require("../../src/enums"); const {enums} = require("../../src/enums");
describe('webhookHelper', () => { describe('webhookHelper', () => {
const channelId = '123';
const authorId = '456';
const guildId = '789';
const text = "hello";
let client, member, attachments, message, webhook;
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
jest.clearAllMocks(); jest.clearAllMocks();
client = {
channels: {
get: jest.fn().mockReturnValue(channelId)
}
}
member = {proxy: "--text", name: 'somePerson', displayname: "Some Person", propic: 'oya.png'};
attachments = {
size: 1,
first: () => {return channelId;}
};
message = {
client,
channelId: channelId,
content: text,
attachments: attachments,
author: {
id: authorId
},
guild: {
guildId: guildId
},
reply: jest.fn().mockResolvedValue(),
delete: jest.fn().mockResolvedValue()
}
webhook = {
send: async() => jest.fn().mockResolvedValue()
}
}) })
describe(`sendMessageAsMember`, () => { describe(`sendMessageAsMember`, () => {
@@ -68,7 +34,9 @@ describe('webhookHelper', () => {
author: { author: {
id: '123' id: '123'
}, },
guildId: '123', guild: {
guildId: '123'
},
reply: jest.fn() reply: jest.fn()
} }
const member = {proxy: "--text", name: 'somePerson', displayname: "Some Person"}; const member = {proxy: "--text", name: 'somePerson', displayname: "Some Person"};
@@ -82,25 +50,26 @@ describe('webhookHelper', () => {
// Arrange // Arrange
messageHelper.parseProxyTags.mockResolvedValue({}); messageHelper.parseProxyTags.mockResolvedValue({});
// Act // Act
const res = await webhookHelper.sendMessageAsMember(client, message) return webhookHelper.sendMessageAsMember(client, message).then((res) => {
// Assert
expect(res).toBeUndefined(); expect(res).toBeUndefined();
expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1); expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1);
expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, null); expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, null);
expect(webhookHelper.replaceMessage).not.toHaveBeenCalled(); expect(webhookHelper.replaceMessage).not.toHaveBeenCalled();
}) })
})
test('calls parseProxyTags and returns if proxyMatch is undefined', async() => { test('calls parseProxyTags and returns if proxyMatch is undefined', async() => {
// Arrange // Arrange
messageHelper.parseProxyTags.mockResolvedValue(undefined); messageHelper.parseProxyTags.mockResolvedValue(undefined);
// Act // Act
const res = await webhookHelper.sendMessageAsMember(client, message) return webhookHelper.sendMessageAsMember(client, message).then((res) => {
// Assert // Assert
expect(res).toBeUndefined(); expect(res).toBeUndefined();
expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1); expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1);
expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, null); expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, null);
expect(webhookHelper.replaceMessage).not.toHaveBeenCalled(); expect(webhookHelper.replaceMessage).not.toHaveBeenCalled();
}) })
})
test('calls parseProxyTags with attachmentUrl', async() => { test('calls parseProxyTags with attachmentUrl', async() => {
// Arrange // Arrange
@@ -110,23 +79,28 @@ describe('webhookHelper', () => {
return {url: 'oya.png'} return {url: 'oya.png'}
} }
} }
// message.attachments.set('attachment', {url: 'oya.png'})
// message.attachments.set('first', () => {return {url: 'oya.png'}})
messageHelper.parseProxyTags.mockResolvedValue(undefined); messageHelper.parseProxyTags.mockResolvedValue(undefined);
// Act // Act
const res = await webhookHelper.sendMessageAsMember(client, message) return webhookHelper.sendMessageAsMember(client, message).then((res) => {
// Assert // Assert
expect(res).toBeUndefined(); expect(res).toBeUndefined();
expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1); expect(messageHelper.parseProxyTags).toHaveBeenCalledTimes(1);
expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, 'oya.png'); expect(messageHelper.parseProxyTags).toHaveBeenCalledWith(message.author.id, content, 'oya.png');
}) })
})
test('if message matches member proxy but is not sent from a guild, throw an error', async() => { test('if message matches member proxy but is not sent from a guild, throw an error', async() => {
// Arrange // Arrange
message.guildId = null;
messageHelper.parseProxyTags.mockResolvedValue(proxyMessage); messageHelper.parseProxyTags.mockResolvedValue(proxyMessage);
// Act and Assert // Act
await expect(webhookHelper.sendMessageAsMember(client, message)).rejects.toThrow(enums.err.NOT_IN_SERVER); return webhookHelper.sendMessageAsMember(client, message).catch((res) => {
// Assert
expect(res).toEqual(new Error(enums.err.NOT_IN_SERVER));
expect(webhookHelper.replaceMessage).not.toHaveBeenCalled(); expect(webhookHelper.replaceMessage).not.toHaveBeenCalled();
}) })
})
test('if message matches member proxy and sent in a guild and has an attachment, reply to message with ping', async() => { test('if message matches member proxy and sent in a guild and has an attachment, reply to message with ping', async() => {
// Arrange // Arrange
@@ -135,12 +109,13 @@ describe('webhookHelper', () => {
messageHelper.parseProxyTags.mockResolvedValue(proxyMessage); messageHelper.parseProxyTags.mockResolvedValue(proxyMessage);
const expected = `${enums.misc.ATTACHMENT_SENT_BY} ${proxyMessage.member.displayname}` const expected = `${enums.misc.ATTACHMENT_SENT_BY} ${proxyMessage.member.displayname}`
// Act // Act
await webhookHelper.sendMessageAsMember(client, message) return webhookHelper.sendMessageAsMember(client, message).then((res) => {
// Assert // Assert
expect(message.reply).toHaveBeenCalledTimes(1); expect(message.reply).toHaveBeenCalledTimes(1);
expect(message.reply).toHaveBeenCalledWith(expected); expect(message.reply).toHaveBeenCalledWith(expected);
expect(webhookHelper.replaceMessage).not.toHaveBeenCalled(); expect(webhookHelper.replaceMessage).not.toHaveBeenCalled();
}) })
})
test('if message matches member proxy and sent in a guild channel and no attachment, calls replace message', async() => { test('if message matches member proxy and sent in a guild channel and no attachment, calls replace message', async() => {
// Arrange // Arrange
@@ -149,26 +124,63 @@ describe('webhookHelper', () => {
messageHelper.parseProxyTags.mockResolvedValue(proxyMessage); messageHelper.parseProxyTags.mockResolvedValue(proxyMessage);
jest.spyOn(webhookHelper, 'replaceMessage').mockResolvedValue(); jest.spyOn(webhookHelper, 'replaceMessage').mockResolvedValue();
// Act // Act
await webhookHelper.sendMessageAsMember(client, message); return webhookHelper.sendMessageAsMember(client, message).then((res) => {
// Assert // Assert
expect(message.reply).not.toHaveBeenCalled(); expect(message.reply).not.toHaveBeenCalled();
expect(webhookHelper.replaceMessage).toHaveBeenCalledTimes(1); expect(webhookHelper.replaceMessage).toHaveBeenCalledTimes(1);
expect(webhookHelper.replaceMessage).toHaveBeenCalledWith(client, message, proxyMessage.message, proxyMessage.member); expect(webhookHelper.replaceMessage).toHaveBeenCalledWith(client, message, proxyMessage.message, proxyMessage.member);
}) })
})
test('if replace message throws error, throw same error and does not call message.reply', async () => { test('if replace message throws error, throw same error', async() => {
// Arrange // Arrange
message.guildId = '123'; message.guildId = '123';
messageHelper.parseProxyTags.mockResolvedValue(proxyMessage); messageHelper.parseProxyTags.mockResolvedValue(proxyMessage);
jest.spyOn(webhookHelper, 'replaceMessage').mockRejectedValue(new Error("error")); jest.spyOn(webhookHelper, 'replaceMessage').mockImplementation(() => {throw new Error("error")});
// Act // Act
await expect(webhookHelper.sendMessageAsMember(client, message)).rejects.toThrow("error"); return webhookHelper.sendMessageAsMember(client, message).catch((res) => {
// Assert // Assert
expect(message.reply).not.toHaveBeenCalled(); expect(message.reply).not.toHaveBeenCalled();
expect(webhookHelper.replaceMessage).toHaveBeenCalledTimes(1);
expect(webhookHelper.replaceMessage).toHaveBeenCalledWith(client, message, proxyMessage.message, proxyMessage.member);
expect(res).toEqual(new Error('error'));
})
}) })
}) })
describe(`replaceMessage`, () => { describe(`replaceMessage`, () => {
const channelId = '123';
const authorId = '456';
const guildId = '789';
const text = "hello";
const client = {
channels: {
get: jest.fn().mockReturnValue(channelId)
}
}
const member = {proxy: "--text", name: 'somePerson', displayname: "Some Person", propic: 'oya.png'};
const attachments= {
size: 1,
first: () => {return channelId;}
};
const message = {
client,
channelId: channelId,
content: text,
attachments: attachments,
author: {
id: authorId
},
guild: {
guildId: guildId
},
reply: jest.fn(),
delete: jest.fn()
}
const webhook = {
send: async() => jest.fn().mockResolvedValue()
}
test('does not call anything if text is 0 or message has no attachments', async() => { test('does not call anything if text is 0 or message has no attachments', async() => {
// Arrange // Arrange
@@ -180,12 +192,13 @@ describe('webhookHelper', () => {
message.attachments = noAttachments; message.attachments = noAttachments;
jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook); jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook);
// Act // Act
await webhookHelper.replaceMessage(client, message, emptyText, member) return webhookHelper.replaceMessage(client, message, emptyText, member).then(() => {
expect(webhookHelper.getOrCreateWebhook).not.toHaveBeenCalled(); expect(webhookHelper.getOrCreateWebhook).not.toHaveBeenCalled();
expect(message.delete).not.toHaveBeenCalled(); expect(message.delete).not.toHaveBeenCalled();
}) })
})
test('calls getOrCreateWebhook and message.delete with correct arguments if text > 0 & < 2000', async() => { test('calls getOrCreateWebhook and message.delete with correct arguments if text >= 0', async() => {
// Arrange // Arrange
message.attachments = { message.attachments = {
size: 0, size: 0,
@@ -194,108 +207,58 @@ describe('webhookHelper', () => {
}; };
jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook); jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook);
// Act // Act
await webhookHelper.replaceMessage(client, message, text, member); return webhookHelper.replaceMessage(client, message, text, member).then((res) => {
// Assert // Assert
expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledTimes(1); expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledTimes(1);
expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledWith(client, channelId); expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledWith(client, channelId);
expect(message.delete).toHaveBeenCalledTimes(1); expect(message.delete).toHaveBeenCalledTimes(1);
expect(message.delete).toHaveBeenCalledWith(); expect(message.delete).toHaveBeenCalledWith();
}) })
})
// TODO: Flaky for some reason. Skipping until attachments are implemented // TODO: flaky for some reason
test.skip('calls getOrCreateWebhook and message.delete with correct arguments if attachments exist', async() => { test('calls getOrCreateWebhook and message.delete with correct arguments if attachments exist', async() => {
// Arrange // Arrange
const emptyText = '' const emptyText = ''
jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook); jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook);
// Act // Act
await webhookHelper.replaceMessage(client, message, emptyText, member); return webhookHelper.replaceMessage(client, message, emptyText, member).then((res) => {
// Assert // Assert
// expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledTimes(1); expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledTimes(1);
// expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledWith(client, channelId); expect(webhookHelper.getOrCreateWebhook).toHaveBeenCalledWith(client, channelId);
expect(message.delete).toHaveBeenCalledTimes(1); expect(message.delete).toHaveBeenCalledTimes(1);
expect(message.delete).toHaveBeenCalledWith(); expect(message.delete).toHaveBeenCalledWith();
}) })
})
test('calls returnBufferFromText if text is more than 2000 characters', async() => { test('calls returnBufferFromText and console error if webhook.send returns error', async() => {
// Arrange // Arrange
const text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb";
message.content = text;
const file = Buffer.from(text, 'utf-8'); const file = Buffer.from(text, 'utf-8');
const returnedBuffer = {text: 'bbbb', file: file}; const returnedBuffer = {text: text, file: file};
const expected = {content: returnedBuffer.text, username: member.displayname, avatar_url: member.propic, files: [{name: 'text.txt', data: returnedBuffer.file}]}; const expected2ndSend = {content: returnedBuffer.text, username: member.displayname, avatar_url: member.propic, files: [{name: 'text.txt', data: returnedBuffer.file}]};
jest.mock('console', () => ({error: jest.fn()}));
jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook); jest.spyOn(webhookHelper, 'getOrCreateWebhook').mockResolvedValue(webhook);
webhook.send = jest.fn(); webhook.send = jest.fn().mockImplementationOnce(async() => {throw new Error('error')});
messageHelper.returnBufferFromText = jest.fn().mockReturnValue(returnedBuffer); messageHelper.returnBufferFromText = jest.fn().mockResolvedValue(returnedBuffer);
// Act // Act
await webhookHelper.replaceMessage(client, message, text, member); return webhookHelper.replaceMessage(client, message, text, member).catch((res) => {
// Assert // Assert
expect(messageHelper.returnBufferFromText).toHaveBeenCalledTimes(1); expect(messageHelper.returnBufferFromText).toHaveBeenCalledTimes(1);
expect(messageHelper.returnBufferFromText).toHaveBeenCalledWith(text); expect(messageHelper.returnBufferFromText).toHaveBeenCalledWith(text);
expect(webhook.send).toHaveBeenCalledTimes(1); expect(webhook.send).toHaveBeenCalledTimes(2);
expect(webhook.send).toHaveBeenCalledWith(expected); expect(webhook.send).toHaveBeenNthCalledWith(2, expected2ndSend);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(new Error('error'));
})
}) })
}) })
describe(`getOrCreateWebhook`, () => { describe(`getOrCreateWebhook`, () => {
let channel;
beforeEach(async () => {
channel = {
createWebhook: jest.fn().mockResolvedValue()
}
jest.spyOn(webhookHelper, 'getWebhook').mockResolvedValue(webhook);
})
test('throws error if channel does not allow webhooks', async() => {
channel.createWebhook = false;
await expect(webhookHelper.getOrCreateWebhook(client, channel)).rejects.toThrow(enums.err.NO_WEBHOOKS_ALLOWED);
})
test('calls getWebhook if channel allows webhooks and returns webhook', async() => {
const res = await webhookHelper.getOrCreateWebhook(client, channel);
expect(webhookHelper.getWebhook).toHaveBeenCalledTimes(1);
expect(webhookHelper.getWebhook).toHaveBeenCalledWith(client, channel);
expect(res).toEqual(webhook);
})
test("calls createWebhook if getWebhook doesn't return webhook", async() => {
jest.spyOn(webhookHelper, 'getWebhook').mockResolvedValue();
await webhookHelper.getOrCreateWebhook(client, channel);
expect(channel.createWebhook).toHaveBeenCalledTimes(1);
expect(channel.createWebhook).toHaveBeenCalledWith({name: 'PluralFlux Proxy Webhook'});
})
}) })
describe(`getWebhook`, () => { describe(`getWebhook`, () => {
let webhook1, webhook2, channel;
beforeEach(() => {
webhook1 = {name: 'PluralFlux Proxy Webhook'};
webhook2 = {name: 'other webhook'};
channel = {
fetchWebhooks: jest.fn().mockResolvedValue([webhook1, webhook2])
}
})
test('calls fetchWebhooks and returns correct webhook', async() => {
// Act
const res = await webhookHelper.getWebhook(client, channel);
// Assert
expect(res).toEqual(webhook1);
expect(channel.fetchWebhooks).toHaveBeenCalledTimes(1);
expect(channel.fetchWebhooks).toHaveBeenCalledWith();
})
test('if fetchWebhooks returns no webhooks, return', async() => {
// Arrange
channel.fetchWebhooks = jest.fn().mockResolvedValue([]);
// Act
const res = await webhookHelper.getWebhook(client, channel);
// Assert
expect(res).toBeUndefined();
})
}) })

View File

@@ -1,16 +0,0 @@
{
"compilerOptions": {
"lib": [
"es2021"
],
"target": "es2021",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./database/build",
"rootDir": "./database",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}