From 729e3f469e303b6539454572dd16cba2313ddc50 Mon Sep 17 00:00:00 2001 From: Pablo Corso Date: Wed, 28 Jun 2023 13:23:37 +0200 Subject: [PATCH] feat(bn): migrate battle notifier to DB --- .eslintrc.json | 3 +- .prettierrc.json | 6 + package.json | 7 +- .../__tests__/notifyBattle.test.js | 134 ++- src/battleNotifier/battleNotifier.js | 4 +- src/battleNotifier/bnCommand/bnCommand.js | 12 + src/battleNotifier/bnCommand/config.js | 1 + .../subCommands/__tests__/setBn.test.js | 12 +- .../subCommands/__tests__/testBn.test.js | 128 ++- .../getBn/__tests__/getUserConfig.test.js | 8 +- .../bnCommand/subCommands/getBn/getStore.js | 15 +- .../bnCommand/subCommands/setBn.js | 1 - .../bnCommand/subCommands/toggleBn.js | 2 +- .../bnStore/__tests__/bnModelUtils.test.js | 152 +++ .../bnStore/__tests__/bnStore.test.js | 193 ++-- src/battleNotifier/bnStore/bnModelUtils.js | 102 ++ src/battleNotifier/bnStore/bnStore.js | 53 +- src/battleNotifier/bnStore/index.js | 17 +- src/battleNotifier/bnStore/jsonFs.js | 17 - src/battleNotifier/notifyBattle.js | 16 +- src/battleNotifier/testUtils/assertions.js | 19 +- src/battleNotifier/testUtils/index.js | 10 +- src/battleNotifier/testUtils/mocks.js | 46 +- src/battleNotifier/testUtils/userConfigs.js | 4 +- .../testUtils/userConfigsExample1.js | 47 +- src/battleNotifier/userConfig/UserConfig.js | 9 +- .../userConfig/__tests__/UserConfig.test.js | 15 +- .../__tests__/userConfigFormatter.test.js | 6 +- .../__tests__/userConfigParser.test.js | 8 +- .../userConfig/userConfigFormatter.js | 10 +- src/config.defaults.js | 2 +- src/discord.js | 9 +- src/discordTester.js | 2 +- vitest.config.js | 9 + yarn.lock | 880 +++++++++++++++++- 35 files changed, 1550 insertions(+), 409 deletions(-) create mode 100644 src/battleNotifier/bnStore/__tests__/bnModelUtils.test.js create mode 100644 src/battleNotifier/bnStore/bnModelUtils.js delete mode 100644 src/battleNotifier/bnStore/jsonFs.js create mode 100644 vitest.config.js diff --git a/.eslintrc.json b/.eslintrc.json index 9b09893..249b5e1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,5 +22,6 @@ } ] }, - "plugins": ["prettier"] + "plugins": ["prettier"], + "types": ["vitest/globals"] } diff --git a/.prettierrc.json b/.prettierrc.json index e69de29..f97bdbe 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "printWidth": 80, + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "avoid" +} diff --git a/package.json b/package.json index b25547a..984b871 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,12 @@ "main": "index.js", "scripts": { "start": "nodemon --es-module-specifier-resolution=node --no-warnings", - "lint": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" ." + "lint": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" .", + "test": "vitest" }, "dependencies": { "add": "^2.0.6", + "apisauce": "^3.0.1", "bbcode-to-markdown": "^1.0.3", "body-parser": "^1.20.0", "cors": "^2.8.5", @@ -24,6 +26,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "nodemon": "^2.0.19", - "prettier": "2.7.1" + "prettier": "2.7.1", + "vitest": "^0.32.2" } } diff --git a/src/battleNotifier/__tests__/notifyBattle.test.js b/src/battleNotifier/__tests__/notifyBattle.test.js index 82f764a..98d55e4 100644 --- a/src/battleNotifier/__tests__/notifyBattle.test.js +++ b/src/battleNotifier/__tests__/notifyBattle.test.js @@ -1,7 +1,7 @@ -const { getSubscribedUserIds } = require('../notifyBattle'); -const { mockStore, userConfigs, mockBattle } = require('../testUtils'); -const { UserConfig } = require('../userConfig'); -const bnBattleAttributes = require('../constants/bnBattleAttributes'); +import { getSubscribedUserIds } from '../notifyBattle'; +import { mockStore, userConfigs, mockBattle } from '../testUtils'; +import { UserConfig } from '../userConfig'; +import { bnBattleAttributes } from '../constants/bnBattleAttributes'; describe('no result cases', () => { test('no users returns empty array', async () => { @@ -17,7 +17,7 @@ describe('no result cases', () => { describe('no subscribed users to battle returns empty array', () => { test('turned off notifications ignores battle', async () => { - const store = mockStore({ '1': userConfigs.off }); + const store = mockStore({ 1: userConfigs.off }); const battle = mockBattle({ battleType: 'Normal', designer: 'Bene' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -25,7 +25,7 @@ describe('no result cases', () => { }); test('ignored battle type and designer', async () => { - const store = mockStore({ '1': userConfigs.ignoredTypesAndDesigners }); + const store = mockStore({ 1: userConfigs.ignoredTypesAndDesigners }); const battle = mockBattle({ battleType: 'Normal', designer: 'Chris' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -33,7 +33,7 @@ describe('no result cases', () => { }); test('empty notify list', async () => { - const store = mockStore({ '1': userConfigs.emptyLists }); + const store = mockStore({ 1: userConfigs.emptyLists }); const battle = mockBattle({ battleType: 'Normal', designer: 'Markku' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -41,7 +41,7 @@ describe('no result cases', () => { }); test('matches battle type but no desinger', async () => { - const store = mockStore({ '1': userConfigs.typesAndDesigners }); + const store = mockStore({ 1: userConfigs.typesAndDesigners }); const battle = mockBattle({ battleType: 'Flag Tag', designer: 'Markku' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -49,7 +49,7 @@ describe('no result cases', () => { }); test('matches designer but no battle type', async () => { - const store = mockStore({ '1': userConfigs.typesAndDesigners }); + const store = mockStore({ 1: userConfigs.typesAndDesigners }); const battle = mockBattle({ battleType: 'Apple', designer: 'Bene' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -60,7 +60,7 @@ describe('no result cases', () => { describe('with result cases', () => { test('matches battle type and designer', async () => { - const store = mockStore({ '1': userConfigs.typesAndDesigners }); + const store = mockStore({ 1: userConfigs.typesAndDesigners }); const battle = mockBattle({ battleType: 'Flag Tag', designer: 'Bene', @@ -71,7 +71,7 @@ describe('with result cases', () => { }); test('matches battle type and designer ignorecase', async () => { - const store = mockStore({ '1': userConfigs.typesAndDesigners }); + const store = mockStore({ 1: userConfigs.typesAndDesigners }); const battle = mockBattle({ battleType: 'Flag Tag', designer: 'bEnE', @@ -82,7 +82,7 @@ describe('with result cases', () => { }); test('matches battle type by any', async () => { - const store = mockStore({ '1': userConfigs.typeOrDesigner }); + const store = mockStore({ 1: userConfigs.typeOrDesigner }); const battle = mockBattle({ battleType: 'First Finish', designer: 'Markku', @@ -93,7 +93,7 @@ describe('with result cases', () => { }); test('matches any battle type by designer', async () => { - const store = mockStore({ '1': userConfigs.typeOrDesigner }); + const store = mockStore({ 1: userConfigs.typeOrDesigner }); const battle = mockBattle({ battleType: 'Speed', designer: 'Insguy', @@ -105,7 +105,7 @@ describe('with result cases', () => { describe('with level name patterns', () => { test('matches simple level pattern', async () => { - const store = mockStore({ '1': userConfigs.levelPatterns.simple }); + const store = mockStore({ 1: userConfigs.levelPatterns.simple }); const battle = mockBattle({ level: 'JoPi42' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -113,7 +113,7 @@ describe('with result cases', () => { }); test('matches simple level pattern ignore case', async () => { - const store = mockStore({ '1': userConfigs.levelPatterns.simple }); + const store = mockStore({ 1: userConfigs.levelPatterns.simple }); const battle = mockBattle({ level: 'jOpI12345' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -121,7 +121,7 @@ describe('with result cases', () => { }); test('does not start with level pattern, does not match', async () => { - const store = mockStore({ '1': userConfigs.levelPatterns.simple }); + const store = mockStore({ 1: userConfigs.levelPatterns.simple }); const battle = mockBattle({ level: 'aJoPi10' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -129,7 +129,7 @@ describe('with result cases', () => { }); test('complex regexp level pattern matches battle', async () => { - const store = mockStore({ '1': userConfigs.levelPatterns.regexp }); + const store = mockStore({ 1: userConfigs.levelPatterns.regexp }); const battle = mockBattle({ level: 'Pob0001' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -138,7 +138,7 @@ describe('with result cases', () => { test('designers and level patterns matches battle', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { designers: ['Markku', 'Zero'], @@ -146,7 +146,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ level: 'Mark0001', designer: 'Markku' }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -155,7 +155,7 @@ describe('with result cases', () => { test('types, designers and level patterns matches battle', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { battleTypes: ['Normal'], @@ -164,7 +164,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ level: 'PipeZero', battleType: 'Normal', @@ -177,7 +177,6 @@ describe('with result cases', () => { test('types, designers and level patterns does not match pattern', async () => { const userConfig = UserConfig({ - username: 'Pab', notifyList: [ { battleTypes: ['Normal'], @@ -186,7 +185,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ level: 'ZerMark1', battleType: 'Normal', @@ -199,7 +198,6 @@ describe('with result cases', () => { test('types, designers and level patterns does not match designer', async () => { const userConfig = UserConfig({ - username: 'Pab', notifyList: [ { battleTypes: ['Normal'], @@ -208,7 +206,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ level: 'ZerMark1', battleType: 'Normal', @@ -221,7 +219,6 @@ describe('with result cases', () => { test('types, designers and level patterns does not match type', async () => { const userConfig = UserConfig({ - username: 'Pab', notifyList: [ { battleTypes: ['Normal', 'Apple', 'Speed'], @@ -230,7 +227,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ level: 'ZerMark1', battleType: 'First Finish', @@ -244,7 +241,7 @@ describe('with result cases', () => { describe('with battle attributes', () => { test('matches battle attribute', async () => { - const store = mockStore({ '1': userConfigs.battleAttributes }); + const store = mockStore({ 1: userConfigs.battleAttributes }); const battle = mockBattle({ seeOthers: true, noTurn: true }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -253,10 +250,10 @@ describe('with result cases', () => { test('matches all battle attributes', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ battleAttributes: bnBattleAttributes.map(a => a.name) }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ seeOthers: true, seeTimes: true, @@ -278,7 +275,7 @@ describe('with result cases', () => { }); test('does not match one battle attribute', async () => { - const store = mockStore({ '1': userConfigs.battleAttributes }); + const store = mockStore({ 1: userConfigs.battleAttributes }); const battle = mockBattle({ seeOthers: true, noTurn: false, @@ -291,7 +288,7 @@ describe('with result cases', () => { test('matches battle attributes and type', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { battleTypes: ['Normal', 'Apple', 'Speed'], @@ -299,7 +296,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ battleType: 'Normal', noTurn: true, @@ -313,7 +310,7 @@ describe('with result cases', () => { test('matches battle attributes does not match type', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { battleTypes: ['Normal', 'Apple', 'Speed'], @@ -321,7 +318,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ battleType: 'First Finish', noTurn: true, @@ -335,7 +332,7 @@ describe('with result cases', () => { test('matches battle attributes and designer', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { designers: ['Markku', 'Zero'], @@ -343,7 +340,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ designer: 'Markku', multi: true, @@ -356,7 +353,7 @@ describe('with result cases', () => { test('matches battle attributes does not match designer', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { designers: ['Markku', 'Zero'], @@ -364,7 +361,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ designer: 'Barryp', multi: true, @@ -377,7 +374,7 @@ describe('with result cases', () => { test('matches battle attributes, type and designer', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [ { battleTypes: ['First Finish', 'Normal'], @@ -386,7 +383,7 @@ describe('with result cases', () => { }, ], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ battleType: 'Normal', designer: 'Zero', @@ -402,10 +399,10 @@ describe('with result cases', () => { describe('with minutes duration', () => { test('matches battle over minDuration', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ minDuration: 20 }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ durationMinutes: 40 }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -414,10 +411,10 @@ describe('with result cases', () => { test('matches battle under maxDuration', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ maxDuration: 40 }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ durationMinutes: 20 }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -426,10 +423,10 @@ describe('with result cases', () => { test('matches battle equal min and max duration', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ minDuration: 40, maxDuration: 40 }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ durationMinutes: 40 }); const actual = await getSubscribedUserIds({ battle, store }); const expected = ['1']; @@ -438,10 +435,10 @@ describe('with result cases', () => { test('does not match battle under minDuration', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ minDuration: 40 }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ durationMinutes: 39 }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -450,10 +447,10 @@ describe('with result cases', () => { test('does not match battle over maxDuration', async () => { const userConfig = UserConfig({ - username: 'Pab', + isOn: true, notifyList: [{ maxDuration: 40 }], }); - const store = mockStore({ '1': userConfig }); + const store = mockStore({ 1: userConfig }); const battle = mockBattle({ durationMinutes: 41 }); const actual = await getSubscribedUserIds({ battle, store }); const expected = []; @@ -463,7 +460,7 @@ describe('with result cases', () => { describe('with types, designers, level patterns, attributes and minmax', () => { test('matches all things', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'First Finish', designer: 'Zero', @@ -478,7 +475,7 @@ describe('with result cases', () => { }); test('matches all except battle type', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Apple', designer: 'Zero', @@ -493,7 +490,7 @@ describe('with result cases', () => { }); test('matches all except designer', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Normal', designer: 'Pab', @@ -508,7 +505,7 @@ describe('with result cases', () => { }); test('matches all except level pattern', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Normal', designer: 'Zero', @@ -523,7 +520,7 @@ describe('with result cases', () => { }); test('matches all except one attribute', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Normal', designer: 'Zero', @@ -537,7 +534,7 @@ describe('with result cases', () => { }); test('matches all except min duration', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Normal', designer: 'Zero', @@ -551,7 +548,7 @@ describe('with result cases', () => { }); test('matches all except max duration', async () => { - const store = mockStore({ '1': userConfigs.all }); + const store = mockStore({ 1: userConfigs.all }); const battle = mockBattle({ battleType: 'Normal', designer: 'Zero', @@ -564,25 +561,4 @@ describe('with result cases', () => { expect(actual).toEqual(expected); }); }); - - describe('test legacy configs', () => { - test('only battleTypes and designers defined works correctly', async () => { - const legacyConfig = { - createdAt: '2020-09-15T17:37:11.563Z', - updatedAt: '2020-09-15T19:37:39.237Z', - isOn: true, - notifyList: [{ battleTypes: ['Normal'], designers: [] }], - ignoreList: [], - username: 'ILKKA', - }; - const store = mockStore({ '1': legacyConfig }); - const battle = mockBattle({ - battleType: 'Normal', - designer: 'Zero', - }); - const actual = await getSubscribedUserIds({ battle, store }); - const expected = ['1']; - expect(actual).toEqual(expected); - }); - }); }); diff --git a/src/battleNotifier/battleNotifier.js b/src/battleNotifier/battleNotifier.js index d739008..0d7f95d 100644 --- a/src/battleNotifier/battleNotifier.js +++ b/src/battleNotifier/battleNotifier.js @@ -13,8 +13,8 @@ const logNotifyUserError = error => { }); }; -const battleNotifier = ({ bnStorePath, client, fallbackChannelId }) => { - const store = createBnStore(bnStorePath); +const battleNotifier = ({ apiUrl, client, fallbackChannelId }) => { + const store = createBnStore(apiUrl); const getFallbackChannel = () => client.channels.cache.get(fallbackChannelId); diff --git a/src/battleNotifier/bnCommand/bnCommand.js b/src/battleNotifier/bnCommand/bnCommand.js index 7e019bf..0bc11af 100644 --- a/src/battleNotifier/bnCommand/bnCommand.js +++ b/src/battleNotifier/bnCommand/bnCommand.js @@ -16,6 +16,10 @@ const noCommandFound = async message => { await message.channel.send(messages.seeAvailableCommands); }; +const isUserLinkedToBn = async ({ user, store }) => { + return await store.isUserLinked(user.id); +}; + export default { name: 'bn', execute: async ({ message, args, store }) => { @@ -23,6 +27,13 @@ export default { const subCommand = args && args[0] && args[0].toLowerCase(); try { + const isLinked = await isUserLinkedToBn({ user, store }); + if (!isLinked) { + const notLinkedMessage = await user.send(messages.notLinked); + await notLinkedMessage.suppressEmbeds(); + return; + } + if (!subCommand) { await setBn({ message, store }); } else if (subCommand === 'get') { @@ -43,6 +54,7 @@ export default { await noCommandFound(message); } } catch (error) { + console.log(error); const errorMessage = error instanceof TimeOutError ? error.message diff --git a/src/battleNotifier/bnCommand/config.js b/src/battleNotifier/bnCommand/config.js index 8ea7c3c..d3558fa 100644 --- a/src/battleNotifier/bnCommand/config.js +++ b/src/battleNotifier/bnCommand/config.js @@ -14,6 +14,7 @@ export const keywords = { export const bnAdminIds = ['219518470767902722']; export const messages = { + notLinked: `To use Battle Notifier you first need to connect your Discord account in Elma Online:\n1. Go to https://elma.online/\n2. Login into your account or register if you don't have one\n3. Go to your notification settings page (https://elma.online/settings/notifications)\n4. Click on "Login in to discord" and follow the instructions\n5. Finally write \`!bn\` in this channel to get started!`, configNotFound: 'You have no notifications set yet, please write `!bn` to start receiving battle notifications.', seeAvailableCommands: 'Write `!bn help` to see the available commands.', diff --git a/src/battleNotifier/bnCommand/subCommands/__tests__/setBn.test.js b/src/battleNotifier/bnCommand/subCommands/__tests__/setBn.test.js index 836194d..1a0a367 100644 --- a/src/battleNotifier/bnCommand/subCommands/__tests__/setBn.test.js +++ b/src/battleNotifier/bnCommand/subCommands/__tests__/setBn.test.js @@ -1,7 +1,7 @@ -const setBn = require('../setBn'); -const { emojis } = require('../../config'); -const { UserConfigLists } = require('../../../userConfig'); -const { +import setBn from '../setBn'; +import { emojis } from '../../config'; +import { UserConfigLists } from '../../../userConfig'; +import { mockStore, mockUser, mockMessage, @@ -9,7 +9,7 @@ const { userConfigsExample1, expectAsyncResult, expectAsyncResultProperty, -} = require('../../../testUtils'); +} from '../../../testUtils'; const { editMessage, @@ -90,7 +90,6 @@ describe('user sets notifications for first time', () => { ...UserConfigLists({ notifyList: [{ battleTypes: ['First Finish'], designers: ['Markku'] }], }), - username: 'NewUser', }); /** Expect bot to reply with newly created user config */ @@ -138,7 +137,6 @@ describe('user edits his current notifications', () => { ...UserConfigLists({ notifyList: [{ battleTypes: ['First Finish'], designers: ['Markku'] }], }), - username: 'Kopaka', }); /** Expect bot to reply with newly created user config */ diff --git a/src/battleNotifier/bnCommand/subCommands/__tests__/testBn.test.js b/src/battleNotifier/bnCommand/subCommands/__tests__/testBn.test.js index 23f2e57..49573fb 100644 --- a/src/battleNotifier/bnCommand/subCommands/__tests__/testBn.test.js +++ b/src/battleNotifier/bnCommand/subCommands/__tests__/testBn.test.js @@ -1,6 +1,6 @@ -const testBn = require('../testBn'); -const { messages, emojis } = require('../../config'); -const { +import testBn from '../testBn'; +import { messages, emojis } from '../../config'; +import { mockMessage, mockStore, mockChannel, @@ -8,13 +8,10 @@ const { userConfigsExample1, expectAsyncResult, expectAsyncResultProperty, -} = require('../../../testUtils'); +} from '../../../testUtils'; -const { - testBnMessage, - incorrectTestMessage, - testResultMessage, -} = testBn.messages; +const { testBnMessage, incorrectTestMessage, testResultMessage } = + testBn.messages; let store; @@ -124,27 +121,110 @@ describe('user with config and good input', () => { expect(userReply.react).toHaveBeenCalledWith(emojis.ok); }; + // TODO: multiple tests fail because of the some reason, fix it + test('battle type and designer matches user config', async () => { - runTestAssertions({ - userId: '1', - userReplyContent: 'First Finish by Sla', - testMessage: 'First Finish battle started by Sla', - }); + const userId = '1'; + const userReplyContent = 'First Finish by Sla'; + const testMessage = 'First Finish battle started by Sla'; + const matches = true; + + const author = mockUser(userId); + const userReply = mockMessage({ content: userReplyContent }); + const channel = mockChannel({ userReply }); + const message = mockMessage({ author, channel }); + + await testBn({ message, store }); + + /** Expect store get to find user config */ + expect(store.get).toHaveBeenCalledTimes(1); + expect(store.get).toHaveBeenCalledWith(userId); + await expectAsyncResult(store.get, userConfigsExample1[userId]); + + /** Expect bot to redirect to channel with message */ + expect(message.send).toHaveBeenCalledTimes(1); + expect(message.send).toHaveBeenCalledWith(testBnMessage); + await expectAsyncResultProperty(message.send, 'channel', channel); + + /** Expect user to reply with test */ + expect(channel.readUserMessage).toHaveBeenCalledTimes(1); + expect(channel.readUserMessage).toHaveBeenCalledWith(author); + await expectAsyncResult(channel.readUserMessage, userReply); + + expect(channel.send).toHaveBeenCalledTimes(2); + expect(channel.send).toHaveBeenNthCalledWith(1, `Test: ${testMessage}`); + expect(channel.send).toHaveBeenNthCalledWith(2, testResultMessage(matches)); + expect(userReply.react).toHaveBeenCalledTimes(1); + expect(userReply.react).toHaveBeenCalledWith(emojis.ok); }); test('level name and designer matches user config', async () => { - runTestAssertions({ - userId: '6', - userReplyContent: 'JoPi50.lev by John', - testMessage: 'JoPi50 battle started by John', - }); + const userId = '6'; + const userReplyContent = 'JoPi50.lev by John'; + const testMessage = 'JoPi50 battle started by John'; + const matches = true; + + const author = mockUser(userId); + const userReply = mockMessage({ content: userReplyContent }); + const channel = mockChannel({ userReply }); + const message = mockMessage({ author, channel }); + + await testBn({ message, store }); + + /** Expect store get to find user config */ + expect(store.get).toHaveBeenCalledTimes(1); + expect(store.get).toHaveBeenCalledWith(userId); + await expectAsyncResult(store.get, userConfigsExample1[userId]); + + /** Expect bot to redirect to channel with message */ + expect(message.send).toHaveBeenCalledTimes(1); + expect(message.send).toHaveBeenCalledWith(testBnMessage); + await expectAsyncResultProperty(message.send, 'channel', channel); + + /** Expect user to reply with test */ + expect(channel.readUserMessage).toHaveBeenCalledTimes(1); + expect(channel.readUserMessage).toHaveBeenCalledWith(author); + await expectAsyncResult(channel.readUserMessage, userReply); + + expect(channel.send).toHaveBeenCalledTimes(2); + expect(channel.send).toHaveBeenNthCalledWith(1, `Test: ${testMessage}`); + expect(channel.send).toHaveBeenNthCalledWith(2, testResultMessage(matches)); + expect(userReply.react).toHaveBeenCalledTimes(1); + expect(userReply.react).toHaveBeenCalledWith(emojis.ok); }); test('level name, battleType and designer matches user config', async () => { - runTestAssertions({ - userId: '6', - userReplyContent: 'Normal JoPi50.lev by John', - testMessage: 'JoPi50 Normal battle started by John', - }); + const userId = '6'; + const userReplyContent = 'Normal JoPi50.lev by John'; + const testMessage = 'JoPi50 Normal battle started by John'; + const matches = true; + + const author = mockUser(userId); + const userReply = mockMessage({ content: userReplyContent }); + const channel = mockChannel({ userReply }); + const message = mockMessage({ author, channel }); + + await testBn({ message, store }); + + /** Expect store get to find user config */ + expect(store.get).toHaveBeenCalledTimes(1); + expect(store.get).toHaveBeenCalledWith(userId); + await expectAsyncResult(store.get, userConfigsExample1[userId]); + + /** Expect bot to redirect to channel with message */ + expect(message.send).toHaveBeenCalledTimes(1); + expect(message.send).toHaveBeenCalledWith(testBnMessage); + await expectAsyncResultProperty(message.send, 'channel', channel); + + /** Expect user to reply with test */ + expect(channel.readUserMessage).toHaveBeenCalledTimes(1); + expect(channel.readUserMessage).toHaveBeenCalledWith(author); + await expectAsyncResult(channel.readUserMessage, userReply); + + expect(channel.send).toHaveBeenCalledTimes(2); + expect(channel.send).toHaveBeenNthCalledWith(1, `Test: ${testMessage}`); + expect(channel.send).toHaveBeenNthCalledWith(2, testResultMessage(matches)); + expect(userReply.react).toHaveBeenCalledTimes(1); + expect(userReply.react).toHaveBeenCalledWith(emojis.ok); }); }); diff --git a/src/battleNotifier/bnCommand/subCommands/getBn/__tests__/getUserConfig.test.js b/src/battleNotifier/bnCommand/subCommands/getBn/__tests__/getUserConfig.test.js index ff92d10..c0c6d27 100644 --- a/src/battleNotifier/bnCommand/subCommands/getBn/__tests__/getUserConfig.test.js +++ b/src/battleNotifier/bnCommand/subCommands/getBn/__tests__/getUserConfig.test.js @@ -1,11 +1,11 @@ -const getUserConfig = require('../getUserConfig'); -const { messages } = require('../../../config'); +import getUserConfig from '../getUserConfig'; +import { messages } from '../../../config'; -const { +import { mockStore, mockUser, userConfigsExample1, -} = require('../../../../testUtils'); +} from '../../../../testUtils'; const { yourConfigMessage } = getUserConfig.messages; diff --git a/src/battleNotifier/bnCommand/subCommands/getBn/getStore.js b/src/battleNotifier/bnCommand/subCommands/getBn/getStore.js index 5edefb6..ce27420 100644 --- a/src/battleNotifier/bnCommand/subCommands/getBn/getStore.js +++ b/src/battleNotifier/bnCommand/subCommands/getBn/getStore.js @@ -1,10 +1,15 @@ -import Discord from 'discord.js'; +import { messages } from '../../config'; const getStore = async ({ user, store }) => { - const embed = new Discord.MessageEmbed() - .setTitle(`Current user notifications file`) - .attachFiles([store.path]); - await user.send(embed); + let response = ''; + try { + const userConfig = await store.getAllActive(); + response = JSON.stringify(userConfig, null, 2); + } catch (error) { + response = messages.configNotFound; + } + + await user.send(response); }; export default getStore; diff --git a/src/battleNotifier/bnCommand/subCommands/setBn.js b/src/battleNotifier/bnCommand/subCommands/setBn.js index ad9005a..03881ab 100644 --- a/src/battleNotifier/bnCommand/subCommands/setBn.js +++ b/src/battleNotifier/bnCommand/subCommands/setBn.js @@ -72,7 +72,6 @@ const setBn = async ({ message, store }) => { if (!areUserConfigListsEmpty(userConfigLists)) { const userConfigValues = { ...userConfigLists, - username: user.username, }; await store.set(user.id, userConfigValues); diff --git a/src/battleNotifier/bnCommand/subCommands/toggleBn.js b/src/battleNotifier/bnCommand/subCommands/toggleBn.js index b1c8faa..6c051b6 100644 --- a/src/battleNotifier/bnCommand/subCommands/toggleBn.js +++ b/src/battleNotifier/bnCommand/subCommands/toggleBn.js @@ -4,7 +4,7 @@ const toggleBn = async ({ message, store, isOn }) => { const user = message.author; const userConfig = await store.get(user.id); if (userConfig) { - await store.set(user.id, { isOn }); + await store.toggleIsOn(user.id, { isOn }); await message.author.send( `Your notifications are now ${isOn ? 'ON' : 'OFF'}`, ); diff --git a/src/battleNotifier/bnStore/__tests__/bnModelUtils.test.js b/src/battleNotifier/bnStore/__tests__/bnModelUtils.test.js new file mode 100644 index 0000000..f2e6c6d --- /dev/null +++ b/src/battleNotifier/bnStore/__tests__/bnModelUtils.test.js @@ -0,0 +1,152 @@ +import { bnKuskiToUserConfig, userConfigToBnKuski } from '../bnModelUtils'; + +describe('bnKuskiToUserConfig', () => { + test('empty object returns empty config', () => { + const actual = bnKuskiToUserConfig({}); + const expected = { + isOn: false, + notifyList: [], + ignoreList: [], + }; + expect(actual).toEqual(expected); + }); + + test('empty strings return empty config', () => { + const actual = bnKuskiToUserConfig({ + BnKuskiRules: [ + { + BattleAttributes: '', + BattleTypes: '', + Designers: '', + LevelPatterns: '', + MaxDuration: null, + MinDuration: null, + IgnoreList: 0, + }, + ], + }); + const expected = { + isOn: false, + notifyList: [ + { + battleAttributes: [], + battleTypes: [], + designers: [], + levelPatterns: [], + maxDuration: 0, + minDuration: 0, + }, + ], + ignoreList: [], + }; + expect(actual).toEqual(expected); + }); + + test('filled object returns filled config', () => { + const BnKuskiRules = [ + { + // spaces are trimmed + BattleAttributes: 'seeOthers, allowStarter', + BattleTypes: 'Normal, First Finish', + Designers: 'John, Adi', + LevelPatterns: 'Pob, JoPi', + MaxDuration: 30, + MinDuration: 50, + IgnoreList: 0, + }, + { + BattleAttributes: 'seeOthers,allowStarter', + BattleTypes: 'Normal,First Finish', + Designers: 'John, Adi', + LevelPatterns: 'Pob, JoPi', + MaxDuration: 30, + MinDuration: 50, + IgnoreList: 1, + }, + ]; + const actual = bnKuskiToUserConfig({ BnEnabled: 1, BnKuskiRules }); + const expected = { + isOn: true, + notifyList: [ + { + battleAttributes: ['seeOthers', 'allowStarter'], + battleTypes: ['Normal', 'First Finish'], + designers: ['John', 'Adi'], + levelPatterns: ['Pob', 'JoPi'], + maxDuration: 30, + minDuration: 50, + }, + ], + ignoreList: [ + { + battleAttributes: ['seeOthers', 'allowStarter'], + battleTypes: ['Normal', 'First Finish'], + designers: ['John', 'Adi'], + levelPatterns: ['Pob', 'JoPi'], + maxDuration: 30, + minDuration: 50, + }, + ], + }; + expect(actual).toEqual(expected); + }); +}); + +describe('userConfigToBnKuski', () => { + test('empty object returns empty config', () => { + const actual = userConfigToBnKuski({}); + const expected = { + BnEnabled: 0, + BnKuskiRules: [], + }; + expect(actual).toEqual(expected); + }); + + test('filled object returns filled config', () => { + const notifyList = [ + { + battleAttributes: ['seeOthers', 'allowStarter'], + battleTypes: ['Normal', 'First Finish'], + designers: ['John', 'Adi'], + levelPatterns: ['Pob', 'JoPi'], + maxDuration: 30, + minDuration: 50, + }, + ]; + const ignoreList = [ + { + battleAttributes: ['seeOthers', 'allowStarter'], + battleTypes: ['Normal', 'First Finish'], + designers: ['John', 'Adi'], + levelPatterns: ['Pob', 'JoPi'], + maxDuration: 30, + minDuration: 50, + }, + ]; + const actual = userConfigToBnKuski({ isOn: true, notifyList, ignoreList }); + const expected = { + BnEnabled: 1, + BnKuskiRules: [ + { + BattleAttributes: 'seeOthers,allowStarter', + BattleTypes: 'Normal,First Finish', + Designers: 'John,Adi', + LevelPatterns: 'Pob,JoPi', + MaxDuration: 30, + MinDuration: 50, + IgnoreList: 0, + }, + { + BattleAttributes: 'seeOthers,allowStarter', + BattleTypes: 'Normal,First Finish', + Designers: 'John,Adi', + LevelPatterns: 'Pob,JoPi', + MaxDuration: 30, + MinDuration: 50, + IgnoreList: 1, + }, + ], + }; + expect(actual).toEqual(expected); + }); +}); diff --git a/src/battleNotifier/bnStore/__tests__/bnStore.test.js b/src/battleNotifier/bnStore/__tests__/bnStore.test.js index b0ec407..e00dabd 100644 --- a/src/battleNotifier/bnStore/__tests__/bnStore.test.js +++ b/src/battleNotifier/bnStore/__tests__/bnStore.test.js @@ -1,95 +1,116 @@ -const bnStore = require('../bnStore'); -const { userConfigsExample1: exampleConfigs } = require('../../testUtils'); -const { UserConfig } = require('../../userConfig'); +import { describe, vi } from 'vitest'; + +import bnStore from '../bnStore'; +import { userConfigsExample1 as exampleConfigs } from '../../testUtils'; +import { mockServerApi } from '../../testUtils'; +import { bnKuskiToUserConfig, userConfigToBnKuski } from '../bnModelUtils'; let store; -let writeJsonFile; -let readJsonFile; -let createParentFolder; -let dateNow; -const testPath = 'path/to/storeFile'; -const mockStore = () => - bnStore({ writeJsonFile, readJsonFile, createParentFolder, dateNow })( - testPath, - ); - -const createdAtTestDate = '2020-09-15T16:49:59.977Z'; -const updatedAtTest = '2020-10-10T12:34:56.789z'; +let api; +const testUrl = 'player/bn/'; +const mockBnStore = () => bnStore({ api })(testUrl); beforeEach(() => { - writeJsonFile = jest.fn(() => Promise.resolve()); - createParentFolder = jest.fn(() => Promise.resolve()); + api = mockServerApi(); }); describe('tests without a config file created yet', () => { beforeEach(() => { - readJsonFile = jest.fn(() => Promise.resolve({})); - dateNow = jest.fn(() => createdAtTestDate); - store = mockStore(); + api.get.mockResolvedValue({ data: {} }); + store = mockBnStore(); }); - test('get user config returns undefined', async () => { + test('get user config returns empty', async () => { const actual = await store.get('1'); - expect(actual).toBeUndefined(); + expect(actual).toEqual({ isOn: false, notifyList: [], ignoreList: [] }); }); test('set user config success', async () => { const userConfig = exampleConfigs['1']; await store.set('1', userConfig); - const expectedConfig = { - '1': { - createdAt: createdAtTestDate, - ...userConfig, - }, - }; - expect(writeJsonFile).toBeCalledWith(testPath, expectedConfig); + const expectedConfig = userConfigToBnKuski(userConfig); + expect(api.post).toBeCalledWith(`${testUrl}1`, expectedConfig); + }); +}); + +describe('toggle on/off', () => { + beforeEach(() => { + store = mockBnStore(); + }); + + test('toggle off', async () => { + await store.toggleIsOn('1', false); + expect(api.post).toBeCalledWith(`${testUrl}1/toggle/0`); + }); + + test('toggle on', async () => { + await store.toggleIsOn('1', true); + expect(api.post).toBeCalledWith(`${testUrl}1/toggle/1`); + }); +}); + +describe('is user linked', () => { + beforeEach(() => { + store = mockBnStore(); + }); + + test('user is linked', async () => { + api.get.mockResolvedValueOnce({ data: {} }); + const actual = await store.isUserLinked('1'); + expect(actual).toBe(true); + }); + + test('user is not linked', async () => { + api.get.mockResolvedValueOnce({ data: null }); + const actual = await store.isUserLinked('1'); + expect(actual).toBe(false); }); }); describe('tests with a config file already created', () => { describe('test get user config', () => { beforeEach(() => { - readJsonFile = jest.fn(() => Promise.resolve(exampleConfigs)); - store = mockStore(); + store = mockBnStore(); }); test('get user config from user', async () => { + api.get.mockResolvedValueOnce({ + data: userConfigToBnKuski(exampleConfigs['1']), + }); const actual = await store.get('1'); expect(actual).toEqual(exampleConfigs['1']); }); test('get all configs', async () => { - const result = await store.getAll(); - expect(result).toEqual(exampleConfigs); + const data = []; + Object.keys(exampleConfigs).forEach(key => { + data.push({ + DiscordId: key, + ...userConfigToBnKuski(exampleConfigs[key]), + }); + }); + api.get.mockResolvedValueOnce({ data }); + const result = await store.getAllActive(); + expect(result).toEqual(data.map(user => bnKuskiToUserConfig(user))); }); }); describe('set new user config', () => { beforeEach(() => { - readJsonFile = jest.fn(() => Promise.resolve(exampleConfigs)); - dateNow = jest.fn(() => createdAtTestDate); - store = mockStore(); + store = mockBnStore(); }); test('with no values', async () => { const newConfig = {}; await store.set('100', newConfig); - const expectedArg = { - ...exampleConfigs, - '100': UserConfig({ - isOn: true, - createdAt: createdAtTestDate, - updatedAt: createdAtTestDate, - }), - }; - expect(writeJsonFile).toHaveBeenLastCalledWith(testPath, expectedArg); + const expectedArg = { BnEnabled: 0, BnKuskiRules: [] }; + expect(api.post).toHaveBeenLastCalledWith(`${testUrl}100`, expectedArg); }); test('set new config with values', async () => { const newConfig = { - userName: 'Spef', notifyList: [ { battleTypes: ['Speed', 'Finish Count'], @@ -99,82 +120,10 @@ describe('tests with a config file already created', () => { }; await store.set('100', newConfig); - const expectedArg = { - ...exampleConfigs, - '100': UserConfig({ - ...newConfig, - isOn: true, - createdAt: createdAtTestDate, - updatedAt: createdAtTestDate, - }), - }; - expect(writeJsonFile).toHaveBeenLastCalledWith(testPath, expectedArg); - }); - }); - - describe('update existing user config', () => { - beforeEach(() => { - readJsonFile = jest.fn(() => Promise.resolve(exampleConfigs)); - dateNow = jest.fn(() => updatedAtTest); - store = mockStore(); - }); - - test('without createdAt sets createdAt from updatedAt', async () => { - const testStore = { - '1': { ...exampleConfigs['1'], createdAt: undefined }, - }; - delete testStore['1'].createdAt; - - readJsonFile = jest.fn(() => Promise.resolve(testStore)); - store = mockStore(); - await store.set('1', {}); - - const expectedArg = { - '1': UserConfig({ - ...exampleConfigs['1'], - updatedAt: updatedAtTest, - createdAt: updatedAtTest, - }), - }; - expect(writeJsonFile).toHaveBeenLastCalledWith(testPath, expectedArg); - }); - - test("with new values doesn't override other current values", async () => { - const newValues = { - notifyList: [{ battleTypes: 'First Finish', designers: 'Sla' }], - }; - await store.set('1', newValues); - - const expectedArg = { - ...exampleConfigs, - '1': UserConfig({ - ...exampleConfigs['1'], - ...newValues, - updatedAt: updatedAtTest, - createdAt: '2020-09-11T20:43:03.537Z', - }), - }; - expect(writeJsonFile).toHaveBeenLastCalledWith(testPath, expectedArg); - }); - - test('with updated values, overrides current values', async () => { - const newValues = { - username: 'Pab', - createdAt: '2019-08-01T21:00:00.123Z', - updatedAt: '2020-08-01T21:00:00.123Z', - isOn: false, - notifyList: [], - ignoreList: [{ battleTypes: ['First Finish'], designers: [] }], - }; - await store.set('1', newValues); - - const expectedArg = { - ...exampleConfigs, - '1': UserConfig({ - ...newValues, - }), - }; - expect(writeJsonFile).toHaveBeenLastCalledWith(testPath, expectedArg); + const expectedArg = userConfigToBnKuski({ + notifyList: newConfig.notifyList, + }); + expect(api.post).toHaveBeenLastCalledWith(`${testUrl}100`, expectedArg); }); }); }); diff --git a/src/battleNotifier/bnStore/bnModelUtils.js b/src/battleNotifier/bnStore/bnModelUtils.js new file mode 100644 index 0000000..3392aa0 --- /dev/null +++ b/src/battleNotifier/bnStore/bnModelUtils.js @@ -0,0 +1,102 @@ +const arrayFromCsv = value => { + if (!value) return []; + return value?.split(',')?.map(item => item.trim()) ?? []; +}; + +const csvFromArray = arr => { + return arr?.join(',') ?? ''; +}; + +const bnKuskiListItemToUserConfig = values => { + const battleTypes = arrayFromCsv(values.BattleTypes); + const designers = arrayFromCsv(values.Designers); + const levelPatterns = arrayFromCsv(values.LevelPatterns); + const battleAttributes = arrayFromCsv(values.BattleAttributes); + const minDuration = values.MinDuration ?? 0; + const maxDuration = values.MaxDuration ?? 0; + return { + battleTypes, + designers, + levelPatterns, + battleAttributes, + minDuration, + maxDuration, + }; +}; + +const bnKuskiListToUserConfig = list => { + return list.map(item => bnKuskiListItemToUserConfig(item)); +}; + +const bnKuskiListsToUserConfig = values => { + const notifyList = values.notifyList + ? bnKuskiListToUserConfig(values.notifyList) + : []; + const ignoreList = values.ignoreList + ? bnKuskiListToUserConfig(values.ignoreList) + : []; + return { notifyList, ignoreList }; +}; + +/** + * Parse incoming data from the API to our local UserConfig model. + */ +export const bnKuskiToUserConfig = values => { + const kuskiIndex = values.KuskiIndex; + const userId = values.DiscordId; + const isOn = values.BnEnabled === 1; + + const notifyList = + values.BnKuskiRules?.filter(item => !item.IgnoreList) ?? []; + const ignoreList = values.BnKuskiRules?.filter(item => item.IgnoreList) ?? []; + const lists = bnKuskiListsToUserConfig({ notifyList, ignoreList }); + + return { kuskiIndex, userId, isOn, ...lists }; +}; + +const userConfigListItemToBnKuski = (values, isIgnoreList) => { + const BattleTypes = csvFromArray(values.battleTypes); + const Designers = csvFromArray(values.designers); + const LevelPatterns = csvFromArray(values.levelPatterns); + const BattleAttributes = csvFromArray(values.battleAttributes); + const MinDuration = values.minDuration ?? 0; + const MaxDuration = values.maxDuration ?? 0; + const IgnoreList = isIgnoreList ? 1 : 0; + return { + BattleTypes, + Designers, + LevelPatterns, + BattleAttributes, + MinDuration, + MaxDuration, + IgnoreList, + }; +}; + +const userConfigListToBnKuski = (list, isIgnoreList) => { + return list.map(item => userConfigListItemToBnKuski(item, isIgnoreList)); +}; + +const userConfigListsToBnKuskiRules = values => { + const notifyList = values.notifyList + ? userConfigListToBnKuski(values.notifyList, false) + : []; + const ignoreList = values.ignoreList + ? userConfigListToBnKuski(values.ignoreList, true) + : []; + return [...notifyList, ...ignoreList]; +}; + +export const userConfigToBnKuski = values => { + const KuskiIndex = values.kuskiIndex; + const DiscordId = values.userId; + const BnEnabled = values.isOn ? 1 : 0; + const notifyList = values.notifyList ?? []; + const ignoreList = values.ignoreList ?? []; + const BnKuskiRules = userConfigListsToBnKuskiRules({ + notifyList, + ignoreList, + }); + + return { KuskiIndex, DiscordId, BnEnabled, BnKuskiRules }; +}; diff --git a/src/battleNotifier/bnStore/bnStore.js b/src/battleNotifier/bnStore/bnStore.js index 15b09e6..4c0acf9 100644 --- a/src/battleNotifier/bnStore/bnStore.js +++ b/src/battleNotifier/bnStore/bnStore.js @@ -1,40 +1,49 @@ import { UserConfig } from '../userConfig'; +import { bnKuskiToUserConfig, userConfigToBnKuski } from './bnModelUtils'; const createBnStore = - ({ writeJsonFile, readJsonFile, createParentFolder, dateNow }) => - path => { - createParentFolder(path); - - const getAll = async () => { - return readJsonFile(path); + ({ api }) => + url => { + const getAllActive = async () => { + const response = await api.get(url); + const result = []; + response.data.forEach(user => { + const parsedData = bnKuskiToUserConfig(user); + result.push(parsedData); + }); + return result; }; const get = async userId => { - const bnStore = await getAll(); - const storedConfig = bnStore[userId]; - return storedConfig && UserConfig(storedConfig); + const response = await api.get(`${url}${userId}`); + const parsedData = response.data && bnKuskiToUserConfig(response.data); + return parsedData && UserConfig(parsedData); }; const set = async (userId, values) => { - const bnStore = await getAll(); - - const updatedAt = dateNow(); - const newConfig = UserConfig({ - createdAt: updatedAt, - ...bnStore[userId], - updatedAt, - ...values, - }); + const newData = UserConfig(values); + + const parsedData = userConfigToBnKuski(newData); + await api.post(`${url}${userId}`, parsedData); + }; + + const isUserLinked = async userId => { + const response = await api.get(`${url}${userId}/linked`); + return Boolean(response.data); + }; - const data = { ...bnStore, [userId]: newConfig }; - await writeJsonFile(path, data); + const toggleIsOn = async (userId, isOn) => { + const value = isOn ? 1 : 0; + await api.post(`${url}${userId}/toggle/${value}`); }; return { + url, get, set, - getAll, - path, + getAllActive, + toggleIsOn, + isUserLinked, }; }; diff --git a/src/battleNotifier/bnStore/index.js b/src/battleNotifier/bnStore/index.js index c44d3ac..d981aab 100644 --- a/src/battleNotifier/bnStore/index.js +++ b/src/battleNotifier/bnStore/index.js @@ -1,10 +1,13 @@ -import { writeJsonFile, readJsonFile } from './jsonFs'; -import { createParentFolder } from '../../fileUtils'; +import { create } from 'apisauce'; +import config from '../../config.js'; import bnStore from './bnStore'; -export default bnStore({ - writeJsonFile, - readJsonFile, - createParentFolder, - dateNow: () => new Date().toISOString(), +const api = create({ + baseURL: config.discord.bn.bnApiUrl, + headers: { + Accept: 'application/json', + 'Cache-Control': 'no-cache', + }, }); + +export default bnStore({ api }); diff --git a/src/battleNotifier/bnStore/jsonFs.js b/src/battleNotifier/bnStore/jsonFs.js deleted file mode 100644 index 63f5b4b..0000000 --- a/src/battleNotifier/bnStore/jsonFs.js +++ /dev/null @@ -1,17 +0,0 @@ -import { readFile, writeFile } from '../../fileUtils'; - -export const readJsonFile = async path => { - let result = {}; - try { - const fileHandle = await readFile(path); - result = JSON.parse(fileHandle.toString()); - } catch (error) { - result = {}; - } - - return result; -}; - -export const writeJsonFile = async (path, data) => { - await writeFile(path, JSON.stringify(data)); -}; diff --git a/src/battleNotifier/notifyBattle.js b/src/battleNotifier/notifyBattle.js index ca934d2..eff6788 100644 --- a/src/battleNotifier/notifyBattle.js +++ b/src/battleNotifier/notifyBattle.js @@ -1,4 +1,4 @@ -import { UserConfig, isSimpleLevelPattern } from './userConfig'; +import userConfig, { UserConfig, isSimpleLevelPattern } from './userConfig'; const matchesValue = (array, value) => { const matchValue = value && value.toLowerCase(); @@ -73,15 +73,15 @@ export const battleMatchesUserConfig = (battle, userConfig) => !battleMatchesConfigList(battle, userConfig.ignoreList); export const getSubscribedUserIds = async ({ battle, store }) => { - const userConfigsById = await store.getAll(); - const storedConfigs = Object.entries(userConfigsById); - - const userIds = storedConfigs.reduce((acc, [userId, storedConfig]) => { - const userConfig = UserConfig(storedConfig); + const userConfigs = await store.getAllActive(); + const userIds = []; + for (const userConfig of userConfigs) { const isSubscribed = userConfig.isOn && battleMatchesUserConfig(battle, userConfig); - return isSubscribed ? [...acc, userId] : acc; - }, []); + if (isSubscribed) { + userIds.push(userConfig.userId); + } + } return userIds; }; diff --git a/src/battleNotifier/testUtils/assertions.js b/src/battleNotifier/testUtils/assertions.js index bc9c089..a768546 100644 --- a/src/battleNotifier/testUtils/assertions.js +++ b/src/battleNotifier/testUtils/assertions.js @@ -1,16 +1,13 @@ -const firstResult = jestFn => jestFn.mock.results[0].value; +import { expect } from 'vitest'; -const expectAsyncResult = async (jestFn, expected) => { - const actual = firstResult(jestFn); - await expect(actual).resolves.toEqual(expected); -}; +export const firstResult = mockFn => mockFn.mock.results[0].value; -const expectAsyncResultProperty = async (jestFn, property, expected) => { - const actual = firstResult(jestFn); - await expect(actual).resolves.toHaveProperty(property, expected); +export const expectAsyncResult = async (mockFn, expected) => { + const actual = firstResult(mockFn); + await expect(actual).toEqual(expected); }; -module.exports = { - expectAsyncResult, - expectAsyncResultProperty, +export const expectAsyncResultProperty = async (mockFn, property, expected) => { + const actual = firstResult(mockFn); + await expect(actual).toHaveProperty(property, expected); }; diff --git a/src/battleNotifier/testUtils/index.js b/src/battleNotifier/testUtils/index.js index 8db80c2..a98e02c 100644 --- a/src/battleNotifier/testUtils/index.js +++ b/src/battleNotifier/testUtils/index.js @@ -1,6 +1,4 @@ -const mocks = require('./mocks'); -const userConfigsExample1 = require('./userConfigsExample1'); -const userConfigs = require('./userConfigs'); -const assertions = require('./assertions'); - -module.exports = { ...mocks, userConfigs, userConfigsExample1, ...assertions }; +export * from './mocks'; +export { default as userConfigsExample1 } from './userConfigsExample1'; +export { default as userConfigs } from './userConfigs'; +export * from './assertions'; diff --git a/src/battleNotifier/testUtils/mocks.js b/src/battleNotifier/testUtils/mocks.js index 7cf3c30..bb99983 100644 --- a/src/battleNotifier/testUtils/mocks.js +++ b/src/battleNotifier/testUtils/mocks.js @@ -1,35 +1,53 @@ -const mockStore = userConfigs => { +import { vi } from 'vitest'; + +export const mockStore = userConfigs => { + return { + get: vi.fn(userId => Promise.resolve(userConfigs[userId])), + set: vi.fn(() => Promise.resolve()), + getAllActive: vi.fn(() => + Promise.resolve( + Object.entries(userConfigs).map(([userId, config]) => ({ + ...config, + userId: userId + '', + })), + ), + ), + toggleIsOn: vi.fn(() => Promise.resolve()), + isUserLinked: vi.fn(() => Promise.resolve()), + }; +}; + +export const mockServerApi = () => { return { - get: jest.fn(id => Promise.resolve(userConfigs[id])), - set: jest.fn(() => Promise.resolve()), - getAll: jest.fn(() => Promise.resolve(userConfigs)), + get: vi.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), }; }; -const mockMessage = ({ author, channel, content }) => { +export const mockMessage = ({ author, channel, content }) => { return { content, author, - send: jest.fn(() => Promise.resolve({ channel })), - react: jest.fn(() => Promise.resolve()), + send: vi.fn(() => Promise.resolve({ channel })), + react: vi.fn(() => Promise.resolve()), }; }; -const mockSendMessage = () => - jest.fn(message => Promise.resolve(mockMessage({ content: message }))); +export const mockSendMessage = () => + vi.fn(message => Promise.resolve(mockMessage({ content: message }))); -const mockUser = (id, username = 'RandomUser') => { +export const mockUser = (id, username = 'RandomUser') => { return { id, username, send: mockSendMessage() }; }; -const mockChannel = ({ userReply }) => { +export const mockChannel = ({ userReply }) => { return { - readUserMessage: jest.fn(() => Promise.resolve(userReply)), + readUserMessage: vi.fn(() => Promise.resolve(userReply)), send: mockSendMessage(), }; }; -const mockBattle = ({ +export const mockBattle = ({ battleType = '', designer = '', level = '', @@ -38,5 +56,3 @@ const mockBattle = ({ }) => { return { battleType, designer, level, durationMinutes, ...attrs }; }; - -module.exports = { mockStore, mockUser, mockMessage, mockChannel, mockBattle }; diff --git a/src/battleNotifier/testUtils/userConfigs.js b/src/battleNotifier/testUtils/userConfigs.js index f3cd8fb..df5e087 100644 --- a/src/battleNotifier/testUtils/userConfigs.js +++ b/src/battleNotifier/testUtils/userConfigs.js @@ -1,4 +1,4 @@ -const userConfigsExample1 = require('./userConfigsExample1'); +import userConfigsExample1 from './userConfigsExample1'; const userConfigs = { typesAndDesigners: userConfigsExample1['1'], @@ -14,4 +14,4 @@ const userConfigs = { all: userConfigsExample1['9'], }; -module.exports = userConfigs; +export default userConfigs; diff --git a/src/battleNotifier/testUtils/userConfigsExample1.js b/src/battleNotifier/testUtils/userConfigsExample1.js index 4fca75e..2422b84 100644 --- a/src/battleNotifier/testUtils/userConfigsExample1.js +++ b/src/battleNotifier/testUtils/userConfigsExample1.js @@ -1,7 +1,5 @@ -module.exports = { - '1': { - createdAt: '2020-09-11T20:43:03.537Z', - updatedAt: '2020-09-11T21:22:50.119Z', +export default { + 1: { isOn: true, notifyList: [ { @@ -14,11 +12,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Kopaka', }, - '2': { - createdAt: '2020-09-11T20:47:49.039Z', - updatedAt: '2020-09-12T19:07:37.900Z', + 2: { isOn: true, notifyList: [ { @@ -39,9 +34,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Pab', }, - '3': { + 3: { isOn: false, notifyList: [ { @@ -54,11 +48,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Sla', }, - '4': { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', + 4: { isOn: true, notifyList: [ { @@ -80,21 +71,13 @@ module.exports = { maxDuration: 0, }, ], - username: 'Bene', }, - '5': { - createdAt: '2020-09-15T17:37:11.563Z', - updatedAt: '2020-09-15T19:37:39.237Z', + 5: { isOn: true, notifyList: [], ignoreList: [], - username: 'Markku', - minDuration: 0, - maxDuration: 0, }, - '6': { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', + 6: { isOn: true, notifyList: [ { @@ -107,11 +90,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Zero', }, - '7': { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', + 7: { isOn: true, notifyList: [ { @@ -124,11 +104,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Zweq', }, - '8': { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', + 8: { isOn: true, notifyList: [ { @@ -141,11 +118,8 @@ module.exports = { }, ], ignoreList: [], - username: 'Barryp', }, - '9': { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', + 9: { isOn: true, notifyList: [ { @@ -158,6 +132,5 @@ module.exports = { }, ], ignoreList: [], - username: 'Pawq', }, }; diff --git a/src/battleNotifier/userConfig/UserConfig.js b/src/battleNotifier/userConfig/UserConfig.js index 1baf42e..5f78350 100644 --- a/src/battleNotifier/userConfig/UserConfig.js +++ b/src/battleNotifier/userConfig/UserConfig.js @@ -26,13 +26,12 @@ export const UserConfigLists = values => { }; export const UserConfig = values => { - const createdAt = values.createdAt || ''; - const updatedAt = values.updatedAt || ''; - const isOn = values.isOn !== undefined ? values.isOn : true; + const kuskiIndex = values.kuskiIndex; + const userId = values.userId; + const isOn = values.isOn !== undefined ? values.isOn : false; const lists = UserConfigLists({ notifyList: values.notifyList, ignoreList: values.ignoreList, }); - const username = values.username || ''; - return { createdAt, updatedAt, isOn, ...lists, username }; + return { kuskiIndex, userId, isOn, ...lists }; }; diff --git a/src/battleNotifier/userConfig/__tests__/UserConfig.test.js b/src/battleNotifier/userConfig/__tests__/UserConfig.test.js index 05f797f..a9aa773 100644 --- a/src/battleNotifier/userConfig/__tests__/UserConfig.test.js +++ b/src/battleNotifier/userConfig/__tests__/UserConfig.test.js @@ -1,20 +1,17 @@ -const { UserConfig, UserConfigLists } = require('../UserConfig'); +import { UserConfig, UserConfigLists } from '../UserConfig'; describe('test user config', () => { test('empty object returns empty config', () => { const actual = UserConfig({}); const expected = { - createdAt: '', - updatedAt: '', - isOn: true, - username: '', + isOn: false, notifyList: [], ignoreList: [], }; expect(actual).toEqual(expected); }); - test('filled object reutsn filled config', () => { + test('filled object returns filled config', () => { const values = { battleAttributes: ['seeOthers', 'allowStarter'], battleTypes: ['Normal', 'First Finish'], @@ -24,18 +21,12 @@ describe('test user config', () => { minDuration: 50, }; const actual = UserConfig({ - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', isOn: true, - username: 'Pab', notifyList: [values], ignoreList: [values], }); const expected = { - createdAt: '2020-09-15T16:49:59.977Z', - updatedAt: '2020-09-15T16:52:33.354Z', isOn: true, - username: 'Pab', notifyList: [values], ignoreList: [values], }; diff --git a/src/battleNotifier/userConfig/__tests__/userConfigFormatter.test.js b/src/battleNotifier/userConfig/__tests__/userConfigFormatter.test.js index 2be1e74..06fb3e7 100644 --- a/src/battleNotifier/userConfig/__tests__/userConfigFormatter.test.js +++ b/src/battleNotifier/userConfig/__tests__/userConfigFormatter.test.js @@ -1,6 +1,6 @@ -const { keywords } = require('../../bnCommand/config'); -const { formatter, UserConfigLists } = require('../../userConfig'); -const { userConfigs } = require('../../testUtils'); +import { keywords } from '../../bnCommand/config'; +import { formatter, UserConfigLists } from '../../userConfig'; +import userConfigs from '../../testUtils/userConfigs'; const userConfigFormatter = formatter({ keywords }); diff --git a/src/battleNotifier/userConfig/__tests__/userConfigParser.test.js b/src/battleNotifier/userConfig/__tests__/userConfigParser.test.js index 356da68..3e9235a 100644 --- a/src/battleNotifier/userConfig/__tests__/userConfigParser.test.js +++ b/src/battleNotifier/userConfig/__tests__/userConfigParser.test.js @@ -1,7 +1,7 @@ -const { keywords } = require('../../bnCommand/config'); -const { bnBattleTypes, bnBattleAttributes } = require('../../constants'); -const parser = require('../userConfigParser'); -const { UserConfigLists } = require('../UserConfig'); +import { keywords } from '../../bnCommand/config'; +import { bnBattleTypes, bnBattleAttributes } from '../../constants'; +import parser from '../userConfigParser'; +import { UserConfigLists } from '../UserConfig'; const userConfigParser = parser({ bnBattleTypes, diff --git a/src/battleNotifier/userConfig/userConfigFormatter.js b/src/battleNotifier/userConfig/userConfigFormatter.js index fd92181..81196cf 100644 --- a/src/battleNotifier/userConfig/userConfigFormatter.js +++ b/src/battleNotifier/userConfig/userConfigFormatter.js @@ -1,15 +1,15 @@ const commaSeparator = ', '; -const cammelCaseRegexp = /([a-z])([A-Z])/g; +const camelCaseRegexp = /([a-z])([A-Z])/g; -const separateCammelCase = value => value.replace(cammelCaseRegexp, '$1 $2'); +const separateCamelCase = value => value.replace(camelCaseRegexp, '$1 $2'); const capitalize = string => { return string.charAt(0).toUpperCase() + string.slice(1); }; const formatValues = (values, separator, format = value => value) => { - const cleanedValeus = values.filter(value => Boolean(value)); - return cleanedValeus.map(value => format(value)).join(separator); + const cleanedValues = values.filter(value => Boolean(value)); + return cleanedValues.map(value => format(value)).join(separator); }; export const areUserConfigListsEmpty = userConfig => { @@ -56,7 +56,7 @@ const userConfigFormatter = ({ keywords }) => { const battleAttributes = formatValues( item.battleAttributes, commaSeparator, - value => separateCammelCase(value).toLowerCase(), + value => separateCamelCase(value).toLowerCase(), ); const formattedAttrs = `(${battleAttributes})`; result = result ? `${result} ${formattedAttrs}` : formattedAttrs; diff --git a/src/config.defaults.js b/src/config.defaults.js index 1d52324..3d2bbe9 100644 --- a/src/config.defaults.js +++ b/src/config.defaults.js @@ -29,7 +29,7 @@ export default { }, prefix: '!', bn: { - storePath: '../bn/bn.store.json', + bnApiUrl: 'https://test.elma.online/api/player/bn/', logsPath: '../bn/logs/', fallbackChannelId: '219884674330132480', }, diff --git a/src/discord.js b/src/discord.js index e3f672c..8714920 100644 --- a/src/discord.js +++ b/src/discord.js @@ -10,10 +10,13 @@ import notifMessage from './notifications'; const client = new Discord.Client(); const isProdEnv = process.env.NODE_ENV === 'production'; -const { storePath, logsPath, fallbackChannelId } = config.discord.bn; +const { bnApiUrl, logsPath, fallbackChannelId } = config.discord.bn; -const bnStorePath = isProdEnv ? storePath : './bn/bn.store.json'; -const battleNotifier = createBN({ bnStorePath, client, fallbackChannelId }); +const battleNotifier = createBN({ + apiUrl: bnApiUrl, + client, + fallbackChannelId, +}); const bnLogsPath = isProdEnv ? logsPath : './bn/'; logger.initialize(bnLogsPath); diff --git a/src/discordTester.js b/src/discordTester.js index d841e53..793ed22 100644 --- a/src/discordTester.js +++ b/src/discordTester.js @@ -1,3 +1,3 @@ -const { discord } = require('./discord'); +import { discord } from './discord'; discord(); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..7bf8148 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [], + test: { + include: ['**/*.test.js'], + globals: true, + }, +}); diff --git a/yarn.lock b/yarn.lock index f250f34..dab2493 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,160 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm64@npm:0.17.19" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm@npm:0.17.19" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-x64@npm:0.17.19" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-arm64@npm:0.17.19" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-x64@npm:0.17.19" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-arm64@npm:0.17.19" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-x64@npm:0.17.19" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm64@npm:0.17.19" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm@npm:0.17.19" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ia32@npm:0.17.19" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-loong64@npm:0.17.19" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-mips64el@npm:0.17.19" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ppc64@npm:0.17.19" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-riscv64@npm:0.17.19" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-s390x@npm:0.17.19" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-x64@npm:0.17.19" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/netbsd-x64@npm:0.17.19" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/openbsd-x64@npm:0.17.19" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/sunos-x64@npm:0.17.19" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-arm64@npm:0.17.19" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-ia32@npm:0.17.19" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-x64@npm:0.17.19" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^1.3.1": version: 1.3.1 resolution: "@eslint/eslintrc@npm:1.3.1" @@ -79,6 +233,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.13": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -133,6 +294,83 @@ __metadata: languageName: node linkType: hard +"@types/chai-subset@npm:^1.3.3": + version: 1.3.3 + resolution: "@types/chai-subset@npm:1.3.3" + dependencies: + "@types/chai": "*" + checksum: 4481da7345022995f5a105e6683744f7203d2c3d19cfe88d8e17274d045722948abf55e0adfd97709e0f043dade37a4d4e98cd4c660e2e8a14f23e6ecf79418f + languageName: node + linkType: hard + +"@types/chai@npm:*, @types/chai@npm:^4.3.5": + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.3.1 + resolution: "@types/node@npm:20.3.1" + checksum: 63a393ab6d947be17320817b35d7277ef03728e231558166ed07ee30b09fd7c08861be4d746f10fdc63ca7912e8cd023939d4eab887ff6580ff704ff24ed810c + languageName: node + linkType: hard + +"@vitest/expect@npm:0.32.2": + version: 0.32.2 + resolution: "@vitest/expect@npm:0.32.2" + dependencies: + "@vitest/spy": 0.32.2 + "@vitest/utils": 0.32.2 + chai: ^4.3.7 + checksum: e16ef72d6ee1db3f5955763962de0322cbc1e29f9c13e8136a7f6fa7c6d238df3a9ce5760e6dacadb7edc740c6148ba2a98a4d9d6f0f46ae77c1404b2977c2a0 + languageName: node + linkType: hard + +"@vitest/runner@npm:0.32.2": + version: 0.32.2 + resolution: "@vitest/runner@npm:0.32.2" + dependencies: + "@vitest/utils": 0.32.2 + concordance: ^5.0.4 + p-limit: ^4.0.0 + pathe: ^1.1.0 + checksum: 6ce62f6f30721cc6ae79317b8934edba36e266c7fefc4be97cc8c16c2878979208e121fd847282f61fd5a7d03e181d6cb46fa839f8f31b83ac77ea618ecb1b9c + languageName: node + linkType: hard + +"@vitest/snapshot@npm:0.32.2": + version: 0.32.2 + resolution: "@vitest/snapshot@npm:0.32.2" + dependencies: + magic-string: ^0.30.0 + pathe: ^1.1.0 + pretty-format: ^27.5.1 + checksum: 94f12fadec50815de62a82e4d344b13b72f8e58f7492f3441d5b4b28d3a557dea32cbd4fa687ff58cc549241c77929aed3a0d5f64bac1603a65f9c7ead3b2ce7 + languageName: node + linkType: hard + +"@vitest/spy@npm:0.32.2": + version: 0.32.2 + resolution: "@vitest/spy@npm:0.32.2" + dependencies: + tinyspy: ^2.1.0 + checksum: e516872c792fe039806a9c4b858f166ddbc14320b1d4c3daa6c562f3f15991df87465107379f011fbd658911e34a68cf2349c1f77d82e08fd0f6a0f20bc71b1b + languageName: node + linkType: hard + +"@vitest/utils@npm:0.32.2": + version: 0.32.2 + resolution: "@vitest/utils@npm:0.32.2" + dependencies: + diff-sequences: ^29.4.3 + loupe: ^2.3.6 + pretty-format: ^27.5.1 + checksum: d4bc065875edf235b0c6b1f8648c10e9655ac45f5c7e89dc892b02e7f96f9bd58a97a4c3fcb3bc7026d6b350e752fb02cb7dcf5690c40792ff361fb6ac433e66 + languageName: node + linkType: hard + "abab@npm:^1.0.3": version: 1.0.4 resolution: "abab@npm:1.0.4" @@ -184,6 +422,13 @@ __metadata: languageName: node linkType: hard +"acorn-walk@npm:^8.2.0": + version: 8.2.0 + resolution: "acorn-walk@npm:8.2.0" + checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 + languageName: node + linkType: hard + "acorn@npm:^4.0.4": version: 4.0.13 resolution: "acorn@npm:4.0.13" @@ -202,6 +447,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.9.0 + resolution: "acorn@npm:8.9.0" + bin: + acorn: bin/acorn + checksum: 25dfb94952386ecfb847e61934de04a4e7c2dc21c2e700fc4e2ef27ce78cb717700c4c4f279cd630bb4774948633c3859fc16063ec8573bda4568e0a312e6744 + languageName: node + linkType: hard + "add@npm:^2.0.6": version: 2.0.6 resolution: "add@npm:2.0.6" @@ -267,6 +521,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" @@ -277,6 +538,15 @@ __metadata: languageName: node linkType: hard +"apisauce@npm:^3.0.1": + version: 3.0.1 + resolution: "apisauce@npm:3.0.1" + dependencies: + axios: ^1.4.0 + checksum: fbf689442b17e1bfcbce9aa105b4ca971ff5b1a264ae30d2ca33df2d074d5ea4a498070fb2be0d14000bb5ff2858351cd67bc20621207b1061cc5fa7113cd6e2 + languageName: node + linkType: hard + "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -338,6 +608,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -359,6 +636,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.4.0": + version: 1.4.0 + resolution: "axios@npm:1.4.0" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 7fb6a4313bae7f45e89d62c70a800913c303df653f19eafec88e56cea2e3821066b8409bc68be1930ecca80e861c52aa787659df0ffec6ad4d451c7816b9386b + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -410,6 +698,13 @@ __metadata: languageName: node linkType: hard +"blueimp-md5@npm:^2.10.0": + version: 2.19.0 + resolution: "blueimp-md5@npm:2.19.0" + checksum: 28095dcbd2c67152a2938006e8d7c74c3406ba6556071298f872505432feb2c13241b0476644160ee0a5220383ba94cb8ccdac0053b51f68d168728f9c382530 + languageName: node + linkType: hard + "body-parser@npm:1.20.0, body-parser@npm:^1.20.0": version: 1.20.0 resolution: "body-parser@npm:1.20.0" @@ -465,6 +760,13 @@ __metadata: languageName: node linkType: hard +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 45a2496a9443abbe7f52a49b22fbe51b1905eff46e03fd5e6c98e3f85077be3f8949685a1849b1a9cd2bc3e5567dfebcf64f01ce01847baf918f1b37c839791a + languageName: node + linkType: hard + "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -515,6 +817,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.7": + version: 4.3.7 + resolution: "chai@npm:4.3.7" + dependencies: + assertion-error: ^1.1.0 + check-error: ^1.0.2 + deep-eql: ^4.1.2 + get-func-name: ^2.0.0 + loupe: ^2.3.1 + pathval: ^1.1.1 + type-detect: ^4.0.5 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c + languageName: node + linkType: hard + "chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -525,6 +842,13 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.2": + version: 1.0.2 + resolution: "check-error@npm:1.0.2" + checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e + languageName: node + linkType: hard + "chokidar@npm:^3.5.2": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -616,6 +940,22 @@ __metadata: languageName: node linkType: hard +"concordance@npm:^5.0.4": + version: 5.0.4 + resolution: "concordance@npm:5.0.4" + dependencies: + date-time: ^3.1.0 + esutils: ^2.0.3 + fast-diff: ^1.2.0 + js-string-escape: ^1.0.1 + lodash: ^4.17.15 + md5-hex: ^3.0.1 + semver: ^7.3.2 + well-known-symbols: ^2.0.0 + checksum: 749153ba711492feb7c3d2f5bb04c107157440b3e39509bd5dd19ee7b3ac751d1e4cd75796d9f702e0a713312dbc661421c68aa4a2c34d5f6d91f47e3a1c64a6 + languageName: node + linkType: hard + "console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -727,6 +1067,15 @@ __metadata: languageName: node linkType: hard +"date-time@npm:^3.1.0": + version: 3.1.0 + resolution: "date-time@npm:3.1.0" + dependencies: + time-zone: ^1.0.0 + checksum: f9cfcd1b15dfeabab15c0b9d18eb9e4e2d9d4371713564178d46a8f91ad577a290b5178b80050718d02d9c0cf646f8a875011e12d1ed05871e9f72c72c8a8fe6 + languageName: node + linkType: hard + "debug@npm:2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -736,7 +1085,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -757,6 +1106,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: ^4.0.0 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f + languageName: node + linkType: hard + "deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -799,6 +1157,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.4.3": + version: 29.4.3 + resolution: "diff-sequences@npm:29.4.3" + checksum: 28b265e04fdddcf7f9f814effe102cc95a9dec0564a579b5aed140edb24fc345c611ca52d76d725a3cab55d3888b915b5e8a4702e0f6058968a90fa5f41fcde7 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -944,6 +1309,83 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.17.5": + version: 0.17.19 + resolution: "esbuild@npm:0.17.19" + dependencies: + "@esbuild/android-arm": 0.17.19 + "@esbuild/android-arm64": 0.17.19 + "@esbuild/android-x64": 0.17.19 + "@esbuild/darwin-arm64": 0.17.19 + "@esbuild/darwin-x64": 0.17.19 + "@esbuild/freebsd-arm64": 0.17.19 + "@esbuild/freebsd-x64": 0.17.19 + "@esbuild/linux-arm": 0.17.19 + "@esbuild/linux-arm64": 0.17.19 + "@esbuild/linux-ia32": 0.17.19 + "@esbuild/linux-loong64": 0.17.19 + "@esbuild/linux-mips64el": 0.17.19 + "@esbuild/linux-ppc64": 0.17.19 + "@esbuild/linux-riscv64": 0.17.19 + "@esbuild/linux-s390x": 0.17.19 + "@esbuild/linux-x64": 0.17.19 + "@esbuild/netbsd-x64": 0.17.19 + "@esbuild/openbsd-x64": 0.17.19 + "@esbuild/sunos-x64": 0.17.19 + "@esbuild/win32-arm64": 0.17.19 + "@esbuild/win32-ia32": 0.17.19 + "@esbuild/win32-x64": 0.17.19 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: ac11b1a5a6008e4e37ccffbd6c2c054746fc58d0ed4a2f9ee643bd030cfcea9a33a235087bc777def8420f2eaafb3486e76adb7bdb7241a9143b43a69a10afd8 + languageName: node + linkType: hard + "escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -1140,7 +1582,7 @@ __metadata: languageName: node linkType: hard -"esutils@npm:^2.0.2": +"esutils@npm:^2.0.2, esutils@npm:^2.0.3": version: 2.0.3 resolution: "esutils@npm:2.0.3" checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 @@ -1235,6 +1677,13 @@ __metadata: languageName: node linkType: hard +"fast-diff@npm:^1.2.0": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3 + languageName: node + linkType: hard + "fast-glob@npm:^3.2.9": version: 3.2.11 resolution: "fast-glob@npm:3.2.11" @@ -1331,6 +1780,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.0": + version: 1.15.2 + resolution: "follow-redirects@npm:1.15.2" + peerDependenciesMeta: + debug: + optional: true + checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -1338,6 +1797,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.8 + mime-types: ^2.1.12 + checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c + languageName: node + linkType: hard + "form-data@npm:~2.3.2": version: 2.3.3 resolution: "form-data@npm:2.3.3" @@ -1440,6 +1910,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.0": + version: 2.0.0 + resolution: "get-func-name@npm:2.0.0" + checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2": version: 1.1.2 resolution: "get-intrinsic@npm:1.1.2" @@ -1861,6 +2338,13 @@ __metadata: languageName: node linkType: hard +"js-string-escape@npm:^1.0.1": + version: 1.0.1 + resolution: "js-string-escape@npm:1.0.1" + checksum: f11e0991bf57e0c183b55c547acec85bd2445f043efc9ea5aa68b41bd2a3e7d3ce94636cb233ae0d84064ba4c1a505d32e969813c5b13f81e7d4be12c59256fe + languageName: node + linkType: hard + "js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -1948,6 +2432,13 @@ __metadata: languageName: node linkType: hard +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + "jsonfile@npm:~1.1.0": version: 1.1.1 resolution: "jsonfile@npm:1.1.1" @@ -1987,6 +2478,13 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^0.4.3": + version: 0.4.3 + resolution: "local-pkg@npm:0.4.3" + checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55 + languageName: node + linkType: hard + "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -2010,6 +2508,22 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.15": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 + languageName: node + linkType: hard + +"loupe@npm:^2.3.1, loupe@npm:^2.3.6": + version: 2.3.6 + resolution: "loupe@npm:2.3.6" + dependencies: + get-func-name: ^2.0.0 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -2026,6 +2540,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.0": + version: 0.30.0 + resolution: "magic-string@npm:0.30.0" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.13 + checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d + languageName: node + linkType: hard + "make-fetch-happen@npm:^10.0.3": version: 10.2.1 resolution: "make-fetch-happen@npm:10.2.1" @@ -2050,6 +2573,15 @@ __metadata: languageName: node linkType: hard +"md5-hex@npm:^3.0.1": + version: 3.0.1 + resolution: "md5-hex@npm:3.0.1" + dependencies: + blueimp-md5: ^2.10.0 + checksum: 6799a19e8bdd3e0c2861b94c1d4d858a89220488d7885c1fa236797e367d0c2e5f2b789e05309307083503f85be3603a9686a5915568a473137d6b4117419cc2 + languageName: node + linkType: hard + "media-typer@npm:0.3.0": version: 0.3.0 resolution: "media-typer@npm:0.3.0" @@ -2217,6 +2749,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.2.0": + version: 1.4.0 + resolution: "mlly@npm:1.4.0" + dependencies: + acorn: ^8.9.0 + pathe: ^1.1.1 + pkg-types: ^1.0.3 + ufo: ^1.1.2 + checksum: ebf2e2b5cfb4c6e45e8d0bbe82710952247023f12626cb0997c41b1bb6e57c8b6fc113aa709228ad511382ab0b4eebaab759806be0578093b3635d3e940bd63b + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -2238,6 +2782,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.6": + version: 3.3.6 + resolution: "nanoid@npm:3.3.6" + bin: + nanoid: bin/nanoid.cjs + checksum: 7d0eda657002738aa5206107bd0580aead6c95c460ef1bdd0b1a87a9c7ae6277ac2e9b945306aaa5b32c6dcb7feaf462d0f552e7f8b5718abfc6ead5c94a71b3 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -2300,6 +2853,7 @@ __metadata: resolution: "nodejs-express-api-starter-kit@workspace:." dependencies: add: ^2.0.6 + apisauce: ^3.0.1 bbcode-to-markdown: ^1.0.3 body-parser: ^1.20.0 cors: ^2.8.5 @@ -2312,6 +2866,7 @@ __metadata: lodash-es: ^4.17.21 nodemon: ^2.0.19 prettier: 2.7.1 + vitest: ^0.32.2 yarn: ^1.22.19 languageName: unknown linkType: soft @@ -2467,6 +3022,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: ^1.0.0 + checksum: 01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b + languageName: node + linkType: hard + "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -2543,6 +3107,20 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^1.1.0, pathe@npm:^1.1.1": + version: 1.1.1 + resolution: "pathe@npm:1.1.1" + checksum: 34ab3da2e5aa832ebc6a330ffe3f73d7ba8aec6e899b53b8ec4f4018de08e40742802deb12cf5add9c73b7bf719b62c0778246bd376ca62b0fb23e0dde44b759 + languageName: node + linkType: hard + +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6 + languageName: node + linkType: hard + "performance-now@npm:^2.1.0": version: 2.1.0 resolution: "performance-now@npm:2.1.0" @@ -2550,6 +3128,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -2557,6 +3142,28 @@ __metadata: languageName: node linkType: hard +"pkg-types@npm:^1.0.3": + version: 1.0.3 + resolution: "pkg-types@npm:1.0.3" + dependencies: + jsonc-parser: ^3.2.0 + mlly: ^1.2.0 + pathe: ^1.1.0 + checksum: 4b305c834b912ddcc8a0fe77530c0b0321fe340396f84cbb87aecdbc126606f47f2178f23b8639e71a4870f9631c7217aef52ffed0ae17ea2dbbe7e43d116a6e + languageName: node + linkType: hard + +"postcss@npm:^8.4.23": + version: 8.4.24 + resolution: "postcss@npm:8.4.24" + dependencies: + nanoid: ^3.3.6 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 814e2126dacfea313588eda09cc99a9b4c26ec55c059188aa7a916d20d26d483483106dc5ff9e560731b59f45c5bb91b945dfadc670aed875cc90ddbbf4e787d + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -2589,6 +3196,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^27.5.1": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: ^5.0.1 + ansi-styles: ^5.0.0 + react-is: ^17.0.1 + checksum: cf610cffcb793885d16f184a62162f2dd0df31642d9a18edf4ca298e909a8fe80bdbf556d5c9573992c102ce8bf948691da91bf9739bee0ffb6e79c8a8a6e088 + languageName: node + linkType: hard + "prism-media@npm:^1.2.2": version: 1.3.4 resolution: "prism-media@npm:1.3.4" @@ -2637,6 +3255,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -2700,6 +3325,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^17.0.1": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8 + languageName: node + linkType: hard + "readable-stream@npm:^3.1.1, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -2796,6 +3428,20 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^3.21.0": + version: 3.25.2 + resolution: "rollup@npm:3.25.2" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 80034ec8d71022b816cd31c24455d7b06ab8d8ff49ab9e583ceb03e2ebd0f6ad00824c6257fa4f8d12a12c2dceda999cec9509c1fff956e8a707b0add1bf2d9c + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -2835,6 +3481,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.2": + version: 7.5.3 + resolution: "semver@npm:7.5.3" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 + languageName: node + linkType: hard + "semver@npm:^7.3.5": version: 7.3.7 resolution: "semver@npm:7.3.7" @@ -2936,6 +3593,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 8aa5a98640ca09fe00d74416eca97551b3e42991614a3d1b824b115fc1401543650914f651ab1311518177e4d297e80b953f4cd4cd7ea1eabe824e8f2091de01 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -2987,6 +3651,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.0.2": + version: 1.0.2 + resolution: "source-map-js@npm:1.0.2" + checksum: c049a7fc4deb9a7e9b481ae3d424cc793cb4845daa690bc5a05d428bf41bf231ced49b4cf0c9e77f9d42fdb3d20d6187619fc586605f5eabe995a316da8d377c + languageName: node + linkType: hard + "source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" @@ -3024,6 +3695,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -3031,6 +3709,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.3.2": + version: 3.3.3 + resolution: "std-env@npm:3.3.3" + checksum: 6665f6d8bd63aae432d3eb9abbd7322847ad0d902603e6dce1e8051b4f42ceeb4f7f96a4faf70bb05ce65ceee2dc982502b701575c8a58b1bfad29f3dbb19f81 + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -3067,6 +3752,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-literal@npm:1.0.1" + dependencies: + acorn: ^8.8.2 + checksum: ab40496820f02220390d95cdd620a997168efb69d5bd7d180bc4ef83ca562a95447843d8c7c88b8284879a29cf4eedc89d8001d1e098c1a1e23d12a9c755dff4 + languageName: node + linkType: hard + "supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -3113,6 +3807,34 @@ __metadata: languageName: node linkType: hard +"time-zone@npm:^1.0.0": + version: 1.0.0 + resolution: "time-zone@npm:1.0.0" + checksum: e46f5a69b8c236dcd8e91e29d40d4e7a3495ed4f59888c3f84ce1d9678e20461421a6ba41233509d47dd94bc18f1a4377764838b21b584663f942b3426dcbce8 + languageName: node + linkType: hard + +"tinybench@npm:^2.5.0": + version: 2.5.0 + resolution: "tinybench@npm:2.5.0" + checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821 + languageName: node + linkType: hard + +"tinypool@npm:^0.5.0": + version: 0.5.0 + resolution: "tinypool@npm:0.5.0" + checksum: 4e0dfd8f28666d541c1d92304222edc4613f05d74fe2243c8520d466a2cc6596011a7072c1c41a7de7522351b82fda07e8038198e8f43673d8d69401c5903f8c + languageName: node + linkType: hard + +"tinyspy@npm:^2.1.0": + version: 2.1.1 + resolution: "tinyspy@npm:2.1.1" + checksum: cfe669803a7f11ca912742b84c18dcc4ceecaa7661c69bc5eb608a8a802d541c48aba220df8929f6c8cd09892ad37cb5ba5958ddbbb57940e91d04681d3cee73 + languageName: node + linkType: hard + "to-markdown@npm:^3.0.4": version: 3.1.1 resolution: "to-markdown@npm:3.1.1" @@ -3208,6 +3930,13 @@ __metadata: languageName: node linkType: hard +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 + languageName: node + linkType: hard + "type-fest@npm:^0.20.2": version: 0.20.2 resolution: "type-fest@npm:0.20.2" @@ -3225,6 +3954,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.1.2": + version: 1.1.2 + resolution: "ufo@npm:1.1.2" + checksum: 83c940a6a23b6d4fc0cd116265bb5dcf88ab34a408ad9196e413270ca607a4781c09b547dc518f43caee128a096f20fe80b5a0e62b4bcc0a868619896106d048 + languageName: node + linkType: hard + "undefsafe@npm:^2.0.5": version: 2.0.5 resolution: "undefsafe@npm:2.0.5" @@ -3307,6 +4043,120 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:0.32.2": + version: 0.32.2 + resolution: "vite-node@npm:0.32.2" + dependencies: + cac: ^6.7.14 + debug: ^4.3.4 + mlly: ^1.2.0 + pathe: ^1.1.0 + picocolors: ^1.0.0 + vite: ^3.0.0 || ^4.0.0 + bin: + vite-node: vite-node.mjs + checksum: 9573fe707d56a0e4d4e1b1f5e23c9e0e7712f86561b63908f42d2fcd6a8097716a722127da7483f8ea12505f61f876e4325fd90505be8f54a1f1330836f9cdc6 + languageName: node + linkType: hard + +"vite@npm:^3.0.0 || ^4.0.0": + version: 4.3.9 + resolution: "vite@npm:4.3.9" + dependencies: + esbuild: ^0.17.5 + fsevents: ~2.3.2 + postcss: ^8.4.23 + rollup: ^3.21.0 + peerDependencies: + "@types/node": ">= 14" + less: "*" + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 8c45a516278d1e0425fac00c0877336790f71484a851a318346a70e0d2aef9f3b9651deb2f9f002c791ceb920eda7d6a3cda753bdefd657321c99f448b02dd25 + languageName: node + linkType: hard + +"vitest@npm:^0.32.2": + version: 0.32.2 + resolution: "vitest@npm:0.32.2" + dependencies: + "@types/chai": ^4.3.5 + "@types/chai-subset": ^1.3.3 + "@types/node": "*" + "@vitest/expect": 0.32.2 + "@vitest/runner": 0.32.2 + "@vitest/snapshot": 0.32.2 + "@vitest/spy": 0.32.2 + "@vitest/utils": 0.32.2 + acorn: ^8.8.2 + acorn-walk: ^8.2.0 + cac: ^6.7.14 + chai: ^4.3.7 + concordance: ^5.0.4 + debug: ^4.3.4 + local-pkg: ^0.4.3 + magic-string: ^0.30.0 + pathe: ^1.1.0 + picocolors: ^1.0.0 + std-env: ^3.3.2 + strip-literal: ^1.0.1 + tinybench: ^2.5.0 + tinypool: ^0.5.0 + vite: ^3.0.0 || ^4.0.0 + vite-node: 0.32.2 + why-is-node-running: ^2.2.2 + peerDependencies: + "@edge-runtime/vm": "*" + "@vitest/browser": "*" + "@vitest/ui": "*" + happy-dom: "*" + jsdom: "*" + playwright: "*" + safaridriver: "*" + webdriverio: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + bin: + vitest: vitest.mjs + checksum: 6679edb1ed29a02d7df9636f1f79b82a11d8fd67507cfaf3b2c1fdf509c6b6f328f64fd9313fc5889d58d839300c5462d6c97f353a0b6404c9e05c2368b3ba9a + languageName: node + linkType: hard + "void-elements@npm:^2.0.1": version: 2.0.1 resolution: "void-elements@npm:2.0.1" @@ -3328,6 +4178,13 @@ __metadata: languageName: node linkType: hard +"well-known-symbols@npm:^2.0.0": + version: 2.0.0 + resolution: "well-known-symbols@npm:2.0.0" + checksum: 4f54bbc3012371cb4d228f436891b8e7536d34ac61a57541890257e96788608e096231e0121ac24d08ef2f908b3eb2dc0adba35023eaeb2a7df655da91415402 + languageName: node + linkType: hard + "whatwg-encoding@npm:^1.0.1": version: 1.0.5 resolution: "whatwg-encoding@npm:1.0.5" @@ -3368,6 +4225,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: ^2.0.0 + stackback: 0.0.2 + bin: + why-is-node-running: cli.js + checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a + languageName: node + linkType: hard + "wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" @@ -3443,3 +4312,10 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"yocto-queue@npm:^1.0.0": + version: 1.0.0 + resolution: "yocto-queue@npm:1.0.0" + checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + languageName: node + linkType: hard