diff --git a/mender/tests/README.md b/mender/tests/README.md new file mode 100644 index 00000000..72f1c7b7 --- /dev/null +++ b/mender/tests/README.md @@ -0,0 +1,110 @@ +# Mender Helm Chart Unit Tests + +This directory contains Helm unit tests for the Mender chart using [helm-unittest](https://github.com/helm-unittest/helm-unittest). + +## Test Suites + +### 1. Database Migrations (`migrations_test.yaml`) +Tests the Helm hook migration jobs for database schema migrations. + +**Coverage:** +- **Common migrations** (Open Source + Enterprise): + - device-auth, deployments, deviceconfig, deviceconnect + - inventory, iot-manager, useradm, workflows +- **Enterprise-only migrations**: + - auditlogs, devicemonitor, tenantadm +- **Verification:** + - Jobs are created when `dbmigration.enable: true` + - Jobs respect service enabled/disabled state + - Enterprise migrations only run when `global.enterprise: true` + - Correct Helm hook annotations (pre-install, pre-upgrade) + - Correct hook weight and delete policy + +### 2. Open Source Backing Services (`opensource-backing-services_test.yaml`) +Tests the integrated MongoDB deployment for open source installations. + +**Coverage:** +- **MongoDB Deployment:** + - Creation when `mongodb.enabled: true` + - Container configuration (image, env vars, ports) + - Health probes (liveness, readiness) + - Volume mounts (data persistence) +- **MongoDB Service:** + - ClusterIP service on port 27017 + - Correct selectors +- **MongoDB Secret:** + - Root password stored as base64 +- **MongoDB PersistentVolumeClaim:** + - Created when `mongodb.persistence.enabled: true` + - Storage class configuration + - Access modes and size + +### 3. NATS Integration (`nats-integration_test.yaml`) +Tests the NATS message broker integration for open source installations. + +**Coverage:** +- **NATS Environment Variables:** + - WORKFLOWS_NATS_URI configuration + - WORKFLOWS_NATS_STREAM_REPLICAS configuration +- **Configuration Modes:** + - Integrated NATS (nats.enabled: true) + - External NATS (via global.nats.URL) + - NATS credentials from existing secret +- **Open Source Defaults:** + - NATS enabled by default + - JetStream replicas configured correctly + +### 4. Workflows HPA (`workflows-hpa_test.yaml`) +Tests the Horizontal Pod Autoscaler for the workflows service. + +**Coverage:** +- HPA creation conditions +- External metrics configuration +- Scale behavior policies +- Replica configuration + +## Running Tests + +### Run all tests: +```bash +helm unittest mender +``` + +### Run specific test suite: +```bash +helm unittest mender --file 'tests/migrations_test.yaml' +helm unittest mender --file 'tests/opensource-backing-services_test.yaml' +helm unittest mender --file 'tests/nats-integration_test.yaml' +helm unittest mender --file 'tests/workflows-hpa_test.yaml' +``` + +## Test Statistics + +- **Total Test Suites:** 4 +- **Total Tests:** 61 +- **Coverage:** + - Database migrations: 20 tests + - MongoDB (Open Source): 25 tests + - NATS integration: 7 tests + - Workflows HPA: 9 tests + +## Open Source vs Enterprise + +The tests verify that the correct backing services are deployed based on the deployment type: + +### Open Source Deployment +- **Integrated MongoDB** (`mongodb.enabled: true`) +- **Integrated NATS** (`nats.enabled: true`) +- **8 common migration jobs** + +### Enterprise Deployment +- **External MongoDB** (via connection string) +- **External NATS** (via `global.nats.URL`) +- **11 migration jobs** (8 common + 3 enterprise-only) + +## Prerequisites + +Install the helm-unittest plugin: +```bash +helm plugin install https://github.com/helm-unittest/helm-unittest +``` diff --git a/mender/tests/migrations_test.yaml b/mender/tests/migrations_test.yaml new file mode 100644 index 00000000..878fee54 --- /dev/null +++ b/mender/tests/migrations_test.yaml @@ -0,0 +1,309 @@ +suite: test database migrations +templates: + - templates/device-auth/job.yaml + - templates/deployments/job.yaml + - templates/deviceconfig/job.yaml + - templates/deviceconnect/job.yaml + - templates/inventory/job.yaml + - templates/iot-manager/job.yaml + - templates/useradm/job.yaml + - templates/workflows/job.yaml + - templates/auditlogs/job.yaml + - templates/devicemonitor/job.yaml + - templates/tenantadm/job.yaml +tests: + ############################################################################## + # Global migration control tests + ############################################################################## + - it: should not create any migration jobs when dbmigration is disabled + set: + dbmigration.enable: false + global.enterprise: true + asserts: + - hasDocuments: + count: 0 + + ############################################################################## + # Common migrations (Open Source + Enterprise) + ############################################################################## + + + # device-auth migration (common) + - it: should create device-auth migration when enabled + set: + dbmigration.enable: true + device_auth.enabled: true + template: templates/device-auth/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*deviceauth-migration$ + - equal: + path: spec.backoffLimit + value: 5 + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + - equal: + path: metadata.annotations["helm.sh/hook-weight"] + value: "10" + - equal: + path: metadata.annotations["helm.sh/hook-delete-policy"] + value: before-hook-creation,hook-succeeded + + - it: should not create device-auth migration when device_auth is disabled + set: + dbmigration.enable: true + device_auth.enabled: false + template: templates/device-auth/job.yaml + asserts: + - hasDocuments: + count: 0 + + # deployments migration (common) + - it: should create deployments migration when enabled + set: + dbmigration.enable: true + deployments.enabled: true + template: templates/deployments/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*deployments-migration$ + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + + # useradm migration (common) + - it: should create useradm migration when enabled + set: + dbmigration.enable: true + useradm.enabled: true + template: templates/useradm/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*useradm-migration$ + + # workflows migration (common) + - it: should create workflows migration when enabled + set: + dbmigration.enable: true + workflows.enabled: true + template: templates/workflows/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*workflows-migration$ + + # inventory migration (common) + - it: should create inventory migration when enabled + set: + dbmigration.enable: true + inventory.enabled: true + template: templates/inventory/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*inventory-migration$ + + # deviceconnect migration (common) + - it: should create deviceconnect migration when enabled + set: + dbmigration.enable: true + deviceconnect.enabled: true + template: templates/deviceconnect/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*deviceconnect-migration$ + + # deviceconfig migration (common) + - it: should create deviceconfig migration when enabled + set: + dbmigration.enable: true + deviceconfig.enabled: true + template: templates/deviceconfig/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*deviceconfig-migration$ + + # iot-manager migration (common) + - it: should create iot-manager migration when enabled + set: + dbmigration.enable: true + iot_manager.enabled: true + template: templates/iot-manager/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*iot-manager-migration$ + + ############################################################################## + # Enterprise-only migrations + ############################################################################## + + + # auditlogs migration (enterprise-only) + - it: should create auditlogs migration for enterprise when enabled + set: + dbmigration.enable: true + global.enterprise: true + auditlogs.enabled: true + template: templates/auditlogs/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*auditlogs-migration$ + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + + - it: should not create auditlogs migration for open source + set: + dbmigration.enable: true + global.enterprise: false + auditlogs.enabled: true + template: templates/auditlogs/job.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should not create auditlogs migration when auditlogs is disabled + set: + dbmigration.enable: true + global.enterprise: true + auditlogs.enabled: false + template: templates/auditlogs/job.yaml + asserts: + - hasDocuments: + count: 0 + + # devicemonitor migration (enterprise-only) + - it: should create devicemonitor migration for enterprise when enabled + set: + dbmigration.enable: true + global.enterprise: true + devicemonitor.enabled: true + template: templates/devicemonitor/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*devicemonitor-migration$ + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + + - it: should not create devicemonitor migration for open source + set: + dbmigration.enable: true + global.enterprise: false + devicemonitor.enabled: true + template: templates/devicemonitor/job.yaml + asserts: + - hasDocuments: + count: 0 + + # tenantadm migration (enterprise-only) + - it: should create tenantadm migration for enterprise when enabled + set: + dbmigration.enable: true + global.enterprise: true + tenantadm.enabled: true + template: templates/tenantadm/job.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Job + - matchRegex: + path: metadata.name + pattern: .*tenantadm-migration$ + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install,pre-upgrade + + - it: should not create tenantadm migration for open source + set: + dbmigration.enable: true + global.enterprise: false + tenantadm.enabled: true + template: templates/tenantadm/job.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should not create tenantadm migration when tenantadm is disabled + set: + dbmigration.enable: true + global.enterprise: true + tenantadm.enabled: false + template: templates/tenantadm/job.yaml + asserts: + - hasDocuments: + count: 0 + + ############################################################################## + # Migration job configuration tests + ############################################################################## + - it: should use restart policy Never for migrations + set: + dbmigration.enable: true + device_auth.enabled: true + template: templates/device-auth/job.yaml + asserts: + - equal: + path: spec.template.spec.restartPolicy + value: Never + + - it: should have correct labels on migration jobs + set: + dbmigration.enable: true + device_auth.enabled: true + template: templates/device-auth/job.yaml + asserts: + - isNotNull: + path: metadata.labels + - isNotNull: + path: spec.template.metadata.labels diff --git a/mender/tests/nats-integration_test.yaml b/mender/tests/nats-integration_test.yaml new file mode 100644 index 00000000..751d7ee8 --- /dev/null +++ b/mender/tests/nats-integration_test.yaml @@ -0,0 +1,116 @@ +suite: test NATS integration for open source +templates: + - templates/workflows/deployment-worker.yaml +tests: + ############################################################################## + # NATS Configuration Tests - Workflows Service + ############################################################################## + - it: should configure NATS environment variables when NATS is enabled + set: + nats.enabled: true + nats.replicas: 3 + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_URI + value: nats://RELEASE-NAME-nats + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_STREAM_REPLICAS + value: "3" + + - it: should use NATS replicas from nats.replicas when NATS is enabled + set: + nats.enabled: true + nats.replicas: 5 + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_STREAM_REPLICAS + value: "5" + + - it: should use workflows.nats.replicas when NATS is disabled (external NATS) + set: + nats.enabled: false + global.nats.URL: "nats://external-nats:4222" + workflows.nats.replicas: 3 + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_STREAM_REPLICAS + value: "3" + + - it: should use external NATS URL when provided + set: + nats.enabled: false + global.nats.URL: "nats://external-nats.example.com:4222" + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_URI + value: "nats://external-nats.example.com:4222" + + - it: should use NATS credentials from existing secret when specified + set: + nats.enabled: true + global.nats.existingSecret: my-nats-secret + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_URI + valueFrom: + secretKeyRef: + name: my-nats-secret + key: NATS_URI + + ############################################################################## + # Open Source Default Configuration + ############################################################################## + - it: should enable NATS by default for open source deployment + set: + global.enterprise: false + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_URI + value: nats://RELEASE-NAME-nats + + - it: should configure JetStream replicas correctly for open source + set: + global.enterprise: false + nats.enabled: true + nats.replicas: 3 + workflows.enabled: true + workflows.automigrate: false + template: templates/workflows/deployment-worker.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKFLOWS_NATS_STREAM_REPLICAS + value: "3" diff --git a/mender/tests/opensource-backing-services_test.yaml b/mender/tests/opensource-backing-services_test.yaml new file mode 100644 index 00000000..c686cb2e --- /dev/null +++ b/mender/tests/opensource-backing-services_test.yaml @@ -0,0 +1,375 @@ +suite: test open source backing services +templates: + - templates/mongodb/deployment.yaml + - templates/mongodb/service.yaml + - templates/mongodb/secret.yaml + - templates/mongodb/pvc.yaml +tests: + ############################################################################## + # MongoDB Deployment Tests + ############################################################################## + - it: should create MongoDB deployment when enabled + set: + mongodb.enabled: true + template: templates/mongodb/deployment.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Deployment + - matchRegex: + path: metadata.name + pattern: .*-mongodb$ + - equal: + path: spec.replicas + value: 1 + - equal: + path: spec.strategy.type + value: Recreate + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install + - equal: + path: metadata.annotations["helm.sh/hook-weight"] + value: "-100" + + - it: should not create MongoDB deployment when disabled + set: + mongodb.enabled: false + template: templates/mongodb/deployment.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should configure MongoDB container correctly + set: + mongodb.enabled: true + mongodb.image.repository: mongo + mongodb.image.tag: "8.0.16" + mongodb.image.pullPolicy: IfNotPresent + template: templates/mongodb/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].name + value: mongodb + - equal: + path: spec.template.spec.containers[0].image + value: mongo:8.0.16 + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: IfNotPresent + + - it: should configure MongoDB environment variables + set: + mongodb.enabled: true + mongodb.auth.rootUsername: "testuser" + mongodb.auth.database: "testdb" + template: templates/mongodb/deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: MONGO_INITDB_ROOT_USERNAME + value: "testuser" + - contains: + path: spec.template.spec.containers[0].env + content: + name: MONGO_INITDB_DATABASE + value: "testdb" + - contains: + path: spec.template.spec.containers[0].env + content: + name: MONGO_INITDB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: RELEASE-NAME-mender-mongodb + key: mongodb-root-password + + - it: should configure MongoDB port correctly + set: + mongodb.enabled: true + template: templates/mongodb/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].ports[0].name + value: mongodb + - equal: + path: spec.template.spec.containers[0].ports[0].containerPort + value: 27017 + - equal: + path: spec.template.spec.containers[0].ports[0].protocol + value: TCP + + - it: should configure MongoDB health probes + set: + mongodb.enabled: true + template: templates/mongodb/deployment.yaml + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe + - equal: + path: spec.template.spec.containers[0].livenessProbe.exec.command[0] + value: mongosh + - equal: + path: spec.template.spec.containers[0].readinessProbe.exec.command[0] + value: mongosh + + - it: should mount data volume correctly + set: + mongodb.enabled: true + mongodb.persistence.enabled: false + template: templates/mongodb/deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: mongodb-data + mountPath: /data/db + - contains: + path: spec.template.spec.volumes + content: + name: mongodb-data + emptyDir: {} + + - it: should use PVC when persistence is enabled + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + template: templates/mongodb/deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: mongodb-data + persistentVolumeClaim: + claimName: RELEASE-NAME-mender-mongodb-pvc + + - it: should have correct component labels + set: + mongodb.enabled: true + template: templates/mongodb/deployment.yaml + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/component"] + value: mongodb + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/component"] + value: mongodb + + ############################################################################## + # MongoDB Service Tests + ############################################################################## + - it: should create MongoDB service when enabled + set: + mongodb.enabled: true + template: templates/mongodb/service.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + - matchRegex: + path: metadata.name + pattern: .*-mongodb$ + - equal: + path: spec.type + value: ClusterIP + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install + - equal: + path: metadata.annotations["helm.sh/hook-weight"] + value: "-100" + + - it: should not create MongoDB service when disabled + set: + mongodb.enabled: false + template: templates/mongodb/service.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should configure MongoDB service port + set: + mongodb.enabled: true + template: templates/mongodb/service.yaml + asserts: + - equal: + path: spec.ports[0].name + value: mongodb + - equal: + path: spec.ports[0].port + value: 27017 + - equal: + path: spec.ports[0].targetPort + value: mongodb + - equal: + path: spec.ports[0].protocol + value: TCP + + - it: should have correct selector for MongoDB service + set: + mongodb.enabled: true + template: templates/mongodb/service.yaml + asserts: + - equal: + path: spec.selector["app.kubernetes.io/component"] + value: mongodb + + ############################################################################## + # MongoDB Secret Tests + ############################################################################## + - it: should create MongoDB secret when enabled + set: + mongodb.enabled: true + mongodb.auth.rootPassword: "testpassword" + template: templates/mongodb/secret.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Secret + - matchRegex: + path: metadata.name + pattern: .*-mongodb$ + - equal: + path: type + value: Opaque + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install + - equal: + path: metadata.annotations["helm.sh/hook-weight"] + value: "-100" + + - it: should not create MongoDB secret when disabled + set: + mongodb.enabled: false + template: templates/mongodb/secret.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should encode MongoDB root password in secret + set: + mongodb.enabled: true + mongodb.auth.rootPassword: "mypassword" + template: templates/mongodb/secret.yaml + asserts: + - isNotNull: + path: data["mongodb-root-password"] + - equal: + path: data["mongodb-root-password"] + value: bXlwYXNzd29yZA== + + ############################################################################## + # MongoDB PVC Tests + ############################################################################## + - it: should create MongoDB PVC when both mongodb and persistence are enabled + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + mongodb.persistence.size: 10Gi + mongodb.persistence.accessMode: ReadWriteOnce + template: templates/mongodb/pvc.yaml + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PersistentVolumeClaim + - matchRegex: + path: metadata.name + pattern: .*-mongodb-pvc$ + - equal: + path: spec.resources.requests.storage + value: 10Gi + - contains: + path: spec.accessModes + content: ReadWriteOnce + - equal: + path: metadata.annotations["helm.sh/hook"] + value: pre-install + - equal: + path: metadata.annotations["helm.sh/hook-weight"] + value: "-150" + + - it: should not create MongoDB PVC when mongodb is disabled + set: + mongodb.enabled: false + mongodb.persistence.enabled: true + template: templates/mongodb/pvc.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should not create MongoDB PVC when persistence is disabled + set: + mongodb.enabled: true + mongodb.persistence.enabled: false + template: templates/mongodb/pvc.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should configure storage class when specified + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + mongodb.persistence.storageClass: fast-ssd + template: templates/mongodb/pvc.yaml + asserts: + - equal: + path: spec.storageClassName + value: fast-ssd + + - it: should not set storage class when not specified + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + template: templates/mongodb/pvc.yaml + asserts: + - isNull: + path: spec.storageClassName + + ############################################################################## + # Open Source Deployment Complete Tests + ############################################################################## + - it: should create deployment for open source with persistence + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + global.enterprise: false + template: templates/mongodb/deployment.yaml + asserts: + - hasDocuments: + count: 1 + + - it: should create service for open source + set: + mongodb.enabled: true + global.enterprise: false + template: templates/mongodb/service.yaml + asserts: + - hasDocuments: + count: 1 + + - it: should create secret for open source + set: + mongodb.enabled: true + global.enterprise: false + template: templates/mongodb/secret.yaml + asserts: + - hasDocuments: + count: 1 + + - it: should create PVC for open source with persistence + set: + mongodb.enabled: true + mongodb.persistence.enabled: true + global.enterprise: false + template: templates/mongodb/pvc.yaml + asserts: + - hasDocuments: + count: 1