From c153378d5a0163cc08e2c2a787316de64938c83d Mon Sep 17 00:00:00 2001 From: Jordon Leach Date: Mon, 26 Feb 2024 09:33:26 -0500 Subject: [PATCH] Update removeEmptyAttrs function to delete empty object - migrate unit test utils and templates --- jest.config.js | 2 +- pkg/kubewarden/utils/object.ts | 16 ++- .../{ => _config_}/FixJSDOMEnvironment.ts | 0 .../{templates => _templates_}/defaultsApp.js | 0 .../policyConfig.js | 0 .../policyPackages.js | 0 .../policyServerPod.js | 0 .../policyTraces.js | 0 .../{templates => _templates_}/questions.js | 0 .../{templates => _templates_}/schemas.js | 0 tests/unit/{utils => _utils_}/wrapper.ts | 0 tests/unit/charts/admission/Admission.spec.ts | 4 +- tests/unit/charts/admission/General.spec.ts | 2 +- .../admission/NamespaceSelector.spec.ts | 2 +- tests/unit/charts/admission/Rules.spec.ts | 2 +- .../Dashboard/DashboardView.spec.ts | 4 +- .../unit/components/MetricsChecklist.spec.ts | 2 +- tests/unit/components/Policies/Create.spec.ts | 4 +- .../components/Policies/PolicyDetail.spec.ts | 2 +- .../components/Policies/PolicyGrid.spec.ts | 4 +- .../components/Questions/QuestionMap.spec.ts | 2 +- .../components/Questions/SequenceTree.spec.ts | 2 +- tests/unit/detail/PolicyServer.spec.ts | 2 +- tests/unit/utils/object.spec.ts | 110 ++++++++++++++++++ 24 files changed, 141 insertions(+), 19 deletions(-) rename tests/unit/{ => _config_}/FixJSDOMEnvironment.ts (100%) rename tests/unit/{templates => _templates_}/defaultsApp.js (100%) rename tests/unit/{templates => _templates_}/policyConfig.js (100%) rename tests/unit/{templates => _templates_}/policyPackages.js (100%) rename tests/unit/{templates => _templates_}/policyServerPod.js (100%) rename tests/unit/{templates => _templates_}/policyTraces.js (100%) rename tests/unit/{templates => _templates_}/questions.js (100%) rename tests/unit/{templates => _templates_}/schemas.js (100%) rename tests/unit/{utils => _utils_}/wrapper.ts (100%) create mode 100644 tests/unit/utils/object.spec.ts diff --git a/jest.config.js b/jest.config.js index e46c27a8..cb6e0f59 100644 --- a/jest.config.js +++ b/jest.config.js @@ -27,5 +27,5 @@ module.exports = { coverageDirectory: '/coverage/unit', coverageReporters: ['json', 'text-summary'], preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', - testEnvironment: './tests/unit/FixJSDOMEnvironment.ts', + testEnvironment: './tests/unit/_config_/FixJSDOMEnvironment.ts', }; diff --git a/pkg/kubewarden/utils/object.ts b/pkg/kubewarden/utils/object.ts index 8237ce07..ab0c1aae 100644 --- a/pkg/kubewarden/utils/object.ts +++ b/pkg/kubewarden/utils/object.ts @@ -1,16 +1,28 @@ import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; -export function removeEmptyAttrs(obj: any) { +export function removeEmptyAttrs(obj: any): any { Object.keys(obj).forEach((key: any) => { const value = obj[key]; - if ( value === undefined || value === null || value === '' || (Array.isArray(value) && !value.length) || (isObject(value) && isEmpty(value)) ) { + // Check for value being empty, null, or an empty array + if ( value === undefined || value === null || value === '' || (Array.isArray(value) && !value.length) ) { delete obj[key]; } else if ( isObject(value) ) { + // Recursively clean the object removeEmptyAttrs(value); + + // After cleaning, if the object is empty, delete it + if ( isEmpty(value) ) { + delete obj[key]; + } } }); + // Added check: If the parent object is now empty, return null to signal removal + if ( isEmpty(obj) ) { + return null; + } + return obj; } diff --git a/tests/unit/FixJSDOMEnvironment.ts b/tests/unit/_config_/FixJSDOMEnvironment.ts similarity index 100% rename from tests/unit/FixJSDOMEnvironment.ts rename to tests/unit/_config_/FixJSDOMEnvironment.ts diff --git a/tests/unit/templates/defaultsApp.js b/tests/unit/_templates_/defaultsApp.js similarity index 100% rename from tests/unit/templates/defaultsApp.js rename to tests/unit/_templates_/defaultsApp.js diff --git a/tests/unit/templates/policyConfig.js b/tests/unit/_templates_/policyConfig.js similarity index 100% rename from tests/unit/templates/policyConfig.js rename to tests/unit/_templates_/policyConfig.js diff --git a/tests/unit/templates/policyPackages.js b/tests/unit/_templates_/policyPackages.js similarity index 100% rename from tests/unit/templates/policyPackages.js rename to tests/unit/_templates_/policyPackages.js diff --git a/tests/unit/templates/policyServerPod.js b/tests/unit/_templates_/policyServerPod.js similarity index 100% rename from tests/unit/templates/policyServerPod.js rename to tests/unit/_templates_/policyServerPod.js diff --git a/tests/unit/templates/policyTraces.js b/tests/unit/_templates_/policyTraces.js similarity index 100% rename from tests/unit/templates/policyTraces.js rename to tests/unit/_templates_/policyTraces.js diff --git a/tests/unit/templates/questions.js b/tests/unit/_templates_/questions.js similarity index 100% rename from tests/unit/templates/questions.js rename to tests/unit/_templates_/questions.js diff --git a/tests/unit/templates/schemas.js b/tests/unit/_templates_/schemas.js similarity index 100% rename from tests/unit/templates/schemas.js rename to tests/unit/_templates_/schemas.js diff --git a/tests/unit/utils/wrapper.ts b/tests/unit/_utils_/wrapper.ts similarity index 100% rename from tests/unit/utils/wrapper.ts rename to tests/unit/_utils_/wrapper.ts diff --git a/tests/unit/charts/admission/Admission.spec.ts b/tests/unit/charts/admission/Admission.spec.ts index 1bc9c057..557083dd 100644 --- a/tests/unit/charts/admission/Admission.spec.ts +++ b/tests/unit/charts/admission/Admission.spec.ts @@ -8,8 +8,8 @@ import Settings from '@kubewarden/chart/kubewarden/admission/Settings.vue'; import Questions from '@kubewarden/components/Questions/index.vue'; import { DEFAULT_POLICY, KUBEWARDEN } from '@kubewarden/types'; -import policyConfig from '../../templates/policyConfig'; -import { question } from '../../templates/questions'; +import policyConfig from '@tests/unit/_templates_/policyConfig'; +import { question } from '@tests/unit/_templates_/questions'; describe('component: Admission', () => { it('settings component should be shown when custom policy', () => { diff --git a/tests/unit/charts/admission/General.spec.ts b/tests/unit/charts/admission/General.spec.ts index 716c960d..b873c5d4 100644 --- a/tests/unit/charts/admission/General.spec.ts +++ b/tests/unit/charts/admission/General.spec.ts @@ -9,7 +9,7 @@ import General from '@kubewarden/chart/kubewarden/admission/General.vue'; import LabeledSelect from '@shell/components/form/LabeledSelect'; import { RadioGroup } from '@components/Form/Radio'; -import policyConfig from '../../templates/policyConfig'; +import policyConfig from '@tests/unit/_templates_/policyConfig'; describe('component: General', () => { it('should display the PolicyServer options if available', () => { diff --git a/tests/unit/charts/admission/NamespaceSelector.spec.ts b/tests/unit/charts/admission/NamespaceSelector.spec.ts index 2530e65e..1a13f43e 100644 --- a/tests/unit/charts/admission/NamespaceSelector.spec.ts +++ b/tests/unit/charts/admission/NamespaceSelector.spec.ts @@ -9,7 +9,7 @@ import NamespaceSelector from '@kubewarden/chart/kubewarden/admission/NamespaceS import KeyValue from '@shell/components/form/KeyValue'; import MatchExpressions from '@shell/components/form/MatchExpressions'; -import policyConfig from '../../templates/policyConfig'; +import policyConfig from '@tests/unit/_templates_/policyConfig'; describe('component: NamespaceSelector', () => { it('matchExpressions should add to the policy namespaceSelector spec', async() => { diff --git a/tests/unit/charts/admission/Rules.spec.ts b/tests/unit/charts/admission/Rules.spec.ts index 369f05d5..4acd243c 100644 --- a/tests/unit/charts/admission/Rules.spec.ts +++ b/tests/unit/charts/admission/Rules.spec.ts @@ -8,7 +8,7 @@ import { KUBEWARDEN } from '@kubewarden/types'; import Rules from '@kubewarden/chart/kubewarden/admission/Rules'; import Rule from '@kubewarden/chart/kubewarden/admission/Rules/Rule.vue'; -import policyConfig from '../../templates/policyConfig'; +import policyConfig from '@tests/unit/_templates_/policyConfig'; describe('component: Rules', () => { it('rules should render rule components based on policy config', async() => { diff --git a/tests/unit/components/Dashboard/DashboardView.spec.ts b/tests/unit/components/Dashboard/DashboardView.spec.ts index acefef3e..4c2edda8 100644 --- a/tests/unit/components/Dashboard/DashboardView.spec.ts +++ b/tests/unit/components/Dashboard/DashboardView.spec.ts @@ -5,8 +5,8 @@ import DashboardView from '@kubewarden/components/Dashboard/DashboardView.vue'; import DefaultsBanner from '@kubewarden/components/DefaultsBanner'; import ConsumptionGauge from '@shell/components/ConsumptionGauge'; -import DEFAULTS_APP from '../../templates/defaultsApp'; -import PS_POD from '../../templates/policyServerPod'; +import DEFAULTS_APP from '@tests/unit/_templates_/defaultsApp'; +import PS_POD from '@tests/unit/_templates_/policyServerPod'; describe('component: DashboardView', () => { const commonMocks = { diff --git a/tests/unit/components/MetricsChecklist.spec.ts b/tests/unit/components/MetricsChecklist.spec.ts index 9f3ea045..86236b96 100644 --- a/tests/unit/components/MetricsChecklist.spec.ts +++ b/tests/unit/components/MetricsChecklist.spec.ts @@ -1,4 +1,4 @@ -import { createWrapper } from '@tests/unit/utils/wrapper'; +import { createWrapper } from '@tests/unit/_utils_/wrapper'; import MetricsChecklist from '@kubewarden/components/MetricsChecklist.vue'; diff --git a/tests/unit/components/Policies/Create.spec.ts b/tests/unit/components/Policies/Create.spec.ts index fee830c8..b7abe170 100644 --- a/tests/unit/components/Policies/Create.spec.ts +++ b/tests/unit/components/Policies/Create.spec.ts @@ -2,11 +2,11 @@ import { describe, expect, it } from '@jest/globals'; import jsyaml from 'js-yaml'; import { KUBEWARDEN } from '@kubewarden/types'; -import { createWrapper } from '@tests/unit/utils/wrapper'; +import { createWrapper } from '@tests/unit/_utils_/wrapper'; import Create from '@kubewarden/components/Policies/Create.vue'; -import policyPackages from '@tests/unit/templates/policyPackages'; +import policyPackages from '@tests/unit/_templates_/policyPackages'; function mockParsePackageMetadata(data) { if (data) { diff --git a/tests/unit/components/Policies/PolicyDetail.spec.ts b/tests/unit/components/Policies/PolicyDetail.spec.ts index 53dff364..5c7d8d35 100644 --- a/tests/unit/components/Policies/PolicyDetail.spec.ts +++ b/tests/unit/components/Policies/PolicyDetail.spec.ts @@ -1,6 +1,6 @@ import { ARTIFACTHUB_PKG_ANNOTATION } from '@kubewarden/types'; -import { createWrapper } from '@tests/unit/utils/wrapper'; +import { createWrapper } from '@tests/unit/_utils_/wrapper'; import PolicyDetail from '@kubewarden/components/Policies/PolicyDetail.vue'; const commonMocks = { $fetchState: { pending: false } }; diff --git a/tests/unit/components/Policies/PolicyGrid.spec.ts b/tests/unit/components/Policies/PolicyGrid.spec.ts index fb8a47f4..49181e59 100644 --- a/tests/unit/components/Policies/PolicyGrid.spec.ts +++ b/tests/unit/components/Policies/PolicyGrid.spec.ts @@ -8,8 +8,8 @@ import { KUBEWARDEN } from '@kubewarden/types'; import PolicyGrid from '@kubewarden/components/Policies/PolicyGrid.vue'; import LabeledSelect from '@shell/components/form/LabeledSelect'; -import policyPackages from '../../templates/policyPackages.js'; -import schemas from '../../templates/schemas.js'; +import policyPackages from '@tests/unit/_templates_/policyPackages.js'; +import schemas from '@tests/unit/_templates_/schemas.js'; const defaultComputed = { allSchemas: jest.fn() }; const defaultMocks = { diff --git a/tests/unit/components/Questions/QuestionMap.spec.ts b/tests/unit/components/Questions/QuestionMap.spec.ts index 91005946..a1410ec3 100644 --- a/tests/unit/components/Questions/QuestionMap.spec.ts +++ b/tests/unit/components/Questions/QuestionMap.spec.ts @@ -7,7 +7,7 @@ import Question from '@kubewarden/components/Questions/index.vue'; import QuestionMap from '@kubewarden/components/Questions/QuestionMap.vue'; import SequenceType from '@kubewarden/components/Questions/SequenceTree.vue'; -import { deepMapQuestion } from '../../templates/questions'; +import { deepMapQuestion } from '@tests/unit/_templates_/questions'; describe('component: QuestionMap', () => { it('emits addSeq with question props', () => { diff --git a/tests/unit/components/Questions/SequenceTree.spec.ts b/tests/unit/components/Questions/SequenceTree.spec.ts index ccf3e1c4..6240433e 100644 --- a/tests/unit/components/Questions/SequenceTree.spec.ts +++ b/tests/unit/components/Questions/SequenceTree.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from '@jest/globals'; import Question from '@kubewarden/components/Questions/index.vue'; import SequenceType from '@kubewarden/components/Questions/SequenceTree.vue'; -import { question, deepSequenceQuestion } from '../../templates/questions'; +import { question, deepSequenceQuestion } from '@tests/unit/_templates_/questions'; function getGroups(flattenedQuestions) { const map = {}; diff --git a/tests/unit/detail/PolicyServer.spec.ts b/tests/unit/detail/PolicyServer.spec.ts index 574e2681..3b725a55 100644 --- a/tests/unit/detail/PolicyServer.spec.ts +++ b/tests/unit/detail/PolicyServer.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from '@jest/globals'; import PolicyServer from '@kubewarden/detail/policies.kubewarden.io.policyserver.vue'; import CountGauge from '@shell/components/CountGauge'; -import TraceTestData from '../templates/policyTraces'; +import TraceTestData from '@tests/unit/_templates_/policyTraces'; const policyGauges = { Active: { diff --git a/tests/unit/utils/object.spec.ts b/tests/unit/utils/object.spec.ts new file mode 100644 index 00000000..da10878a --- /dev/null +++ b/tests/unit/utils/object.spec.ts @@ -0,0 +1,110 @@ +import { removeEmptyAttrs } from '@kubewarden/utils/object'; + +describe('removeEmptyAttrs', () => { + it('should remove attributes that are undefined, null, or empty', () => { + const obj = { + name: 'Test', + value: null, + details: {}, + list: [], + nested: { + emptyString: '', + emptyObj: {}, + emptyArray: [], + }, + }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual({ name: 'Test' }); + }); + + it('should remove deeply nested empty objects', () => { + const obj = { level1: { level2: { level3: {} } } }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual(null); + }); + + it('should handle arrays and non-object values correctly', () => { + const obj = { + arrayNotEmpty: [1, 2, 3], + stringValue: 'hello', + numberValue: 123, + booleanValue: false, + emptyObject: {}, + }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual({ + arrayNotEmpty: [1, 2, 3], + stringValue: 'hello', + numberValue: 123, + booleanValue: false, + }); + }); + + it('should remove settings entirely if cpu and memory are empty', () => { + const obj = { + apiVersion: 'policies.kubewarden.io.v1', + kind: 'ClusterAdmissionPolicy', + spec: { + backgroundAudit: true, + policyServer: 'default', + settings: { + cpu: {}, + memory: {}, + }, + }, + }; + const expectedObj = { + apiVersion: 'policies.kubewarden.io.v1', + kind: 'ClusterAdmissionPolicy', + spec: { + backgroundAudit: true, + policyServer: 'default' + } + }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual(expectedObj); + }); + + it('should remove the entire object if it becomes empty after cleanup', () => { + const obj = { + spec: { + settings: { + cpu: {}, + memory: {}, + }, + }, + }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual(null); + }); + + it('should remove only the empty memory object while preserving non-empty cpu object', () => { + const obj = { + settings: { + cpu: { + defaultLimit: '100m', + defaultRequest: '100m', + maxLimit: '100m', + }, + memory: {}, + }, + }; + const expectedObj = { + settings: { + cpu: { + defaultLimit: '100m', + defaultRequest: '100m', + maxLimit: '100m', + }, + }, + }; + const cleanedObj = removeEmptyAttrs(obj); + + expect(cleanedObj).toEqual(expectedObj); + }); +});