From b1a5861e24d19b00a1767e31316e0e83633adc9b Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 5 Sep 2025 01:19:40 +0200 Subject: [PATCH 01/10] Setup get funcions for pretemp and estofex --- package.json | 1 + src/routes/forecast-reports.ts | 35 +++++++++++++++++++++++++++++++++ src/server.ts | 2 ++ src/services/estofex.ts | 13 ++++++++++++ src/services/pretemp.ts | 36 ++++++++++++++++++++++++++++++++++ src/services/telegram.ts | 4 ++++ src/utilites/common.ts | 4 ++++ yarn.lock | 12 ++++++++++++ 8 files changed, 107 insertions(+) create mode 100644 src/routes/forecast-reports.ts create mode 100644 src/services/estofex.ts create mode 100644 src/services/pretemp.ts diff --git a/package.json b/package.json index f7695e3..fa6f989 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "axios": "^1.10.0", "dotenv": "^17.2.0", + "fast-xml-parser": "^5.2.5", "fastify": "^5.4.0", "i18next": "^25.3.2", "lodash": "^4.17.21", diff --git a/src/routes/forecast-reports.ts b/src/routes/forecast-reports.ts new file mode 100644 index 0000000..f159cf4 --- /dev/null +++ b/src/routes/forecast-reports.ts @@ -0,0 +1,35 @@ +import { config } from '../config/config' +import { getEstofexReport } from '../services/estofex' +import { getTomorrowPretempReport } from '../services/pretemp' +import { sendPhotoMessage } from '../services/telegram' + +export const registerForecastReportsRoutes = (fastify) => { + fastify.route({ + method: 'POST', + url: '/check-pretemp-report', + handler: async (_, reply) => { + const tomorrowReport = await getTomorrowPretempReport() + + if (!tomorrowReport) { + return reply.status(204).send(undefined) + } + + await sendPhotoMessage(config.chat_id, tomorrowReport, 'Nuovo report Pretemp disponibile') + + reply.status(200).send(tomorrowReport) + }, + }) + fastify.route({ + method: 'POST', + url: '/check-estofex-report', + handler: async (_, reply) => { + const tomorrowReport = await getEstofexReport() + + if (!tomorrowReport) { + return reply.status(204).send(undefined) + } + + reply.status(200).send(tomorrowReport) + }, + }) +} diff --git a/src/server.ts b/src/server.ts index d1b25c5..1d2f370 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,6 +3,7 @@ import { registerMeteoAlertsRoutes } from './routes/meteo-alerts' import i18next from 'i18next' import italian from './resources/locales/it.json' import { registerTestMessageRoutes } from './routes/test-message' +import { registerForecastReportsRoutes } from './routes/forecast-reports' const translations = { it: { @@ -30,6 +31,7 @@ export const startServer = async () => { registerTestMessageRoutes(fastify) registerMeteoAlertsRoutes(fastify) + registerForecastReportsRoutes(fastify) await fastify.listen({ port: 3000, diff --git a/src/services/estofex.ts b/src/services/estofex.ts new file mode 100644 index 0000000..ee0ca8f --- /dev/null +++ b/src/services/estofex.ts @@ -0,0 +1,13 @@ +import axios from 'axios' +import { XMLParser } from 'fast-xml-parser' + +export const getEstofexReport = async () => { + const xmlUrl = 'https://www.estofex.org/cgi-bin/polygon/showforecast.cgi?xml=yes' + + const xmlData = await axios.get(xmlUrl).then((response) => response.data) + + const parser = new XMLParser() + const jsonData = parser.parse(xmlData) + + return jsonData +} diff --git a/src/services/pretemp.ts b/src/services/pretemp.ts new file mode 100644 index 0000000..a48be71 --- /dev/null +++ b/src/services/pretemp.ts @@ -0,0 +1,36 @@ +import axios from 'axios' +import moment from 'moment' +import { toFirstLetterUpperCase } from '../utilites/common' +import customMoment from '../custom-components/custom-moment' + +export const getPretempReport = async (date: moment.Moment) => { + const formattedDate = date.format('DD_MM_YYYY') + const month = date.format('MMMM').toLowerCase() + const urls = [ + `https://pretemp.altervista.org/archivio/${date.year()}/${month}/cartine/${formattedDate}.png`, + `https://pretemp.altervista.org/archivio/${date.year()}/${toFirstLetterUpperCase(month)}/cartine/${formattedDate}.png`, + ] + + let image: string | undefined = undefined + + for (let i = 0; i < urls.length; i++) { + const url = urls[i] + + try { + await axios.head(url) + } catch { + continue + } + + image = url + + // image = await axios.get(url).then((response) => response.data) + } + + return image +} + +export const getTomorrowPretempReport = async () => { + const tomorrow = customMoment().subtract(1, 'day') + return getPretempReport(tomorrow) +} diff --git a/src/services/telegram.ts b/src/services/telegram.ts index d2c4a41..16dcd20 100644 --- a/src/services/telegram.ts +++ b/src/services/telegram.ts @@ -6,3 +6,7 @@ const bot = new Telegram.Telegraf(config.telegram_token) export const sendTelegramMessage = async (chatId: string, text: string, extra?: Telegram.Types.ExtraReplyMessage) => { await bot.telegram.sendMessage(chatId, text, extra) } + +export const sendPhotoMessage = async (chatId: string, photoUrl: string, caption?: string, extra?: Telegram.Types.ExtraPhoto) => { + await bot.telegram.sendPhoto(chatId, photoUrl, { caption, ...extra }) +} diff --git a/src/utilites/common.ts b/src/utilites/common.ts index 8418e6e..fbdbe15 100644 --- a/src/utilites/common.ts +++ b/src/utilites/common.ts @@ -3,3 +3,7 @@ import i18next, { TOptions } from 'i18next' export const translateKey = (key: string, language: string, options: TOptions = {}) => { return i18next.t(key, { lng: language, ...options }) } + +export const toFirstLetterUpperCase = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1) +} diff --git a/yarn.lock b/yarn.lock index 0849212..fda2885 100644 --- a/yarn.lock +++ b/yarn.lock @@ -578,6 +578,13 @@ fast-uri@^3.0.0, fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== +fast-xml-parser@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz#4809fdfb1310494e341098c25cb1341a01a9144a" + integrity sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ== + dependencies: + strnum "^2.1.0" + fastify@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/fastify/-/fastify-5.4.0.tgz#82bf56e0bc36ba8dfb0bd372a0de8b62ccf3287c" @@ -1313,6 +1320,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strnum@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.1.tgz#cf2a6e0cf903728b8b2c4b971b7e36b4e82d46ab" + integrity sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw== + supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" From c3fd8e59c34db551f281867be676c5e3a57d88d9 Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Thu, 30 Oct 2025 23:49:58 +0100 Subject: [PATCH 02/10] Implement Estofex report validation and update forecast report routes --- src/controllers/estofex.ts | 9 +++++++++ src/routes/forecast-reports.ts | 15 +++++++++++---- src/services/estofex.ts | 32 +++++++++++++++++++++++++++++--- src/services/pretemp.ts | 2 -- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 src/controllers/estofex.ts diff --git a/src/controllers/estofex.ts b/src/controllers/estofex.ts new file mode 100644 index 0000000..8eed761 --- /dev/null +++ b/src/controllers/estofex.ts @@ -0,0 +1,9 @@ +import { EstofexReport } from '../services/estofex' + +export const checkEstofexReport = (report: EstofexReport): boolean => { + if (!report.forecast || !report.forecast.start_time || !report.forecast.expiry_time) { + return false + } + + return true +} diff --git a/src/routes/forecast-reports.ts b/src/routes/forecast-reports.ts index f159cf4..0f4dd02 100644 --- a/src/routes/forecast-reports.ts +++ b/src/routes/forecast-reports.ts @@ -1,5 +1,6 @@ import { config } from '../config/config' -import { getEstofexReport } from '../services/estofex' +import { checkEstofexReport } from '../controllers/estofex' +import { getEstofexImage, getEstofexReport } from '../services/estofex' import { getTomorrowPretempReport } from '../services/pretemp' import { sendPhotoMessage } from '../services/telegram' @@ -16,7 +17,7 @@ export const registerForecastReportsRoutes = (fastify) => { await sendPhotoMessage(config.chat_id, tomorrowReport, 'Nuovo report Pretemp disponibile') - reply.status(200).send(tomorrowReport) + reply.status(204).send(undefined) }, }) fastify.route({ @@ -25,11 +26,17 @@ export const registerForecastReportsRoutes = (fastify) => { handler: async (_, reply) => { const tomorrowReport = await getEstofexReport() - if (!tomorrowReport) { + const isReportValid = checkEstofexReport(tomorrowReport) + + if (!isReportValid) { return reply.status(204).send(undefined) } - reply.status(200).send(tomorrowReport) + const estofexImage = await getEstofexImage() + + await sendPhotoMessage(config.chat_id, estofexImage, 'Nuovo report Estofex disponibile') + + reply.status(204).send(undefined) }, }) } diff --git a/src/services/estofex.ts b/src/services/estofex.ts index ee0ca8f..287b952 100644 --- a/src/services/estofex.ts +++ b/src/services/estofex.ts @@ -1,13 +1,39 @@ import axios from 'axios' import { XMLParser } from 'fast-xml-parser' +export interface EstofexReport { + forecast?: Partial<{ + forecast_type: string + start_time: Partial<{ + '@_value': string + }> + expiry_time: Partial<{ + '@_value': string + }> + issue_time: Partial<{ + '@_value': string + }> + }> +} + export const getEstofexReport = async () => { const xmlUrl = 'https://www.estofex.org/cgi-bin/polygon/showforecast.cgi?xml=yes' const xmlData = await axios.get(xmlUrl).then((response) => response.data) - const parser = new XMLParser() - const jsonData = parser.parse(xmlData) + const parser = new XMLParser({ + ignoreAttributes: false, + }) + + return parser.parse(xmlData) as EstofexReport +} + +export const getEstofexImage = async () => { + const imageUrl = 'https://www.estofex.org/forecasts/tempmap/.png' + + try { + await axios.head(imageUrl) + } catch {} - return jsonData + return imageUrl } diff --git a/src/services/pretemp.ts b/src/services/pretemp.ts index a48be71..5e4115a 100644 --- a/src/services/pretemp.ts +++ b/src/services/pretemp.ts @@ -23,8 +23,6 @@ export const getPretempReport = async (date: moment.Moment) => { } image = url - - // image = await axios.get(url).then((response) => response.data) } return image From 0a8c60800168c8a4b63f9280f02c5a6ce4ebeab1 Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:20:39 +0100 Subject: [PATCH 03/10] Refactor report handling: remove unused controllers, update last alert report logic, and enhance Estofex report validation --- src/controllers/estofex.ts | 9 --------- src/models/last-alert-report.ts | 11 +++++++---- src/routes/forecast-reports.ts | 23 ++++++++++++++++++++++- src/routes/meteo-alerts.ts | 7 +++++-- src/services/postgresql.ts | 19 +++++++++++++++++++ src/services/pretemp.ts | 2 +- src/utilites/estofex.ts | 19 +++++++++++++++++++ src/{controllers => utilites}/telegram.ts | 4 ++-- 8 files changed, 75 insertions(+), 19 deletions(-) delete mode 100644 src/controllers/estofex.ts create mode 100644 src/utilites/estofex.ts rename src/{controllers => utilites}/telegram.ts (92%) diff --git a/src/controllers/estofex.ts b/src/controllers/estofex.ts deleted file mode 100644 index 8eed761..0000000 --- a/src/controllers/estofex.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EstofexReport } from '../services/estofex' - -export const checkEstofexReport = (report: EstofexReport): boolean => { - if (!report.forecast || !report.forecast.start_time || !report.forecast.expiry_time) { - return false - } - - return true -} diff --git a/src/models/last-alert-report.ts b/src/models/last-alert-report.ts index c158813..66d77f1 100644 --- a/src/models/last-alert-report.ts +++ b/src/models/last-alert-report.ts @@ -5,8 +5,13 @@ const tableName = 'django_models_latestreport' export interface LastAlertReport { id: number report_id: string + is_critic: boolean + estofex_sent: boolean + pretemp_sent: boolean } +type EditableLastAlertReport = Omit + export const getLastAlertReport = async (): Promise => { const query = `SELECT * FROM ${tableName} ` @@ -19,8 +24,6 @@ export const getLastAlertReport = async (): Promise => { return reports[0] } -export const updateLastAlertReport = async (reportId: string): Promise => { - const query = `UPDATE ${tableName} SET report_id = $1 WHERE id = 1` - - await database.query(query, [reportId]) +export const updateLastAlertReport = async (report: Partial): Promise => { + await database.edit(tableName, report, 1) } diff --git a/src/routes/forecast-reports.ts b/src/routes/forecast-reports.ts index 0f4dd02..b0de503 100644 --- a/src/routes/forecast-reports.ts +++ b/src/routes/forecast-reports.ts @@ -1,14 +1,21 @@ import { config } from '../config/config' -import { checkEstofexReport } from '../controllers/estofex' +import { checkEstofexReport } from '../utilites/estofex' import { getEstofexImage, getEstofexReport } from '../services/estofex' import { getTomorrowPretempReport } from '../services/pretemp' import { sendPhotoMessage } from '../services/telegram' +import { getLastAlertReport, updateLastAlertReport } from '../models/last-alert-report' export const registerForecastReportsRoutes = (fastify) => { fastify.route({ method: 'POST', url: '/check-pretemp-report', handler: async (_, reply) => { + const lastAlertReport = await getLastAlertReport() + + if (!lastAlertReport.is_critic || lastAlertReport.pretemp_sent) { + return reply.status(204).send(undefined) + } + const tomorrowReport = await getTomorrowPretempReport() if (!tomorrowReport) { @@ -17,6 +24,10 @@ export const registerForecastReportsRoutes = (fastify) => { await sendPhotoMessage(config.chat_id, tomorrowReport, 'Nuovo report Pretemp disponibile') + await updateLastAlertReport({ + pretemp_sent: true, + }) + reply.status(204).send(undefined) }, }) @@ -24,6 +35,12 @@ export const registerForecastReportsRoutes = (fastify) => { method: 'POST', url: '/check-estofex-report', handler: async (_, reply) => { + const lastAlertReport = await getLastAlertReport() + + if (!lastAlertReport.is_critic || lastAlertReport.estofex_sent) { + return reply.status(204).send(undefined) + } + const tomorrowReport = await getEstofexReport() const isReportValid = checkEstofexReport(tomorrowReport) @@ -36,6 +53,10 @@ export const registerForecastReportsRoutes = (fastify) => { await sendPhotoMessage(config.chat_id, estofexImage, 'Nuovo report Estofex disponibile') + await updateLastAlertReport({ + estofex_sent: true, + }) + reply.status(204).send(undefined) }, }) diff --git a/src/routes/meteo-alerts.ts b/src/routes/meteo-alerts.ts index abf0344..66e2a0c 100644 --- a/src/routes/meteo-alerts.ts +++ b/src/routes/meteo-alerts.ts @@ -1,4 +1,4 @@ -import { sendNewTomorrowAlertMessage } from '../controllers/telegram' +import { sendNewTomorrowAlertMessage } from '../utilites/telegram' import { getLastAlertReport, updateLastAlertReport } from '../models/last-alert-report' import { getTomorrowMeteoAlert } from '../services/meteo-alerts' import { parseMeteoAlert } from '../utilites/meteo-alerts' @@ -23,7 +23,10 @@ export const registerMeteoAlertsRoutes = (fastify) => { sendNewTomorrowAlertMessage(parsedAlert) } - await updateLastAlertReport(parsedAlert.id) + await updateLastAlertReport({ + report_id: parsedAlert.id, + is_critic: parsedAlert.isCritic, + }) } reply.status(200).send(parsedAlert) diff --git a/src/services/postgresql.ts b/src/services/postgresql.ts index 2be8120..f6518cf 100644 --- a/src/services/postgresql.ts +++ b/src/services/postgresql.ts @@ -92,4 +92,23 @@ export default class PostgreSQL { throw new Error(error) } } + + public async edit(tableName: string, object: Omit, 'id'>, objectId: number) { + const keys = Object.keys(object).map((key, index) => `${checkAndTransformKey(key)} = $${index + 1}`) + + const values: any[] = Object.values(object) + + const query = `UPDATE ${checkAndTransformKey(tableName)} ${tableName} SET ${keys} WHERE id = $${keys.length + 1} RETURNING *` + + const rows = await this.query(query, [...values, objectId]) + + return rows[0] + } +} + +// Add backtick to sql reserved keywords +export const checkAndTransformKey = (key: string): string => { + const protectedKeywords = ['key', 'table', 'group', 'from', 'desc', 'condition', 'before', 'grant', 'user', 'is'] + + return protectedKeywords.includes(key) ? `"${key}"` : key } diff --git a/src/services/pretemp.ts b/src/services/pretemp.ts index 5e4115a..91eb805 100644 --- a/src/services/pretemp.ts +++ b/src/services/pretemp.ts @@ -29,6 +29,6 @@ export const getPretempReport = async (date: moment.Moment) => { } export const getTomorrowPretempReport = async () => { - const tomorrow = customMoment().subtract(1, 'day') + const tomorrow = customMoment().add(1, 'day') return getPretempReport(tomorrow) } diff --git a/src/utilites/estofex.ts b/src/utilites/estofex.ts new file mode 100644 index 0000000..cc21586 --- /dev/null +++ b/src/utilites/estofex.ts @@ -0,0 +1,19 @@ +import customMoment from '../custom-components/custom-moment' +import { EstofexReport } from '../services/estofex' + +export const checkEstofexReport = (report: EstofexReport): boolean => { + if (!report.forecast || !report.forecast.start_time || !report.forecast.expiry_time) { + return false + } + + if (!report.forecast.start_time['@_value'] || !report.forecast.expiry_time['@_value']) { + return false + } + + const startTime = customMoment(parseInt(report.forecast.start_time['@_value'], 10)) + const expiryTime = customMoment(parseInt(report.forecast.expiry_time['@_value'], 10)) + + const tomorrow = customMoment().add(1, 'day') + + return startTime.isBefore(tomorrow) && expiryTime.isAfter(tomorrow) +} diff --git a/src/controllers/telegram.ts b/src/utilites/telegram.ts similarity index 92% rename from src/controllers/telegram.ts rename to src/utilites/telegram.ts index 7c6262d..06d4eb2 100644 --- a/src/controllers/telegram.ts +++ b/src/utilites/telegram.ts @@ -1,7 +1,7 @@ import { sendTelegramMessage } from '../services/telegram' import { InlineKeyboardButton } from 'telegraf/typings/core/types/typegram' -import { translateKey } from '../utilites/common' -import { ParsedMeteoAlert } from '../utilites/meteo-alerts' +import { translateKey } from './common' +import { ParsedMeteoAlert } from './meteo-alerts' import { config } from '../config/config' const separator = '--------------------------------' From 8ad92398a4ee5234e8e30cafee4e75599692cc1b Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:23:17 +0100 Subject: [PATCH 04/10] Enhance cron job logging: create log files for pretemp and Estofex reports --- Dockerfile | 5 ++++- cron.txt | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a5f2efa..fc9eb8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,10 @@ RUN crontab -r || true RUN crontab /etc/cron.d/mida-cron # Create log directory for cron -RUN mkdir -p /var/log/cron && touch /var/log/cron/meteo_alerts.log +RUN mkdir -p /var/log/cron +RUN touch /var/log/cron/meteo_alerts.log +RUN touch /var/log/cron/check_pretemp_report.log +RUN touch /var/log/cron/check_estofex_report.log EXPOSE 3000 diff --git a/cron.txt b/cron.txt index eda5608..19a6fbd 100644 --- a/cron.txt +++ b/cron.txt @@ -1 +1,3 @@ */5 * * * * curl -X POST http://localhost:3000/meteo-alerts >> /var/log/cron/meteo_alerts.log 2>&1 +*/5 * * * * curl -X POST http://localhost:3000/check-pretemp-report >> /var/log/cron/check_pretemp_report.log 2>&1 +*/5 * * * * curl -X POST http://localhost:3000/check-estofex-report >> /var/log/cron/check_estofex_report.log 2>&1 From ba7100835f1db2c3ebb80c2309df9f9b0bf280ae Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:26:13 +0100 Subject: [PATCH 05/10] Add unit tests for toFirstLetterUpperCase function --- tests/utilities/common.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/utilities/common.ts diff --git a/tests/utilities/common.ts b/tests/utilities/common.ts new file mode 100644 index 0000000..52874ff --- /dev/null +++ b/tests/utilities/common.ts @@ -0,0 +1,34 @@ +import { expect } from 'chai' +import { toFirstLetterUpperCase } from '../../src/utilites/common' + +describe('toFirstLetterUpperCase', () => { + it('capitalizes the first letter of a lowercase word', () => { + expect(toFirstLetterUpperCase('hello')).to.equal('Hello') + }) + + it('returns the same string if first letter is already uppercase', () => { + expect(toFirstLetterUpperCase('Hello')).to.equal('Hello') + }) + + it('returns empty string unchanged', () => { + expect(toFirstLetterUpperCase('')).to.equal('') + }) + + it('capitalizes a single-letter string', () => { + expect(toFirstLetterUpperCase('a')).to.equal('A') + }) + + it('does not change strings that start with a non-letter character', () => { + expect(toFirstLetterUpperCase('1abc')).to.equal('1abc') + expect(toFirstLetterUpperCase('!bang')).to.equal('!bang') + }) + + it('capitalizes unicode first letters', () => { + expect(toFirstLetterUpperCase('éclair')).to.equal('Éclair') + }) + + it('only changes the first character and leaves the rest intact', () => { + expect(toFirstLetterUpperCase('hELLO')).to.equal('HELLO') + expect(toFirstLetterUpperCase('hello world')).to.equal('Hello world') + }) +}) From 89fe30a39fbf89e308e82ddf0d14367db5f53b28 Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:30:14 +0100 Subject: [PATCH 06/10] Add unit tests for parseMeteoAlert function to validate alert parsing logic --- tests/utilities/meteo-alerts.ts | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/utilities/meteo-alerts.ts diff --git a/tests/utilities/meteo-alerts.ts b/tests/utilities/meteo-alerts.ts new file mode 100644 index 0000000..19c5d7b --- /dev/null +++ b/tests/utilities/meteo-alerts.ts @@ -0,0 +1,79 @@ +import { expect } from 'chai' +import { describe, it } from 'mocha' +import { parseMeteoAlert } from '../../src/utilites/meteo-alerts' +import { alertZones, MeteoAlertType } from '../../src/services/meteo-alerts' + +describe('parseMeteoAlert', () => { + it('throws if the requested zone is not present in the alert', () => { + const zone = 'non_existent_zone' + const alert: any = { + link: '/alerts/allerta_123_45.pdf', + title: 'Test alert', + // no zone property + } + + expect(() => parseMeteoAlert(alert, zone as any)).to.throw(`Zone ${zone} not found in alert data`) + }) + + it('throws if alert.link does not contain a filename', () => { + const zone = alertZones[0] + const alert: any = { + link: '/path/with/trailing/slash/', + title: 'Test alert', + [zone]: { + ghiaccio_pioggia_gela: null, + }, + } + + expect(() => parseMeteoAlert(alert, zone)).to.throw(`Invalid link format: ${alert.link}`) + }) + + it('parses id, updates link, computes isCritic and criticZoneData and omits zone keys from root', () => { + const zone = alertZones[0] + // Construct zone data using the known keys from the utility's correctColors mapping. + const zoneData = { + ghiaccio_pioggia_gela: null, // allowed / removed from criticZoneData + idraulica: MeteoAlertType.yellow, // allowed by correctColors but is not in colorsToRemove -> should appear in criticZoneData + idrogeologica: null, + mareggiate: null, + neve: MeteoAlertType.yellow, // NOT allowed by correctColors.neve -> makes isCritic true and included in criticZoneData + stato_mare: null, + temperature_estreme: MeteoAlertType.green, + temporali: MeteoAlertType.green, + vento: MeteoAlertType.red, // NOT allowed -> included in criticZoneData + } as any + + const originalLink = '/alerts/allerta_123_45.pdf' + const alert: any = { + link: originalLink, + title: 'Sample alert title', + otherProp: 42, + [zone]: zoneData, + } + + const parsed = parseMeteoAlert(alert, zone) + + // id: 'allerta_123_45' -> remove .pdf -> 'allerta_123_45' -> replace first '_' -> 'allerta/123_45' -> remove 'allerta' -> '/123_45' + expect(parsed.id).to.equal('/123_45') + + // link must be prefixed with the base URL + expect(parsed.link).to.equal(`https://allertameteo.regione.emilia-romagna.it${originalLink}`) + + // zoneData should be preserved on the returned object + expect(parsed.zoneData).to.deep.equal(zoneData) + + // isCritic should be true because 'neve' and 'vento' use types not allowed by correctColors + expect(parsed.isCritic).to.equal(true) + + // criticZoneData should include only keys whose value is not in colorsToRemove ([null, green]) + // From our zoneData that means idraulica (yellow), neve (yellow), vento (red) + expect(parsed.criticZoneData).to.deep.equal({ + idraulica: MeteoAlertType.yellow, + neve: MeteoAlertType.yellow, + vento: MeteoAlertType.red, + }) + + // The top-level returned object should have omitted the original zone key (it is provided separately as zoneData) + expect((parsed as any)[zone]).to.equal(undefined) + }) +}) From 1939a01915f2350825ea0fa1cdf718da8c252e6e Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:36:29 +0100 Subject: [PATCH 07/10] Update chai and @types/chai versions in package.json and yarn.lock for consistency --- package.json | 4 +-- yarn.lock | 88 ++++++++++++++++++++++++++++------------------------ 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index fa6f989..fb23317 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "telegraf": "^4.16.3" }, "devDependencies": { - "@types/chai": "^5.2.2", + "@types/chai": "4", "@types/lodash": "^4.17.20", "@types/mocha": "^10.0.1", "@types/node": "22", "@types/pg": "^8.15.4", "@types/sinon": "^17.0.4", - "chai": "^5.2.1", + "chai": "4.3.10", "mocha": "^10.2.0", "nodemon": "^3.1.10", "prettier": "^3.6.2", diff --git a/yarn.lock b/yarn.lock index fda2885..9a24388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,17 +121,10 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@types/chai@^5.2.2": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" - integrity sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== - dependencies: - "@types/deep-eql" "*" - -"@types/deep-eql@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" - integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== +"@types/chai@4": + version "4.3.20" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.20.tgz#cb291577ed342ca92600430841a00329ba05cecc" + integrity sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ== "@types/lodash@^4.17.20": version "4.17.20" @@ -254,10 +247,10 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -assertion-error@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" - integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== asynckit@^0.4.0: version "0.4.0" @@ -354,16 +347,18 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -chai@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-5.2.1.tgz#a9502462bdc79cf90b4a0953537a9908aa638b47" - integrity sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A== +chai@4.3.10: + version "4.3.10" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" + integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== dependencies: - assertion-error "^2.0.1" - check-error "^2.1.1" - deep-eql "^5.0.1" - loupe "^3.1.0" - pathval "^2.0.0" + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" chalk@^4.1.0: version "4.1.2" @@ -373,10 +368,12 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -check-error@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" - integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" chokidar@^3.5.2, chokidar@^3.5.3: version "3.6.0" @@ -448,10 +445,12 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -deep-eql@^5.0.1: - version "5.0.2" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" - integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== +deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== + dependencies: + type-detect "^4.0.0" delayed-stream@~1.0.0: version "1.0.0" @@ -678,6 +677,11 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -881,10 +885,12 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -loupe@^3.1.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.4.tgz#784a0060545cb38778ffb19ccde44d7870d5fdd9" - integrity sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg== +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" make-error@^1.1.1: version "1.3.6" @@ -1027,10 +1033,10 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -pathval@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" - integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== pg-cloudflare@^1.2.7: version "1.2.7" @@ -1413,7 +1419,7 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-detect@^4.1.0: +type-detect@^4.0.0, type-detect@^4.0.8, type-detect@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== From dab147c16286eab544de2a85c128226600d4d42a Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 00:38:20 +0100 Subject: [PATCH 08/10] Add "type" field to package.json for module compatibility --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fb23317..c8c41ca 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "main": "dist/index.js", "license": "MIT", + "type": "commonjs", "scripts": { "start": "nodemon src/index.ts", "dist": "tsc", From 01fc763dfc0558cad95f7b56574057caaaf8ebe5 Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 01:21:53 +0100 Subject: [PATCH 09/10] Update package.json: remove "type" field and standardize chai and sinon versions --- package.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c8c41ca..2833a68 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "main": "dist/index.js", "license": "MIT", - "type": "commonjs", "scripts": { "start": "nodemon src/index.ts", "dist": "tsc", @@ -22,17 +21,17 @@ "telegraf": "^4.16.3" }, "devDependencies": { - "@types/chai": "4", + "@types/chai": "^4.3.6", "@types/lodash": "^4.17.20", "@types/mocha": "^10.0.1", "@types/node": "22", "@types/pg": "^8.15.4", - "@types/sinon": "^17.0.4", - "chai": "4.3.10", + "@types/sinon": "^17.0.3", + "chai": "^4.3.8", "mocha": "^10.2.0", "nodemon": "^3.1.10", "prettier": "^3.6.2", - "sinon": "^21.0.0", + "sinon": "^18.0.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" } From bc08fda9dcead57cd5de7bf548a430f3fbfce0da Mon Sep 17 00:00:00 2001 From: Nico Montanari Date: Fri, 31 Oct 2025 10:33:50 +0100 Subject: [PATCH 10/10] Update yarn.lock: synchronize sinon and chai versions, remove unused dependencies --- yarn.lock | 91 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9a24388..0fbf102 100644 --- a/yarn.lock +++ b/yarn.lock @@ -73,29 +73,40 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@sinonjs/commons@^3.0.1": +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^13.0.5": +"@sinonjs/fake-timers@11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^13.0.1": version "13.0.5" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== dependencies: "@sinonjs/commons" "^3.0.1" -"@sinonjs/samsam@^8.0.1": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" - integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== +"@sinonjs/samsam@^8.0.0": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.3.tgz#eb6ffaef421e1e27783cc9b52567de20cb28072d" + integrity sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ== dependencies: "@sinonjs/commons" "^3.0.1" - lodash.get "^4.4.2" type-detect "^4.1.0" +"@sinonjs/text-encoding@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" + integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== + "@telegraf/types@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@telegraf/types/-/types-7.1.0.tgz#d8bd9b2f5070b4de46971416e890338cd89fc23d" @@ -121,7 +132,7 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@types/chai@4": +"@types/chai@^4.3.6": version "4.3.20" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.20.tgz#cb291577ed342ca92600430841a00329ba05cecc" integrity sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ== @@ -159,7 +170,7 @@ pg-protocol "*" pg-types "^2.2.0" -"@types/sinon@^17.0.4": +"@types/sinon@^17.0.3": version "17.0.4" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.4.tgz#fd9a3e8e07eea1a3f4a6f82a972c899e5778f369" integrity sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew== @@ -347,10 +358,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -chai@4.3.10: - version "4.3.10" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" - integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== +chai@^4.3.8: + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== dependencies: assertion-error "^1.1.0" check-error "^1.0.3" @@ -358,7 +369,7 @@ chai@4.3.10: get-func-name "^2.0.2" loupe "^2.3.6" pathval "^1.1.1" - type-detect "^4.0.8" + type-detect "^4.1.0" chalk@^4.1.0: version "4.1.2" @@ -472,11 +483,6 @@ diff@^5.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== -diff@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" - integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== - dotenv@^17.2.0: version "17.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.0.tgz#e19678fdabcf86d4bfdb6764a758d7d44efbb6a2" @@ -851,6 +857,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + light-my-request@^6.0.0: version "6.6.0" resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-6.6.0.tgz#c9448772323f65f33720fb5979c7841f14060add" @@ -867,11 +878,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -969,6 +975,17 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nise@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" + just-extend "^6.2.0" + path-to-regexp "^8.1.0" + node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -1033,6 +1050,11 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== +path-to-regexp@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -1282,16 +1304,17 @@ simple-update-notifier@^2.0.0: dependencies: semver "^7.5.3" -sinon@^21.0.0: - version "21.0.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-21.0.0.tgz#dbda73abc7e6cb803fef3368cfbecbb5936e8a9e" - integrity sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw== +sinon@^18.0.0: + version "18.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.1.tgz#464334cdfea2cddc5eda9a4ea7e2e3f0c7a91c5e" + integrity sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw== dependencies: "@sinonjs/commons" "^3.0.1" - "@sinonjs/fake-timers" "^13.0.5" - "@sinonjs/samsam" "^8.0.1" - diff "^7.0.0" - supports-color "^7.2.0" + "@sinonjs/fake-timers" "11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.2.0" + nise "^6.0.0" + supports-color "^7" sonic-boom@^4.0.1: version "4.2.0" @@ -1338,7 +1361,7 @@ supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -1419,7 +1442,7 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-detect@^4.0.0, type-detect@^4.0.8, type-detect@^4.1.0: +type-detect@^4.0.0, type-detect@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==