diff --git a/data/reminder_covers_event.testdata.json b/data/reminder_covers_event.testdata.json new file mode 100644 index 0000000..8fca1b6 --- /dev/null +++ b/data/reminder_covers_event.testdata.json @@ -0,0 +1,122 @@ +[ + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 0, + "from": 24, + "to": 48 + }, + "event": { + "begin": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 25 + }, + "time": { + "hour": 12, + "minute": 0, + "second": 0 + } + } + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 23 + }, + "time": { + "hour": 10, + "minute": 0, + "second": 0 + } + }, + "interval": 1 + }, + "output": false + }, + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 0, + "from": 24, + "to": 48 + }, + "event": { + "begin": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 25 + }, + "time": { + "hour": 12, + "minute": 0, + "second": 0 + } + } + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 24 + }, + "time": { + "hour": 10, + "minute": 0, + "second": 0 + } + }, + "interval": 1 + }, + "output": true + }, + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 0, + "from": 24, + "to": 48 + }, + "event": { + "begin": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 25 + }, + "time": { + "hour": 12, + "minute": 0, + "second": 0 + } + } + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 25 + }, + "time": { + "hour": 10, + "minute": 0, + "second": 0 + } + }, + "interval": 1 + }, + "output": false + } +] diff --git a/data/reminder_due.testdata.json b/data/reminder_due.testdata.json new file mode 100644 index 0000000..1d7211c --- /dev/null +++ b/data/reminder_due.testdata.json @@ -0,0 +1,77 @@ +[ + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 16, + "from": 24, + "to": 48 + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 27 + }, + "time": { + "hour": 15, + "minute": 30, + "second": 0 + } + }, + "interval": 1 + }, + "output": false + }, + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 16, + "from": 24, + "to": 48 + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 27 + }, + "time": { + "hour": 16, + "minute": 30, + "second": 0 + } + }, + "interval": 1 + }, + "output": true + }, + { + "input": { + "reminder": { + "frequency": "daily", + "offset": 16, + "from": 24, + "to": 48 + }, + "datetime": { + "timezone_shift": 0, + "date": { + "year": 2025, + "month": 6, + "day": 27 + }, + "time": { + "hour": 17, + "minute": 30, + "second": 0 + } + }, + "interval": 1 + }, + "output": false + } +] diff --git a/ivaldi.json b/ivaldi.json index b3a009a..c3c033f 100644 --- a/ivaldi.json +++ b/ivaldi.json @@ -12,6 +12,8 @@ "email", "telegram", "url", + "json", + "file", "conf", "log", "args" @@ -20,6 +22,7 @@ } ], "sources": [ + "helpers/test.ts", "types.ts", "sources/ical_feed.ts", "sources/_functions.ts", diff --git a/source/helpers/test.ts b/source/helpers/test.ts new file mode 100644 index 0000000..5c87834 --- /dev/null +++ b/source/helpers/test.ts @@ -0,0 +1,124 @@ +/* +This file is part of »munin«. + +Copyright 2025 'Fenris Wolf' + +»munin« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»munin« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »munin«. If not, see . + */ + + +namespace _munin.helpers.test +{ + + /** + * @todo outsource + */ + type type_testcase_raw = { + active ?: boolean; + name ?: string; + input : type_input; + output : type_output; + }; + + + /** + * @todo outsource + */ + export type type_testcase = { + name : string; + input : type_input; + output : type_output; + }; + + + /** + * @todo outsource + */ + export async function get_data( + path : string, + { + "default_active": default_active = true, + } : { + default_active ?: boolean; + } = { + } + ) : Promise>> + { + const content : string = await lib_plankton.file.read(path); + const testcases_raw : Array> = ( + lib_plankton.json.decode(content) as Array> + ); + const testcases : Array> = ( + testcases_raw + .filter( + testcase_raw => (testcase_raw.active ?? default_active), + ) + .map( + (testcase_raw, index) => ({ + "name": ( + testcase_raw.name + ?? + lib_plankton.string.coin( + "{{path}} | #{{index}}", + { + "path": path, + "index": (index + 1).toFixed(0), + } + ) + ), + "input": testcase_raw.input, + "output": testcase_raw.output, + }) + ) + ); + return testcases; + } + + + /** + * @todo outsource + */ + export function start( + name : string + ) : void + { + lib_plankton.log._info( + "test.run", + { + "details": { + "name": name, + } + } + ); + } + + + /** + * @todo outsource + */ + export function fail( + name : string + ) : void + { + lib_plankton.log._error( + "test.failed", + { + "details": { + "name": name, + } + } + ); + } + +} diff --git a/source/logic.ts b/source/logic.ts index a9efba2..9a76d62 100644 --- a/source/logic.ts +++ b/source/logic.ts @@ -21,6 +21,23 @@ along with »munin«. If not, see . namespace _munin.logic { + /** + */ + export function frequency_decode( + frequency_encoded : string + ) : _munin.enum_frequency + { + switch (frequency_encoded) + { + case "hourly": return _munin.enum_frequency.hourly; + case "daily": return _munin.enum_frequency.daily; + case "weekly": return _munin.enum_frequency.weekly; + case "monthly": return _munin.enum_frequency.monthly; + default: {throw new Error("unhandled");} + } + } + + /** */ function frequency_anchor( @@ -99,7 +116,7 @@ namespace _munin.logic /** */ - export function reminder_event_in_window( + export function reminder_covers_event( reminder : type_reminder, event : _munin.type_event, { @@ -131,7 +148,7 @@ namespace _munin.logic window_to ); lib_plankton.log._info( - "munin.logic.reminder_event_in_window", + "munin.logic.reminder_covers_event", { "details": { "reminder": reminder, @@ -183,7 +200,7 @@ namespace _munin.logic else { const events_matching : Array<_munin.type_event> = events.filter( - event => reminder_event_in_window( + event => reminder_covers_event( reminder, event, { diff --git a/source/test.ts b/source/test.ts index 91bed40..b570f3f 100644 --- a/source/test.ts +++ b/source/test.ts @@ -20,192 +20,47 @@ along with »munin«. If not, see . namespace _munin.test { - + /** */ - type type_datetime = { - year : int; - month : int; - day : int; - hour : int; - minute : int; - second : int; - }; - - - /** - */ - function datetime_to_pit( - datetime : type_datetime - ) : lib_plankton.pit.type_pit + async function reminder_due( + ) : Promise { - return lib_plankton.pit.from_datetime( - { - "date": { - "year": datetime.year, - "month": datetime.month, - "day": datetime.day, - }, - "time": { - "hour": datetime.hour, - "minute": datetime.minute, - "second": datetime.second, - }, - "timezone_shift": 0, - } - ); - } - - - /** - */ - function start( - name : string - ) : void - { - lib_plankton.log._info( - "test.run", - { - "details": { - "name": name, - } - } - ); - } - - - /** - */ - function fail( - name : string - ) : void - { - lib_plankton.log._error( - "test.failed", - { - "details": { - "name": name, - } - } - ); - } - - - /** - */ - function frequency_decode( - frequency_encoded : string - ) : _munin.enum_frequency - { - switch (frequency_encoded) - { - case "hourly": return _munin.enum_frequency.hourly; - case "daily": return _munin.enum_frequency.daily; - case "weekly": return _munin.enum_frequency.weekly; - case "monthly": return _munin.enum_frequency.monthly; - default: {throw new Error("unhandled");} - } - } - - - /** - */ - export function reminder_due( - ) : void - { - type type_testcase = { - name : string; - input : { - reminder : { - frequency : string; - offset : int; - from : int; - to : int; - }; - datetime : type_datetime; - interval : int; + type type_input = { + reminder : { + frequency : string; + offset : int; + from : int; + to : int; }; - output : boolean; + datetime : lib_plankton.pit.type_datetime; + interval : int; }; - const testcases : Array = [ + type type_output = boolean; + const testcases : Array< + _munin.helpers.test.type_testcase< + type_input, + type_output + > + > = await _munin.helpers.test.get_data( + "data/reminder_due.testdata.json", { - "name": "reminder_due.test1", - "input": { - "reminder": { - "frequency": "daily", - "offset": 16, - "from": 24, - "to": 48 - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 27, - "hour": 15, - "minute": 30, - "second": 0 - }, - "interval": 1 - }, - "output": false - }, - { - "name": "reminder_due.test2", - "input": { - "reminder": { - "frequency": "daily", - "offset": 16, - "from": 24, - "to": 48 - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 27, - "hour": 16, - "minute": 30, - "second": 0, - }, - "interval": 1 - }, - "output": true - }, - { - "name": "reminder_due.test3", - "input": { - "reminder": { - "frequency": "daily", - "offset": 16, - "from": 24, - "to": 48 - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 27, - "hour": 17, - "minute": 30, - "second": 0 - }, - "interval": 1 - }, - "output": false - }, - ]; + } + ); for (const testcase of testcases) { - start(testcase.name); + _munin.helpers.test.start(testcase.name); // execution const result : boolean = _munin.logic.reminder_due( { - "frequency": frequency_decode(testcase.input.reminder.frequency), + "frequency": _munin.logic.frequency_decode(testcase.input.reminder.frequency), "offset": testcase.input.reminder.offset, "from": testcase.input.reminder.from, "to": testcase.input.reminder.to, }, { - "pit": datetime_to_pit(testcase.input.datetime), + "pit": lib_plankton.pit.from_datetime(testcase.input.datetime), "interval": testcase.input.interval, } ); @@ -213,7 +68,7 @@ namespace _munin.test // assertions if (result !== testcase.output) { - fail(testcase.name); + _munin.helpers.test.fail(testcase.name); } } } @@ -221,163 +76,75 @@ namespace _munin.test /** */ - export function reminder_event_in_window( - ) : void + async function reminder_covers_event( + ) : Promise { - type type_testcase = { - name : string; - input : { - reminder : { - frequency : string; - offset : int; - from : int; - to : int; - }; - event : { - begin : type_datetime; - }; - datetime : type_datetime; - interval : int; + type type_input = { + reminder : { + frequency : string; + offset : int; + from : int; + to : int; }; - output : boolean; + event : { + begin : lib_plankton.pit.type_datetime; + }; + datetime : lib_plankton.pit.type_datetime; + interval : int; }; - const testcases : Array = [ + type type_output = boolean; + const testcases : Array< + _munin.helpers.test.type_testcase< + type_input, + type_output + > + > = await _munin.helpers.test.get_data( + "data/reminder_covers_event.testdata.json", { - "name": "reminder_event_in_window.test1", - "input": { - "reminder": { - "frequency": "daily", - "offset": 0, - "from": 24, - "to": 48 - }, - "event": { - "begin": { - "year": 2025, - "month": 6, - "day": 25, - "hour": 12, - "minute": 0, - "second": 0 - } - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 23, - "hour": 10, - "minute": 0, - "second": 0 - }, - "interval": 1 - }, - "output": false - }, - { - "name": "reminder_event_in_window.test2", - "input": { - "reminder": { - "frequency": "daily", - "offset": 0, - "from": 24, - "to": 48 - }, - "event": { - "begin": { - "year": 2025, - "month": 6, - "day": 25, - "hour": 12, - "minute": 0, - "second": 0 - } - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 24, - "hour": 10, - "minute": 0, - "second": 0 - }, - "interval": 1 - }, - "output": true - }, - { - "name": "reminder_event_in_window.test3", - "input": { - "reminder": { - "frequency": "daily", - "offset": 0, - "from": 24, - "to": 48 - }, - "event": { - "begin": { - "year": 2025, - "month": 6, - "day": 25, - "hour": 12, - "minute": 0, - "second": 0 - } - }, - "datetime": { - "year": 2025, - "month": 6, - "day": 25, - "hour": 10, - "minute": 0, - "second": 0 - }, - "interval": 1 - }, - "output": false - }, - ]; + } + ); for (const testcase of testcases) { - start(testcase.name); + _munin.helpers.test.start(testcase.name); // execution - const result : boolean = _munin.logic.reminder_event_in_window( + const result : boolean = _munin.logic.reminder_covers_event( { - "frequency": frequency_decode(testcase.input.reminder.frequency), + "frequency": _munin.logic.frequency_decode(testcase.input.reminder.frequency), "offset": testcase.input.reminder.offset, "from": testcase.input.reminder.from, "to": testcase.input.reminder.to, }, { "title": "test", - "begin": lib_plankton.pit.to_datetime(datetime_to_pit(testcase.input.event.begin)), + "begin": testcase.input.event.begin, "end": null, "description": null, "location": null, "tags": null, }, { - "pit": datetime_to_pit(testcase.input.datetime), + "pit": lib_plankton.pit.from_datetime(testcase.input.datetime), } ); // assertions if (result !== testcase.output) { - fail(testcase.name); + _munin.helpers.test.fail(testcase.name); } } } - + /** */ - export function all( - ) : void + export async function all( + ) : Promise { - reminder_due(); - reminder_event_in_window(); + await reminder_due(); + await reminder_covers_event(); } } diff --git a/tools/build b/tools/build index 155b24a..f85b346 100755 --- a/tools/build +++ b/tools/build @@ -1,4 +1,13 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash +## core tools/ivaldi build -cd build && npm install nodemailer ; cd - + +## data +mkdir -p build/data +cp -r -u -v data/* build/data/ + +## node modules +node_modules="" +node_modules="${node_modules} nodemailer" +cd build && npm install ${node_modules} ; cd -