From 7ceb15226690496a9c6e6c8ab4478fa033330885 Mon Sep 17 00:00:00 2001 From: danielr Date: Tue, 21 Jul 2026 11:30:21 +0300 Subject: [PATCH 1/7] Add --monitor-policy flag to app-create and app-update commands Introduce an optional --monitor-policy flag (format: "type=[, value=]") that maps to the AppTrust monitor_policy configuration on the create and update application requests. Supported types: none, time_frame_in_months, version_count; value is required for the enabled types and must be omitted for none. Co-authored-by: Cursor --- .../commands/application/application_utils.go | 56 +++++++++ .../commands/application/create_app_cmd.go | 5 +- .../application/create_app_cmd_test.go | 107 ++++++++++++++++++ .../commands/application/update_app_cmd.go | 2 + .../application/update_app_cmd_test.go | 35 ++++++ apptrust/commands/flags.go | 4 + apptrust/model/app_descriptor.go | 39 +++++-- 7 files changed, 237 insertions(+), 11 deletions(-) diff --git a/apptrust/commands/application/application_utils.go b/apptrust/commands/application/application_utils.go index 2481a07..2bb2c69 100644 --- a/apptrust/commands/application/application_utils.go +++ b/apptrust/commands/application/application_utils.go @@ -2,6 +2,7 @@ package application import ( "fmt" + "strconv" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -84,5 +85,60 @@ func populateApplicationFromFlags(ctx *components.Context, descriptor *model.App descriptor.GroupOwners = &groupOwners } + if err := populateMonitorPolicyFromFlags(ctx, descriptor); err != nil { + return err + } + + return nil +} + +func populateMonitorPolicyFromFlags(ctx *components.Context, descriptor *model.AppDescriptor) error { + if !ctx.IsFlagSet(commands.MonitorPolicyFlag) { + return nil + } + + fields, err := utils.ParseKeyValueString(ctx.GetStringFlagValue(commands.MonitorPolicyFlag), ",") + if err != nil { + return fmt.Errorf("failed to parse --%s: %w", commands.MonitorPolicyFlag, err) + } + + for key := range fields { + if key != "type" && key != "value" { + return fmt.Errorf("invalid field '%s' in --%s (supported fields: type, value)", key, commands.MonitorPolicyFlag) + } + } + + typeStr, typeSet := fields["type"] + if !typeSet { + return fmt.Errorf("--%s requires a 'type' field", commands.MonitorPolicyFlag) + } + + policyType, err := utils.ValidateEnumFlag(commands.MonitorPolicyFlag, typeStr, model.MonitorPolicyTypeNone, model.MonitorPolicyTypeValues) + if err != nil { + return err + } + + policy := &model.MonitorPolicy{Type: policyType} + valueStr, valueSet := fields["value"] + + if policyType == model.MonitorPolicyTypeNone { + if valueSet { + return fmt.Errorf("'value' must not be set in --%s when type is '%s'", commands.MonitorPolicyFlag, model.MonitorPolicyTypeNone) + } + } else { + if !valueSet { + return fmt.Errorf("'value' is required in --%s when type is '%s'", commands.MonitorPolicyFlag, policyType) + } + value, err := strconv.Atoi(valueStr) + if err != nil { + return fmt.Errorf("'value' in --%s must be an integer: %w", commands.MonitorPolicyFlag, err) + } + if value <= 0 { + return fmt.Errorf("'value' in --%s must be a positive integer", commands.MonitorPolicyFlag) + } + policy.Value = &value + } + + descriptor.MonitorPolicy = policy return nil } diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index ec7e404..d3489bb 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -164,6 +164,7 @@ func validateNoSpecAndFlagsTogether(ctx *components.Context) error { commands.LabelsFlag, commands.UserOwnersFlag, commands.GroupOwnersFlag, + commands.MonitorPolicyFlag, } for _, flag := range otherAppFlags { if ctx.IsFlagSet(flag) { @@ -197,12 +198,14 @@ Common patterns: $ jf apptrust app-create my-app --project=default --application-name="My App" --desc="Service X" $ jf apptrust app-create my-app --project=default --business-criticality=high --maturity-level=production $ jf apptrust app-create my-app --project=default --labels="team=core;area=platform" --user-owners="alice;bob" + $ jf apptrust app-create my-app --project=default --monitor-policy="type=version_count, value=5" $ jf apptrust app-create my-app --spec=app-spec.json --spec-vars="ENV=prod" Gotchas: -- --spec is mutually exclusive with --application-name, --project, --desc, --business-criticality, --maturity-level, --labels, --user-owners, --group-owners. +- --spec is mutually exclusive with --application-name, --project, --desc, --business-criticality, --maturity-level, --labels, --user-owners, --group-owners, --monitor-policy. - If --application-name is omitted, the application-key is used as the display name. - --labels uses semicolon separators (not commas) and key=value pairs. +- --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". Related: jf apptrust app-update, jf apptrust app-delete, jf apptrust version-create`, Category: common.CategoryApplication, diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index f8a1d74..c788485 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -63,6 +63,113 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { assert.NoError(t, err) } +func TestCreateAppCommand_Run_MonitorPolicy(t *testing.T) { + versionCountValue := 5 + + tests := []struct { + name string + monitorPolicy string + expectsError bool + errorContains string + expectedPolicy *model.MonitorPolicy + }{ + { + name: "version count", + monitorPolicy: "type=version_count, value=5", + expectedPolicy: &model.MonitorPolicy{Type: "version_count", Value: &versionCountValue}, + }, + { + name: "time frame in months", + monitorPolicy: "type=time_frame_in_months, value=5", + expectedPolicy: &model.MonitorPolicy{Type: "time_frame_in_months", Value: &versionCountValue}, + }, + { + name: "none without value", + monitorPolicy: "type=none", + expectedPolicy: &model.MonitorPolicy{Type: "none"}, + }, + { + name: "none with value is rejected", + monitorPolicy: "type=none, value=5", + expectsError: true, + errorContains: "'value' must not be set", + }, + { + name: "missing value for enabled type", + monitorPolicy: "type=version_count", + expectsError: true, + errorContains: "'value' is required", + }, + { + name: "missing type", + monitorPolicy: "value=5", + expectsError: true, + errorContains: "requires a 'type' field", + }, + { + name: "invalid type", + monitorPolicy: "type=bogus, value=5", + expectsError: true, + errorContains: "invalid value for --monitor-policy", + }, + { + name: "non-integer value", + monitorPolicy: "type=version_count, value=abc", + expectsError: true, + errorContains: "must be an integer", + }, + { + name: "non-positive value", + monitorPolicy: "type=version_count, value=0", + expectsError: true, + errorContains: "must be a positive integer", + }, + { + name: "unknown field", + monitorPolicy: "type=version_count, value=5, foo=bar", + expectsError: true, + errorContains: "invalid field 'foo'", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := &components.Context{ + Arguments: []string{"app-key"}, + } + ctx.AddStringFlag("project", "test-project") + ctx.AddStringFlag("url", "https://example.com") + ctx.AddStringFlag("monitor-policy", tt.monitorPolicy) + + var actualPayload *model.AppDescriptor + mockAppService := mockapps.NewMockApplicationService(ctrl) + if !tt.expectsError { + mockAppService.EXPECT().CreateApplication(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { + actualPayload = req + return nil, nil + }).Times(1) + } + + cmd := &createAppCommand{ + applicationService: mockAppService, + } + + err := cmd.prepareAndRunCommand(ctx) + if tt.expectsError { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedPolicy, actualPayload.MonitorPolicy) + } + }) + } +} + func TestCreateAppCommand_Error(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index f5ac398..0f667da 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -109,10 +109,12 @@ Common patterns: $ jf apptrust app-update my-app --add-labels="env=prod;tier=critical" $ jf apptrust app-update my-app --remove-labels="env=staging" $ jf apptrust app-update my-app --user-owners="alice;bob" --group-owners="platform-team" + $ jf apptrust app-update my-app --monitor-policy="type=version_count, value=5" Gotchas: - --labels replaces the full label set; --add-labels and --remove-labels modify incrementally. - --user-owners / --group-owners take a semicolon-separated list and send exactly the owners you specify; there are no incremental add/remove-owner flags (unlike --add-labels / --remove-labels for labels). +- --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". When --monitor-policy is not provided, the current policy is left unchanged. - Application key cannot be changed; use app-delete and app-create if you need a different key. Related: jf apptrust app-create, jf apptrust app-delete`, diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index d12e26b..0fc7062 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -255,6 +255,37 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { }, }, }, + { + name: "monitor-policy with value", + ctxSetup: func(ctx *components.Context) { + ctx.Arguments = []string{"app-key"} + ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=version_count, value=5") + }, + expectsPayload: &model.AppDescriptor{ + ApplicationKey: "app-key", + MonitorPolicy: &model.MonitorPolicy{Type: "version_count", Value: intPtr(5)}, + }, + }, + { + name: "monitor-policy none disables monitoring", + ctxSetup: func(ctx *components.Context) { + ctx.Arguments = []string{"app-key"} + ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=none") + }, + expectsPayload: &model.AppDescriptor{ + ApplicationKey: "app-key", + MonitorPolicy: &model.MonitorPolicy{Type: "none"}, + }, + }, + { + name: "invalid monitor-policy type", + ctxSetup: func(ctx *components.Context) { + ctx.Arguments = []string{"app-key"} + ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=bogus, value=5") + }, + expectsError: true, + errorContains: "invalid value for --monitor-policy", + }, { name: "invalid add-labels format - missing equals", ctxSetup: func(ctx *components.Context) { @@ -365,3 +396,7 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { func stringPtr(s string) *string { return &s } + +func intPtr(i int) *int { + return &i +} diff --git a/apptrust/commands/flags.go b/apptrust/commands/flags.go index ada8dfb..262bd42 100644 --- a/apptrust/commands/flags.go +++ b/apptrust/commands/flags.go @@ -44,6 +44,7 @@ const ( RemoveLabelsFlag = "remove-labels" UserOwnersFlag = "user-owners" GroupOwnersFlag = "group-owners" + MonitorPolicyFlag = "monitor-policy" SyncFlag = "sync" PromotionTypeFlag = "promotion-type" DryRunFlag = "dry-run" @@ -97,6 +98,7 @@ var flagsMap = map[string]components.Flag{ RemoveLabelsFlag: components.NewStringFlag(RemoveLabelsFlag, "List of semicolon-separated (;) labels to remove in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "semicolon-separated (;) list of user owners in the form of \"user1;user2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "semicolon-separated (;) list of group owners in the form of \"group1;group2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), + MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "The operational-validity monitoring configuration, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". 'value' must be a positive integer and is required when type is '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', and must be omitted when type is '"+model.MonitorPolicyTypeNone+"'. When omitted, monitoring is off.", func(f *components.StringFlag) { f.Mandatory = false }), SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()), PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }), DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()), @@ -278,6 +280,7 @@ var commandFlags = map[string][]string{ LabelsFlag, UserOwnersFlag, GroupOwnersFlag, + MonitorPolicyFlag, SpecFlag, SpecVarsFlag, }, @@ -296,6 +299,7 @@ var commandFlags = map[string][]string{ RemoveLabelsFlag, UserOwnersFlag, GroupOwnersFlag, + MonitorPolicyFlag, }, AppDelete: { diff --git a/apptrust/model/app_descriptor.go b/apptrust/model/app_descriptor.go index 8948072..4f4654d 100644 --- a/apptrust/model/app_descriptor.go +++ b/apptrust/model/app_descriptor.go @@ -11,6 +11,10 @@ const ( MaturityLevelExperimental = "experimental" MaturityLevelProduction = "production" MaturityLevelEndOfLife = "end_of_life" + + MonitorPolicyTypeNone = "none" + MonitorPolicyTypeTimeframe = "time_frame_in_months" + MonitorPolicyTypeVersionCount = "version_count" ) var ( @@ -28,6 +32,12 @@ var ( MaturityLevelProduction, MaturityLevelEndOfLife, } + + MonitorPolicyTypeValues = []string{ + MonitorPolicyTypeNone, + MonitorPolicyTypeTimeframe, + MonitorPolicyTypeVersionCount, + } ) type LabelEntry struct { @@ -35,20 +45,29 @@ type LabelEntry struct { Value string `json:"value"` } +// MonitorPolicy is the operational-validity monitoring configuration for an application. +// Type is "none" to disable monitoring, or one of "time_frame_in_months" / "version_count". +// Value is required for the enabled types and must be empty for "none". +type MonitorPolicy struct { + Type string `json:"type"` + Value *int `json:"value,omitempty"` +} + type LabelUpdates struct { Remove []LabelEntry `json:"remove,omitempty"` Add []LabelEntry `json:"add,omitempty"` } type AppDescriptor struct { - ApplicationKey string `json:"application_key"` - ApplicationName string `json:"application_name,omitempty"` - ProjectKey string `json:"project_key,omitempty"` - Description *string `json:"description,omitempty"` - MaturityLevel *string `json:"maturity_level,omitempty"` - BusinessCriticality *string `json:"criticality,omitempty"` - Labels *[]LabelEntry `json:"labels,omitempty"` - LabelUpdates *LabelUpdates `json:"label_updates,omitempty"` - UserOwners *[]string `json:"user_owners,omitempty"` - GroupOwners *[]string `json:"group_owners,omitempty"` + ApplicationKey string `json:"application_key"` + ApplicationName string `json:"application_name,omitempty"` + ProjectKey string `json:"project_key,omitempty"` + Description *string `json:"description,omitempty"` + MaturityLevel *string `json:"maturity_level,omitempty"` + BusinessCriticality *string `json:"criticality,omitempty"` + Labels *[]LabelEntry `json:"labels,omitempty"` + LabelUpdates *LabelUpdates `json:"label_updates,omitempty"` + UserOwners *[]string `json:"user_owners,omitempty"` + GroupOwners *[]string `json:"group_owners,omitempty"` + MonitorPolicy *MonitorPolicy `json:"monitor_policy,omitempty"` } From 62afb442d06d28f4a2a9134bf2842550357111e9 Mon Sep 17 00:00:00 2001 From: danielr Date: Tue, 21 Jul 2026 14:29:17 +0300 Subject: [PATCH 2/7] Refactor monitor-policy flag parsing into utils.ParseMonitorPolicyFlag Move the monitor-policy parsing/validation into a utils helper and handle it inline in populateApplicationFromFlags, matching the other flag fields. Remove non-conventional comments. Co-authored-by: Cursor --- .../commands/application/application_utils.go | 56 ++----------------- .../application/create_app_cmd_test.go | 4 +- .../application/update_app_cmd_test.go | 2 +- apptrust/commands/utils/utils.go | 45 +++++++++++++++ apptrust/model/app_descriptor.go | 3 - 5 files changed, 52 insertions(+), 58 deletions(-) diff --git a/apptrust/commands/application/application_utils.go b/apptrust/commands/application/application_utils.go index 2bb2c69..0e016a5 100644 --- a/apptrust/commands/application/application_utils.go +++ b/apptrust/commands/application/application_utils.go @@ -2,7 +2,6 @@ package application import ( "fmt" - "strconv" "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/commands/utils" @@ -85,60 +84,13 @@ func populateApplicationFromFlags(ctx *components.Context, descriptor *model.App descriptor.GroupOwners = &groupOwners } - if err := populateMonitorPolicyFromFlags(ctx, descriptor); err != nil { - return err - } - - return nil -} - -func populateMonitorPolicyFromFlags(ctx *components.Context, descriptor *model.AppDescriptor) error { - if !ctx.IsFlagSet(commands.MonitorPolicyFlag) { - return nil - } - - fields, err := utils.ParseKeyValueString(ctx.GetStringFlagValue(commands.MonitorPolicyFlag), ",") - if err != nil { - return fmt.Errorf("failed to parse --%s: %w", commands.MonitorPolicyFlag, err) - } - - for key := range fields { - if key != "type" && key != "value" { - return fmt.Errorf("invalid field '%s' in --%s (supported fields: type, value)", key, commands.MonitorPolicyFlag) - } - } - - typeStr, typeSet := fields["type"] - if !typeSet { - return fmt.Errorf("--%s requires a 'type' field", commands.MonitorPolicyFlag) - } - - policyType, err := utils.ValidateEnumFlag(commands.MonitorPolicyFlag, typeStr, model.MonitorPolicyTypeNone, model.MonitorPolicyTypeValues) - if err != nil { - return err - } - - policy := &model.MonitorPolicy{Type: policyType} - valueStr, valueSet := fields["value"] - - if policyType == model.MonitorPolicyTypeNone { - if valueSet { - return fmt.Errorf("'value' must not be set in --%s when type is '%s'", commands.MonitorPolicyFlag, model.MonitorPolicyTypeNone) - } - } else { - if !valueSet { - return fmt.Errorf("'value' is required in --%s when type is '%s'", commands.MonitorPolicyFlag, policyType) - } - value, err := strconv.Atoi(valueStr) + if ctx.IsFlagSet(commands.MonitorPolicyFlag) { + monitorPolicy, err := utils.ParseMonitorPolicyFlag(ctx.GetStringFlagValue(commands.MonitorPolicyFlag)) if err != nil { - return fmt.Errorf("'value' in --%s must be an integer: %w", commands.MonitorPolicyFlag, err) - } - if value <= 0 { - return fmt.Errorf("'value' in --%s must be a positive integer", commands.MonitorPolicyFlag) + return fmt.Errorf("failed to parse --%s: %w", commands.MonitorPolicyFlag, err) } - policy.Value = &value + descriptor.MonitorPolicy = monitorPolicy } - descriptor.MonitorPolicy = policy return nil } diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index c788485..8afebc8 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -104,13 +104,13 @@ func TestCreateAppCommand_Run_MonitorPolicy(t *testing.T) { name: "missing type", monitorPolicy: "value=5", expectsError: true, - errorContains: "requires a 'type' field", + errorContains: "missing required 'type' field", }, { name: "invalid type", monitorPolicy: "type=bogus, value=5", expectsError: true, - errorContains: "invalid value for --monitor-policy", + errorContains: "invalid type 'bogus'", }, { name: "non-integer value", diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index 0fc7062..0cc1c70 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -284,7 +284,7 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=bogus, value=5") }, expectsError: true, - errorContains: "invalid value for --monitor-policy", + errorContains: "invalid type 'bogus'", }, { name: "invalid add-labels format - missing equals", diff --git a/apptrust/commands/utils/utils.go b/apptrust/commands/utils/utils.go index 6173a62..f1b4169 100644 --- a/apptrust/commands/utils/utils.go +++ b/apptrust/commands/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "fmt" "slices" + "strconv" "strings" "github.com/jfrog/jfrog-cli-application/apptrust/model" @@ -168,6 +169,50 @@ func ParseListPropertiesFlag(propertiesStr string) (map[string][]string, error) return result, nil } +func ParseMonitorPolicyFlag(flagValue string) (*model.MonitorPolicy, error) { + fields, err := ParseKeyValueString(flagValue, ",") + if err != nil { + return nil, err + } + + for key := range fields { + if key != "type" && key != "value" { + return nil, errorutils.CheckErrorf("invalid field '%s' (supported fields: type, value)", key) + } + } + + policyType, typeSet := fields["type"] + if !typeSet { + return nil, errorutils.CheckErrorf("missing required 'type' field") + } + if !slices.Contains(model.MonitorPolicyTypeValues, policyType) { + return nil, errorutils.CheckErrorf("invalid type '%s' (supported types: %s)", policyType, coreutils.ListToText(model.MonitorPolicyTypeValues)) + } + + valueStr, valueSet := fields["value"] + isNone := policyType == model.MonitorPolicyTypeNone + if isNone && valueSet { + return nil, errorutils.CheckErrorf("'value' must not be set when type is '%s'", model.MonitorPolicyTypeNone) + } + if !isNone && !valueSet { + return nil, errorutils.CheckErrorf("'value' is required when type is '%s'", policyType) + } + + policy := &model.MonitorPolicy{Type: policyType} + if valueSet { + value, err := strconv.Atoi(valueStr) + if err != nil { + return nil, errorutils.CheckErrorf("'value' must be an integer: %s", err.Error()) + } + if value <= 0 { + return nil, errorutils.CheckErrorf("'value' must be a positive integer") + } + policy.Value = &value + } + + return policy, nil +} + func ParseLabelKeyValuePairs(flagValue string) ([]model.LabelEntry, error) { if flagValue == "" { return []model.LabelEntry{}, nil diff --git a/apptrust/model/app_descriptor.go b/apptrust/model/app_descriptor.go index 4f4654d..c7f18b3 100644 --- a/apptrust/model/app_descriptor.go +++ b/apptrust/model/app_descriptor.go @@ -45,9 +45,6 @@ type LabelEntry struct { Value string `json:"value"` } -// MonitorPolicy is the operational-validity monitoring configuration for an application. -// Type is "none" to disable monitoring, or one of "time_frame_in_months" / "version_count". -// Value is required for the enabled types and must be empty for "none". type MonitorPolicy struct { Type string `json:"type"` Value *int `json:"value,omitempty"` From 941213e054c576bec19bd5fa55834e9f64095440 Mon Sep 17 00:00:00 2001 From: danielr Date: Wed, 22 Jul 2026 10:54:51 +0300 Subject: [PATCH 3/7] Add e2e tests for monitor-policy on app-create and app-update Cover version_count and time_frame_in_months on create, and time_frame_in_months, version_count, and none on update, verifying the persisted policy via the applications GET endpoint. Co-authored-by: Cursor --- e2e/application_test.go | 100 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/e2e/application_test.go b/e2e/application_test.go index 40a1b54..3122d22 100644 --- a/e2e/application_test.go +++ b/e2e/application_test.go @@ -87,6 +87,106 @@ func TestUpdateApp(t *testing.T) { utils.DeleteApplication(t, appKey) } +func TestCreateAppWithMonitorPolicy(t *testing.T) { + projectKey := utils.GetTestProjectKey(t) + + tests := []struct { + name string + monitorPolicy string + expectedType string + expectedValue *int + }{ + { + name: "version count", + monitorPolicy: "type=version_count, value=5", + expectedType: model.MonitorPolicyTypeVersionCount, + expectedValue: intPtr(5), + }, + { + name: "time frame in months", + monitorPolicy: "type=time_frame_in_months, value=3", + expectedType: model.MonitorPolicyTypeTimeframe, + expectedValue: intPtr(3), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-create-monitor-" + strings.ReplaceAll(tt.name, " ", "-")) + + err := utils.AppTrustCli.Exec("app-create", appKey, + "--project="+projectKey, + "--application-name="+appKey, + "--monitor-policy="+tt.monitorPolicy) + assert.NoError(t, err) + + app, _, err := utils.GetApplication(appKey) + assert.NoError(t, err) + assert.Equal(t, appKey, app.ApplicationKey) + if assert.NotNil(t, app.MonitorPolicy) { + assert.Equal(t, tt.expectedType, app.MonitorPolicy.Type) + assert.Equal(t, tt.expectedValue, app.MonitorPolicy.Value) + } + + utils.DeleteApplication(t, appKey) + }) + } +} + +func TestUpdateAppMonitorPolicy(t *testing.T) { + tests := []struct { + name string + monitorPolicy string + expectedType string + expectedValue *int + }{ + { + name: "time frame in months", + monitorPolicy: "type=time_frame_in_months, value=6", + expectedType: model.MonitorPolicyTypeTimeframe, + expectedValue: intPtr(6), + }, + { + name: "version count", + monitorPolicy: "type=version_count, value=4", + expectedType: model.MonitorPolicyTypeVersionCount, + expectedValue: intPtr(4), + }, + { + name: "none", + monitorPolicy: "type=none", + expectedType: model.MonitorPolicyTypeNone, + expectedValue: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + appKey := utils.GenerateUniqueKey("app-update-monitor-" + strings.ReplaceAll(tt.name, " ", "-")) + + utils.CreateBasicApplication(t, appKey) + + err := utils.AppTrustCli.Exec("app-update", appKey, + "--monitor-policy="+tt.monitorPolicy) + assert.NoError(t, err) + + app, _, err := utils.GetApplication(appKey) + assert.NoError(t, err) + assert.Equal(t, appKey, app.ApplicationKey) + if assert.NotNil(t, app.MonitorPolicy) { + assert.Equal(t, tt.expectedType, app.MonitorPolicy.Type) + assert.Equal(t, tt.expectedValue, app.MonitorPolicy.Value) + } + + utils.DeleteApplication(t, appKey) + }) + } +} + +func intPtr(i int) *int { + return &i +} + func TestDeleteApp(t *testing.T) { appKey := utils.GenerateUniqueKey("app-delete") utils.CreateBasicApplication(t, appKey) From 1ffcfdbc92f194ffef4b769af0d7fd36b44d835b Mon Sep 17 00:00:00 2001 From: danielr Date: Wed, 22 Jul 2026 10:58:49 +0300 Subject: [PATCH 4/7] Move IntPtr helper into e2e utils package Co-authored-by: Cursor --- e2e/application_test.go | 12 ++++-------- e2e/utils/e2e_utils.go | 4 ++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/e2e/application_test.go b/e2e/application_test.go index 3122d22..438e247 100644 --- a/e2e/application_test.go +++ b/e2e/application_test.go @@ -100,13 +100,13 @@ func TestCreateAppWithMonitorPolicy(t *testing.T) { name: "version count", monitorPolicy: "type=version_count, value=5", expectedType: model.MonitorPolicyTypeVersionCount, - expectedValue: intPtr(5), + expectedValue: utils.IntPtr(5), }, { name: "time frame in months", monitorPolicy: "type=time_frame_in_months, value=3", expectedType: model.MonitorPolicyTypeTimeframe, - expectedValue: intPtr(3), + expectedValue: utils.IntPtr(3), }, } @@ -144,13 +144,13 @@ func TestUpdateAppMonitorPolicy(t *testing.T) { name: "time frame in months", monitorPolicy: "type=time_frame_in_months, value=6", expectedType: model.MonitorPolicyTypeTimeframe, - expectedValue: intPtr(6), + expectedValue: utils.IntPtr(6), }, { name: "version count", monitorPolicy: "type=version_count, value=4", expectedType: model.MonitorPolicyTypeVersionCount, - expectedValue: intPtr(4), + expectedValue: utils.IntPtr(4), }, { name: "none", @@ -183,10 +183,6 @@ func TestUpdateAppMonitorPolicy(t *testing.T) { } } -func intPtr(i int) *int { - return &i -} - func TestDeleteApp(t *testing.T) { appKey := utils.GenerateUniqueKey("app-delete") utils.CreateBasicApplication(t, appKey) diff --git a/e2e/utils/e2e_utils.go b/e2e/utils/e2e_utils.go index 00a4948..e3e12d4 100644 --- a/e2e/utils/e2e_utils.go +++ b/e2e/utils/e2e_utils.go @@ -93,3 +93,7 @@ func GenerateUniqueKey(prefix string) string { timestamp := strconv.FormatInt(time.Now().Unix(), 10) return fmt.Sprintf("%s-%s", prefix, timestamp) } + +func IntPtr(i int) *int { + return &i +} From 73893f1c03113bf33f378d90d5e38b8527fd27ee Mon Sep 17 00:00:00 2001 From: danielr Date: Wed, 22 Jul 2026 11:11:23 +0300 Subject: [PATCH 5/7] CI: allow fork PR checkout in e2e pull_request_target workflow Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 031a3e1..c13898c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -29,6 +29,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} + allow-unsafe-pr-checkout: true - name: Setup Go uses: actions/setup-go@v6 From fc4efd2bdac2173c08524e583ba30bc0a3234b16 Mon Sep 17 00:00:00 2001 From: danielr Date: Thu, 23 Jul 2026 17:52:17 +0300 Subject: [PATCH 6/7] Address review comments on monitor-policy - Reword --monitor-policy flag description for clarity - Unit-test ParseMonitorPolicyFlag cases in utils_test.go - Replace dedicated monitor-policy tests with samples in existing create/update tests - Remove intPtr/IntPtr helpers in favor of local variables Co-authored-by: Cursor --- .../application/create_app_cmd_test.go | 114 +----------------- .../application/update_app_cmd_test.go | 30 +---- apptrust/commands/flags.go | 2 +- apptrust/commands/utils/utils_test.go | 104 ++++++++++++++++ e2e/application_test.go | 106 ++-------------- e2e/utils/e2e_utils.go | 4 - 6 files changed, 122 insertions(+), 238 deletions(-) diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index 8afebc8..ac1ca7c 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -22,6 +22,7 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { description := "Test application" businessCriticality := "high" maturityLevel := "production" + monitorPolicyValue := 5 ctx := &components.Context{ Arguments: []string{"app-key"}, @@ -34,6 +35,7 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { ctx.AddStringFlag("labels", "env=prod;region=us-east") ctx.AddStringFlag("user-owners", "john.doe;jane.smith") ctx.AddStringFlag("group-owners", "devops;security") + ctx.AddStringFlag("monitor-policy", "type=version_count, value=5") ctx.AddStringFlag("url", "https://example.com") requestPayload := &model.AppDescriptor{ @@ -47,8 +49,9 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { {Key: "env", Value: "prod"}, {Key: "region", Value: "us-east"}, }, - UserOwners: &[]string{"john.doe", "jane.smith"}, - GroupOwners: &[]string{"devops", "security"}, + UserOwners: &[]string{"john.doe", "jane.smith"}, + GroupOwners: &[]string{"devops", "security"}, + MonitorPolicy: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, } mockAppService := mockapps.NewMockApplicationService(ctrl) @@ -63,113 +66,6 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { assert.NoError(t, err) } -func TestCreateAppCommand_Run_MonitorPolicy(t *testing.T) { - versionCountValue := 5 - - tests := []struct { - name string - monitorPolicy string - expectsError bool - errorContains string - expectedPolicy *model.MonitorPolicy - }{ - { - name: "version count", - monitorPolicy: "type=version_count, value=5", - expectedPolicy: &model.MonitorPolicy{Type: "version_count", Value: &versionCountValue}, - }, - { - name: "time frame in months", - monitorPolicy: "type=time_frame_in_months, value=5", - expectedPolicy: &model.MonitorPolicy{Type: "time_frame_in_months", Value: &versionCountValue}, - }, - { - name: "none without value", - monitorPolicy: "type=none", - expectedPolicy: &model.MonitorPolicy{Type: "none"}, - }, - { - name: "none with value is rejected", - monitorPolicy: "type=none, value=5", - expectsError: true, - errorContains: "'value' must not be set", - }, - { - name: "missing value for enabled type", - monitorPolicy: "type=version_count", - expectsError: true, - errorContains: "'value' is required", - }, - { - name: "missing type", - monitorPolicy: "value=5", - expectsError: true, - errorContains: "missing required 'type' field", - }, - { - name: "invalid type", - monitorPolicy: "type=bogus, value=5", - expectsError: true, - errorContains: "invalid type 'bogus'", - }, - { - name: "non-integer value", - monitorPolicy: "type=version_count, value=abc", - expectsError: true, - errorContains: "must be an integer", - }, - { - name: "non-positive value", - monitorPolicy: "type=version_count, value=0", - expectsError: true, - errorContains: "must be a positive integer", - }, - { - name: "unknown field", - monitorPolicy: "type=version_count, value=5, foo=bar", - expectsError: true, - errorContains: "invalid field 'foo'", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := &components.Context{ - Arguments: []string{"app-key"}, - } - ctx.AddStringFlag("project", "test-project") - ctx.AddStringFlag("url", "https://example.com") - ctx.AddStringFlag("monitor-policy", tt.monitorPolicy) - - var actualPayload *model.AppDescriptor - mockAppService := mockapps.NewMockApplicationService(ctrl) - if !tt.expectsError { - mockAppService.EXPECT().CreateApplication(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ interface{}, req *model.AppDescriptor) ([]byte, error) { - actualPayload = req - return nil, nil - }).Times(1) - } - - cmd := &createAppCommand{ - applicationService: mockAppService, - } - - err := cmd.prepareAndRunCommand(ctx) - if tt.expectsError { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errorContains) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedPolicy, actualPayload.MonitorPolicy) - } - }) - } -} - func TestCreateAppCommand_Error(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index 0cc1c70..ccb7099 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -111,6 +111,8 @@ func TestUpdateAppCommand_WrongNumberOfArguments(t *testing.T) { } func TestUpdateAppCommand_FlagsSuite(t *testing.T) { + monitorPolicyValue := 5 + tests := []struct { name string ctxSetup func(*components.Context) @@ -256,36 +258,16 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { }, }, { - name: "monitor-policy with value", + name: "monitor-policy", ctxSetup: func(ctx *components.Context) { ctx.Arguments = []string{"app-key"} ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=version_count, value=5") }, expectsPayload: &model.AppDescriptor{ ApplicationKey: "app-key", - MonitorPolicy: &model.MonitorPolicy{Type: "version_count", Value: intPtr(5)}, - }, - }, - { - name: "monitor-policy none disables monitoring", - ctxSetup: func(ctx *components.Context) { - ctx.Arguments = []string{"app-key"} - ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=none") - }, - expectsPayload: &model.AppDescriptor{ - ApplicationKey: "app-key", - MonitorPolicy: &model.MonitorPolicy{Type: "none"}, + MonitorPolicy: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, }, }, - { - name: "invalid monitor-policy type", - ctxSetup: func(ctx *components.Context) { - ctx.Arguments = []string{"app-key"} - ctx.AddStringFlag(commands.MonitorPolicyFlag, "type=bogus, value=5") - }, - expectsError: true, - errorContains: "invalid type 'bogus'", - }, { name: "invalid add-labels format - missing equals", ctxSetup: func(ctx *components.Context) { @@ -396,7 +378,3 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { func stringPtr(s string) *string { return &s } - -func intPtr(i int) *int { - return &i -} diff --git a/apptrust/commands/flags.go b/apptrust/commands/flags.go index 262bd42..3e5da1c 100644 --- a/apptrust/commands/flags.go +++ b/apptrust/commands/flags.go @@ -98,7 +98,7 @@ var flagsMap = map[string]components.Flag{ RemoveLabelsFlag: components.NewStringFlag(RemoveLabelsFlag, "List of semicolon-separated (;) labels to remove in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "semicolon-separated (;) list of user owners in the form of \"user1;user2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "semicolon-separated (;) list of group owners in the form of \"group1;group2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), - MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "The operational-validity monitoring configuration, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". 'value' must be a positive integer and is required when type is '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', and must be omitted when type is '"+model.MonitorPolicyTypeNone+"'. When omitted, monitoring is off.", func(f *components.StringFlag) { f.Mandatory = false }), + MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "Defines how long application versions remain monitored, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". For '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', 'value' is the number of months or versions to keep monitoring (a positive integer). For '"+model.MonitorPolicyTypeNone+"', monitoring is disabled and 'value' must be omitted. When the flag is omitted, monitoring is off.", func(f *components.StringFlag) { f.Mandatory = false }), SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()), PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }), DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()), diff --git a/apptrust/commands/utils/utils_test.go b/apptrust/commands/utils/utils_test.go index b5387ab..d817a63 100644 --- a/apptrust/commands/utils/utils_test.go +++ b/apptrust/commands/utils/utils_test.go @@ -165,6 +165,110 @@ func TestParseNameVersionPairs(t *testing.T) { } } +func TestParseMonitorPolicyFlag(t *testing.T) { + valueFive := 5 + valueThree := 3 + + tests := []struct { + name string + input string + expected *model.MonitorPolicy + expectErr bool + errorMsg string + }{ + { + name: "version count", + input: "type=version_count, value=5", + expected: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &valueFive}, + }, + { + name: "time frame in months", + input: "type=time_frame_in_months, value=3", + expected: &model.MonitorPolicy{Type: model.MonitorPolicyTypeTimeframe, Value: &valueThree}, + }, + { + name: "none without value", + input: "type=none", + expected: &model.MonitorPolicy{Type: model.MonitorPolicyTypeNone}, + }, + { + name: "missing type", + input: "value=5", + expectErr: true, + errorMsg: "missing required 'type' field", + }, + { + name: "invalid type", + input: "type=bogus, value=5", + expectErr: true, + errorMsg: "invalid type 'bogus'", + }, + { + name: "missing value for enabled type", + input: "type=version_count", + expectErr: true, + errorMsg: "'value' is required", + }, + { + name: "value set for none", + input: "type=none, value=5", + expectErr: true, + errorMsg: "'value' must not be set", + }, + { + name: "non-integer value", + input: "type=version_count, value=abc", + expectErr: true, + errorMsg: "must be an integer", + }, + { + name: "zero value", + input: "type=version_count, value=0", + expectErr: true, + errorMsg: "must be a positive integer", + }, + { + name: "negative value", + input: "type=version_count, value=-1", + expectErr: true, + errorMsg: "must be a positive integer", + }, + { + name: "unknown field", + input: "type=version_count, value=5, foo=bar", + expectErr: true, + errorMsg: "invalid field 'foo'", + }, + { + name: "empty string", + input: "", + expectErr: true, + errorMsg: "missing required 'type' field", + }, + { + name: "invalid key-value pair", + input: "type", + expectErr: true, + errorMsg: "invalid key-value pair", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := ParseMonitorPolicyFlag(tt.input) + if tt.expectErr { + assert.Error(t, err, "ParseMonitorPolicyFlag(%q) expected error, got nil", tt.input) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg, "error message should contain %q", tt.errorMsg) + } + return + } + assert.NoError(t, err, "ParseMonitorPolicyFlag(%q) unexpected error: %v", tt.input, err) + assert.Equal(t, tt.expected, result, "ParseMonitorPolicyFlag(%q) = %v, want %v", tt.input, result, tt.expected) + }) + } +} + func TestParseLabelKeyValuePairs(t *testing.T) { tests := []struct { name string diff --git a/e2e/application_test.go b/e2e/application_test.go index 438e247..7284b80 100644 --- a/e2e/application_test.go +++ b/e2e/application_test.go @@ -20,6 +20,7 @@ func TestCreateApp(t *testing.T) { maturityLevel := "production" userOwners := []string{"admin", "developer"} groupOwners := []string{"devops-team", "security-team"} + monitorPolicyValue := 5 err := utils.AppTrustCli.Exec("app-create", appKey, "--project="+projectKey, @@ -29,7 +30,8 @@ func TestCreateApp(t *testing.T) { "--maturity-level="+maturityLevel, "--labels=env=prod;team=devops", "--user-owners="+strings.Join(userOwners, ";"), - "--group-owners="+strings.Join(groupOwners, ";")) + "--group-owners="+strings.Join(groupOwners, ";"), + "--monitor-policy=type=version_count, value=5") assert.NoError(t, err) // Fetch and verify the application was created correctly @@ -44,6 +46,7 @@ func TestCreateApp(t *testing.T) { assert.ElementsMatch(t, []model.LabelEntry{{Key: "env", Value: "prod"}, {Key: "team", Value: "devops"}}, *app.Labels) assert.Equal(t, userOwners, *app.UserOwners) assert.Equal(t, groupOwners, *app.GroupOwners) + assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, app.MonitorPolicy) utils.DeleteApplication(t, appKey) } @@ -60,6 +63,7 @@ func TestUpdateApp(t *testing.T) { updatedMaturityLevel := "production" updatedUserOwners := []string{"app-admin", "frog"} updatedGroupOwners := []string{"dev-team", "security-team"} + monitorPolicyValue := 6 err := utils.AppTrustCli.Exec("app-update", appKey, "--application-name="+updatedAppName, @@ -68,7 +72,8 @@ func TestUpdateApp(t *testing.T) { "--maturity-level="+updatedMaturityLevel, "--labels=env=qa;team=dev", "--user-owners="+strings.Join(updatedUserOwners, ";"), - "--group-owners="+strings.Join(updatedGroupOwners, ";")) + "--group-owners="+strings.Join(updatedGroupOwners, ";"), + "--monitor-policy=type=time_frame_in_months, value=6") assert.NoError(t, err) // Fetch and verify the application was updated correctly @@ -83,106 +88,11 @@ func TestUpdateApp(t *testing.T) { assert.ElementsMatch(t, []model.LabelEntry{{Key: "env", Value: "qa"}, {Key: "team", Value: "dev"}}, *app.Labels) assert.Equal(t, updatedUserOwners, *app.UserOwners) assert.Equal(t, updatedGroupOwners, *app.GroupOwners) + assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeTimeframe, Value: &monitorPolicyValue}, app.MonitorPolicy) utils.DeleteApplication(t, appKey) } -func TestCreateAppWithMonitorPolicy(t *testing.T) { - projectKey := utils.GetTestProjectKey(t) - - tests := []struct { - name string - monitorPolicy string - expectedType string - expectedValue *int - }{ - { - name: "version count", - monitorPolicy: "type=version_count, value=5", - expectedType: model.MonitorPolicyTypeVersionCount, - expectedValue: utils.IntPtr(5), - }, - { - name: "time frame in months", - monitorPolicy: "type=time_frame_in_months, value=3", - expectedType: model.MonitorPolicyTypeTimeframe, - expectedValue: utils.IntPtr(3), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - appKey := utils.GenerateUniqueKey("app-create-monitor-" + strings.ReplaceAll(tt.name, " ", "-")) - - err := utils.AppTrustCli.Exec("app-create", appKey, - "--project="+projectKey, - "--application-name="+appKey, - "--monitor-policy="+tt.monitorPolicy) - assert.NoError(t, err) - - app, _, err := utils.GetApplication(appKey) - assert.NoError(t, err) - assert.Equal(t, appKey, app.ApplicationKey) - if assert.NotNil(t, app.MonitorPolicy) { - assert.Equal(t, tt.expectedType, app.MonitorPolicy.Type) - assert.Equal(t, tt.expectedValue, app.MonitorPolicy.Value) - } - - utils.DeleteApplication(t, appKey) - }) - } -} - -func TestUpdateAppMonitorPolicy(t *testing.T) { - tests := []struct { - name string - monitorPolicy string - expectedType string - expectedValue *int - }{ - { - name: "time frame in months", - monitorPolicy: "type=time_frame_in_months, value=6", - expectedType: model.MonitorPolicyTypeTimeframe, - expectedValue: utils.IntPtr(6), - }, - { - name: "version count", - monitorPolicy: "type=version_count, value=4", - expectedType: model.MonitorPolicyTypeVersionCount, - expectedValue: utils.IntPtr(4), - }, - { - name: "none", - monitorPolicy: "type=none", - expectedType: model.MonitorPolicyTypeNone, - expectedValue: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - appKey := utils.GenerateUniqueKey("app-update-monitor-" + strings.ReplaceAll(tt.name, " ", "-")) - - utils.CreateBasicApplication(t, appKey) - - err := utils.AppTrustCli.Exec("app-update", appKey, - "--monitor-policy="+tt.monitorPolicy) - assert.NoError(t, err) - - app, _, err := utils.GetApplication(appKey) - assert.NoError(t, err) - assert.Equal(t, appKey, app.ApplicationKey) - if assert.NotNil(t, app.MonitorPolicy) { - assert.Equal(t, tt.expectedType, app.MonitorPolicy.Type) - assert.Equal(t, tt.expectedValue, app.MonitorPolicy.Value) - } - - utils.DeleteApplication(t, appKey) - }) - } -} - func TestDeleteApp(t *testing.T) { appKey := utils.GenerateUniqueKey("app-delete") utils.CreateBasicApplication(t, appKey) diff --git a/e2e/utils/e2e_utils.go b/e2e/utils/e2e_utils.go index e3e12d4..00a4948 100644 --- a/e2e/utils/e2e_utils.go +++ b/e2e/utils/e2e_utils.go @@ -93,7 +93,3 @@ func GenerateUniqueKey(prefix string) string { timestamp := strconv.FormatInt(time.Now().Unix(), 10) return fmt.Sprintf("%s-%s", prefix, timestamp) } - -func IntPtr(i int) *int { - return &i -} From 7584a9654b839167fd8bf8a62a5871508676684d Mon Sep 17 00:00:00 2001 From: danielr Date: Thu, 23 Jul 2026 18:04:46 +0300 Subject: [PATCH 7/7] Drop create-only 'monitoring off when omitted' note from --monitor-policy description Co-authored-by: Cursor --- apptrust/commands/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apptrust/commands/flags.go b/apptrust/commands/flags.go index 3e5da1c..d0dd291 100644 --- a/apptrust/commands/flags.go +++ b/apptrust/commands/flags.go @@ -98,7 +98,7 @@ var flagsMap = map[string]components.Flag{ RemoveLabelsFlag: components.NewStringFlag(RemoveLabelsFlag, "List of semicolon-separated (;) labels to remove in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "semicolon-separated (;) list of user owners in the form of \"user1;user2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "semicolon-separated (;) list of group owners in the form of \"group1;group2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), - MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "Defines how long application versions remain monitored, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". For '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', 'value' is the number of months or versions to keep monitoring (a positive integer). For '"+model.MonitorPolicyTypeNone+"', monitoring is disabled and 'value' must be omitted. When the flag is omitted, monitoring is off.", func(f *components.StringFlag) { f.Mandatory = false }), + MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "Defines how long application versions remain monitored, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". For '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', 'value' is the number of months or versions to keep monitoring (a positive integer). For '"+model.MonitorPolicyTypeNone+"', monitoring is disabled and 'value' must be omitted.", func(f *components.StringFlag) { f.Mandatory = false }), SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()), PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }), DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()),