From efd3755f2146f212962f561e603d98d9d933c642 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Mon, 18 Mar 2024 17:02:57 +0400 Subject: [PATCH 1/8] fix(util): fix error names --- src/util/errors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/errors.ts b/src/util/errors.ts index 72edf7052..a82c13bc8 100644 --- a/src/util/errors.ts +++ b/src/util/errors.ts @@ -5,8 +5,8 @@ const CUSTOM_ERRORS = [ 'ManifestValidationError', 'ModuleInitializationError', 'InputValidationError', - 'InvalidAggregationParams', - 'InvalidGrouping', + 'InvalidAggregationParamsError', + 'InvalidGroupingError', 'PluginCredentialError', 'PluginInitalizationError', 'WriteFileError', From b34da8e6d10c53be5ad52acce1969cac92d7c092 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Mon, 18 Mar 2024 17:03:21 +0400 Subject: [PATCH 2/8] fix(util): rename errors in aggregation helper --- src/util/aggregation-helper.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/aggregation-helper.ts b/src/util/aggregation-helper.ts index b0833007e..3be503ffc 100644 --- a/src/util/aggregation-helper.ts +++ b/src/util/aggregation-helper.ts @@ -6,7 +6,7 @@ import {CONFIG, STRINGS} from '../config'; import {AggregationResult} from '../types/aggregation'; import {PluginParams} from '../types/interface'; -const {InvalidAggregationParams} = ERRORS; +const {InvalidAggregationParamsError} = ERRORS; const {INVALID_AGGREGATION_METHOD, METRIC_MISSING} = STRINGS; const {AGGREGATION_ADDITIONAL_PARAMS} = CONFIG; @@ -19,7 +19,9 @@ const checkIfMetricsAreValid = (metrics: string[]) => { const method = parameterize.getAggregationMethod(metric); if (method === 'none') { - throw new InvalidAggregationParams(INVALID_AGGREGATION_METHOD(metric)); + throw new InvalidAggregationParamsError( + INVALID_AGGREGATION_METHOD(metric) + ); } }); }; @@ -39,7 +41,7 @@ export const aggregateInputsIntoOne = ( return inputs.reduce((acc, input, index) => { for (const metric of extendedMetrics) { if (!(metric in input)) { - throw new InvalidAggregationParams(METRIC_MISSING(metric, index)); + throw new InvalidAggregationParamsError(METRIC_MISSING(metric, index)); } /** Checks if metric is timestamp or duration, then adds to aggregated value. */ From 4ad2f91d1583b7f6f578266a10e2b8556d574096 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Mon, 18 Mar 2024 17:03:38 +0400 Subject: [PATCH 3/8] fix(builtins): rename errors in group by --- src/builtins/group-by.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtins/group-by.ts b/src/builtins/group-by.ts index 32d5ff711..436e309ef 100644 --- a/src/builtins/group-by.ts +++ b/src/builtins/group-by.ts @@ -5,7 +5,7 @@ import {STRINGS} from '../config'; import {GroupByPlugin, PluginParams} from '../types/interface'; import {GroupByConfig} from '../types/group-by'; -const {InvalidGrouping} = ERRORS; +const {InvalidGroupingError} = ERRORS; const {INVALID_GROUP_BY} = STRINGS; @@ -56,7 +56,7 @@ export const GroupBy = (): GroupByPlugin => { inputs.reduce((acc, input) => { const groups = config.group.map(groupType => { if (!input[groupType]) { - throw new InvalidGrouping(INVALID_GROUP_BY(groupType)); + throw new InvalidGroupingError(INVALID_GROUP_BY(groupType)); } return input[groupType]; From 769613bd1c11cf543c4431fe280648cd7022ecd8 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Mon, 18 Mar 2024 17:03:57 +0400 Subject: [PATCH 4/8] test(util): add cases for aggregation helper --- .../unit/util/aggregation-helper.test.ts | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/src/__tests__/unit/util/aggregation-helper.test.ts b/src/__tests__/unit/util/aggregation-helper.test.ts index 301f3c8b9..20690bc2f 100644 --- a/src/__tests__/unit/util/aggregation-helper.test.ts +++ b/src/__tests__/unit/util/aggregation-helper.test.ts @@ -1,9 +1,12 @@ import {aggregateInputsIntoOne} from '../../../util/aggregation-helper'; import {ERRORS} from '../../../util/errors'; +import {STRINGS} from '../../../config'; + import {PluginParams} from '../../../types/interface'; -const {InvalidAggregationParams} = ERRORS; +const {InvalidAggregationParamsError} = ERRORS; +const {INVALID_AGGREGATION_METHOD, METRIC_MISSING} = STRINGS; describe('util/aggregation-helper: ', () => { describe('aggregateInputsIntoOne(): ', () => { @@ -12,12 +15,16 @@ describe('util/aggregation-helper: ', () => { const metrics: string[] = ['cpu/number-cores']; const isTemporal = false; - expect.assertions(1); + expect.assertions(2); try { aggregateInputsIntoOne(inputs, metrics, isTemporal); } catch (error) { - expect(error).toBeInstanceOf(InvalidAggregationParams); + expect(error).toBeInstanceOf(InvalidAggregationParamsError); + + if (error instanceof InvalidAggregationParamsError) { + expect(error.message).toEqual(INVALID_AGGREGATION_METHOD(metrics[0])); + } } }); @@ -26,13 +33,68 @@ describe('util/aggregation-helper: ', () => { const metrics: string[] = ['cpu/utilization']; const isTemporal = false; - expect.assertions(1); + expect.assertions(2); try { aggregateInputsIntoOne(inputs, metrics, isTemporal); } catch (error) { - expect(error).toBeInstanceOf(InvalidAggregationParams); + expect(error).toBeInstanceOf(InvalidAggregationParamsError); + + if (error instanceof InvalidAggregationParamsError) { + expect(error.message).toEqual(METRIC_MISSING(metrics[0], 0)); + } } }); + + it('passes `timestamp`, `duration` to aggregator if aggregation is temporal.', () => { + const inputs: PluginParams[] = [ + {timestamp: '', duration: 10, carbon: 10}, + {timestamp: '', duration: 10, carbon: 20}, + ]; + const metrics: string[] = ['carbon']; + const isTemporal = true; + + const expectedValue = { + timestamp: '', + duration: 10, + carbon: inputs[0].carbon + inputs[1].carbon, + }; + const aggregated = aggregateInputsIntoOne(inputs, metrics, isTemporal); + expect(aggregated).toEqual(expectedValue); + }); + + it('skips `timestamp`, `duration` if aggregation is not temporal.', () => { + const inputs: PluginParams[] = [ + {timestamp: '', duration: 10, carbon: 10}, + {timestamp: '', duration: 10, carbon: 20}, + ]; + const metrics: string[] = ['carbon']; + const isTemporal = false; + + const expectedValue = { + carbon: inputs[0].carbon + inputs[1].carbon, + }; + const aggregated = aggregateInputsIntoOne(inputs, metrics, isTemporal); + expect(aggregated).toEqual(expectedValue); + }); + + it('calculates average of metrics.', () => { + const inputs: PluginParams[] = [ + {timestamp: '', duration: 10, 'cpu/utilization': 10}, + {timestamp: '', duration: 10, 'cpu/utilization': 90}, + ]; + const metrics: string[] = ['cpu/utilization']; + const isTemporal = false; + + const expectedValue = { + 'cpu/utilization': + (inputs[0]['cpu/utilization'] + inputs[1]['cpu/utilization']) / + inputs.length, + }; + const aggregated = aggregateInputsIntoOne(inputs, metrics, isTemporal); + expect(aggregated).toEqual(expectedValue); + expect(aggregated.timestamp).toBeUndefined(); + expect(aggregated.duration).toBeUndefined(); + }); }); }); From 74b2f5295e89a493f93d3cf58022b71dd7248495 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Tue, 19 Mar 2024 15:20:23 +0400 Subject: [PATCH 5/8] test(util): init helpers --- src/__tests__/unit/util/helpers.test.ts | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/__tests__/unit/util/helpers.test.ts diff --git a/src/__tests__/unit/util/helpers.test.ts b/src/__tests__/unit/util/helpers.test.ts new file mode 100644 index 000000000..9feffc5f3 --- /dev/null +++ b/src/__tests__/unit/util/helpers.test.ts @@ -0,0 +1,40 @@ +const mockWarn = jest.fn(); +const mockError = jest.fn(); + +jest.mock('../../../util/logger', () => ({ + logger: { + warn: mockWarn, + error: mockError, + }, +})); + +import {andHandle} from '../../../util/helpers'; +import {ERRORS} from '../../../util/errors'; + +const {WriteFileError} = ERRORS; + +describe('util/helpers: ', () => { + describe('andHandle(): ', () => { + afterEach(() => { + mockWarn.mockReset(); + mockError.mockReset(); + }); + + it('logs error and warn in case of error is unknown.', () => { + const message = 'mock-message'; + const MockError = class extends Error {}; + + andHandle(new MockError(message)); + expect(mockWarn).toHaveBeenCalledTimes(1); + expect(mockError).toHaveBeenCalledTimes(1); + }); + + it('logs error in case of error is unknown.', () => { + const message = 'mock-message'; + + andHandle(new WriteFileError(message)); + expect(mockWarn).toHaveBeenCalledTimes(0); + expect(mockError).toHaveBeenCalledTimes(1); + }); + }); +}); From 8d1c1943aab72d3af77c76955f5594cdc5f3b8a0 Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Tue, 19 Mar 2024 17:50:02 +0400 Subject: [PATCH 6/8] fix(config): remove newline from strings --- src/config/strings.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/strings.ts b/src/config/strings.ts index 754951fc4..5781bc5e2 100644 --- a/src/config/strings.ts +++ b/src/config/strings.ts @@ -20,8 +20,7 @@ Incubation projects are experimental, offer no support guarantee, have minimal g SOMETHING_WRONG: 'Something wrong with cli arguments. Please check docs.', ISSUE_TEMPLATE: ` Impact Framework is an alpha release from the Green Software Foundation and is released to capture early feedback. If you'd like to offer some feedback, please use this issue template: -https://github.com/Green-Software-Foundation/if/issues/new?assignees=&labels=feedback&projects=&template=feedback.md&title=Feedback+-+ -`, +https://github.com/Green-Software-Foundation/if/issues/new?assignees=&labels=feedback&projects=&template=feedback.md&title=Feedback+-+`, INVALID_MODULE_PATH: (path: string) => `Provided module: '${path}' is invalid or not found.`, INVALID_TIME_NORMALIZATION: 'Start time or end time is missing.', From c45d6eb8f33d39966d24c0ff14a9d95fb55c63dd Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Tue, 19 Mar 2024 17:50:26 +0400 Subject: [PATCH 7/8] test(config): implement cases for strings --- src/__tests__/unit/config/strings.test.ts | 104 ++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/__tests__/unit/config/strings.test.ts b/src/__tests__/unit/config/strings.test.ts index e2a97d4b0..6315edeb6 100644 --- a/src/__tests__/unit/config/strings.test.ts +++ b/src/__tests__/unit/config/strings.test.ts @@ -1,14 +1,30 @@ import {STRINGS} from '../../../config/strings'; const { + NOT_NATIVE_PLUGIN, INVALID_MODULE_PATH, INVALID_AGGREGATION_METHOD, METRIC_MISSING, AVOIDING_PADDING, AVOIDING_PADDING_BY_EDGES, + INVALID_GROUP_BY, + REJECTING_OVERRIDE, + INVALID_EXHAUST_PLUGIN, + UNKNOWN_PARAM, + NOT_INITALIZED_PLUGIN, } = STRINGS; describe('config/strings: ', () => { + describe('NOT_NATIVE_PLUGIN(): ', () => { + it('successfully injects path into message.', () => { + const path = 'mock/path'; + const expectedMessage = ` +[!important] You are using plugin ${path} which is not part of the Impact Framework standard library. You should do your own research to ensure the plugins are up to date and accurate. They may not be actively maintained.`; + + expect(NOT_NATIVE_PLUGIN(path)).toEqual(expectedMessage); + }); + }); + describe('INVALID_MODULE_PATH(): ', () => { it('successfully appends given param to message.', () => { const param = 'mock-param'; @@ -19,6 +35,45 @@ describe('config/strings: ', () => { }); }); + describe('AVOIDING_PADDING(): ', () => { + it('successfully appends given param to message.', () => { + const param = 'mock-param'; + + const expectedMessage = `Avoiding padding at ${param}`; + + expect(AVOIDING_PADDING(param)).toEqual(expectedMessage); + }); + }); + + describe('AVOIDING_PADDING_BY_EDGES(): ', () => { + it('successfully appends given start and end params.', () => { + const start = true; + const end = true; + + const expectedMessage = 'Avoiding padding at start and end'; + + expect(AVOIDING_PADDING_BY_EDGES(start, end)).toEqual(expectedMessage); + }); + + it('successfully appends given start param.', () => { + const start = true; + const end = false; + + const expectedMessage = 'Avoiding padding at start'; + + expect(AVOIDING_PADDING_BY_EDGES(start, end)).toEqual(expectedMessage); + }); + + it('successfully appends given end param.', () => { + const start = false; + const end = true; + + const expectedMessage = 'Avoiding padding at end'; + + expect(AVOIDING_PADDING_BY_EDGES(start, end)).toEqual(expectedMessage); + }); + }); + describe('INVALID_AGGREGATION_METHOD(): ', () => { it('successfully appends given param to message.', () => { const param = 'mock-param'; @@ -40,6 +95,37 @@ describe('config/strings: ', () => { }); }); + describe('INVALID_GROUP_BY(): ', () => { + it('injects type in given message.', () => { + const type = 'mock-type'; + const message = `Invalid group ${type}.`; + + expect(INVALID_GROUP_BY(type)).toEqual(message); + }); + }); + + describe('REJECTING_OVERRIDE(): ', () => { + it('inejcts param name into message.', () => { + const param: any = { + name: 'mock-name', + description: 'mock-description', + aggregation: 'sum', + unit: 'mock-unit', + }; + + expect(REJECTING_OVERRIDE(param)); + }); + }); + + describe('INVALID_EXHAUST_PLUGIN(): ', () => { + it('injects plugin name into message.', () => { + const pluginName = 'mock-plugin'; + const message = `Invalid exhaust plugin: ${pluginName}.`; + + expect(INVALID_EXHAUST_PLUGIN(pluginName)).toEqual(message); + }); + }); + describe('AVOIDING_PADDING(): ', () => { it('successfully appends given param to message.', () => { const description_suffix = 'pad description'; @@ -50,6 +136,24 @@ describe('config/strings: ', () => { }); }); + describe('UNKNOWN_PARAM(): ', () => { + it('injects name into message.', () => { + const name = 'mock-name'; + const message = `Unknown parameter: ${name}. Using 'sum' aggregation method.`; + + expect(UNKNOWN_PARAM(name)).toEqual(message); + }); + }); + + describe('NOT_INITALIZED_PLUGIN(): ', () => { + it('injects name into message.', () => { + const name = 'mock-name'; + const message = `Not initalized plugin: ${name}. Check if ${name} is in 'manifest.initalize.plugins'.`; + + expect(NOT_INITALIZED_PLUGIN(name)).toEqual(message); + }); + }); + describe('AVOIDING_PADDING_BY_EDGES(): ', () => { it('successfully combines boolean params into a description.', () => { let description_suffix = 'start and end'; From e9dabc0a489de5086dc79b64d536e46c80583b2f Mon Sep 17 00:00:00 2001 From: Narek Hovhannisyan Date: Tue, 19 Mar 2024 19:32:38 +0400 Subject: [PATCH 8/8] fix(mocks): remove unused function arguments --- src/__mocks__/fs/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__mocks__/fs/index.ts b/src/__mocks__/fs/index.ts index 002082b3b..e6370223d 100644 --- a/src/__mocks__/fs/index.ts +++ b/src/__mocks__/fs/index.ts @@ -1,6 +1,6 @@ import * as YAML from 'js-yaml'; -export const readFile = async (filePath: string, _format: string) => { +export const readFile = async (filePath: string) => { /** mock for util/json */ if (filePath.includes('json-reject')) { return Promise.reject(new Error('rejected')); @@ -39,7 +39,7 @@ export const readFile = async (filePath: string, _format: string) => { cpu/utilization: 16`; }; -export const mkdir = (dirPath: string, _object: any) => dirPath; +export const mkdir = (dirPath: string) => dirPath; export const writeFile = async (pathToFile: string, content: string) => { if (pathToFile === 'reject') {