Improve ban subcommand#123
Conversation
… is invoked without a reason explicitly
MatousJobanek
left a comment
There was a problem hiding this comment.
Great start 👍
Let me know if you need any help with the unit tests - see my last comment of the review for the hints.
|
|
||
| const ( | ||
| menuKey string = "menu.json" | ||
| configMapName string = "banning-reasons" |
There was a problem hiding this comment.
I would probably name it ban-reason-config
| if len(args) == 2 { | ||
| // Traditional usage: both usersignup name and ban reason provided | ||
| banReason = args[1] | ||
| } else { | ||
| // Interactive mode: only usersignup name provided, need to get reason from ConfigMap menu | ||
| ctx.Printlnf("No ban reason provided. Checking for available reasons from ConfigMap...") |
There was a problem hiding this comment.
I would rather use the following:
- drop the second argument (the ban reason)
- always try to get the ConfigMap
- if ConfigMap is not available (or is empty) then ask user to provide the reason as an input in the interactive flow
| if apierrors.IsNotFound(err) { | ||
| return nil, fmt.Errorf("failed to get ConfigMap: %w", err) | ||
| } | ||
| return nil, err |
There was a problem hiding this comment.
You can simplify it only to
| if apierrors.IsNotFound(err) { | |
| return nil, fmt.Errorf("failed to get ConfigMap: %w", err) | |
| } | |
| return nil, err | |
| return nil, err |
There was a problem hiding this comment.
Ok, I just wanted to be more specific here.
| configMap := &corev1.ConfigMap{} | ||
| err = cl.Get(context.TODO(), types.NamespacedName{ | ||
| Name: configMapName, | ||
| Namespace: "toolchain-host-operator", |
There was a problem hiding this comment.
| Namespace: "toolchain-host-operator", | |
| Namespace: cfg.OperatorNamespace, |
so it's not hard-coded, but loaded from the ksctl.yaml file
| if menuJSON, exists := configMap.Data[menuKey]; exists && menuJSON != "" { | ||
| //var menus []Menu | ||
| if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { | ||
| return nil, fmt.Errorf("ConfigMap doesn't contain %s key: %w", menuKey, err) |
There was a problem hiding this comment.
the error returned is not because the ConfigMap wouldn't contain the key, but because the value doesn't contain the correct JSON format.
The error that it doesn't contain the key should be returned somewhere else
| t.Run("single workload menu item", func(t *testing.T) { | ||
| // This test demonstrates the structure but cannot test interactivity | ||
|
|
||
| // given | ||
| menu := []cmd.Menu{ | ||
| { | ||
| Kind: "workload", | ||
| Description: "Select workload type", | ||
| Options: []string{"container", "vm"}, | ||
| }, | ||
| } | ||
|
|
||
| // Verify menu structure is correct for processing | ||
| assert.Len(t, menu, 1) | ||
| assert.Equal(t, "workload", menu[0].Kind) | ||
| assert.Equal(t, "Select workload type", menu[0].Description) | ||
| assert.Len(t, menu[0].Options, 2) | ||
| assert.Contains(t, menu[0].Options, "container") | ||
| }) | ||
|
|
||
| t.Run("multiple menu items structure", func(t *testing.T) { | ||
| // given | ||
| menu := []cmd.Menu{ | ||
| { | ||
| Kind: "workload", | ||
| Description: "Select workload type", | ||
| Options: []string{"container", "vm"}, | ||
| }, | ||
| { | ||
| Kind: "behavior", | ||
| Description: "Select behavior classification", | ||
| Options: []string{"malicious", "suspicious", "policy-violation"}, | ||
| }, | ||
| { | ||
| Kind: "detection", | ||
| Description: "Select detection mechanism", | ||
| Options: []string{"automated", "manual", "user-report"}, | ||
| }, | ||
| } | ||
|
|
||
| // Verify menu structure supports all BanInfo fields | ||
| kindMap := make(map[string]bool) | ||
| for _, item := range menu { | ||
| kindMap[item.Kind] = true | ||
| assert.NotEmpty(t, item.Description) | ||
| assert.NotEmpty(t, item.Options) | ||
| } | ||
|
|
||
| assert.True(t, kindMap["workload"]) | ||
| assert.True(t, kindMap["behavior"]) | ||
| assert.True(t, kindMap["detection"]) | ||
| }) |
There was a problem hiding this comment.
correct me if I missed something, but it looks like that you don't verify any actual logic in these two tests.
| func TestBanMenuMappingLogic(t *testing.T) { | ||
| // This test verifies the mapping logic that converts menu selections to BanInfo | ||
|
|
||
| t.Run("verify BanInfo field mapping", func(t *testing.T) { | ||
| // Test data that simulates what would be collected from the interactive menu | ||
| testCases := []struct { | ||
| name string | ||
| kind string | ||
| answer string | ||
| expected func(*cmd.BanInfo) string | ||
| }{ | ||
| { | ||
| name: "workload mapping", | ||
| kind: "workload", | ||
| answer: "compute-intensive", | ||
| expected: func(info *cmd.BanInfo) string { | ||
| return info.WorkloadType | ||
| }, | ||
| }, | ||
| { | ||
| name: "behavior mapping", | ||
| kind: "behavior", | ||
| answer: "malicious", | ||
| expected: func(info *cmd.BanInfo) string { | ||
| return info.BehaviorClassification | ||
| }, | ||
| }, | ||
| { | ||
| name: "detection mapping", | ||
| kind: "detection", | ||
| answer: "automated", | ||
| expected: func(info *cmd.BanInfo) string { | ||
| return info.DetectionMechanism | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| // This demonstrates the expected mapping behavior | ||
| banInfo := &cmd.BanInfo{} | ||
|
|
||
| // Simulate the switch statement logic from banMenu | ||
| switch tc.kind { | ||
| case "workload": | ||
| banInfo.WorkloadType = tc.answer | ||
| case "behavior": | ||
| banInfo.BehaviorClassification = tc.answer | ||
| case "detection": | ||
| banInfo.DetectionMechanism = tc.answer | ||
| } | ||
|
|
||
| assert.Equal(t, tc.expected(banInfo), tc.answer) | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| func TestMenuStruct(t *testing.T) { | ||
| t.Run("JSON unmarshaling works correctly", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[ | ||
| { | ||
| "kind": "workload", | ||
| "description": "Select workload type", | ||
| "options": ["container", "vm"] | ||
| }, | ||
| { | ||
| "kind": "behavior", | ||
| "description": "Select behavior classification", | ||
| "options": ["malicious", "suspicious"] | ||
| } | ||
| ]` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Len(t, menus, 2) | ||
|
|
||
| // Verify first menu | ||
| assert.Equal(t, "workload", menus[0].Kind) | ||
| assert.Equal(t, "Select workload type", menus[0].Description) | ||
| assert.Len(t, menus[0].Options, 2) | ||
| assert.Contains(t, menus[0].Options, "container") | ||
| assert.Contains(t, menus[0].Options, "vm") | ||
|
|
||
| // Verify second menu | ||
| assert.Equal(t, "behavior", menus[1].Kind) | ||
| assert.Equal(t, "Select behavior classification", menus[1].Description) | ||
| assert.Len(t, menus[1].Options, 2) | ||
| assert.Contains(t, menus[1].Options, "malicious") | ||
| assert.Contains(t, menus[1].Options, "suspicious") | ||
| }) | ||
|
|
||
| t.Run("empty JSON array unmarshals correctly", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[]` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Empty(t, menus) | ||
| }) | ||
|
|
||
| t.Run("malformed JSON returns error", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[{invalid json` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.Error(t, err) | ||
| }) | ||
| } | ||
|
|
||
| func TestBanInfoStruct(t *testing.T) { | ||
| t.Run("JSON marshaling works correctly", func(t *testing.T) { | ||
| // given | ||
| banInfo := &cmd.BanInfo{ | ||
| WorkloadType: "container", | ||
| BehaviorClassification: "malicious", | ||
| DetectionMechanism: "automated", | ||
| } | ||
|
|
||
| // when | ||
| jsonData, err := json.Marshal(banInfo) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Contains(t, string(jsonData), "container") | ||
| assert.Contains(t, string(jsonData), "malicious") | ||
| assert.Contains(t, string(jsonData), "automated") | ||
|
|
||
| // Verify it can be unmarshaled back | ||
| var unmarshaled cmd.BanInfo | ||
| err = json.Unmarshal(jsonData, &unmarshaled) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, banInfo.WorkloadType, unmarshaled.WorkloadType) | ||
| assert.Equal(t, banInfo.BehaviorClassification, unmarshaled.BehaviorClassification) | ||
| assert.Equal(t, banInfo.DetectionMechanism, unmarshaled.DetectionMechanism) | ||
| }) | ||
|
|
||
| t.Run("empty BanInfo marshals to empty fields", func(t *testing.T) { | ||
| // given | ||
| banInfo := &cmd.BanInfo{} | ||
|
|
||
| // when | ||
| jsonData, err := json.Marshal(banInfo) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Contains(t, string(jsonData), `"workloadType":""`) | ||
| assert.Contains(t, string(jsonData), `"behaviorClassification":""`) | ||
| assert.Contains(t, string(jsonData), `"detectionMechanism":""`) | ||
| }) | ||
| } | ||
|
|
||
| // TestBanMenuInteractiveLogic tests the interactive menu logic in BanMenu function (lines 76-110) | ||
| func TestBanMenuLogic(t *testing.T) { | ||
| t.Run("BanMenu with menu content creates proper data structures", func(t *testing.T) { | ||
| // This test verifies the logic of creating huh.Option structures and the mapping | ||
| // We can't test the actual interaction, but we can test the data preparation | ||
|
|
||
| // given | ||
| menu := []cmd.Menu{ | ||
| { | ||
| Kind: "workload", | ||
| Description: "Select workload type", | ||
| Options: []string{"container", "vm"}, | ||
| }, | ||
| { | ||
| Kind: "behavior", | ||
| Description: "Select behavior classification", | ||
| Options: []string{"malicious", "suspicious"}, | ||
| }, | ||
| } | ||
|
|
||
| // Verify the menu structure that would be processed in lines 77-95 | ||
| for _, item := range menu { | ||
| // Simulate the options creation logic from lines 79-82 | ||
| options := make([]map[string]string, len(item.Options)) | ||
| for i, opt := range item.Options { | ||
| options[i] = map[string]string{"Key": opt, "Value": opt} | ||
| } | ||
|
|
||
| // Verify options are created correctly | ||
| assert.Len(t, options, len(item.Options)) | ||
| for i, opt := range item.Options { | ||
| assert.Equal(t, opt, options[i]["Key"]) | ||
| assert.Equal(t, opt, options[i]["Value"]) | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| t.Run("BanMenu mapping logic from answers to BanInfo", func(t *testing.T) { | ||
| // Test the switch statement logic that maps answers to BanInfo fields | ||
|
|
||
| testCases := []struct { | ||
| name string | ||
| answers map[string]string | ||
| expectedInfo *cmd.BanInfo | ||
| }{ | ||
| { | ||
| name: "all fields mapped correctly", | ||
| answers: map[string]string{ | ||
| "workload": "container", | ||
| "behavior": "malicious", | ||
| "detection": "automated", | ||
| }, | ||
| expectedInfo: &cmd.BanInfo{ | ||
| WorkloadType: "container", | ||
| BehaviorClassification: "malicious", | ||
| DetectionMechanism: "automated", | ||
| }, | ||
| }, | ||
| { | ||
| name: "partial mapping - only workload", | ||
| answers: map[string]string{ | ||
| "workload": "vm", | ||
| }, | ||
| expectedInfo: &cmd.BanInfo{ | ||
| WorkloadType: "vm", | ||
| BehaviorClassification: "", | ||
| DetectionMechanism: "", | ||
| }, | ||
| }, | ||
| { | ||
| name: "unknown kind ignored", | ||
| answers: map[string]string{ | ||
| "workload": "container", | ||
| "unknown": "should-be-ignored", | ||
| }, | ||
| expectedInfo: &cmd.BanInfo{ | ||
| WorkloadType: "container", | ||
| BehaviorClassification: "", | ||
| DetectionMechanism: "", | ||
| }, | ||
| }, | ||
| { | ||
| name: "empty answers", | ||
| answers: map[string]string{}, | ||
| expectedInfo: &cmd.BanInfo{ | ||
| WorkloadType: "", | ||
| BehaviorClassification: "", | ||
| DetectionMechanism: "", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| // Simulate the logic from lines 102-112 | ||
| banInfo := &cmd.BanInfo{} | ||
|
|
||
| for kind, answer := range tc.answers { | ||
| switch kind { | ||
| case "workload": | ||
| banInfo.WorkloadType = answer | ||
| case "behavior": | ||
| banInfo.BehaviorClassification = answer | ||
| case "detection": | ||
| banInfo.DetectionMechanism = answer | ||
| } | ||
| } | ||
|
|
||
| assert.Equal(t, tc.expectedInfo.WorkloadType, banInfo.WorkloadType) | ||
| assert.Equal(t, tc.expectedInfo.BehaviorClassification, banInfo.BehaviorClassification) | ||
| assert.Equal(t, tc.expectedInfo.DetectionMechanism, banInfo.DetectionMechanism) | ||
| }) | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
the same for these test cases, I don't see any "band command" logic that would be tested here.
We don't need to test the logic of json.Marshal per se - we expect that it works.
Also, we should not simulate the actual logic from the command - we should rather call the logic to test that it really works.
In other words, unit-tests should always test the "production" logic.
|
|
||
| // TestBanInteractiveModeWithValidConfigMap tests the complete interactive flow | ||
| func TestBanWithValidConfigMap(t *testing.T) { | ||
| t.Run("Ban function interactive mode output messages (line 167)", func(t *testing.T) { |
There was a problem hiding this comment.
this test name looks a bit weird.
| options[i] = huh.Option[string]{Key: opt, Value: opt} | ||
| } | ||
|
|
||
| form := huh.NewSelect[string](). |
There was a problem hiding this comment.
according to the huh README, it looks like it should be defined as part of the Form & Group:
https://github.com/charmbracelet/huh?tab=readme-ov-file#tutorial
| // This test will fail at the interactive part, but we can verify initial processing | ||
| // We expect it to get to the interactive menu and fail there | ||
|
|
||
| // when | ||
| err := cmd.Ban(ctx, userSignup.Name) | ||
|
|
||
| // then | ||
| // Should fail at the interactive part (huh.Select.Run()), but we can verify: |
There was a problem hiding this comment.
I see that you still have issues with testing the interactive mode. It's a bit tricky but there is a way to do that.
The problematic line is when it tries to "Run" the form:
form.Run()it returns an error.
The solution would be moving this "run" call into a parameter of the Ban command - something like this:
type runFormFunc func(form *huh.Form) error
func Ban(ctx *clicontext.CommandContext, runForm runFormFunc, args ...string) error {so it can be used:
RunE: func(cmd *cobra.Command, args []string) error {
term := ioutils.NewTerminal(cmd.InOrStdin, cmd.OutOrStdout)
ctx := clicontext.NewCommandContext(term, client.DefaultNewClient)
showForm := func(form *huh.Form) error {
if err := form.Run(); err != nil {
return fmt.Errorf("failed to show interactive menu: %w", err)
}
return nil
}
return Ban(ctx, showForm, args...)
},but in unit tests you can do something else. There are a bunch of other methods you can call on the form:
form.Init() // to init the form
form.View() // to return the form as the string (for example to be printed out in case of debugging)but what is more important, you can select between the options and fields via form.Update
form.Update(tea.KeyMsg{Type: tea.KeyDown}) // moves the selection one option down
form.Update(huh.NextField()) // goes to the next selection fieldnot sure if there is a better way of testing the huh forms. @xcoulon do you know?
…e TestBanWithValidConfigMap function
…ason if the configmap doesn't exist or is empty
…ing interactive menu
| if len(args) == 0 { | ||
| return fmt.Errorf("UserSignup name is required") | ||
| } |
There was a problem hiding this comment.
this is actually not needed - the validation is already done by Cobra when you defined
Args: cobra.ExactArgs(1),| if len(args) == 0 { | |
| return fmt.Errorf("UserSignup name is required") | |
| } |
| cfgMapContent, err := getValuesFromConfigMap(ctx) | ||
|
|
||
| if err != nil || len(cfgMapContent) == 0 { | ||
| fmt.Printf("failed to load reasons from ConfigMap: %s", err) |
There was a problem hiding this comment.
| fmt.Printf("failed to load reasons from ConfigMap: %s", err) | |
| if err != nil { | |
| ctx.Printlnf("failed to load reasons from ConfigMap %q: %s", configMapName, err) | |
| } else { | |
| ctx.Printlnf("the provided ConfigMap %q is empty", configMapName) | |
| } |
| //err := runForm(form) | ||
| err := runForm(form) | ||
| if err != nil { | ||
| return fmt.Errorf("banning option could not be obtained: %w", err) |
There was a problem hiding this comment.
| return fmt.Errorf("banning option could not be obtained: %w", err) | |
| return fmt.Errorf("ban reason could not be obtained: %w", err) |
| fmt.Printf("\nYour selection:\n") | ||
| for kind, optionSelected := range answers { | ||
| fmt.Printf("- %s:\t%s\n", kind, optionSelected) |
There was a problem hiding this comment.
let's use the ctx *clicontext.CommandContext for printing out messages as we do it at other places
| func TestMenuStruct(t *testing.T) { | ||
| t.Run("JSON unmarshaling works correctly", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[ | ||
| { | ||
| "kind": "workload", | ||
| "description": "Select workload type", | ||
| "options": ["container", "vm"] | ||
| }, | ||
| { | ||
| "kind": "behavior", | ||
| "description": "Select behavior classification", | ||
| "options": ["malicious", "suspicious"] | ||
| } | ||
| ]` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Len(t, menus, 2) | ||
|
|
||
| // Verify first menu | ||
| assert.Equal(t, "workload", menus[0].Kind) | ||
| assert.Equal(t, "Select workload type", menus[0].Description) | ||
| assert.Len(t, menus[0].Options, 2) | ||
| assert.Contains(t, menus[0].Options, "container") | ||
| assert.Contains(t, menus[0].Options, "vm") | ||
|
|
||
| // Verify second menu | ||
| assert.Equal(t, "behavior", menus[1].Kind) | ||
| assert.Equal(t, "Select behavior classification", menus[1].Description) | ||
| assert.Len(t, menus[1].Options, 2) | ||
| assert.Contains(t, menus[1].Options, "malicious") | ||
| assert.Contains(t, menus[1].Options, "suspicious") | ||
| }) | ||
|
|
||
| t.Run("empty JSON array unmarshals correctly", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[]` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Empty(t, menus) | ||
| }) | ||
|
|
||
| t.Run("malformed JSON returns error", func(t *testing.T) { | ||
| // given | ||
| jsonData := `[{invalid json` | ||
|
|
||
| // when | ||
| var menus []cmd.Menu | ||
| err := json.Unmarshal([]byte(jsonData), &menus) | ||
|
|
||
| // then | ||
| require.Error(t, err) | ||
| }) | ||
| } |
There was a problem hiding this comment.
as I mentioned in my previous review - these tests don't verify anything from the "ban command" logic, let's drop them.
| emptyConfigMap := &corev1.ConfigMap{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "ban-reason-config", | ||
| Namespace: test.HostOperatorNs, | ||
| }, | ||
| Data: map[string]string{ | ||
| "menu.json": "[]", // Empty array | ||
| }, | ||
| } |
There was a problem hiding this comment.
I think that the creation is used in the same way in the other tests, you could extract that to a helper function
func newBanReasonConfigMap(key, value string) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "ban-reason-config",
Namespace: test.HostOperatorNs,
},
Data: map[string]string{
key: value,
},
}
}and then use it:
emptyConfigMap := newBanReasonConfigMap("Menu.json", "[]") // Empty arrayand
configMapWithoutMenu := newBanReasonConfigMap("other-key", "some-value") // No Menu.json keyetc...
|
|
||
| func TestBanCmdInteractiveMode(t *testing.T) { | ||
| t.Run("interactive mode with ConfigMap present", func(t *testing.T) { | ||
| t.Skip("Skipping interactive test - requires actual terminal interaction") |
|
|
||
| // then | ||
| require.NoError(t, err) | ||
| assert.Contains(t, term.Output(), "Checking for available reasons from ConfigMap...") |
There was a problem hiding this comment.
with my proposed changes in the other comment, you can check the actual reason why the select mode wasn't used
| assert.Contains(t, term.Output(), "Checking for available reasons from ConfigMap...") | |
| assert.Contains(t, term.Output(), "the provided ConfigMap \"ban-reason-config\" is empty") |
MatousJobanek
left a comment
There was a problem hiding this comment.
Looks good. let's clean it up from all the zombies.
There is actually another thing to be updated - we need to add "read ConfigMap" permissions to the "ban-user" role here:
ksctl/resources/roles/host.yaml
Lines 111 to 134 in 66afa66
This template is used to generate the permissions based on ksctl-admins.yaml file as we have it in sandbox-sre
| ) | ||
|
|
||
| if err := runForm(form); err != nil { | ||
| return nil, err //fmt.Errorf("failed to show interactive menu: %w", err) |
There was a problem hiding this comment.
let's clean up all these zombies 👻
| return nil, err //fmt.Errorf("failed to show interactive menu: %w", err) | |
| return nil, err |
|
|
||
| } | ||
|
|
||
| //return answers, nil |
There was a problem hiding this comment.
| //return answers, nil |
|
|
||
| var menus []Menu | ||
| if menuJSON, exists := configMap.Data[menuKey]; exists && menuJSON != "" { | ||
| //var menus []Menu |
There was a problem hiding this comment.
| //var menus []Menu |
|
|
||
| ctx.Printlnf("\nYour selection:\n") | ||
| for kind, optionSelected := range answers { | ||
| fmt.Printf("- %s:\t%s\n", kind, optionSelected) |
There was a problem hiding this comment.
| fmt.Printf("- %s:\t%s\n", kind, optionSelected) | |
| ctx.Printf("- %s:\t%s\n", kind, optionSelected) |
| /* emptyConfigMap := &corev1.ConfigMap{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "ban-reason-config", | ||
| Namespace: test.HostOperatorNs, | ||
| }, | ||
| Data: map[string]string{ | ||
| "menu.json": "[]", // Empty array | ||
| }, | ||
| }*/ |
There was a problem hiding this comment.
🙃
| /* emptyConfigMap := &corev1.ConfigMap{ | |
| ObjectMeta: metav1.ObjectMeta{ | |
| Name: "ban-reason-config", | |
| Namespace: test.HostOperatorNs, | |
| }, | |
| Data: map[string]string{ | |
| "menu.json": "[]", // Empty array | |
| }, | |
| }*/ |
There was a problem hiding this comment.
and in other test-cases too
| err := cmd.Ban(ctx, func(form *huh.Form) error { | ||
| form.Init() | ||
| form.Update(banReasonInput) | ||
| // form.View() |
There was a problem hiding this comment.
👻
| // form.View() |
MatousJobanek
left a comment
There was a problem hiding this comment.
Nice 🥇 Great job 🥇 🚀
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #123 +/- ##
==========================================
+ Coverage 70.54% 70.74% +0.20%
==========================================
Files 56 56
Lines 4013 4116 +103
==========================================
+ Hits 2831 2912 +81
- Misses 958 974 +16
- Partials 224 230 +6
🚀 New features to boost your workflow:
|
See https://issues.redhat.com/browse/SANDBOX-1402