From 46864f8ab52ba43c03ead946b4d6618c4f98453b Mon Sep 17 00:00:00 2001 From: Marko Bevc Date: Thu, 9 Jul 2026 18:58:25 +0100 Subject: [PATCH 1/3] feat: add `--sort-direction` flag to `list controls` Aligns the command with the new list-controls API spec, which adds a sort_direction query parameter (asc/desc, server default asc). The flag follows the existing kosli list environments pattern: empty default, only sent when set. --- cmd/kosli/listControls.go | 17 ++++++++++++++--- cmd/kosli/listControls_test.go | 10 ++++++++++ cmd/kosli/root.go | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cmd/kosli/listControls.go b/cmd/kosli/listControls.go index 6a1f2bc5c..45789da68 100644 --- a/cmd/kosli/listControls.go +++ b/cmd/kosli/listControls.go @@ -49,13 +49,20 @@ kosli list controls \ --archived \ --api-token yourAPIToken \ --org yourOrgName + +# list controls sorted in descending name order: +kosli list controls \ + --sort-direction desc \ + --api-token yourAPIToken \ + --org yourOrgName ` type listControlsOptions struct { listOptions - search string - tags []string - archived bool + search string + tags []string + archived bool + sortDirection string } type listControlsResponse struct { @@ -90,6 +97,7 @@ func newListControlsCmd(out io.Writer) *cobra.Command { cmd.Flags().StringVar(&o.search, "search", "", controlSearchFlag) cmd.Flags().StringArrayVar(&o.tags, "tag", []string{}, controlTagFlag) cmd.Flags().BoolVar(&o.archived, "archived", false, controlArchivedFlag) + cmd.Flags().StringVar(&o.sortDirection, "sort-direction", "", controlSortDirectionFlag) return cmd } @@ -112,6 +120,9 @@ func (o *listControlsOptions) run(out io.Writer) error { if o.archived { params.Set("archived", "true") } + if o.sortDirection != "" { + params.Set("sort_direction", o.sortDirection) + } reqURL := base + "?" + params.Encode() reqParams := &requests.RequestParams{ diff --git a/cmd/kosli/listControls_test.go b/cmd/kosli/listControls_test.go index 347e803b0..9659d5d03 100644 --- a/cmd/kosli/listControls_test.go +++ b/cmd/kosli/listControls_test.go @@ -100,6 +100,16 @@ func (suite *ListControlsCommandTestSuite) TestListControlsCmd() { cmd: "list controls --tag slice2b:tagtest --output json" + suite.defaultKosliArguments, goldenJson: []jsonCheck{{"controls", "length:1"}, {"controls.[0].identifier", "tagged-control"}}, }, + { + name: "--sort-direction asc returns controls in ascending name order", + cmd: "list controls --search list-control --sort-direction asc --output json" + suite.defaultKosliArguments, + goldenJson: []jsonCheck{{"controls", "length:2"}, {"controls.[0].identifier", "list-control-1"}, {"controls.[1].identifier", "list-control-2"}}, + }, + { + name: "--sort-direction desc returns controls in descending name order", + cmd: "list controls --search list-control --sort-direction desc --output json" + suite.defaultKosliArguments, + goldenJson: []jsonCheck{{"controls", "length:2"}, {"controls.[0].identifier", "list-control-2"}, {"controls.[1].identifier", "list-control-1"}}, + }, { name: "archived controls are excluded by default", cmd: "list controls --search archived-control --output json" + suite.defaultKosliArguments, diff --git a/cmd/kosli/root.go b/cmd/kosli/root.go index 2d278e7b1..21043a49a 100644 --- a/cmd/kosli/root.go +++ b/cmd/kosli/root.go @@ -305,6 +305,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file, controlSearchFlag = "[optional] Only list controls whose name or identifier contains this substring (case-insensitive)." controlTagFlag = "[optional] Filter by tag, given as 'key' or 'key:value'. Can be repeated." controlArchivedFlag = "[optional] List archived controls instead of active ones." + controlSortDirectionFlag = "[optional] The direction to sort controls in. Valid values are: [asc, desc]. (defaults to asc)" envNameFlag = "The Kosli environment name to assert the artifact against." pathsWatchFlag = "[optional] Watch the filesystem for changes and report snapshots of artifacts running in specific filesystem paths to Kosli." getAttestationFingerprintFlag = "[conditional] The fingerprint of the artifact for the attestation. Cannot be used together with --trail or --attestation-id." From ba24322a9025d3a272862cfb15ebfba6ada13840 Mon Sep 17 00:00:00 2001 From: Marko Bevc Date: Thu, 9 Jul 2026 19:13:39 +0100 Subject: [PATCH 2/3] chore: improve list controls ordering testing --- cmd/kosli/listControls_test.go | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/cmd/kosli/listControls_test.go b/cmd/kosli/listControls_test.go index 9659d5d03..647a6c78f 100644 --- a/cmd/kosli/listControls_test.go +++ b/cmd/kosli/listControls_test.go @@ -1,9 +1,11 @@ package main import ( + "encoding/json" "fmt" "testing" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -100,16 +102,6 @@ func (suite *ListControlsCommandTestSuite) TestListControlsCmd() { cmd: "list controls --tag slice2b:tagtest --output json" + suite.defaultKosliArguments, goldenJson: []jsonCheck{{"controls", "length:1"}, {"controls.[0].identifier", "tagged-control"}}, }, - { - name: "--sort-direction asc returns controls in ascending name order", - cmd: "list controls --search list-control --sort-direction asc --output json" + suite.defaultKosliArguments, - goldenJson: []jsonCheck{{"controls", "length:2"}, {"controls.[0].identifier", "list-control-1"}, {"controls.[1].identifier", "list-control-2"}}, - }, - { - name: "--sort-direction desc returns controls in descending name order", - cmd: "list controls --search list-control --sort-direction desc --output json" + suite.defaultKosliArguments, - goldenJson: []jsonCheck{{"controls", "length:2"}, {"controls.[0].identifier", "list-control-2"}, {"controls.[1].identifier", "list-control-1"}}, - }, { name: "archived controls are excluded by default", cmd: "list controls --search archived-control --output json" + suite.defaultKosliArguments, @@ -125,6 +117,28 @@ func (suite *ListControlsCommandTestSuite) TestListControlsCmd() { runTestCmd(suite.T(), tests) } +// asserts both directions against each other rather than each against the +// server default (asc): if the flag were silently dropped, both runs would +// return the default order and the reversal below would fail. +func (suite *ListControlsCommandTestSuite) TestListControlsSortDirectionIsHonored() { + identifiers := func(direction string) []string { + cmd := "list controls --search list-control --sort-direction " + direction + + " --output json" + suite.defaultKosliArguments + _, combined, _, _, err := executeCommandC(cmd) + require.NoError(suite.T(), err) + var response listControlsResponse + require.NoError(suite.T(), json.Unmarshal([]byte(combined), &response)) + ids := []string{} + for _, control := range response.Controls { + ids = append(ids, control["identifier"].(string)) + } + return ids + } + + require.Equal(suite.T(), []string{"list-control-1", "list-control-2"}, identifiers("asc")) + require.Equal(suite.T(), []string{"list-control-2", "list-control-1"}, identifiers("desc")) +} + func TestListControlsCommandTestSuite(t *testing.T) { suite.Run(t, new(ListControlsCommandTestSuite)) } From 751513a6b876d73d0ad25dcc07f224059fa77119 Mon Sep 17 00:00:00 2001 From: Marko Bevc Date: Thu, 9 Jul 2026 22:43:02 +0100 Subject: [PATCH 3/3] fix: improve list controls testing --- cmd/kosli/listControls_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kosli/listControls_test.go b/cmd/kosli/listControls_test.go index 647a6c78f..00d28a7a5 100644 --- a/cmd/kosli/listControls_test.go +++ b/cmd/kosli/listControls_test.go @@ -15,7 +15,7 @@ type ListControlsCommandTestSuite struct { acmeOrgKosliArguments string } -func (suite *ListControlsCommandTestSuite) SetupTest() { +func (suite *ListControlsCommandTestSuite) SetupSuite() { global = &GlobalOpts{ ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", Org: "docs-cmd-test-user",