From a01f61e79effb88cfd31280a485b7d4ad77d661f Mon Sep 17 00:00:00 2001 From: PierreDemailly <39910767+PierreDemailly@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:18:31 +0100 Subject: [PATCH] feat(config): add missing duration pattern validations (#134) --- src/config/src/schemas/configSchema.json | 12 +- src/config/src/schemas/rules.json | 6 +- .../test/compositeRules.validation.spec.ts | 172 +++++++++++++++++- src/config/test/rule.alert.validation.spec.ts | 84 ++++++++- .../test/selfMonitoring.validation.spec.ts | 53 ++++++ 5 files changed, 319 insertions(+), 8 deletions(-) diff --git a/src/config/src/schemas/configSchema.json b/src/config/src/schemas/configSchema.json index 2593cb3..a4bb85b 100644 --- a/src/config/src/schemas/configSchema.json +++ b/src/config/src/schemas/configSchema.json @@ -143,7 +143,8 @@ "interval": { "title": "The interval of time to pause alert", "description": "A duration string (e.g. '1m', '1h', '1d').", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" }, "activationThreshold": { "title": "The number of alert before activating the throttle", @@ -203,7 +204,8 @@ "interval": { "title": "Defines the maximum interval, default one day", "description": "A duration string (e.g. '1m', '1h', '1d').", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" }, "template": { "title": "The template used to send the alert", @@ -240,7 +242,8 @@ "interval": { "title": "The interval of time to pause alert", "description": "A duration string (e.g. '1m', '1h', '1d').", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" } }, "required": ["interval"], @@ -252,7 +255,8 @@ }, "muteDuration": { "title": "Defines the duration for which rules should be muted", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" } }, "additionalProperties": false, diff --git a/src/config/src/schemas/rules.json b/src/config/src/schemas/rules.json index e987887..26d1e7c 100644 --- a/src/config/src/schemas/rules.json +++ b/src/config/src/schemas/rules.json @@ -117,7 +117,8 @@ "interval": { "title": "The interval of time in which the logs will be counted", "description": "A duration string (e.g. '1m', '1h', '1d').", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" }, "label": { "title": "The label to watch", @@ -321,7 +322,8 @@ "interval": { "title": "The interval of time to pause alert", "description": "A duration string (e.g. '1m', '1h', '1d').", - "type": "string" + "type": "string", + "pattern": "^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$" }, "activationThreshold": { "title": "The number of alert before activating the throttle", diff --git a/src/config/test/compositeRules.validation.spec.ts b/src/config/test/compositeRules.validation.spec.ts index c7546b5..9d9b93e 100644 --- a/src/config/test/compositeRules.validation.spec.ts +++ b/src/config/test/compositeRules.validation.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ // Import Node.js Dependencies import assert from "node:assert"; import { describe, it } from "node:test"; @@ -6,6 +7,22 @@ import { describe, it } from "node:test"; import { validateConfig } from "../src/validate"; import { VALID_CONFIG } from "./helpers"; +// CONSTANTS +const kDurations = [ + "20milliseconds", "20 milliseconds", + "20msecs", "20 msecs", + "20ms", "20 ms", + "20seconds", "20 seconds", + "20secs", "20 secs", + "20s", "20 s", + "20minutes", "20 minutes", + "20mins", "20 mins", + "20m", "20 m", + "20hours", "20 hours", + "20hrs", "20 hrs", + "20h", "20 h" +]; + describe("Composite rules validations", () => { it("property 'compositeRules' should be optional", () => { assert.doesNotThrow(() => { @@ -378,7 +395,7 @@ describe("Composite rules validations", () => { }); }); - it("compositeRules property 'interval' can must be string", () => { + it("compositeRules property 'interval' must be string", () => { assert.throws(() => { validateConfig({ ...VALID_CONFIG, @@ -399,6 +416,48 @@ describe("Composite rules validations", () => { }); }); + it("compositeRules property 'interval' must be a duration", () => { + assert.throws(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + interval: "foo" + } + ] + }); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /compositeRules/0/interval: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`compositeRules property 'interval' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + interval: duration + } + ] + }); + }); + }); + } + it("compositeRules property 'throttle' can be set", () => { assert.doesNotThrow(() => { validateConfig({ @@ -511,6 +570,75 @@ describe("Composite rules validations", () => { }); }); + it("compositeRules property 'throttle.interval' must be string", () => { + assert.throws(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + throttle: { + interval: true as any + } + } + ] + }); + }, { + name: "Error", + message: "Invalid config: /compositeRules/0/throttle/interval: must be string" + }); + }); + + it("compositeRules property 'throttle.interval' must be a duration", () => { + assert.throws(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + throttle: { + interval: "foo" + } + } + ] + }); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /compositeRules/0/throttle/interval: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`compositeRules property 'throttle.interval' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + throttle: { + interval: duration + } + } + ] + }); + }); + }); + } + it("compositeRules property 'throttle.count' must be an integer", () => { assert.throws(() => { validateConfig({ @@ -613,6 +741,48 @@ describe("Composite rules validations", () => { }); }); + it("compositeRules property 'muteDuration' must be a duration", () => { + assert.throws(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + muteDuration: "foo" + } + ] + }); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /compositeRules/0/muteDuration: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`compositeRules property 'muteDuration' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig({ + ...VALID_CONFIG, + compositeRules: [ + { + notifCount: 12, + template: { + title: "title" + }, + name: "foo", + muteDuration: duration + } + ] + }); + }); + }); + } + it("compositeRules cannot have additional property", () => { assert.throws(() => { validateConfig({ diff --git a/src/config/test/rule.alert.validation.spec.ts b/src/config/test/rule.alert.validation.spec.ts index 96af47c..eb958b1 100644 --- a/src/config/test/rule.alert.validation.spec.ts +++ b/src/config/test/rule.alert.validation.spec.ts @@ -10,6 +10,20 @@ import { PartialSigynAlert } from "../src/types"; // CONSTANTS const kValidRule = VALID_CONFIG.rules[0]; +const kDurations = [ + "20milliseconds", "20 milliseconds", + "20msecs", "20 msecs", + "20ms", "20 ms", + "20seconds", "20 seconds", + "20secs", "20 secs", + "20s", "20 s", + "20minutes", "20 minutes", + "20mins", "20 mins", + "20m", "20 m", + "20hours", "20 hours", + "20hrs", "20 hrs", + "20h", "20 h" +]; function mergeAlert(alert: Partial) { return { @@ -206,6 +220,48 @@ describe("Rule alert validations", () => { }); }); + it("rule alert property 'on.interval' must be string", () => { + assert.throws(() => { + validateConfig(mergeAlert({ + on: { + ...kValidRule.alert.on, + interval: true as any + } + })); + }, { + name: "Error", + message: "Invalid config: /rules/0/alert/on/interval: must be string" + }); + }); + + it("rule alert property 'on.interval' must be a duration", () => { + assert.throws(() => { + validateConfig(mergeAlert({ + on: { + ...kValidRule.alert.on, + interval: "foo" + } + })); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /rules/0/alert/on/interval: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`rule alert property 'on.interval' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig(mergeAlert({ + on: { + ...kValidRule.alert.on, + interval: duration + } + })); + }); + }); + } + it("rule alert property 'on.interval' or 'on.minimumLabelCount' should be required when rule is label percent threhsold based (value)", () => { assert.throws(() => { validateConfig(mergeAlert({ @@ -475,7 +531,7 @@ describe("Rule alert validations", () => { }); }); - it("rule alert property 'throttle.interval' must be a duration", () => { + it("rule alert property 'throttle.interval' must be string", () => { assert.throws(() => { validateConfig(mergeAlert({ throttle: { @@ -488,6 +544,32 @@ describe("Rule alert validations", () => { }); }); + it("rule alert property 'throttle.interval' must be a duration", () => { + assert.throws(() => { + validateConfig(mergeAlert({ + throttle: { + interval: "foo" + } + })); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /rules/0/alert/throttle/interval: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`rule alert property 'throttle.interval' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig(mergeAlert({ + throttle: { + interval: duration + } + })); + }); + }); + } + it("rule alert property 'throttle.count' must be an integer", () => { assert.throws(() => { validateConfig(mergeAlert({ diff --git a/src/config/test/selfMonitoring.validation.spec.ts b/src/config/test/selfMonitoring.validation.spec.ts index 3471705..6f56632 100644 --- a/src/config/test/selfMonitoring.validation.spec.ts +++ b/src/config/test/selfMonitoring.validation.spec.ts @@ -6,6 +6,23 @@ import { describe, it } from "node:test"; import { validateConfig } from "../src/validate"; import { VALID_CONFIG } from "./helpers"; + +// CONSTANTS +const kDurations = [ + "20milliseconds", "20 milliseconds", + "20msecs", "20 msecs", + "20ms", "20 ms", + "20seconds", "20 seconds", + "20secs", "20 secs", + "20s", "20 s", + "20minutes", "20 minutes", + "20mins", "20 mins", + "20m", "20 m", + "20hours", "20 hours", + "20hrs", "20 hrs", + "20h", "20 h" +]; + describe("Self-monitoring validations", () => { it("property 'selfMonitoring' should be optional", () => { assert.doesNotThrow(() => { @@ -379,6 +396,42 @@ describe("Self-monitoring validations", () => { }); }); + it("property 'selfMonitoring.interval' must be a duration", () => { + assert.throws(() => { + validateConfig({ + ...VALID_CONFIG, + selfMonitoring: { + template: "foo", + notifiers: ["discord"], + throttle: { + interval: "foo" + } + } + }); + }, { + name: "Error", + // eslint-disable-next-line max-len + message: "Invalid config: /selfMonitoring/throttle/interval: must match pattern \"^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$\"" + }); + }); + + for (const duration of kDurations) { + it(`property 'selfMonitoring.interval' can be '${duration}'`, () => { + assert.doesNotThrow(() => { + validateConfig({ + ...VALID_CONFIG, + selfMonitoring: { + template: "foo", + notifiers: ["discord"], + throttle: { + interval: duration + } + } + }); + }); + }); + } + it("property 'selfMonitoring.throttle.count' must be an integer", () => { assert.throws(() => { validateConfig({