Compare commits
5 Commits
2ab5a181bd
...
automated-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57aaf04119 | ||
| dc988a41c1 | |||
|
|
43dfa7b5ad | ||
|
|
8f56fd9b92 | ||
|
|
702e291fcb |
@@ -19,10 +19,10 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
|
||||
/*
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = [];
|
||||
const choices = []; // The array to be filled with the autocorrect values
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
log.INFO(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered);
|
||||
await interaction.respond(filtered.map(choice => ({name: choice.name, value: choice.name})));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { addSource } from '../../rss-manager/feedHandler.mjs'
|
||||
const log = new DebugBuilder("server", "discordBot.command.add");
|
||||
import { addSource } from '../../rss-manager/sourceManager.mjs'
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssAdd");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('add')
|
||||
.setName('rss-add')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
@@ -22,7 +22,7 @@ export const data = new SlashCommandBuilder()
|
||||
.setRequired(false))
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/add [title] [https://domain.com/feed.xml] [category]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const example = "/rss-add [title] [https://domain.com/feed.xml] [category]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export const execute = async (nodeIo, interaction) => {
|
||||
log.DEBUG("Result from adding entry", result);
|
||||
|
||||
if (result) {
|
||||
interaction.reply(`Adding ${title} to the list of RSS sources`);
|
||||
interaction.reply(`Successfully added ${title} to the list of RSS sources`);
|
||||
} else {
|
||||
interaction.reply(`${title} already exists in the list of RSS sources`);
|
||||
}
|
||||
58
discordBot/commands/rssRemove.mjs
Normal file
58
discordBot/commands/rssRemove.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { removeSource } from '../../rss-manager/sourceManager.mjs'
|
||||
import { getAllFeeds, deleteFeedByTitle } from '../../modules/mongo-wrappers/mongoFeedsWrappers.mjs'
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssRemove");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-remove')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('The title of the RSS feed')
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true))
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-remove [title]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
* Function to give the user auto-reply suggestions
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = await getAllFeeds() ?? [];
|
||||
log.INFO("RSS Remove Choices:", choices);
|
||||
const filtered = choices.filter(choice => choice.title.startsWith(focusedValue));
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered.map(choice => ({ name: choice.title, value: choice.title })));
|
||||
}
|
||||
|
||||
/**
|
||||
* The function to run when the command is called by a discord user
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
var title = interaction.options.getString('title');
|
||||
interaction.reply(`Removing ${title} from the list of RSS sources, please wait...`);
|
||||
|
||||
const results = await deleteFeedByTitle(title);
|
||||
if (!results) {
|
||||
log.WARN(`Failed to remove source: ${title}`);
|
||||
interaction.editReply(`Failed to remove source: '${title}'`);
|
||||
return;
|
||||
}
|
||||
interaction.editReply(`${title} was successfully removed from the RSS sources.`)
|
||||
} catch (err) {
|
||||
log.ERROR(err)
|
||||
interaction.editReply(err.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.triggerRss");
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssTrigger");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { updateFeeds } from '../../rss-manager/feedHandler.mjs'
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('trigger-rss')
|
||||
.setName('rss-trigger')
|
||||
.setDescription('Manually triggers an RSS feed update');
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/trigger-rss"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const example = "/rss-trigger"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
@@ -12,7 +12,7 @@ const log = new DebugBuilder("server", "discordBot.modules.rssWrappers");
|
||||
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/g;
|
||||
const youtubeVideoRegex = /((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g;
|
||||
|
||||
export class EmmeliaEmbedBuilder extends EmbedBuilder {
|
||||
export class DRBEmbedBuilder extends EmbedBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.setTimestamp();
|
||||
@@ -66,7 +66,7 @@ export const sendPost = (post, source, channel) => {
|
||||
log.DEBUG("Post content: ", postContent);
|
||||
|
||||
try {
|
||||
const rssMessage = new EmmeliaEmbedBuilder()
|
||||
const rssMessage = new DRBEmbedBuilder()
|
||||
.setColor(0x0099FF)
|
||||
.setTitle(postTitle)
|
||||
.setURL(postLink)
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
// Import necessary modules
|
||||
import debug from 'debug';
|
||||
import { config } from 'dotenv';
|
||||
import { writeFile } from 'fs';
|
||||
config();
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { inspect } from 'util';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
|
||||
const logLocation = process.env.LOG_LOCATION;
|
||||
|
||||
/**
|
||||
* Write a given message to the log file
|
||||
* @param {any} logMessage The message to write to the log file
|
||||
* @param {string} appName The app name that created the log entry
|
||||
*/
|
||||
const writeToLog = async (logMessage, appName) => {
|
||||
const logLocation = join(process.env.LOG_LOCATION ?? `./logs/${appName}.log`);
|
||||
|
||||
// Ensure the log directory exists
|
||||
try {
|
||||
await fs.mkdir(dirname(logLocation), { recursive: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Ensure the message is a string
|
||||
logMessage = `${String(logMessage)}\n`;
|
||||
|
||||
writeFile(
|
||||
logLocation ?? `./${appName}.log`,
|
||||
logMessage,
|
||||
{ encoding: "utf-8", flag: 'a+' },
|
||||
(err) => {
|
||||
if (err) console.error(err);
|
||||
}
|
||||
);
|
||||
// Write to the file
|
||||
try {
|
||||
await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,37 +40,31 @@ const writeToLog = async (logMessage, appName) => {
|
||||
*/
|
||||
export class DebugBuilder {
|
||||
constructor(appName, fileName) {
|
||||
this.INFO = (...messageParts) => {
|
||||
const _info = debug(`${appName}:${fileName}:INFO`);
|
||||
_info(messageParts);
|
||||
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:INFO\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
|
||||
};
|
||||
|
||||
this.DEBUG = (...messageParts) => {
|
||||
const _debug = debug(`${appName}:${fileName}:DEBUG`);
|
||||
_debug(messageParts);
|
||||
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:DEBUG\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
|
||||
};
|
||||
|
||||
this.VERBOSE = (...messageParts) => {
|
||||
const _verbose = debug(`${appName}:${fileName}:VERBOSE`);
|
||||
_verbose(messageParts);
|
||||
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:VERBOSE\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
|
||||
};
|
||||
|
||||
this.WARN = (...messageParts) => {
|
||||
const _warn = debug(`${appName}:${fileName}:WARNING`);
|
||||
_warn(messageParts);
|
||||
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:WARNING\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
|
||||
const buildLogger = (level) => (...messageParts) => {
|
||||
const logger = debug(`${appName}:${fileName}:${level}`);
|
||||
logger(messageParts);
|
||||
|
||||
const timeStamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });
|
||||
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map(part => inspect(part)).join(' ')}`;
|
||||
|
||||
// Write to console
|
||||
console.log(message);
|
||||
|
||||
// Write to logfile
|
||||
writeToLog(message, appName);
|
||||
};
|
||||
|
||||
this.INFO = buildLogger('INFO');
|
||||
this.DEBUG = buildLogger('DEBUG');
|
||||
this.VERBOSE = buildLogger('VERBOSE');
|
||||
this.WARN = buildLogger('WARNING');
|
||||
this.ERROR = (...messageParts) => {
|
||||
const _error = debug(`${appName}:${fileName}:ERROR`);
|
||||
_error(messageParts);
|
||||
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:ERROR\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
|
||||
buildLogger('ERROR')(...messageParts);
|
||||
|
||||
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
|
||||
writeToLog("!--- EXITING ---!", appName);
|
||||
setTimeout(() => process.exit(), process.env.EXIT_ON_ERROR_DELAY ?? 0);
|
||||
const exitDelay = parseInt(process.env.EXIT_ON_ERROR_DELAY, 10) || 0;
|
||||
setTimeout(() => process.exit(1), exitDelay);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +43,17 @@ import {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a feed by the title
|
||||
export const getFeedByTitle = async (title) => {
|
||||
try {
|
||||
const feed = await getDocumentByField(feedCollectionName, 'title', title);
|
||||
return feed;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for updating a feed by link
|
||||
export const updateFeedByLink = async (link, updatedFields) => {
|
||||
@@ -65,6 +76,17 @@ import {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for deleting a feed by title
|
||||
export const deleteFeedByTitle = async (title) => {
|
||||
try {
|
||||
const deletedCount = await deleteDocumentByField(feedCollectionName, 'title', title);
|
||||
return deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for inserting a post
|
||||
export const createPost = async (post) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "mocha --timeout 5000",
|
||||
"test": "jasmine",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "Logan Cusano",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createFeed, getAllFeeds, getFeedByLink, updateFeedByLink, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import { getAllFeeds, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import crypto from 'crypto';
|
||||
import { sendPost } from '../discordBot/modules/rssWrappers.mjs';
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { removeSource } from './sourceManager.mjs'
|
||||
import UserAgent from "user-agents";
|
||||
import Parser from 'rss-parser';
|
||||
|
||||
@@ -19,14 +20,16 @@ const parser = new Parser({
|
||||
});
|
||||
|
||||
const log = new DebugBuilder("server", "feedHandler");
|
||||
const sourceFailureLimit = process.env.RSS_SOURCE_FAILURE_LIMIT ?? 5;
|
||||
const runningSourcesToRemove = {}; // This holds the sources that are pending removal (they've failed to load, return data, etc.)
|
||||
|
||||
|
||||
export const returnHash = (...stringsIncluded) => {
|
||||
return crypto.createHash('sha1').update(stringsIncluded.join("-<<??//\\\\??>>-")).digest("base64");
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the active RSS feeds and send any new posts to their discord channels
|
||||
* @param {any} client The discord client to send posts with
|
||||
* @returns {any}
|
||||
*/
|
||||
export const updateFeeds = async (client) => {
|
||||
if (!client) throw new Error("Client object not passed");
|
||||
|
||||
@@ -87,57 +90,4 @@ export const updateFeeds = async (client) => {
|
||||
log.ERROR("Error updating feeds:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const addSource = async (title, link, category, guildId, channelId, callback) => {
|
||||
try {
|
||||
const feed = { title, link, category, guild_id: guildId, channel_id: channelId };
|
||||
const record = await createFeed(feed);
|
||||
log.DEBUG("Source added:", record);
|
||||
callback(null, record);
|
||||
} catch (err) {
|
||||
log.ERROR("Error adding source:", err);
|
||||
callback(err, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeSource = async (sourceURL) => {
|
||||
log.INFO("Removing source:", sourceURL);
|
||||
|
||||
if (!runningSourcesToRemove[sourceURL]) {
|
||||
runningSourcesToRemove[sourceURL] = { count: 1, timestamp: Date.now(), ignoredAttempts: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsedTime = Date.now() - runningSourcesToRemove[sourceURL].timestamp;
|
||||
const waitTime = runningSourcesToRemove[sourceURL].count * 30000;
|
||||
|
||||
if (elapsedTime <= waitTime) {
|
||||
runningSourcesToRemove[sourceURL].ignoredAttempts += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (runningSourcesToRemove[sourceURL].count < sourceFailureLimit) {
|
||||
runningSourcesToRemove[sourceURL].count += 1;
|
||||
runningSourcesToRemove[sourceURL].timestamp = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const record = await getFeedByLink(sourceURL);
|
||||
if (!record) {
|
||||
log.ERROR("Source not found in storage");
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await deleteFeedByLink(sourceURL);
|
||||
if (!results) {
|
||||
log.WARN("Failed to remove source");
|
||||
return;
|
||||
}
|
||||
|
||||
log.DEBUG("Source removed after exceeding failure limit:", sourceURL);
|
||||
} catch (err) {
|
||||
log.ERROR("Error removing source from storage:", err);
|
||||
}
|
||||
};
|
||||
};
|
||||
75
rss-manager/sourceManager.mjs
Normal file
75
rss-manager/sourceManager.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
|
||||
class SourceManager {
|
||||
constructor(sourceFailureLimit) {
|
||||
this.sourceFailureLimit = sourceFailureLimit;
|
||||
this.runningSourcesToRemove = {};
|
||||
}
|
||||
|
||||
async removeSource(sourceURL) {
|
||||
log.INFO(`Removing source: ${sourceURL}`);
|
||||
|
||||
const currentTime = Date.now();
|
||||
const sourceData = this.runningSourcesToRemove[sourceURL];
|
||||
|
||||
if (!sourceData) {
|
||||
this.runningSourcesToRemove[sourceURL] = { count: 1, timestamp: currentTime, ignoredAttempts: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsedTimeSinceLastAttempt = currentTime - sourceData.timestamp;
|
||||
const waitTime = sourceData.count * 30000;
|
||||
|
||||
if (elapsedTimeSinceLastAttempt <= waitTime) {
|
||||
sourceData.ignoredAttempts += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceData.count < this.sourceFailureLimit) {
|
||||
sourceData.count += 1;
|
||||
sourceData.timestamp = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const record = await getFeedByLink(sourceURL);
|
||||
if (!record) {
|
||||
log.ERROR(`Source not found in storage: ${sourceURL}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await deleteFeedByLink(sourceURL);
|
||||
if (!results) {
|
||||
log.WARN(`Failed to remove source: ${sourceURL}`);
|
||||
return;
|
||||
}
|
||||
|
||||
log.DEBUG(`Source removed after exceeding failure limit: ${sourceURL}`);
|
||||
// Optionally, clean up the entry from runningSourcesToRemove
|
||||
delete this.runningSourcesToRemove[sourceURL];
|
||||
} catch (err) {
|
||||
log.ERROR(`Error removing source from storage: ${sourceURL}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
async addSource(title, link, category, guildId, channelId, callback) {
|
||||
try {
|
||||
const feed = { title, link, category, guild_id: guildId, channel_id: channelId };
|
||||
const record = await createFeed(feed);
|
||||
log.DEBUG("Source added:", record);
|
||||
if (callback) callback(null, record);
|
||||
} catch (err) {
|
||||
log.ERROR("Error adding source:", err);
|
||||
if (callback) callback(err, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a default instance of SourceManager
|
||||
const defaultSourceManager = new SourceManager();
|
||||
|
||||
// Export the class and default instance methods
|
||||
export { SourceManager };
|
||||
export const addSource = defaultSourceManager.addSource.bind(defaultSourceManager);
|
||||
export const removeSource = defaultSourceManager.removeSource.bind(defaultSourceManager);
|
||||
13
spec/support/jasmine.json
Normal file
13
spec/support/jasmine.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"spec_dir": "spec",
|
||||
"spec_files": [
|
||||
"**/*[sS]pec.?(m)js"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/**/*.?(m)js"
|
||||
],
|
||||
"env": {
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": true
|
||||
}
|
||||
}
|
||||
29
spec/test/rssManager.spec.js.b
Normal file
29
spec/test/rssManager.spec.js.b
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as feedHandler from '../../rss-manager/feedHandler.mjs';
|
||||
import * as mw from '../../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import * as drw from '../../discordBot/modules/rssWrappers.mjs';
|
||||
|
||||
describe('feedHandler', () => {
|
||||
it('should call updateFeeds', async () => {
|
||||
// Spy on the updateFeeds function
|
||||
const feedsSpy = spyOn(mw, 'getAllFeeds').and.stub();
|
||||
const sendPostSpy = spyOn(drw, 'sendPost').and.stub();
|
||||
|
||||
// Call the function that triggers updateFeeds
|
||||
// For example:
|
||||
// someFunctionThatCallsUpdateFeeds();
|
||||
console.log(await spyOn(feedHandler, 'updateFeeds').and.callThrough({
|
||||
channels: {
|
||||
cache: {
|
||||
get: () => ([{
|
||||
// Stub methods or properties of the channel object as needed for testing
|
||||
}])
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Add your expectations here to ensure updateFeeds was called
|
||||
expect(feedsSpy).toHaveBeenCalled();
|
||||
expect(sendPostSpy).toHaveBeenCalled();
|
||||
// Add more specific expectations if needed
|
||||
});
|
||||
});
|
||||
494
spec/test/socketServerWrappers.spec.js
Normal file
494
spec/test/socketServerWrappers.spec.js
Normal file
@@ -0,0 +1,494 @@
|
||||
// Import necessary modules for testing
|
||||
import ioClient from 'socket.io-client';
|
||||
import { deleteNodeByNuid, getNodeByNuid } from '../../modules/mongo-wrappers/mongoNodesWrappers.mjs';
|
||||
import { deleteSystemByName, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
||||
import { nodeDisconnectWrapper, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem, checkIfNodeIsConnectedToVC, getNodeDiscordUsername, getNodeDiscordID, requestBotLeaveServer, requestNodeJoinSystem, requestNodeUpdate } from '../../modules/socketServerWrappers.mjs';
|
||||
import { nodeIo } from '../../modules/socketServer.mjs';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
process.env.SERVER_PORT = 6000
|
||||
|
||||
// Define necessary variables for testing, such as mocked database connections or socket instances
|
||||
const localNodeConfig = {
|
||||
serverIp: 'localhost',
|
||||
serverPort: process.env.SERVER_PORT,
|
||||
node: {
|
||||
nuid: "4f29a6340901a12affc87047c0ac16b01b92496c460c880a2459abe8c7928374",
|
||||
name: "testyv7",
|
||||
location: "china",
|
||||
capabilities: ["radio"]
|
||||
},
|
||||
nearbySystems: {
|
||||
"Testing P25 System Name": {
|
||||
"frequencies": [
|
||||
155344000,
|
||||
155444000,
|
||||
155555000,
|
||||
155588550
|
||||
],
|
||||
"mode": "p25",
|
||||
"trunkFile": "trunk.tsv",
|
||||
"whitelistFile": "whitelist.tsv"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updatedLocalNodeConfig = {
|
||||
node: {
|
||||
nuid: localNodeConfig.node.nuid,
|
||||
name: "updatedName",
|
||||
location: "updatedLocation",
|
||||
capabilities: ["radio", "weather"] // Updated capabilities
|
||||
},
|
||||
nearbySystems: {
|
||||
"Testing P25 System Name": {
|
||||
"frequencies": [
|
||||
155444000,
|
||||
155555000,
|
||||
155500000
|
||||
],
|
||||
"mode": "p25",
|
||||
"trunkFile": "trunk2.tsv",
|
||||
"whitelistFile": "whitelist2.tsv"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Socket Server - Core Tests', () => {
|
||||
// Start the Socket.IO server before running tests
|
||||
let clientSocket; // The socket client
|
||||
let serverClientSocket // The open client socket on the server
|
||||
beforeAll(done => {
|
||||
// Startup the node server
|
||||
nodeIo.listen(process.env.SERVER_PORT || 3000, () => {
|
||||
console.log(`server running at http://localhost:${process.env.SERVER_PORT}`);
|
||||
});
|
||||
|
||||
// Connect a client socket to the server
|
||||
clientSocket = ioClient.connect(`http://localhost:${process.env.SERVER_PORT}`);
|
||||
|
||||
nodeIo.on('connection', (socket) => {
|
||||
serverClientSocket = socket;
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
// Close the Socket.IO server after running tests
|
||||
afterAll(async () => {
|
||||
// Disconnect client socket
|
||||
clientSocket.disconnect();
|
||||
|
||||
// Close the server
|
||||
nodeIo.close();
|
||||
|
||||
// Remove the test data
|
||||
deleteNodeByNuid(localNodeConfig.node.nuid); // Delete the user
|
||||
deleteSystemByName(Object.keys(localNodeConfig.nearbySystems)[0])
|
||||
});
|
||||
// Test Node Login functionality
|
||||
describe('Node Login', () => {
|
||||
it('Should add a new node if it does not exist', async () => {
|
||||
// Simulate a node login request
|
||||
// Use the getNodeByNuid mock function to simulate checking if node exists
|
||||
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
// Assert that existingNode is null before node login
|
||||
expect(existingNode).toEqual(null);
|
||||
|
||||
// Wait for the update
|
||||
const node_login = new Promise(res => {
|
||||
clientSocket.on('node-login-successful', async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
|
||||
// Emit the login command
|
||||
clientSocket.emit("node-login", localNodeConfig.node);
|
||||
|
||||
// Wait for the successful login event
|
||||
await node_login;
|
||||
|
||||
// Now we need to check if the node is added to the database
|
||||
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||
const addedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
console.log("Added Node:", addedNode);
|
||||
|
||||
// Assert that the node is added correctly
|
||||
expect(addedNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(addedNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(addedNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(addedNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(addedNode['capabilities']).toEqual(localNodeConfig.node.capabilities);
|
||||
})
|
||||
it('Should update a node if it exists', async () => {
|
||||
// Simulate a node login request
|
||||
// Use the getNodeByNuid mock function to simulate checking if node exists
|
||||
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
// Assert that existingNode is matches the existing data before logging in
|
||||
expect(existingNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(localNodeConfig.node.capabilities);
|
||||
|
||||
// Wait for the update
|
||||
const node_login = new Promise(res => {
|
||||
clientSocket.on('node-login-successful', async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
|
||||
// Emit the login command
|
||||
clientSocket.emit("node-login", updatedLocalNodeConfig.node);
|
||||
|
||||
// Wait for the successful login event
|
||||
await node_login;
|
||||
|
||||
// Now we need to check if the node is added to the database
|
||||
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
console.log("Updated Node:", updatedNode);
|
||||
|
||||
// Assert that the node is added correctly
|
||||
expect(updatedNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
})
|
||||
});
|
||||
|
||||
// Test Node Update functionality
|
||||
describe('Node Update', () => {
|
||||
it('Should add a node\'s nearby systems', async () => {
|
||||
// Simulate an update request sent from the client to the server
|
||||
|
||||
// Get the existing node in the database
|
||||
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
// Assert that existingNode matches the updatedLocalNodeConfig
|
||||
expect(existingNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
|
||||
// Get the system from the DB
|
||||
const existsingSystem = await getSystemByName("Testing P25 System Name");
|
||||
|
||||
// Assert that there is no existing system in the DB
|
||||
expect(existsingSystem).toEqual(null);
|
||||
|
||||
// Wait for the update
|
||||
const node_system_update = new Promise(res => {
|
||||
clientSocket.on('node-update-successful', async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
|
||||
// Emit the update command
|
||||
clientSocket.emit("node-update", updatedLocalNodeConfig);
|
||||
|
||||
// Wait for the successful update event
|
||||
await node_system_update;
|
||||
|
||||
// Now we need to check if the system is added to the database
|
||||
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
console.log("Updated Node:", updatedNode);
|
||||
|
||||
// Assert that the node is added correctly
|
||||
expect(updatedNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const addedSystem = await getSystemByName("Testing P25 System Name");
|
||||
|
||||
console.log("Added system:", addedSystem);
|
||||
|
||||
expect(addedSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(addedSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(addedSystem.nodes).toEqual(updatedLocalNodeConfig.node.nuid) // Check if this node ID is in the nodes array
|
||||
expect(addedSystem['frequencies']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(addedSystem['mode']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||
});
|
||||
|
||||
it('should update a node and its nearby systems', async () => {
|
||||
// Get the existing node in the database
|
||||
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
// Assert that existingNode matches the updatedLocalNodeConfig
|
||||
expect(existingNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const existingSystem = await getSystemByName("Testing P25 System Name");
|
||||
expect(existingSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(existingSystem.nodes).toContain(updatedLocalNodeConfig.node.nuid); // Check if this node ID is in the nodes array
|
||||
expect(existingSystem['frequencies']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(existingSystem['mode']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||
|
||||
// Wait for the update
|
||||
const node_update = new Promise(res => {
|
||||
clientSocket.on('node-update-successful', async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
|
||||
// Emit the update command
|
||||
clientSocket.emit("node-update", localNodeConfig);
|
||||
|
||||
// Wait for the successful update event
|
||||
await node_update;
|
||||
|
||||
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||
|
||||
console.log("Updated Node:", updatedNode);
|
||||
|
||||
// Assert that the node is added correctly
|
||||
expect(updatedNode['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(localNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const updatedSystem = await getSystemByName("Testing P25 System Name");
|
||||
|
||||
console.log("Updated system:", updatedSystem);
|
||||
|
||||
expect(updatedSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(updatedSystem.nodes).toContain(localNodeConfig.node.nuid); // Check if this node ID is in the nodes array
|
||||
expect(updatedSystem['frequencies']).toEqual(localNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(updatedSystem['mode']).toEqual(localNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeCurrentListeningSystem
|
||||
describe('Get Node Current Listening System', () => {
|
||||
it('should correctly determine if the node is connected to a voice channel', async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const isConnectedToVC = true;
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-connected-status', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving the connection status from the client
|
||||
callback(isConnectedToVC);
|
||||
});
|
||||
|
||||
// Call the function to check if the node is connected to a voice channel
|
||||
const response = checkIfNodeIsConnectedToVC(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected connection status
|
||||
expect(response).toEqual(isConnectedToVC);
|
||||
});
|
||||
});
|
||||
|
||||
// Test checkIfNodeIsConnectedToVC
|
||||
describe('Check if Node is Connected to VC', () => {
|
||||
it('Should correctly determine if the node is connected to a voice channel', async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const isConnectedToVC = true;
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-connected-status', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).to.equal(guildId);
|
||||
// Simulate receiving the connection status from the client
|
||||
callback(isConnectedToVC);
|
||||
});
|
||||
|
||||
// Call the function to check if the node is connected to a voice channel
|
||||
const response = checkIfNodeIsConnectedToVC(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected connection status
|
||||
expect(response).to.equal(isConnectedToVC);
|
||||
});
|
||||
});
|
||||
|
||||
// Test checkIfNodeHasOpenDiscordClient
|
||||
describe('Check if Node has an open discord client', () => {
|
||||
it('should correctly determine if the node has an open Discord client', async () => {
|
||||
const isDiscordOpen = true;
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-discord-open-client', (callback) => {
|
||||
// Simulate receiving the client status from the client
|
||||
callback(isDiscordOpen);
|
||||
});
|
||||
|
||||
// Call the function to check if the node has an open Discord client
|
||||
const response = checkIfNodeHasOpenDiscordClient(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected client status
|
||||
expect(response).toEqual(isDiscordOpen);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeDiscordUsername
|
||||
describe('Get the discord username from the client', () => {
|
||||
it('should request the username from a specific client', async () => {
|
||||
const discordUsername = "Test Discord Username";
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-get-discord-username', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving the username from the client
|
||||
callback(discordUsername);
|
||||
});
|
||||
|
||||
// Call the function to get the Discord username
|
||||
const username = getNodeDiscordUsername(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(username);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const username = await nodeReply;
|
||||
|
||||
// Assert that the username matches the expected username
|
||||
expect(username).toEqual(discordUsername);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeDiscordID
|
||||
describe('Get the discord ID from the client', () => {
|
||||
it('Should get the ID from the client', async () => {
|
||||
// Mocked Discord ID
|
||||
const discordId = "mockDiscordID";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
// Listen for the 'node-get-discord-id' event from the server
|
||||
clientSocket.once('node-get-discord-id', (callback) => {
|
||||
// Simulate receiving the Discord ID from the client
|
||||
callback(discordId);
|
||||
});
|
||||
|
||||
// Call the function to get the Discord ID
|
||||
const response = getNodeDiscordID(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected Discord ID
|
||||
expect(response).toEqual(discordId);
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeJoinSystem
|
||||
describe('Request Node Join System', () => {
|
||||
it('Should send a request to the node to join a system', async () => {
|
||||
const systemName = 'mockSystemName';
|
||||
const channelId = 'mockChannelId';
|
||||
const token = 'mockToken';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-join', (joinData) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(joinData.clientID).toEqual(token);
|
||||
expect(joinData.channelID).toEqual(channelId);
|
||||
expect(joinData.system).toEqual(systemName);
|
||||
// Simulate receiving a success callback from the client
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Call the function to request joining a system
|
||||
requestNodeJoinSystem(serverClientSocket, systemName, channelId, token);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeLeaveSystem
|
||||
describe('Request Node Leave System', () => {
|
||||
it('Should send a request to the node to leave a given server', async () => {
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-leave', (passedGuildId) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving a success callback from the client
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Call the function to request joining a system
|
||||
requestBotLeaveServer(serverClientSocket, guildId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeUpdate
|
||||
describe('Request Node Update', () => {
|
||||
it('Should send the node a request to check for an update', async () => {
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise((resolve) => {
|
||||
clientSocket.once('node-request-update', (callback) => {
|
||||
// Simulate an out of date request
|
||||
expect(callback).toBeDefined();
|
||||
callback(true);
|
||||
});
|
||||
|
||||
// Call the function to request updating node information
|
||||
const response = requestNodeUpdate(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test nodeDisconnectWrapper
|
||||
describe('Node Disconnect Wrapper', () => {
|
||||
it('Should disconnect the node and trigger cleanup actions', async () => {
|
||||
// Mock the socket ID
|
||||
const socketId = 'mockSocketId';
|
||||
|
||||
// Call the nodeDisconnectWrapper function
|
||||
const result = await nodeDisconnectWrapper(socketId);
|
||||
|
||||
// Assert that the result is as expected (if any)
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user