package handler import ( "fmt" "testing" "time" rbdmodel "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/db" dbmodel "github.com/goodrain/rainbond/db/model" helmcmd "github.com/goodrain/rainbond/pkg/helm" httputil "github.com/goodrain/rainbond/util/http" "github.com/stretchr/testify/assert" helmchart "helm.sh/helm/v3/pkg/chart" helmrelease "helm.sh/helm/v3/pkg/release" helmtime "helm.sh/helm/v3/pkg/time" ) func TestGetHelmReleaseHandlerSingleton(t *testing.T) { h1 := GetHelmReleaseHandler() h2 := GetHelmReleaseHandler() assert.NotNil(t, h1) assert.Equal(t, h1, h2) } // capability_id: rainbond.helm-release.install-defaults func TestHelmReleaseInstallRequestNormalizeDefaults(t *testing.T) { req := HelmReleaseInstallRequest{ RepoName: "bitnami", ChartName: "nginx", ReleaseName: "demo", } req.Normalize() assert.Equal(t, HelmReleaseSourceStore, req.SourceType) assert.Equal(t, "nginx", req.Chart) assert.Equal(t, "nginx", req.ChartName) } // capability_id: rainbond.helm-release.install-validate func TestHelmReleaseInstallRequestValidate(t *testing.T) { cases := []struct { name string req HelmReleaseInstallRequest wantErr string }{ { name: "store ok", req: HelmReleaseInstallRequest{ SourceType: HelmReleaseSourceStore, RepoName: "bitnami", Chart: "nginx", ReleaseName: "demo", }, }, { name: "repo needs repo or direct chart url", req: HelmReleaseInstallRequest{ SourceType: HelmReleaseSourceRepo, ReleaseName: "demo", }, wantErr: "repo source requires repo_url and chart_name, or chart_url", }, { name: "oci prefix required", req: HelmReleaseInstallRequest{ SourceType: HelmReleaseSourceOCI, ChartURL: "https://charts.example.com/nginx.tgz", ReleaseName: "demo", }, wantErr: "oci source requires chart_url with oci:// prefix", }, { name: "upload requires event id", req: HelmReleaseInstallRequest{ SourceType: HelmReleaseSourceUpload, ReleaseName: "demo", }, wantErr: "event_id is required for upload source", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { tt.req.Normalize() err := tt.req.Validate() if tt.wantErr == "" { assert.NoError(t, err) return } if assert.Error(t, err) { assert.Equal(t, tt.wantErr, err.Error()) } }) } } // capability_id: rainbond.helm-release.preview-source-error func TestWrapHelmChartPreviewSourceErrorConvertsToBadRequest(t *testing.T) { err := wrapHelmChartPreviewSourceError(fmt.Errorf("locate chart oci://example.com/demo: tls: handshake failure")) assert.Error(t, err) _, ok := err.(httputil.ErrBadRequest) assert.True(t, ok) assert.Equal(t, "locate chart oci://example.com/demo: tls: handshake failure", err.Error()) } // capability_id: rainbond.helm-release.preview-source-error func TestWrapHelmChartPreviewSourceErrorPreservesBadRequest(t *testing.T) { original := httputil.NewErrBadRequest(fmt.Errorf("chart_url is required")) err := wrapHelmChartPreviewSourceError(original) assert.Error(t, err) assert.Equal(t, original, err) } // capability_id: rainbond.helm-release.default-namespace func TestHelmReleaseNamespaceUsesTenantNamespaceWhenPresent(t *testing.T) { tenant := &dbmodel.Tenants{ Namespace: "team-namespace", UUID: "tenant-uuid", } namespace := helmReleaseNamespace(tenant) assert.Equal(t, "team-namespace", namespace) } // capability_id: rainbond.helm-release.default-namespace func TestHelmReleaseNamespaceFallsBackToTenantUUID(t *testing.T) { tenant := &dbmodel.Tenants{ UUID: "tenant-uuid", } namespace := helmReleaseNamespace(tenant) assert.Equal(t, "tenant-uuid", namespace) } // capability_id: rainbond.helm-release.resolve-namespace func TestResolveHelmReleaseNamespaceUsesExplicitNamespace(t *testing.T) { namespace, err := GetHelmReleaseHandler().resolveNamespace("demo-team", "demo-namespace") assert.NoError(t, err) assert.Equal(t, "demo-namespace", namespace) } // capability_id: rainbond.helm-release.resolve-namespace-fallback // capability_id: rainbond.helm-release.resolve-namespace-fallback func TestResolveHelmReleaseNamespaceFallsBackToTenantNamespace(t *testing.T) { tenantDao := &testTenantDao{ tenant: &dbmodel.Tenants{ Name: "demo-team", UUID: "tenant-uuid", Namespace: "tenant-namespace", }, } db.SetTestManager(testManager{tenantDao: tenantDao}) defer db.SetTestManager(nil) namespace, err := GetHelmReleaseHandler().resolveNamespace("demo-team", "") assert.NoError(t, err) assert.Equal(t, "demo-team", tenantDao.requestedFor) assert.Equal(t, "tenant-namespace", namespace) } // capability_id: rainbond.helm-release.list-summary func TestSummarizeHelmReleaseBuildsStableDTO(t *testing.T) { release := &helmrelease.Release{ Name: "demo-release", Version: 3, Namespace: "demo-namespace", Chart: &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "mysql", Version: "9.4.2", AppVersion: "8.0.36", }, }, Info: &helmrelease.Info{ Status: helmrelease.StatusDeployed, LastDeployed: helmtime.Time{Time: time.Date(2026, 3, 20, 9, 30, 0, 0, time.UTC)}, }, } summary := summarizeHelmRelease(release) assert.Equal(t, "demo-release", summary.Name) assert.Equal(t, "mysql", summary.Chart) assert.Equal(t, "9.4.2", summary.ChartVersion) assert.Equal(t, "8.0.36", summary.AppVersion) assert.Equal(t, "deployed", summary.Status) assert.Equal(t, 3, summary.Version) assert.Equal(t, "demo-namespace", summary.Namespace) assert.Equal(t, "2026-03-20T09:30:00Z", summary.Updated) } // capability_id: rainbond.helm-release.detail-summary func TestSummarizeHelmReleaseDetailBuildsStableDTO(t *testing.T) { release := &helmrelease.Release{ Name: "demo-release", Version: 5, Namespace: "demo-namespace", Config: map[string]interface{}{ "replicaCount": 2, "service": map[string]interface{}{ "type": "ClusterIP", }, }, Chart: &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "grafana", Version: "8.12.1", AppVersion: "12.1.1", }, }, Info: &helmrelease.Info{ Status: helmrelease.StatusDeployed, Description: "Install complete", LastDeployed: helmtime.Time{Time: time.Date(2026, 3, 20, 9, 30, 0, 0, time.UTC)}, }, } summary := summarizeHelmReleaseDetail(release) assert.Equal(t, "demo-release", summary.Name) assert.Equal(t, "demo-namespace", summary.Namespace) assert.Equal(t, "grafana", summary.Chart) assert.Equal(t, "8.12.1", summary.ChartVersion) assert.Equal(t, "12.1.1", summary.AppVersion) assert.Equal(t, "deployed", summary.Status) assert.Equal(t, 5, summary.Revision) assert.Equal(t, "Install complete", summary.Description) assert.Equal(t, "2026-03-20T09:30:00Z", summary.Updated) assert.Contains(t, summary.Values, "replicaCount: 2") assert.Contains(t, summary.Values, "type: ClusterIP") } // capability_id: rainbond.helm-release.history-summary func TestSummarizeHelmReleaseHistoryBuildsStableDTO(t *testing.T) { history := helmcmd.ReleaseHistory{ { Revision: 3, Updated: helmtime.Time{Time: time.Date(2026, 3, 20, 7, 20, 0, 0, time.UTC)}, Status: "deployed", Chart: "nginx-15.10.1", AppVersion: "1.27.1", Description: "Upgrade complete", }, { Revision: 2, Updated: helmtime.Time{Time: time.Date(2026, 3, 19, 7, 20, 0, 0, time.UTC)}, Status: "superseded", Chart: "nginx-15.9.0", AppVersion: "1.27.0", Description: "Rollback complete", }, } items := summarizeHelmReleaseHistory(history) if assert.Len(t, items, 2) { assert.Equal(t, 3, items[0].Revision) assert.Equal(t, "nginx", items[0].Chart) assert.Equal(t, "15.10.1", items[0].ChartVersion) assert.Equal(t, "1.27.1", items[0].AppVersion) assert.Equal(t, "deployed", items[0].Status) assert.Equal(t, "Upgrade complete", items[0].Description) assert.Equal(t, "2026-03-20T07:20:00Z", items[0].Updated) } } // capability_id: rainbond.helm-release.classify-resources func TestSplitHelmReleaseResourcesClassifiesKinds(t *testing.T) { resources := []NsResourceInfo{ {Name: "web", Kind: rbdmodel.Deployment}, {Name: "daemon", Kind: "DaemonSet"}, {Name: "svc", Kind: rbdmodel.Service}, {Name: "cfg", Kind: rbdmodel.ConfigMap}, {Name: "secret", Kind: rbdmodel.Secret}, } workloads, services, others := splitHelmReleaseResources(resources) if assert.Len(t, workloads, 2) { assert.Equal(t, "web", workloads[0].Name) assert.Equal(t, "daemon", workloads[1].Name) } if assert.Len(t, services, 1) { assert.Equal(t, "svc", services[0].Name) } if assert.Len(t, others, 2) { assert.Equal(t, "cfg", others[0].Name) assert.Equal(t, "secret", others[1].Name) } } // capability_id: rainbond.helm-release.match-managed-resource func TestIsHelmReleaseResourceMatchesManagedByAndInstanceLabels(t *testing.T) { assert.True(t, isHelmReleaseResource(map[string]string{ "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/instance": "demo-release", }, "demo-release")) assert.False(t, isHelmReleaseResource(map[string]string{ "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/instance": "other-release", }, "demo-release")) assert.False(t, isHelmReleaseResource(map[string]string{ "app.kubernetes.io/managed-by": "rainbond", "app.kubernetes.io/instance": "demo-release", }, "demo-release")) } // capability_id: rainbond.helm-release.rollback-validate func TestHelmReleaseRollbackRequestValidate(t *testing.T) { req := HelmReleaseRollbackRequest{} err := req.Validate() if assert.Error(t, err) { assert.Equal(t, "revision must be greater than 0", err.Error()) } req.Revision = 2 assert.NoError(t, req.Validate()) } // capability_id: rainbond.helm-release.upgrade-chart-guard func TestValidateUpgradeChartNameRejectsMismatchByDefault(t *testing.T) { currentRelease := &helmrelease.Release{ Chart: &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "nginx", }, }, } targetChart := &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "apisix", }, } err := validateUpgradeChartName(currentRelease, targetChart, false) if assert.Error(t, err) { assert.Equal(t, `upgrade chart name "apisix" does not match current release chart "nginx"`, err.Error()) } } // capability_id: rainbond.helm-release.upgrade-chart-guard func TestValidateUpgradeChartNameAllowsMismatchWithExplicitConfirmation(t *testing.T) { currentRelease := &helmrelease.Release{ Chart: &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "nginx", }, }, } targetChart := &helmchart.Chart{ Metadata: &helmchart.Metadata{ Name: "apisix", }, } err := validateUpgradeChartName(currentRelease, targetChart, true) assert.NoError(t, err) }