From d4ac083d2cb48c13371edf7c9f7d40f5e6d6b383 Mon Sep 17 00:00:00 2001 From: mayor Date: Thu, 21 May 2026 17:34:20 -0400 Subject: [PATCH] fix(cli): map isHelmInstallEnabled to helmEnabled in EP preview licenseCapabilities The enterprise-portal preview tool was not mapping IsHelmInstallEnabled, IsKurlInstallEnabled, or IsHelmAirgapEnabled from the Customer struct to the previewLicense struct. These fields were also missing from the types.Customer struct, so the vendor API response values were silently dropped during JSON deserialization. Add the three missing fields to types.Customer and wire them through customerToPreviewLicense() so the preview UI's licenseCapabilities object reflects the actual customer settings. Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/cmd/enterprise_portal_preview.go | 3 + cli/cmd/enterprise_portal_preview_test.go | 169 ++++++++++++++++++++++ pkg/types/customer.go | 2 + pkg/types/customer_test.go | 87 ++++++++++- 4 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 cli/cmd/enterprise_portal_preview_test.go diff --git a/cli/cmd/enterprise_portal_preview.go b/cli/cmd/enterprise_portal_preview.go index 73a299a5d..dba4b4ee7 100644 --- a/cli/cmd/enterprise_portal_preview.go +++ b/cli/cmd/enterprise_portal_preview.go @@ -376,6 +376,9 @@ func customerToPreviewLicense(app *types.App, c types.Customer) previewLicense { IsEmbeddedClusterDownloadEnabled: c.IsEmbeddedClusterDownloadEnabled, IsEmbeddedClusterMultiNodeEnabled: c.IsEmbeddedClusterMultinodeEnabled, IsKotsInstallEnabled: c.IsKotsInstallEnabled, + IsHelmInstallEnabled: c.IsHelmInstallEnabled, + IsKurlInstallEnabled: c.IsKurlInstallEnabled, + IsHelmAirgapEnabled: c.IsHelmAirgapEnabled, EntitlementFields: []previewEntitlementField{}, EntitlementValues: values, } diff --git a/cli/cmd/enterprise_portal_preview_test.go b/cli/cmd/enterprise_portal_preview_test.go new file mode 100644 index 000000000..1679d8c0b --- /dev/null +++ b/cli/cmd/enterprise_portal_preview_test.go @@ -0,0 +1,169 @@ +package cmd + +import ( + "testing" + + "github.com/replicatedhq/replicated/pkg/types" + "github.com/stretchr/testify/assert" +) + +func TestCustomerToPreviewLicense_HelmInstallEnabled(t *testing.T) { + app := &types.App{ + ID: "app_abc", + Name: "Test App", + } + + tests := []struct { + name string + customer types.Customer + want bool + }{ + { + name: "IsHelmInstallEnabled true is mapped", + customer: types.Customer{ + ID: "cust_abc", + Name: "Helm Customer", + IsHelmInstallEnabled: true, + }, + want: true, + }, + { + name: "IsHelmInstallEnabled false is mapped", + customer: types.Customer{ + ID: "cust_def", + Name: "No Helm Customer", + IsHelmInstallEnabled: false, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := customerToPreviewLicense(app, tt.customer) + assert.Equal(t, tt.want, pl.IsHelmInstallEnabled) + }) + } +} + +func TestCustomerToPreviewLicense_KurlInstallEnabled(t *testing.T) { + app := &types.App{ + ID: "app_abc", + Name: "Test App", + } + + tests := []struct { + name string + customer types.Customer + want bool + }{ + { + name: "IsKurlInstallEnabled true is mapped", + customer: types.Customer{ + ID: "cust_abc", + Name: "Kurl Customer", + IsKurlInstallEnabled: true, + }, + want: true, + }, + { + name: "IsKurlInstallEnabled false is mapped", + customer: types.Customer{ + ID: "cust_def", + Name: "No Kurl Customer", + IsKurlInstallEnabled: false, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := customerToPreviewLicense(app, tt.customer) + assert.Equal(t, tt.want, pl.IsKurlInstallEnabled) + }) + } +} + +func TestCustomerToPreviewLicense_HelmAirgapEnabled(t *testing.T) { + app := &types.App{ + ID: "app_abc", + Name: "Test App", + } + + tests := []struct { + name string + customer types.Customer + want bool + }{ + { + name: "IsHelmAirgapEnabled true is mapped", + customer: types.Customer{ + ID: "cust_abc", + Name: "Airgap Customer", + IsHelmAirgapEnabled: true, + }, + want: true, + }, + { + name: "IsHelmAirgapEnabled false is mapped", + customer: types.Customer{ + ID: "cust_def", + Name: "No Airgap Customer", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pl := customerToPreviewLicense(app, tt.customer) + assert.Equal(t, tt.want, pl.IsHelmAirgapEnabled) + }) + } +} + +func TestCustomerToPreviewLicense_AllCapabilities(t *testing.T) { + app := &types.App{ + ID: "app_abc", + Name: "Test App", + } + + cust := types.Customer{ + ID: "cust_full", + Name: "Full Customer", + Email: "full@example.com", + InstallationID: "inst_123", + IsAirgapEnabled: true, + IsGitopsSupported: true, + IsIdentityServiceSupported: true, + IsGeoaxisSupported: true, + IsSnapshotSupported: true, + IsSupportBundleUploadEnabled: true, + IsEmbeddedClusterDownloadEnabled: true, + IsEmbeddedClusterMultinodeEnabled: true, + IsKotsInstallEnabled: true, + IsHelmInstallEnabled: true, + IsKurlInstallEnabled: true, + IsHelmAirgapEnabled: true, + } + + pl := customerToPreviewLicense(app, cust) + + assert.Equal(t, "inst_123", pl.ID) + assert.Equal(t, app.ID, pl.AppID) + assert.Equal(t, app.Name, pl.AppName) + assert.Equal(t, "cust_full", pl.CustomerID) + assert.Equal(t, "Full Customer", pl.CustomerName) + assert.Equal(t, "full@example.com", pl.CustomerEmail) + + assert.True(t, pl.IsAirgapSupported) + assert.True(t, pl.IsGitopsSupported) + assert.True(t, pl.IsIdentityServiceSupported) + assert.True(t, pl.IsGeoaxisSupported) + assert.True(t, pl.IsSnapshotSupported) + assert.True(t, pl.IsSupportBundleUploadSupported) + assert.True(t, pl.IsEmbeddedClusterDownloadEnabled) + assert.True(t, pl.IsEmbeddedClusterMultiNodeEnabled) + assert.True(t, pl.IsKotsInstallEnabled) + assert.True(t, pl.IsHelmInstallEnabled) + assert.True(t, pl.IsKurlInstallEnabled) + assert.True(t, pl.IsHelmAirgapEnabled) +} diff --git a/pkg/types/customer.go b/pkg/types/customer.go index ea0c011db..4f9307798 100644 --- a/pkg/types/customer.go +++ b/pkg/types/customer.go @@ -20,11 +20,13 @@ type Customer struct { IsEmbeddedClusterDownloadEnabled bool `json:"isEmbeddedClusterDownloadEnabled"` IsEmbeddedClusterMultinodeEnabled bool `json:"isEmbeddedClusterMultinodeEnabled"` IsGeoaxisSupported bool `json:"isGeoaxisSupported"` + IsHelmAirgapEnabled bool `json:"isHelmAirgapEnabled"` IsHelmInstallEnabled bool `json:"isHelmInstallEnabled"` IsHelmVMDownloadEnabled bool `json:"isHelmVmDownloadEnabled"` IsIdentityServiceSupported bool `json:"isIdentityServiceSupported"` IsInstallerSupportEnabled bool `json:"isInstallerSupportEnabled"` IsKotsInstallEnabled bool `json:"isKotsInstallEnabled"` + IsKurlInstallEnabled bool `json:"isKurlInstallEnabled"` IsSnapshotSupported bool `json:"isSnapshotSupported"` IsSupportBundleUploadEnabled bool `json:"isSupportBundleUploadEnabled"` IsGitopsSupported bool `json:"isGitopsSupported"` diff --git a/pkg/types/customer_test.go b/pkg/types/customer_test.go index a74a1d31e..3cc677b99 100644 --- a/pkg/types/customer_test.go +++ b/pkg/types/customer_test.go @@ -40,24 +40,96 @@ func TestCustomer_UnmarshalJSON_IsHelmInstallEnabled(t *testing.T) { } } -func TestCustomer_MarshalJSON_IsHelmInstallEnabled(t *testing.T) { +func TestCustomer_UnmarshalJSON_IsKurlInstallEnabled(t *testing.T) { + tests := []struct { + name string + payload string + wantKurl bool + }{ + { + name: "isKurlInstallEnabled true", + payload: `{"id":"cust_abc","isKurlInstallEnabled":true}`, + wantKurl: true, + }, + { + name: "isKurlInstallEnabled false", + payload: `{"id":"cust_abc","isKurlInstallEnabled":false}`, + wantKurl: false, + }, + { + name: "isKurlInstallEnabled absent defaults to false", + payload: `{"id":"cust_abc"}`, + wantKurl: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var c Customer + err := json.Unmarshal([]byte(tt.payload), &c) + require.NoError(t, err) + assert.Equal(t, tt.wantKurl, c.IsKurlInstallEnabled) + }) + } +} + +func TestCustomer_UnmarshalJSON_IsHelmAirgapEnabled(t *testing.T) { + tests := []struct { + name string + payload string + wantAirgap bool + }{ + { + name: "isHelmAirgapEnabled true", + payload: `{"id":"cust_abc","isHelmAirgapEnabled":true}`, + wantAirgap: true, + }, + { + name: "isHelmAirgapEnabled false", + payload: `{"id":"cust_abc","isHelmAirgapEnabled":false}`, + wantAirgap: false, + }, + { + name: "isHelmAirgapEnabled absent defaults to false", + payload: `{"id":"cust_abc"}`, + wantAirgap: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var c Customer + err := json.Unmarshal([]byte(tt.payload), &c) + require.NoError(t, err) + assert.Equal(t, tt.wantAirgap, c.IsHelmAirgapEnabled) + }) + } +} + +func TestCustomer_MarshalJSON_NewFields(t *testing.T) { c := Customer{ ID: "cust_abc", Name: "Test", IsHelmInstallEnabled: true, + IsKurlInstallEnabled: true, + IsHelmAirgapEnabled: true, } data, err := json.Marshal(c) require.NoError(t, err) - assert.Contains(t, string(data), `"isHelmInstallEnabled":true`) + jsonStr := string(data) + assert.Contains(t, jsonStr, `"isHelmInstallEnabled":true`) + assert.Contains(t, jsonStr, `"isKurlInstallEnabled":true`) + assert.Contains(t, jsonStr, `"isHelmAirgapEnabled":true`) } -func TestCustomer_RoundTrip_IsHelmInstallEnabled(t *testing.T) { +func TestCustomer_RoundTrip_AllInstallFields(t *testing.T) { original := Customer{ ID: "cust_abc", Name: "Test", IsHelmInstallEnabled: true, + IsKurlInstallEnabled: true, + IsHelmAirgapEnabled: true, + IsKotsInstallEnabled: true, } data, err := json.Marshal(original) @@ -68,6 +140,9 @@ func TestCustomer_RoundTrip_IsHelmInstallEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, original.IsHelmInstallEnabled, restored.IsHelmInstallEnabled) + assert.Equal(t, original.IsKurlInstallEnabled, restored.IsKurlInstallEnabled) + assert.Equal(t, original.IsHelmAirgapEnabled, restored.IsHelmAirgapEnabled) + assert.Equal(t, original.IsKotsInstallEnabled, restored.IsKotsInstallEnabled) } func TestCustomer_UnmarshalJSON_IsHelmVMDownloadEnabled_BackwardsCompat(t *testing.T) { @@ -115,7 +190,6 @@ func TestCustomer_MarshalJSON_IsHelmVMDownloadEnabled_BackwardsCompat(t *testing } func TestCustomer_UnmarshalJSON_BothHelmFields(t *testing.T) { - // Verify both fields can coexist in the same payload payload := `{ "id": "cust_abc", "name": "Test Customer", @@ -147,7 +221,6 @@ func TestCustomer_MarshalJSON_BothHelmFields(t *testing.T) { } func TestCustomer_UnmarshalJSON_FullAPIPayload(t *testing.T) { - // Simulates a realistic Vendor API response payload payload := `{ "id": "cust_abc123", "customId": "custom-id-456", @@ -158,11 +231,13 @@ func TestCustomer_UnmarshalJSON_FullAPIPayload(t *testing.T) { "isEmbeddedClusterDownloadEnabled": true, "isEmbeddedClusterMultinodeEnabled": false, "isGeoaxisSupported": false, + "isHelmAirgapEnabled": true, "isHelmInstallEnabled": true, "isHelmVmDownloadEnabled": false, "isIdentityServiceSupported": false, "isInstallerSupportEnabled": true, "isKotsInstallEnabled": true, + "isKurlInstallEnabled": true, "isSnapshotSupported": true, "isSupportBundleUploadEnabled": true, "isGitopsSupported": false @@ -181,11 +256,13 @@ func TestCustomer_UnmarshalJSON_FullAPIPayload(t *testing.T) { assert.True(t, c.IsEmbeddedClusterDownloadEnabled) assert.False(t, c.IsEmbeddedClusterMultinodeEnabled) assert.False(t, c.IsGeoaxisSupported) + assert.True(t, c.IsHelmAirgapEnabled) assert.True(t, c.IsHelmInstallEnabled) assert.False(t, c.IsHelmVMDownloadEnabled) assert.False(t, c.IsIdentityServiceSupported) assert.True(t, c.IsInstallerSupportEnabled) assert.True(t, c.IsKotsInstallEnabled) + assert.True(t, c.IsKurlInstallEnabled) assert.True(t, c.IsSnapshotSupported) assert.True(t, c.IsSupportBundleUploadEnabled) assert.False(t, c.IsGitopsSupported)