From a08951aaa36659fd3af4b056ff7ba2b3f6fb85bb Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Sat, 8 Nov 2025 17:23:18 +0300 Subject: [PATCH] test(charts): comprehensive test improvements and expansions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1: Improve and expand existing test coverage with edge cases, validations, and regression tests. Changes: - Rename cache_persistence_test → persistence_test with full coverage for config, media, and cache persistence (60+ tests) - Rename service_ipv6_test → service_test with full service coverage including types, ports, annotations, LoadBalancer settings (80+ tests) - Rename startup_probe_test → probes_test covering all three probe types: startup, liveness, readiness with all configurations (60+ tests) - Expand networkpolicy_test with IPv4/IPv6 CIDR validations, egress restrictions, and edge cases (+13 tests) - Expand init_containers_test with full container spec and merge order validation (+5 tests) - Expand envfrom_test with prefix, optional, and multiple source combinations (+9 tests) Test coverage improvements: - All edge cases for persistence types (pvc, hostPath, emptyDir) - Service type validation (ClusterIP, NodePort, LoadBalancer, ExternalName) - Probe types validation (httpGet, tcpSocket, exec) - NetworkPolicy CIDR format validation (IPv4/IPv6) - Comprehensive regression tests for default behavior Total: 6 improved test files, 230+ new test cases Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../tests/cache_persistence_test.yaml | 160 ----- charts/jellyfin/tests/envfrom_test.yaml | 148 ++++ .../jellyfin/tests/init_containers_test.yaml | 131 ++++ charts/jellyfin/tests/networkpolicy_test.yaml | 213 ++++++ charts/jellyfin/tests/persistence_test.yaml | 663 ++++++++++++++++++ charts/jellyfin/tests/probes_test.yaml | 447 ++++++++++++ charts/jellyfin/tests/service_ipv6_test.yaml | 128 ---- charts/jellyfin/tests/service_test.yaml | 465 ++++++++++++ charts/jellyfin/tests/startup_probe_test.yaml | 92 --- 9 files changed, 2067 insertions(+), 380 deletions(-) delete mode 100644 charts/jellyfin/tests/cache_persistence_test.yaml create mode 100644 charts/jellyfin/tests/persistence_test.yaml create mode 100644 charts/jellyfin/tests/probes_test.yaml delete mode 100644 charts/jellyfin/tests/service_ipv6_test.yaml create mode 100644 charts/jellyfin/tests/service_test.yaml delete mode 100644 charts/jellyfin/tests/startup_probe_test.yaml diff --git a/charts/jellyfin/tests/cache_persistence_test.yaml b/charts/jellyfin/tests/cache_persistence_test.yaml deleted file mode 100644 index fafcf9e..0000000 --- a/charts/jellyfin/tests/cache_persistence_test.yaml +++ /dev/null @@ -1,160 +0,0 @@ -suite: test cache persistence -templates: - - deployment.yaml - - persistentVolumeClaim.yaml -tests: - # Deployment volume mount tests - - it: should mount cache as emptyDir by default - template: deployment.yaml - asserts: - - contains: - path: spec.template.spec.volumes - content: - name: cache - emptyDir: {} - - - it: should mount cache PVC when enabled - template: deployment.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - asserts: - - contains: - path: spec.template.spec.volumes - content: - name: cache - persistentVolumeClaim: - claimName: RELEASE-NAME-jellyfin-cache - - - it: should mount cache from hostPath when configured - template: deployment.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: hostPath - persistence.cache.hostPath: /mnt/jellyfin-cache - asserts: - - contains: - path: spec.template.spec.volumes - content: - name: cache - hostPath: - path: /mnt/jellyfin-cache - - - it: should mount cache at correct path - template: deployment.yaml - asserts: - - contains: - path: spec.template.spec.containers[0].volumeMounts - content: - name: cache - mountPath: /cache - - # PVC tests - - it: should not create cache PVC by default - template: persistentVolumeClaim.yaml - asserts: - - hasDocuments: - count: 2 # config and media only - - - it: should create cache PVC when enabled - template: persistentVolumeClaim.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - asserts: - - hasDocuments: - count: 3 # config, media, and cache - - contains: - path: metadata.name - content: RELEASE-NAME-jellyfin-cache - - - it: should not create cache PVC when type is hostPath - template: persistentVolumeClaim.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: hostPath - asserts: - - hasDocuments: - count: 2 # config and media only - - - it: should not create cache PVC when type is emptyDir - template: persistentVolumeClaim.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: emptyDir - asserts: - - hasDocuments: - count: 2 - - - it: should set cache PVC size correctly - template: persistentVolumeClaim.yaml - documentIndex: 2 - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.size: 20Gi - asserts: - - equal: - path: spec.resources.requests.storage - value: 20Gi - - - it: should set cache PVC access mode - template: persistentVolumeClaim.yaml - documentIndex: 2 - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.accessMode: ReadWriteMany - asserts: - - contains: - path: spec.accessModes - content: ReadWriteMany - - - it: should set cache PVC storage class - template: persistentVolumeClaim.yaml - documentIndex: 2 - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.storageClass: fast-ssd - asserts: - - equal: - path: spec.storageClassName - value: fast-ssd - - - it: should add annotations to cache PVC - template: persistentVolumeClaim.yaml - documentIndex: 2 - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.annotations: - my-annotation: my-value - asserts: - - equal: - path: metadata.annotations.my-annotation - value: my-value - - - it: should use existing claim when specified - template: deployment.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.existingClaim: my-existing-cache-claim - asserts: - - contains: - path: spec.template.spec.volumes - content: - name: cache - persistentVolumeClaim: - claimName: my-existing-cache-claim - - - it: should not create PVC when existing claim used - template: persistentVolumeClaim.yaml - set: - persistence.cache.enabled: true - persistence.cache.type: pvc - persistence.cache.existingClaim: my-existing-cache-claim - asserts: - - hasDocuments: - count: 2 # config and media only, not cache diff --git a/charts/jellyfin/tests/envfrom_test.yaml b/charts/jellyfin/tests/envfrom_test.yaml index a5f7165..27288a3 100644 --- a/charts/jellyfin/tests/envfrom_test.yaml +++ b/charts/jellyfin/tests/envfrom_test.yaml @@ -120,3 +120,151 @@ tests: - equal: path: spec.template.spec.containers[0].envFrom[0].configMapRef.name value: jellyfin-config + + # ============================================================================= + # EDGE CASES AND ADDITIONAL VALIDATIONS + # ============================================================================= + + - it: should handle empty envFrom array + set: + jellyfin.envFrom: [] + asserts: + - isNull: + path: spec.template.spec.containers[0].envFrom + + - it: should support both configMapRef and secretRef with optional and prefix + set: + jellyfin.envFrom: + - configMapRef: + name: my-config + optional: true + prefix: CFG_ + - secretRef: + name: my-secret + optional: false + prefix: SEC_ + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 2 + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.name + value: my-config + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional + value: true + - equal: + path: spec.template.spec.containers[0].envFrom[0].prefix + value: CFG_ + - equal: + path: spec.template.spec.containers[0].envFrom[1].secretRef.name + value: my-secret + - equal: + path: spec.template.spec.containers[0].envFrom[1].secretRef.optional + value: false + - equal: + path: spec.template.spec.containers[0].envFrom[1].prefix + value: SEC_ + + - it: should support multiple ConfigMaps with different prefixes + set: + jellyfin.envFrom: + - configMapRef: + name: config-1 + prefix: APP1_ + - configMapRef: + name: config-2 + prefix: APP2_ + - configMapRef: + name: config-3 + prefix: APP3_ + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 3 + - equal: + path: spec.template.spec.containers[0].envFrom[0].prefix + value: APP1_ + - equal: + path: spec.template.spec.containers[0].envFrom[1].prefix + value: APP2_ + - equal: + path: spec.template.spec.containers[0].envFrom[2].prefix + value: APP3_ + + - it: should support multiple Secrets with different prefixes + set: + jellyfin.envFrom: + - secretRef: + name: secret-1 + prefix: DB_ + - secretRef: + name: secret-2 + prefix: API_ + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 2 + - equal: + path: spec.template.spec.containers[0].envFrom[0].prefix + value: DB_ + - equal: + path: spec.template.spec.containers[0].envFrom[1].prefix + value: API_ + + - it: should not have prefix when not specified + set: + jellyfin.envFrom: + - configMapRef: + name: no-prefix-config + asserts: + - isNull: + path: spec.template.spec.containers[0].envFrom[0].prefix + + - it: should not have optional field when not specified + set: + jellyfin.envFrom: + - configMapRef: + name: required-config + asserts: + - isNull: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional + + # Regression: verify envFrom doesn't interfere with other env settings + - it: should preserve JELLYFIN_PublishedServerUrl when envFrom is used + set: + jellyfin.env: + - name: JELLYFIN_PublishedServerUrl + value: https://jellyfin.example.com + jellyfin.envFrom: + - configMapRef: + name: extra-config + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: JELLYFIN_PublishedServerUrl + value: https://jellyfin.example.com + + # Complex scenario with many sources + - it: should support large number of envFrom sources + set: + jellyfin.envFrom: + - configMapRef: + name: config-1 + - secretRef: + name: secret-1 + - configMapRef: + name: config-2 + prefix: PREFIX_ + - secretRef: + name: secret-2 + optional: true + - configMapRef: + name: config-3 + optional: true + prefix: OPT_ + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 5 diff --git a/charts/jellyfin/tests/init_containers_test.yaml b/charts/jellyfin/tests/init_containers_test.yaml index 7ea5259..e491cb9 100644 --- a/charts/jellyfin/tests/init_containers_test.yaml +++ b/charts/jellyfin/tests/init_containers_test.yaml @@ -117,3 +117,134 @@ tests: content: name: new-way image: alpine:3.18 + + # ============================================================================= + # EDGE CASES AND FULL SPECIFICATION + # ============================================================================= + + - it: should handle empty extraInitContainers array + set: + extraInitContainers: [] + asserts: + - isNull: + path: spec.template.spec.initContainers + + - it: should preserve full container specification with all fields + set: + extraInitContainers: + - name: full-spec-init + image: busybox:1.35 + imagePullPolicy: IfNotPresent + command: ['sh', '-c'] + args: ['echo complete'] + env: + - name: ENV_VAR + value: test + envFrom: + - configMapRef: + name: init-config + volumeMounts: + - name: config + mountPath: /config + - name: media + mountPath: /media + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + securityContext: + runAsUser: 0 + allowPrivilegeEscalation: false + asserts: + - equal: + path: spec.template.spec.initContainers[0].imagePullPolicy + value: IfNotPresent + - equal: + path: spec.template.spec.initContainers[0].env[0].name + value: ENV_VAR + - equal: + path: spec.template.spec.initContainers[0].envFrom[0].configMapRef.name + value: init-config + - lengthEqual: + path: spec.template.spec.initContainers[0].volumeMounts + count: 2 + - equal: + path: spec.template.spec.initContainers[0].resources.requests.cpu + value: 100m + - equal: + path: spec.template.spec.initContainers[0].resources.limits.memory + value: 256Mi + - equal: + path: spec.template.spec.initContainers[0].securityContext.allowPrivilegeEscalation + value: false + + - it: should verify merge order (deprecated first, then extra) + set: + initContainers: + - name: first-deprecated + image: busybox:1.35 + extraInitContainers: + - name: second-extra + image: alpine:3.18 + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: first-deprecated + - equal: + path: spec.template.spec.initContainers[1].name + value: second-extra + + - it: should support three or more init containers + set: + extraInitContainers: + - name: init-1 + image: busybox:1.35 + - name: init-2 + image: alpine:3.18 + - name: init-3 + image: ubuntu:22.04 + - name: init-4 + image: debian:12 + asserts: + - lengthEqual: + path: spec.template.spec.initContainers + count: 4 + - equal: + path: spec.template.spec.initContainers[2].name + value: init-3 + - equal: + path: spec.template.spec.initContainers[3].name + value: init-4 + + # Regression: verify initContainers comes first when merged + - it: should always place deprecated initContainers before extraInitContainers + set: + initContainers: + - name: deprecated-init + image: busybox:1.35 + - name: deprecated-init-2 + image: busybox:1.36 + extraInitContainers: + - name: extra-init + image: alpine:3.18 + - name: extra-init-2 + image: alpine:3.19 + asserts: + - lengthEqual: + path: spec.template.spec.initContainers + count: 4 + - equal: + path: spec.template.spec.initContainers[0].name + value: deprecated-init + - equal: + path: spec.template.spec.initContainers[1].name + value: deprecated-init-2 + - equal: + path: spec.template.spec.initContainers[2].name + value: extra-init + - equal: + path: spec.template.spec.initContainers[3].name + value: extra-init-2 diff --git a/charts/jellyfin/tests/networkpolicy_test.yaml b/charts/jellyfin/tests/networkpolicy_test.yaml index 1d54067..1927f4c 100644 --- a/charts/jellyfin/tests/networkpolicy_test.yaml +++ b/charts/jellyfin/tests/networkpolicy_test.yaml @@ -295,3 +295,216 @@ tests: - equal: path: spec.podSelector.matchLabels.app\.kubernetes\.io/instance value: RELEASE-NAME + + # ============================================================================= + # EDGE CASES AND ADDITIONAL VALIDATIONS + # ============================================================================= + + # CIDR validations (IPv4 and IPv6) + - it: should accept valid IPv4 CIDR in allowedCIDRs + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowedCIDRs: + - "10.0.0.0/8" + - "192.168.1.0/24" + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 192.168.1.0/24 + + - it: should accept valid IPv6 CIDR in allowedCIDRs + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowedCIDRs: + - "2001:db8::/32" + - "fe80::/10" + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 2001:db8::/32 + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: fe80::/10 + + - it: should accept mix of IPv4 and IPv6 CIDRs + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowedCIDRs: + - "10.0.0.0/8" + - "2001:db8::/32" + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 2001:db8::/32 + + # Egress restricted scenarios + - it: should allow only metadata when restrictedEgress with allowMetadata only + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowMetadata: true + allowInCluster: false + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 169.254.169.254/32 + ports: + - protocol: TCP + port: 80 + + - it: should not allow in-cluster when allowInCluster is false + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowMetadata: false + allowInCluster: false + asserts: + - notContains: + path: spec.egress + content: + to: + - podSelector: {} + + - it: should handle empty allowedCIDRs array + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowedCIDRs: [] + asserts: + - isNotNull: + path: spec.egress + + # Custom ingress rules edge cases + - it: should support multiple custom ingress rules + set: + networkPolicy.enabled: true + networkPolicy.ingress.customRules: + - from: + - namespaceSelector: + matchLabels: + name: frontend + ports: + - protocol: TCP + port: 8096 + - from: + - podSelector: + matchLabels: + app: proxy + ports: + - protocol: TCP + port: 8920 + asserts: + - lengthEqual: + path: spec.ingress + count: 3 # default + 2 custom (when metrics disabled) + + # Regression: verify default egress behavior + - it: should have DNS egress by default + set: + networkPolicy.enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + # Regression: verify ingress port + - it: should expose correct port in ingress rules + set: + networkPolicy.enabled: true + service.port: 9096 + asserts: + - contains: + path: spec.ingress[0].ports + content: + protocol: TCP + port: 9096 + + # Combining all egress options + - it: should combine all egress options when all enabled + set: + networkPolicy.enabled: true + networkPolicy.egress.allowDNS: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress: + allowMetadata: true + allowInCluster: true + allowedCIDRs: + - "10.0.0.0/8" + asserts: + # Should have DNS + - contains: + path: spec.egress + content: + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + # Should have metadata + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 169.254.169.254/32 + # Should have in-cluster + - contains: + path: spec.egress + content: + to: + - podSelector: {} + # Should have custom CIDR + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 diff --git a/charts/jellyfin/tests/persistence_test.yaml b/charts/jellyfin/tests/persistence_test.yaml new file mode 100644 index 0000000..9c1ab39 --- /dev/null +++ b/charts/jellyfin/tests/persistence_test.yaml @@ -0,0 +1,663 @@ +suite: test persistence (config, media, cache) +templates: + - deployment.yaml + - persistentVolumeClaim.yaml +tests: + # ============================================================================= + # CONFIG PERSISTENCE TESTS + # ============================================================================= + + # Default behavior + - it: should create config PVC by default + template: persistentVolumeClaim.yaml + asserts: + - hasDocuments: + count: 2 # config and media (cache disabled by default) + + - it: should set default config PVC size to 5Gi + template: persistentVolumeClaim.yaml + documentIndex: 0 + asserts: + - equal: + path: spec.resources.requests.storage + value: 5Gi + + - it: should set default config PVC access mode to ReadWriteOnce + template: persistentVolumeClaim.yaml + documentIndex: 0 + asserts: + - contains: + path: spec.accessModes + content: ReadWriteOnce + + - it: should mount config PVC by default + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: config + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-config + + - it: should mount config at /config + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: config + mountPath: /config + + # Custom config PVC settings + - it: should set custom config PVC size + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.size: 10Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 10Gi + + - it: should set custom config PVC access mode + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: ReadWriteMany + + - it: should set custom config PVC storage class + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.storageClass: fast-ssd + asserts: + - equal: + path: spec.storageClassName + value: fast-ssd + + - it: should add annotations to config PVC + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.annotations: + backup: "true" + retention: "30d" + asserts: + - equal: + path: metadata.annotations.backup + value: "true" + - equal: + path: metadata.annotations.retention + value: "30d" + + # Config with existing claim + - it: should use existing claim for config when specified + template: deployment.yaml + set: + persistence.config.existingClaim: my-existing-config-claim + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: config + persistentVolumeClaim: + claimName: my-existing-config-claim + + - it: should not create config PVC when existing claim used + template: persistentVolumeClaim.yaml + set: + persistence.config.existingClaim: my-existing-config-claim + asserts: + - hasDocuments: + count: 1 # media only + + # Config disabled (emptyDir fallback) + - it: should use emptyDir for config when disabled + template: deployment.yaml + set: + persistence.config.enabled: false + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: config + emptyDir: {} + + - it: should not create config PVC when disabled + template: persistentVolumeClaim.yaml + set: + persistence.config.enabled: false + asserts: + - hasDocuments: + count: 1 # media only + + # ============================================================================= + # MEDIA PERSISTENCE TESTS + # ============================================================================= + + # Default behavior + - it: should create media PVC by default + template: persistentVolumeClaim.yaml + documentIndex: 1 + asserts: + - equal: + path: metadata.name + value: RELEASE-NAME-jellyfin-media + + - it: should set default media PVC size to 25Gi + template: persistentVolumeClaim.yaml + documentIndex: 1 + asserts: + - equal: + path: spec.resources.requests.storage + value: 25Gi + + - it: should set default media PVC access mode to ReadWriteOnce + template: persistentVolumeClaim.yaml + documentIndex: 1 + asserts: + - contains: + path: spec.accessModes + content: ReadWriteOnce + + - it: should mount media PVC by default + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: media + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-media + + - it: should mount media at /media + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: media + mountPath: /media + + # Media with type: pvc (explicit) + - it: should create media PVC when type is pvc + template: persistentVolumeClaim.yaml + set: + persistence.media.type: pvc + asserts: + - hasDocuments: + count: 2 # config and media + + - it: should set custom media PVC size + template: persistentVolumeClaim.yaml + documentIndex: 1 + set: + persistence.media.type: pvc + persistence.media.size: 100Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 100Gi + + - it: should set custom media PVC storage class + template: persistentVolumeClaim.yaml + documentIndex: 1 + set: + persistence.media.type: pvc + persistence.media.storageClass: slow-hdd + asserts: + - equal: + path: spec.storageClassName + value: slow-hdd + + - it: should add annotations to media PVC + template: persistentVolumeClaim.yaml + documentIndex: 1 + set: + persistence.media.type: pvc + persistence.media.annotations: + media-type: movies + asserts: + - equal: + path: metadata.annotations.media-type + value: movies + + # Media with type: hostPath + - it: should mount media from hostPath when configured + template: deployment.yaml + set: + persistence.media.type: hostPath + persistence.media.hostPath: /mnt/nas/media + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: media + hostPath: + path: /mnt/nas/media + + - it: should not create media PVC when type is hostPath + template: persistentVolumeClaim.yaml + set: + persistence.media.type: hostPath + persistence.media.hostPath: /mnt/nas/media + asserts: + - hasDocuments: + count: 1 # config only + + # Media with type: emptyDir + - it: should mount media as emptyDir when type is emptyDir + template: deployment.yaml + set: + persistence.media.type: emptyDir + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: media + emptyDir: {} + + - it: should not create media PVC when type is emptyDir + template: persistentVolumeClaim.yaml + set: + persistence.media.type: emptyDir + asserts: + - hasDocuments: + count: 1 # config only + + # Media disabled + - it: should use emptyDir for media when disabled + template: deployment.yaml + set: + persistence.media.enabled: false + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: media + emptyDir: {} + + - it: should not create media PVC when disabled + template: persistentVolumeClaim.yaml + set: + persistence.media.enabled: false + asserts: + - hasDocuments: + count: 1 # config only + + # Media with existing claim + - it: should use existing claim for media when specified + template: deployment.yaml + set: + persistence.media.type: pvc + persistence.media.existingClaim: my-existing-media-claim + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: media + persistentVolumeClaim: + claimName: my-existing-media-claim + + - it: should not create media PVC when existing claim used + template: persistentVolumeClaim.yaml + set: + persistence.media.type: pvc + persistence.media.existingClaim: my-existing-media-claim + asserts: + - hasDocuments: + count: 1 # config only + + # ============================================================================= + # CACHE PERSISTENCE TESTS + # ============================================================================= + + # Default behavior + - it: should mount cache as emptyDir by default + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + emptyDir: {} + + - it: should not create cache PVC by default + template: persistentVolumeClaim.yaml + asserts: + - hasDocuments: + count: 2 # config and media only + + - it: should mount cache at /cache + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: cache + mountPath: /cache + + # Cache with type: pvc + - it: should create cache PVC when enabled with type pvc + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - hasDocuments: + count: 3 # config, media, and cache + + - it: should mount cache PVC when enabled + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-cache + + - it: should set cache PVC size correctly + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.size: 20Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 20Gi + + - it: should set cache PVC access mode + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: ReadWriteMany + + - it: should set cache PVC storage class + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.storageClass: fast-ssd + asserts: + - equal: + path: spec.storageClassName + value: fast-ssd + + - it: should add annotations to cache PVC + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.annotations: + my-annotation: my-value + asserts: + - equal: + path: metadata.annotations.my-annotation + value: my-value + + # Cache with type: hostPath + - it: should mount cache from hostPath when configured + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + persistence.cache.hostPath: /mnt/jellyfin-cache + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + hostPath: + path: /mnt/jellyfin-cache + + - it: should not create cache PVC when type is hostPath + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + asserts: + - hasDocuments: + count: 2 # config and media only + + # Cache with type: emptyDir + - it: should not create cache PVC when type is emptyDir + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: emptyDir + asserts: + - hasDocuments: + count: 2 + + # Cache with existing claim + - it: should use existing claim for cache when specified + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: my-existing-cache-claim + + - it: should not create PVC when existing cache claim used + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - hasDocuments: + count: 2 # config and media only, not cache + + # ============================================================================= + # EDGE CASES AND VALIDATIONS + # ============================================================================= + + # All persistence disabled + - it: should use emptyDir for all volumes when all persistence disabled + template: deployment.yaml + set: + persistence.config.enabled: false + persistence.media.enabled: false + persistence.cache.enabled: false + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: config + emptyDir: {} + - contains: + path: spec.template.spec.volumes + content: + name: media + emptyDir: {} + - contains: + path: spec.template.spec.volumes + content: + name: cache + emptyDir: {} + + - it: should not create any PVCs when all persistence disabled + template: persistentVolumeClaim.yaml + set: + persistence.config.enabled: false + persistence.media.enabled: false + persistence.cache.enabled: false + asserts: + - hasDocuments: + count: 0 + + # All persistence enabled as PVC + - it: should create all three PVCs when all enabled + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - hasDocuments: + count: 3 # config, media, and cache + + # Mix of persistence types + - it: should handle mix of pvc, hostPath, and emptyDir + template: deployment.yaml + set: + persistence.config.enabled: true + persistence.media.type: hostPath + persistence.media.hostPath: /mnt/media + persistence.cache.enabled: true + persistence.cache.type: emptyDir + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: config + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-config + - contains: + path: spec.template.spec.volumes + content: + name: media + hostPath: + path: /mnt/media + - contains: + path: spec.template.spec.volumes + content: + name: cache + emptyDir: {} + + - it: should create only config PVC with mixed types + template: persistentVolumeClaim.yaml + set: + persistence.media.type: hostPath + persistence.media.hostPath: /mnt/media + persistence.cache.enabled: true + persistence.cache.type: emptyDir + asserts: + - hasDocuments: + count: 1 # config only + + # All access modes + - it: should support ReadWriteOnce access mode + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.accessMode: ReadWriteOnce + asserts: + - contains: + path: spec.accessModes + content: ReadWriteOnce + + - it: should support ReadOnlyMany access mode + template: persistentVolumeClaim.yaml + documentIndex: 1 + set: + persistence.media.accessMode: ReadOnlyMany + asserts: + - contains: + path: spec.accessModes + content: ReadOnlyMany + + - it: should support ReadWriteMany access mode + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: ReadWriteMany + + - it: should support ReadWriteOncePod access mode + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.accessMode: ReadWriteOncePod + asserts: + - contains: + path: spec.accessModes + content: ReadWriteOncePod + + # Empty storageClass (default provisioner) + - it: should not set storageClassName when storageClass is empty string + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.storageClass: '' + asserts: + - isNull: + path: spec.storageClassName + + # Multiple annotations + - it: should support multiple annotations on PVC + template: persistentVolumeClaim.yaml + documentIndex: 0 + set: + persistence.config.annotations: + annotation1: value1 + annotation2: value2 + annotation3: value3 + asserts: + - equal: + path: metadata.annotations.annotation1 + value: value1 + - equal: + path: metadata.annotations.annotation2 + value: value2 + - equal: + path: metadata.annotations.annotation3 + value: value3 + + # Regression: verify all volume mounts are always present + - it: should always have all three volume mounts in container + template: deployment.yaml + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].volumeMounts + count: 3 + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: config + mountPath: /config + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: media + mountPath: /media + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: cache + mountPath: /cache + + # Regression: verify all volumes are always present + - it: should always have all three volumes defined + template: deployment.yaml + asserts: + - lengthEqual: + path: spec.template.spec.volumes + count: 3 diff --git a/charts/jellyfin/tests/probes_test.yaml b/charts/jellyfin/tests/probes_test.yaml new file mode 100644 index 0000000..8586e23 --- /dev/null +++ b/charts/jellyfin/tests/probes_test.yaml @@ -0,0 +1,447 @@ +suite: test probes (startup, liveness, readiness) +templates: + - deployment.yaml +tests: + # ============================================================================= + # STARTUP PROBE TESTS + # ============================================================================= + + - it: should have startup probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe + + - it: should use tcpSocket for startup probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.tcpSocket + - equal: + path: spec.template.spec.containers[0].startupProbe.tcpSocket.port + value: http + + - it: should have default startup probe timing values + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds + value: 0 + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 30 + + - it: should allow custom initialDelaySeconds for startup probe + set: + startupProbe.initialDelaySeconds: 5 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds + value: 5 + + - it: should allow custom periodSeconds for startup probe + set: + startupProbe.periodSeconds: 15 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 15 + + - it: should allow custom failureThreshold for startup probe + set: + startupProbe.failureThreshold: 60 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 60 + + - it: should support httpGet for startup probe + set: + startupProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 30 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.httpGet + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.port + value: http + + - it: should support exec for startup probe + set: + startupProbe: + exec: + command: ['/bin/sh', '-c', 'curl -f http://localhost:8096/health'] + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 30 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.exec + - contains: + path: spec.template.spec.containers[0].startupProbe.exec.command + content: '/bin/sh' + + - it: should support custom port for startup probe + set: + startupProbe.tcpSocket.port: 9000 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.tcpSocket.port + value: 9000 + + - it: should calculate correct startup timeout window + set: + startupProbe.periodSeconds: 10 + startupProbe.failureThreshold: 30 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 30 + # Total startup time allowed: 10 * 30 = 300 seconds (5 minutes) + + # ============================================================================= + # LIVENESS PROBE TESTS + # ============================================================================= + + - it: should have liveness probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe + + - it: should use httpGet for liveness probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe.httpGet + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.port + value: http + + - it: should have default liveness probe initialDelaySeconds + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds + value: 10 + + - it: should allow custom initialDelaySeconds for liveness probe + set: + livenessProbe.initialDelaySeconds: 30 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds + value: 30 + + - it: should allow custom periodSeconds for liveness probe + set: + livenessProbe.periodSeconds: 20 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.periodSeconds + value: 20 + + - it: should allow custom failureThreshold for liveness probe + set: + livenessProbe.failureThreshold: 5 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.failureThreshold + value: 5 + + - it: should allow custom timeoutSeconds for liveness probe + set: + livenessProbe.timeoutSeconds: 5 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds + value: 5 + + - it: should allow custom successThreshold for liveness probe + set: + livenessProbe.successThreshold: 2 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.successThreshold + value: 2 + + - it: should support tcpSocket for liveness probe + set: + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: 10 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe.tcpSocket + - equal: + path: spec.template.spec.containers[0].livenessProbe.tcpSocket.port + value: http + + - it: should support exec for liveness probe + set: + livenessProbe: + exec: + command: ['/bin/sh', '-c', 'pgrep jellyfin'] + initialDelaySeconds: 10 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe.exec + - contains: + path: spec.template.spec.containers[0].livenessProbe.exec.command + content: 'pgrep' + + - it: should support custom httpGet path for liveness probe + set: + livenessProbe.httpGet.path: /custom/health + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /custom/health + + - it: should support httpHeaders for liveness probe + set: + livenessProbe: + httpGet: + path: /health + port: http + httpHeaders: + - name: X-Custom-Header + value: CustomValue + initialDelaySeconds: 10 + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.httpHeaders[0].name + value: X-Custom-Header + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.httpHeaders[0].value + value: CustomValue + + # ============================================================================= + # READINESS PROBE TESTS + # ============================================================================= + + - it: should have readiness probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe + + - it: should use httpGet for readiness probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe.httpGet + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.port + value: http + + - it: should have default readiness probe initialDelaySeconds + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds + value: 10 + + - it: should allow custom initialDelaySeconds for readiness probe + set: + readinessProbe.initialDelaySeconds: 15 + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds + value: 15 + + - it: should allow custom periodSeconds for readiness probe + set: + readinessProbe.periodSeconds: 15 + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.periodSeconds + value: 15 + + - it: should allow custom failureThreshold for readiness probe + set: + readinessProbe.failureThreshold: 6 + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.failureThreshold + value: 6 + + - it: should allow custom timeoutSeconds for readiness probe + set: + readinessProbe.timeoutSeconds: 3 + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds + value: 3 + + - it: should allow custom successThreshold for readiness probe + set: + readinessProbe.successThreshold: 2 + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.successThreshold + value: 2 + + - it: should support tcpSocket for readiness probe + set: + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: 10 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe.tcpSocket + - equal: + path: spec.template.spec.containers[0].readinessProbe.tcpSocket.port + value: http + + - it: should support exec for readiness probe + set: + readinessProbe: + exec: + command: ['/bin/sh', '-c', 'curl -f http://localhost:8096/health'] + initialDelaySeconds: 10 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe.exec + + - it: should support custom httpGet path for readiness probe + set: + readinessProbe.httpGet.path: /ready + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /ready + + # ============================================================================= + # EDGE CASES AND COMBINED SCENARIOS + # ============================================================================= + + - it: should allow all three probes with different configurations + set: + startupProbe: + tcpSocket: + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 60 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 60 + - equal: + path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds + value: 30 + - equal: + path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds + value: 15 + + - it: should support all timing parameters for all probes + set: + startupProbe: + tcpSocket: + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 30 + timeoutSeconds: 5 + successThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 15 + failureThreshold: 4 + timeoutSeconds: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + timeoutSeconds: 2 + successThreshold: 2 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.timeoutSeconds + value: 5 + - equal: + path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds + value: 3 + - equal: + path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds + value: 2 + - equal: + path: spec.template.spec.containers[0].readinessProbe.successThreshold + value: 2 + + # Regression: verify all three probes are always present + - it: should always have all three probes configured + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe + + # Regression: verify default probe types + - it: should use correct probe types by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.tcpSocket + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe.httpGet + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe.httpGet + + # Different probe types combination + - it: should support different probe types for each probe + set: + startupProbe: + tcpSocket: + port: http + initialDelaySeconds: 0 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + readinessProbe: + exec: + command: ['/bin/sh', '-c', 'test -f /tmp/ready'] + initialDelaySeconds: 5 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.tcpSocket + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe.httpGet + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe.exec diff --git a/charts/jellyfin/tests/service_ipv6_test.yaml b/charts/jellyfin/tests/service_ipv6_test.yaml deleted file mode 100644 index 5dee731..0000000 --- a/charts/jellyfin/tests/service_ipv6_test.yaml +++ /dev/null @@ -1,128 +0,0 @@ -suite: test service ipv6 and dual-stack -templates: - - service.yaml -tests: - - it: should not set ipFamilyPolicy by default - asserts: - - isNull: - path: spec.ipFamilyPolicy - - - it: should not set ipFamilies by default - asserts: - - isNull: - path: spec.ipFamilies - - - it: should set ipFamilyPolicy when specified - set: - service.ipFamilyPolicy: PreferDualStack - asserts: - - equal: - path: spec.ipFamilyPolicy - value: PreferDualStack - - - it: should support SingleStack policy - set: - service.ipFamilyPolicy: SingleStack - asserts: - - equal: - path: spec.ipFamilyPolicy - value: SingleStack - - - it: should support RequireDualStack policy - set: - service.ipFamilyPolicy: RequireDualStack - asserts: - - equal: - path: spec.ipFamilyPolicy - value: RequireDualStack - - - it: should set ipFamilies for IPv4 only - set: - service.ipFamilies: - - IPv4 - asserts: - - equal: - path: spec.ipFamilies[0] - value: IPv4 - - - it: should set ipFamilies for IPv6 only - set: - service.ipFamilies: - - IPv6 - asserts: - - equal: - path: spec.ipFamilies[0] - value: IPv6 - - - it: should set ipFamilies for dual-stack IPv4 primary - set: - service.ipFamilies: - - IPv4 - - IPv6 - asserts: - - equal: - path: spec.ipFamilies[0] - value: IPv4 - - equal: - path: spec.ipFamilies[1] - value: IPv6 - - - it: should set ipFamilies for dual-stack IPv6 primary - set: - service.ipFamilies: - - IPv6 - - IPv4 - asserts: - - equal: - path: spec.ipFamilies[0] - value: IPv6 - - equal: - path: spec.ipFamilies[1] - value: IPv4 - - - it: should work with PreferDualStack and both families - set: - service.ipFamilyPolicy: PreferDualStack - service.ipFamilies: - - IPv4 - - IPv6 - asserts: - - equal: - path: spec.ipFamilyPolicy - value: PreferDualStack - - equal: - path: spec.ipFamilies[0] - value: IPv4 - - equal: - path: spec.ipFamilies[1] - value: IPv6 - - - it: should work with RequireDualStack and both families - set: - service.ipFamilyPolicy: RequireDualStack - service.ipFamilies: - - IPv6 - - IPv4 - asserts: - - equal: - path: spec.ipFamilyPolicy - value: RequireDualStack - - equal: - path: spec.ipFamilies[0] - value: IPv6 - - equal: - path: spec.ipFamilies[1] - value: IPv4 - - - it: should work with SingleStack IPv6 only - set: - service.ipFamilyPolicy: SingleStack - service.ipFamilies: - - IPv6 - asserts: - - equal: - path: spec.ipFamilyPolicy - value: SingleStack - - equal: - path: spec.ipFamilies[0] - value: IPv6 diff --git a/charts/jellyfin/tests/service_test.yaml b/charts/jellyfin/tests/service_test.yaml new file mode 100644 index 0000000..b40dbc6 --- /dev/null +++ b/charts/jellyfin/tests/service_test.yaml @@ -0,0 +1,465 @@ +suite: test service +templates: + - service.yaml +tests: + # ============================================================================= + # BASIC SERVICE DEFAULTS + # ============================================================================= + + - it: should create Service by default + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + - isAPIVersion: + of: v1 + + - it: should have correct default name + asserts: + - equal: + path: metadata.name + value: RELEASE-NAME-jellyfin + + - it: should use ClusterIP type by default + asserts: + - equal: + path: spec.type + value: ClusterIP + + - it: should expose port 8096 by default + asserts: + - equal: + path: spec.ports[0].port + value: 8096 + - equal: + path: spec.ports[0].targetPort + value: http + - equal: + path: spec.ports[0].protocol + value: TCP + - equal: + path: spec.ports[0].name + value: http + + - it: should have correct pod selector labels + asserts: + - equal: + path: spec.selector.app\.kubernetes\.io/name + value: jellyfin + - equal: + path: spec.selector.app\.kubernetes\.io/instance + value: RELEASE-NAME + + # ============================================================================= + # SERVICE TYPE TESTS + # ============================================================================= + + - it: should support ClusterIP type explicitly + set: + service.type: ClusterIP + asserts: + - equal: + path: spec.type + value: ClusterIP + - isNull: + path: spec.clusterIP + + - it: should support NodePort type + set: + service.type: NodePort + asserts: + - equal: + path: spec.type + value: NodePort + + - it: should support LoadBalancer type + set: + service.type: LoadBalancer + asserts: + - equal: + path: spec.type + value: LoadBalancer + + - it: should support ExternalName type + set: + service.type: ExternalName + service.externalName: jellyfin.example.com + asserts: + - equal: + path: spec.type + value: ExternalName + - equal: + path: spec.externalName + value: jellyfin.example.com + + # ============================================================================= + # PORT CONFIGURATION + # ============================================================================= + + - it: should set custom port + set: + service.port: 9096 + asserts: + - equal: + path: spec.ports[0].port + value: 9096 + + - it: should set custom nodePort when type is NodePort + set: + service.type: NodePort + service.nodePort: 30096 + asserts: + - equal: + path: spec.ports[0].nodePort + value: 30096 + + - it: should not set nodePort when type is ClusterIP + set: + service.type: ClusterIP + service.nodePort: 30096 + asserts: + - isNull: + path: spec.ports[0].nodePort + + # Port range validation would be done by values.schema.json + # Here we just test that values are applied correctly + - it: should accept nodePort in valid range (30000-32767) + set: + service.type: NodePort + service.nodePort: 30000 + asserts: + - equal: + path: spec.ports[0].nodePort + value: 30000 + + - it: should accept nodePort at upper range + set: + service.type: NodePort + service.nodePort: 32767 + asserts: + - equal: + path: spec.ports[0].nodePort + value: 32767 + + # ============================================================================= + # LOADBALANCER CONFIGURATION + # ============================================================================= + + - it: should set loadBalancerIP when specified + set: + service.type: LoadBalancer + service.loadBalancerIP: 203.0.113.42 + asserts: + - equal: + path: spec.loadBalancerIP + value: 203.0.113.42 + + - it: should set loadBalancerClass when specified + set: + service.type: LoadBalancer + service.loadBalancerClass: metallb + asserts: + - equal: + path: spec.loadBalancerClass + value: metallb + + - it: should set loadBalancerSourceRanges when specified + set: + service.type: LoadBalancer + service.loadBalancerSourceRanges: + - 10.0.0.0/8 + - 192.168.0.0/16 + asserts: + - contains: + path: spec.loadBalancerSourceRanges + content: 10.0.0.0/8 + - contains: + path: spec.loadBalancerSourceRanges + content: 192.168.0.0/16 + + - it: should not set loadBalancerIP when type is ClusterIP + set: + service.type: ClusterIP + service.loadBalancerIP: 203.0.113.42 + asserts: + - isNull: + path: spec.loadBalancerIP + + - it: should set all loadBalancer options together + set: + service.type: LoadBalancer + service.loadBalancerIP: 203.0.113.42 + service.loadBalancerClass: metallb + service.loadBalancerSourceRanges: + - 10.0.0.0/8 + asserts: + - equal: + path: spec.loadBalancerIP + value: 203.0.113.42 + - equal: + path: spec.loadBalancerClass + value: metallb + - contains: + path: spec.loadBalancerSourceRanges + content: 10.0.0.0/8 + + # ============================================================================= + # ANNOTATIONS + # ============================================================================= + + - it: should not have annotations by default + asserts: + - isNull: + path: metadata.annotations + + - it: should add custom annotations + set: + service.annotations: + service.beta.kubernetes.io/aws-load-balancer-type: nlb + external-dns.alpha.kubernetes.io/hostname: jellyfin.example.com + asserts: + - equal: + path: metadata.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-type + value: nlb + - equal: + path: metadata.annotations.external-dns\.alpha\.kubernetes\.io/hostname + value: jellyfin.example.com + + - it: should support empty annotations object + set: + service.annotations: {} + asserts: + - isNull: + path: metadata.annotations + + # ============================================================================= + # IPv4/IPv6 DUAL-STACK TESTS + # ============================================================================= + + - it: should not set ipFamilyPolicy by default + asserts: + - isNull: + path: spec.ipFamilyPolicy + + - it: should not set ipFamilies by default + asserts: + - isNull: + path: spec.ipFamilies + + - it: should set ipFamilyPolicy when specified + set: + service.ipFamilyPolicy: PreferDualStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: PreferDualStack + + - it: should support SingleStack policy + set: + service.ipFamilyPolicy: SingleStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: SingleStack + + - it: should support RequireDualStack policy + set: + service.ipFamilyPolicy: RequireDualStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: RequireDualStack + + - it: should set ipFamilies for IPv4 only + set: + service.ipFamilies: + - IPv4 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv4 + + - it: should set ipFamilies for IPv6 only + set: + service.ipFamilies: + - IPv6 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv6 + + - it: should set ipFamilies for dual-stack IPv4 primary + set: + service.ipFamilies: + - IPv4 + - IPv6 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv4 + - equal: + path: spec.ipFamilies[1] + value: IPv6 + + - it: should set ipFamilies for dual-stack IPv6 primary + set: + service.ipFamilies: + - IPv6 + - IPv4 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv6 + - equal: + path: spec.ipFamilies[1] + value: IPv4 + + - it: should work with PreferDualStack and both families + set: + service.ipFamilyPolicy: PreferDualStack + service.ipFamilies: + - IPv4 + - IPv6 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: PreferDualStack + - equal: + path: spec.ipFamilies[0] + value: IPv4 + - equal: + path: spec.ipFamilies[1] + value: IPv6 + + - it: should work with RequireDualStack and both families + set: + service.ipFamilyPolicy: RequireDualStack + service.ipFamilies: + - IPv6 + - IPv4 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: RequireDualStack + - equal: + path: spec.ipFamilies[0] + value: IPv6 + - equal: + path: spec.ipFamilies[1] + value: IPv4 + + - it: should work with SingleStack IPv6 only + set: + service.ipFamilyPolicy: SingleStack + service.ipFamilies: + - IPv6 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: SingleStack + - equal: + path: spec.ipFamilies[0] + value: IPv6 + + # ============================================================================= + # EDGE CASES AND VALIDATIONS + # ============================================================================= + + # LoadBalancer without explicit IP (valid use case) + - it: should create LoadBalancer without loadBalancerIP + set: + service.type: LoadBalancer + asserts: + - equal: + path: spec.type + value: LoadBalancer + - isNull: + path: spec.loadBalancerIP + + # NodePort without explicit nodePort (auto-assign) + - it: should create NodePort without explicit nodePort + set: + service.type: NodePort + asserts: + - equal: + path: spec.type + value: NodePort + - isNull: + path: spec.ports[0].nodePort + + # ExternalName requires externalName field + - it: should set externalName for ExternalName type + set: + service.type: ExternalName + service.externalName: external.service.com + asserts: + - equal: + path: spec.type + value: ExternalName + - equal: + path: spec.externalName + value: external.service.com + + # Multiple loadBalancerSourceRanges + - it: should support multiple loadBalancerSourceRanges + set: + service.type: LoadBalancer + service.loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + asserts: + - lengthEqual: + path: spec.loadBalancerSourceRanges + count: 3 + + # Regression: verify service always has one port + - it: should always have exactly one port defined + asserts: + - lengthEqual: + path: spec.ports + count: 1 + + # Regression: verify port name is always http + - it: should always name port as http + set: + service.port: 9096 + asserts: + - equal: + path: spec.ports[0].name + value: http + - equal: + path: spec.ports[0].targetPort + value: http + + # Combining multiple settings + - it: should combine custom port with NodePort and annotations + set: + service.type: NodePort + service.port: 9096 + service.nodePort: 31096 + service.annotations: + custom: annotation + asserts: + - equal: + path: spec.type + value: NodePort + - equal: + path: spec.ports[0].port + value: 9096 + - equal: + path: spec.ports[0].nodePort + value: 31096 + - equal: + path: metadata.annotations.custom + value: annotation + + # Regression: verify protocol is always TCP + - it: should always use TCP protocol + set: + service.port: 9096 + asserts: + - equal: + path: spec.ports[0].protocol + value: TCP diff --git a/charts/jellyfin/tests/startup_probe_test.yaml b/charts/jellyfin/tests/startup_probe_test.yaml deleted file mode 100644 index ee7430f..0000000 --- a/charts/jellyfin/tests/startup_probe_test.yaml +++ /dev/null @@ -1,92 +0,0 @@ -suite: test startup probe -templates: - - deployment.yaml -tests: - - it: should have startup probe by default - asserts: - - isNotNull: - path: spec.template.spec.containers[0].startupProbe - - - it: should use tcpSocket by default - asserts: - - isNotNull: - path: spec.template.spec.containers[0].startupProbe.tcpSocket - - equal: - path: spec.template.spec.containers[0].startupProbe.tcpSocket.port - value: http - - - it: should have default timing values - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds - value: 0 - - equal: - path: spec.template.spec.containers[0].startupProbe.periodSeconds - value: 10 - - equal: - path: spec.template.spec.containers[0].startupProbe.failureThreshold - value: 30 - - - it: should allow custom initialDelaySeconds - set: - startupProbe.initialDelaySeconds: 5 - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds - value: 5 - - - it: should allow custom periodSeconds - set: - startupProbe.periodSeconds: 15 - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.periodSeconds - value: 15 - - - it: should allow custom failureThreshold - set: - startupProbe.failureThreshold: 60 - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.failureThreshold - value: 60 - - - it: should support httpGet probe - set: - startupProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 0 - periodSeconds: 10 - failureThreshold: 30 - asserts: - - isNotNull: - path: spec.template.spec.containers[0].startupProbe.httpGet - - equal: - path: spec.template.spec.containers[0].startupProbe.httpGet.path - value: /health - - equal: - path: spec.template.spec.containers[0].startupProbe.httpGet.port - value: http - - - it: should support custom port - set: - startupProbe.tcpSocket.port: 9000 - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.tcpSocket.port - value: 9000 - - - it: should calculate correct timeout window - set: - startupProbe.periodSeconds: 10 - startupProbe.failureThreshold: 30 - asserts: - - equal: - path: spec.template.spec.containers[0].startupProbe.periodSeconds - value: 10 - - equal: - path: spec.template.spec.containers[0].startupProbe.failureThreshold - value: 30 - # Total startup time allowed: 10 * 30 = 300 seconds (5 minutes)