17 Commits

Author SHA1 Message Date
4bb267f990 merge upstream
All checks were successful
Build Dev instance / build (push) Successful in 1m15s
2026-03-10 15:38:47 +11:00
2b31cc2ae9 perf: Merge develop into main (#36)
* Update dockerfile for standalone deployment (#23)

* Add files via upload

* Update Docker image for pluralflux service

* removing unnecessary network and container name definitions

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* Converting ES6 back to CJS (#25)

converting back to cjs

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* Fix: Further converting ES6 to CJS - Making exports named instead of default (#26)

* adding to git ignore

* making imports named not default to not break all my tests

* adjusted setup for memberhelper test

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* feat: add db migrations with typeORM (#28)

* adding typescript packages for typeORM

* add typeORM initial files

* updating package scripts

* updating compose.yaml to have an exposed port for the postgres

* modifying setup for typeORM

* update database stuff and and package.json to help generate migrations

* made models and migrations in typeORM

* delete unneeded database.js

* made database pattern ignored by jest

* remove sequelize

* separate member repo from member helper

* not sure why i made everything numbers in the model but it's fixed now

* edited package.json script

* remove unused index.ts

* adjusted files to reference repository correctly and appdatasource

* made appdatasource export as named

* removed start-db script

* added init to appdatasource in bot.js

* migrations finally!

* new migration matching model names I want

* updating tests

* removing testpathignore patterns since it seems to be unecessary?

* adjusting migrations to match current schema

* removed reference to secrets file

* delete old migration

* Revert "delete old migration"

This reverts commit db1efa39a7a80d8976878856250ccaac6a753ab2.

* Revert "adjusting migrations to match current schema"

This reverts commit ef89a83f6a2ef0643d6ace0a3fcf9c40f4bc6dd6.

* just deleted system creation since it's got nothing in it anyway

* renamed memberRepository to memberRepo for consistency

* added await back to parseMemberCommand call to memberArgumentHandler

* changed call to memberHelper.getMembersByAuthor to memberRepo

* renamed repo updateMemberValue to updateMemberField

* removed throw references in repo docstrings

* remove unneeded subscriber directory ref

* changed createdAt and updatedAt columns to be auto-generated

made member table have timezone

* changed casing of isInitialized in mock for bot.js

* removed % from ILike query so that it doesn't match substrings/wildcard

* renamed some stray updateMemberValue in mocks -> updateMemberField

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* feat: Add migration to migrate existing data to new member table (#29)

added migration to fill new Member table with data from Members

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* fix: update message helper reference hotfix (#30)

* forgot to update a reference in messageHelper to memberRepo instead of memberHelper

* turned off data-source logging

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* fix: update dockerfile to run npm start (#31)

fix: update dockerfile to run npm start (which runs ts-node) instead of node

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* added .env with examples, updated data-source to be access a docker container instead of relying on loopback

* i forgot to git add data-source.ts 🤦

* fix: changed property reference for createMember in repo (#32)

fix for createMember object references (was referencing non-existent properties)

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* fix: memberRepo methods syntax (#35)

* rearranged update member field and remove member to match expected structure in typeORM

* update docstring

* change insert to save in memberRepo

* added command in package.json

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* Delete duplicate members migration (#33)

* add migration to delete duplicates that currently exist in the db

* added a name attribute for consistency

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* Add unique index migration (#34)

* add migration to delete duplicates that currently exist in the db

* change model and migration to add a unique index constraint to id and name

* renamed unique index name to be readable

* redid model and migration to use @Unique instead of @Index

* remove //Here comment

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>

* why wont these workflows stay up gahdamn

---------

Co-authored-by: Laika Bozhko <63646916+LaikaBzko@users.noreply.github.com>
Co-authored-by: Aster Fialla <asterfialla@gmail.com>
Co-authored-by: laika <laika@sanya.gay>
2026-03-09 09:00:15 -04:00
b602e654ec why wont these workflows stay up gahdamn
All checks were successful
Build Dev instance / build (push) Successful in 1m40s
2026-03-06 17:15:51 -05:00
732ad36bba Add unique index migration (#34)
* add migration to delete duplicates that currently exist in the db

* change model and migration to add a unique index constraint to id and name

* renamed unique index name to be readable

* redid model and migration to use @Unique instead of @Index

* remove //Here comment

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-06 16:52:26 -05:00
8446559bfb Delete duplicate members migration (#33)
* add migration to delete duplicates that currently exist in the db

* added a name attribute for consistency

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-06 16:43:44 -05:00
14ef1581c1 fix: memberRepo methods syntax (#35)
* rearranged update member field and remove member to match expected structure in typeORM

* update docstring

* change insert to save in memberRepo

* added command in package.json

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-06 16:39:23 -05:00
10eab6de74 fix: changed property reference for createMember in repo (#32)
fix for createMember object references (was referencing non-existent properties)

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-05 13:16:29 -05:00
d8682c2a1b i forgot to git add data-source.ts 🤦 2026-03-05 17:25:39 +11:00
20e8564c15 added .env with examples, updated data-source to be access a docker container instead of relying on loopback 2026-03-05 17:22:22 +11:00
78cda7d3c4 fix: update dockerfile to run npm start (#31)
fix: update dockerfile to run npm start (which runs ts-node) instead of node

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-03 07:43:25 -05:00
21587dcb7a fix: update message helper reference hotfix (#30)
* forgot to update a reference in messageHelper to memberRepo instead of memberHelper

* turned off data-source logging

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-02 08:50:41 -05:00
aeb7d88ee9 feat: Add migration to migrate existing data to new member table (#29)
added migration to fill new Member table with data from Members

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-01 22:10:03 -05:00
8fe53563d0 feat: add db migrations with typeORM (#28)
* adding typescript packages for typeORM

* add typeORM initial files

* updating package scripts

* updating compose.yaml to have an exposed port for the postgres

* modifying setup for typeORM

* update database stuff and and package.json to help generate migrations

* made models and migrations in typeORM

* delete unneeded database.js

* made database pattern ignored by jest

* remove sequelize

* separate member repo from member helper

* not sure why i made everything numbers in the model but it's fixed now

* edited package.json script

* remove unused index.ts

* adjusted files to reference repository correctly and appdatasource

* made appdatasource export as named

* removed start-db script

* added init to appdatasource in bot.js

* migrations finally!

* new migration matching model names I want

* updating tests

* removing testpathignore patterns since it seems to be unecessary?

* adjusting migrations to match current schema

* removed reference to secrets file

* delete old migration

* Revert "delete old migration"

This reverts commit db1efa39a7a80d8976878856250ccaac6a753ab2.

* Revert "adjusting migrations to match current schema"

This reverts commit ef89a83f6a2ef0643d6ace0a3fcf9c40f4bc6dd6.

* just deleted system creation since it's got nothing in it anyway

* renamed memberRepository to memberRepo for consistency

* added await back to parseMemberCommand call to memberArgumentHandler

* changed call to memberHelper.getMembersByAuthor to memberRepo

* renamed repo updateMemberValue to updateMemberField

* removed throw references in repo docstrings

* remove unneeded subscriber directory ref

* changed createdAt and updatedAt columns to be auto-generated

made member table have timezone

* changed casing of isInitialized in mock for bot.js

* removed % from ILike query so that it doesn't match substrings/wildcard

* renamed some stray updateMemberValue in mocks -> updateMemberField

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-03-01 21:25:49 -05:00
d14e89e8b2 Fix: Further converting ES6 to CJS - Making exports named instead of default (#26)
* adding to git ignore

* making imports named not default to not break all my tests

* adjusted setup for memberhelper test

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-02-28 15:28:27 -05:00
39a7115803 Converting ES6 back to CJS (#25)
converting back to cjs

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-02-28 14:39:32 -05:00
Laika Bozhko
72b70f5175 Update dockerfile for standalone deployment (#23)
* Add files via upload

* Update Docker image for pluralflux service

* removing unnecessary network and container name definitions

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-02-28 14:37:59 -05:00
df80eca0ec refactor: Removing then/catch from async/await calls (#22)
* refactored async/await for import helper to not also use then/catch

* added enum

* refactor webhookHelper and tests to not use then/catch

* changed docstring

* refactoring bot and tests to not use then/catch

* refactoring commands.js and tests to not use then/catch

* refactoring memberHelper.js and tests to not use then/catch

* removing then/catch from messageHelper.test.js

* fixed set up for commands tests

* edited bot to have top level main function

* one more test in commands.js, and removed console.error

* fixed typo in webhookHelper

* forgot to switch over some tests in bot.test and commands.test

* removed console.log from import helper

* put console.error in commands

* converted utils.js to not use then/catch

* tested utils checkImageFormatValidity

* removed jest-fetch-mock since it turns out I was just manually mocking it anyway

* refactored database to not use then/catch

* added dash to commands.js and test to pass

* added the remaining webhook tests

* changed utils to check for 10MB size not 1MB

* removed unnecessary try/catch from utils

* Simplify getWebhook to use .find() instead of foreach logic

* make memberCommand exit when error occurs with parseMemberCommand

* changed commands.js to not have user interaction within the catch

* updated console.error message in database.js

* made importHelper mock throw error instead of "resolve" error

* replaced "pk;" with "pf;" in test

* Got rid of unnecessary check for empty message from user (Fluxer doesn't allow this to happen)

Removed export of token

* getAllMembersInfo checks for fields.length

* added default case to memberCommandHandler to throw error if command is not recognized

* reversed check for valid proxy (was returning valid if the proxy existed and invalid if it didn't)

* pushes e.message instead of full error object to errors array in importHelper

* adjusted tests to properly use mockRejectedValue for async rejections

* changed getAllMembersInfo map to say `index` not `name` as it actually gets the index of a member and then the member object

* adjusted importHelper to properly test throwing of aggregate error

* revamped setting of expiration warning (moved to utils and changed logic, wrote tests)

---------

Co-authored-by: Aster Fialla <asterfialla@gmail.com>
2026-02-25 19:30:39 -05:00
33 changed files with 3032 additions and 1661 deletions

View File

@@ -0,0 +1,49 @@
name: Build Dev instance
on:
push:
branches: ["develop", "Develop"]
pull_request:
branches: ["develop", "Develop"]
workflow_dispatch:
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-dev: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 ${{ secrets.BOT_DIRECTORY }}
docker compose pull
docker compose up -d pluralflux-dev

View File

@@ -0,0 +1,47 @@
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 ${{ secrets.BOT_DIRECTORY }}
docker compose pull
docker compose up -d pluralflux-prod

View File

@@ -0,0 +1,26 @@
name: Auto-Sync from Mirror
on:
push:
repository: "Pluralflux/Pluralflux"
branches: [main,develop]
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Fork
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITEA_TOKEN }}
- name: Pull from Mirror
run: |
git remote add upstream https://engineering.sanya.gay/PluralFlux/PluralFlux.git
git fetch upstream --prune
git reset --hard origin/main
git push origin "refs/remotes/upstream/*:refs/heads/*" --force-with-lease
git merge upstream/main -m "Syncing from github"
git push origin main

11
.gitignore vendored
View File

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

View File

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

View File

@@ -1,46 +1,25 @@
services:
main:
build: .
image: engineering.sanya.gay/pluralflux/pluralflux
container_name: pluralflux
restart: unless-stopped
networks:
- pluralflux-net
env_file: "variables.env"
postgres:
image: postgres:latest
container_name: pluralflux-postgres
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_pwd
secrets:
- postgres_pwd
env_file: "variables.env"
volumes:
- pgdata:/var/lib/postgresql
- ./pgBackup:/mnt/pgBackup
ports:
- "5432:5432"
networks:
- pluralflux-net
pgadmin:
image: dpage/pgadmin4:latest
container_name: pluralflux-pgadmin
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: code@asterfialla.com
PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/postgres_pwd
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
secrets:
- postgres_pwd
env_file: "variables.env"
depends_on:
- postgres
networks:
- pluralflux-net
networks:
pluralflux-net:
volumes:
- pgadmindata:/var/lib/pgadmin
volumes:
pgdata:
secrets:
postgres_pwd:
file: ./secrets/postgres-password.txt
pgdata:

26
database/data-source.ts Normal file
View File

@@ -0,0 +1,26 @@
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: process.env.POSTGRES_ENDPOINT,
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",
},
});

40
database/entity/Member.ts Normal file
View File

@@ -0,0 +1,40 @@
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Unique} from "typeorm"
@Entity({name: "Member", synchronize: true})
@Unique("UQ_Member_userid_name", ['userid', 'name'])
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

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,12 @@
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"`);
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DeleteDuplicates1772825438973 implements MigrationInterface {
name= "DeleteDuplicates1772825438973"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE
FROM "Member" a USING "Member" b
WHERE a.id
> b.id
AND a.name = b.name
AND a.userid = b.userid;`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View File

@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Update1772830252670 implements MigrationInterface {
name = 'Update1772830252670'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Member" ADD CONSTRAINT "UQ_Member_userid_name" UNIQUE ("userid", "name")`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Member" DROP CONSTRAINT "UQ_Member_userid_name"`);
}
}

2030
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,84 +0,0 @@
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 helperEnums = {};
const enums = {};
helperEnums.err = {
enums.err = {
NO_MEMBER: "No such member was found.",
NO_NAME_PROVIDED: "No member name was provided for",
NO_VALUE: "has not been set for this member.",
@@ -22,10 +22,11 @@ helperEnums.err = {
NO_MEMBERS_IMPORTED: 'No members were imported.',
ERRORS_OCCURRED: "These errors occurred:",
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."
}
helperEnums.help = {
enums.help = {
SHORT_DESC_HELP: "Lists available commands.",
SHORT_DESC_MEMBER: "Accesses subcommands related to proxy members.",
SHORT_DESC_IMPORT: "Imports from PluralKit.",
@@ -42,8 +43,11 @@ helperEnums.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."
}
helperEnums.misc = {
ATTACHMENT_SENT_BY: "Attachment sent by:"
enums.misc = {
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/"
}
export const enums = helperEnums;
module.exports.enums = enums;

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,74 @@
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({ 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 await members.save({
name: createObj.name, userid: createObj.userid, 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({
name: ILike(memberName),
userid: authorId
}, {[columnName]: value})
return updated.affected;
}
module.exports.memberRepo = memberRepo;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,86 @@
const {enums} = require("../../src/enums");
const fetchMock = require('jest-fetch-mock');
fetchMock.enableMocks();
const {utils} = require("../../src/helpers/utils.js");
describe('utils', () => {
const attachmentUrl = 'oya.png';
const expirationString = new Date("2026-01-01").toDateString();
let blob;
beforeEach(() => {
jest.resetModules();
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(() => {

View File

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

16
tsconfig.json Normal file
View File

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

7
variables.env Normal file
View File

@@ -0,0 +1,7 @@
FLUXER_BOT_TOKEN=<>
POSTGRES_PASSWORD=<>
POSTGRES_ENDPOINT=postgres
PGADMIN_DEFAULT_EMAIL: <>
PGADMIN_DEFAULT_PASSWORD: <>
PGADMIN_CONFIG_SERVER_MODE: 'False'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'