From 7481df0b4746f636952d4b02a9c9aaeb4160549c Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Wed, 21 Jan 2026 16:21:05 +0100 Subject: [PATCH 1/7] Add --fingerprint flag to list trails, and change endpoint the command points to --- cmd/kosli/listTrails.go | 13 +++++-------- cmd/kosli/listTrails_test.go | 14 ++++++++++++-- cmd/kosli/root.go | 2 ++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index 7860c5359..b8f47c991 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -50,7 +50,8 @@ kosli list trails \ type listTrailsOptions struct { listOptions - flowName string + flowName string + fingerprint string } type Trail struct { @@ -90,20 +91,16 @@ func newListTrailsCmd(out io.Writer) *cobra.Command { }, } - cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlag) + cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlagOptional) + cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", fingerprintInTrailsFlag) // We set the defauly page limit to 0 so that all results are returned if the flag is not provided addListFlags(cmd, &o.listOptions, 0) - err := RequireFlags(cmd, []string{"flow"}) - if err != nil { - logger.Error("failed to configure required flags: %v", err) - } - return cmd } func (o *listTrailsOptions) run(out io.Writer) error { - url := fmt.Sprintf("%s/api/v2/trails/%s/%s?per_page=%d&page=%d", global.Host, global.Org, o.flowName, o.pageLimit, o.pageNumber) + url := fmt.Sprintf("%s/api/v2/trails/%s/?flow_name=%s&fingerprint=%s&per_page=%d&page=%d", global.Host, global.Org, o.flowName, o.fingerprint, o.pageLimit, o.pageNumber) reqParams := &requests.RequestParams{ Method: http.MethodGet, diff --git a/cmd/kosli/listTrails_test.go b/cmd/kosli/listTrails_test.go index 81c6e700a..39380a156 100644 --- a/cmd/kosli/listTrails_test.go +++ b/cmd/kosli/listTrails_test.go @@ -13,6 +13,8 @@ import ( type ListTrailsCommandTestSuite struct { suite.Suite flowName string + trailName string + fingerprint string defaultKosliArguments string acmeOrgKosliArguments string } @@ -20,19 +22,23 @@ type ListTrailsCommandTestSuite struct { func (suite *ListTrailsCommandTestSuite) SetupTest() { global = &GlobalOpts{ ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", - Org: "docs-cmd-test-user", + Org: `docs-cmd-test-user`, Host: "http://localhost:8001", } suite.flowName = "list-trails" + suite.trailName = "trail-name" + suite.fingerprint = "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9" suite.defaultKosliArguments = fmt.Sprintf(" --flow %s --host %s --org %s --api-token %s", suite.flowName, global.Host, global.Org, global.ApiToken) CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) - BeginTrail("trail-name", suite.flowName, "", suite.Suite.T()) + BeginTrail(suite.trailName, suite.flowName, "", suite.Suite.T()) global.Org = "acme-org" global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) suite.acmeOrgKosliArguments = fmt.Sprintf(" --flow %s --host %s --org %s --api-token %s", suite.flowName, global.Host, global.Org, global.ApiToken) + + CreateArtifactOnTrail(suite.flowName, suite.trailName, "artifact", suite.fingerprint, "artifact-name", suite.Suite.T()) } func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { @@ -80,6 +86,10 @@ func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { cmd: fmt.Sprintf(`list trails --page-limit 15 --page 2 %s`, suite.defaultKosliArguments), golden: "", }, + { + name: "can list trails that contain an artifact with the provided fingerprint", + cmd: fmt.Sprintf(`list trails --fingerprint %s %s`, suite.fingerprint, suite.defaultKosliArguments), + }, } runTestCmd(suite.Suite.T(), tests) diff --git a/cmd/kosli/root.go b/cmd/kosli/root.go index 7bdaf9908..5b3c1d54f 100644 --- a/cmd/kosli/root.go +++ b/cmd/kosli/root.go @@ -91,6 +91,8 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file, debugFlag = "[optional] Print debug logs to stdout. A boolean flag https://docs.kosli.com/faq/#boolean-flags (default false)" artifactTypeFlag = "The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '--fingerprint' on commands that allow it)." flowNameFlag = "The Kosli flow name." + flowNameFlagOptional = "[optional] The Kosli flow name." + fingerprintInTrailsFlag = "[optional] The SHA256 fingerprint of the artifact to filter trails by." trailNameFlag = "The Kosli trail name." trailNameFlagOptional = "[optional] The Kosli trail name." templateArtifactName = "The name of the artifact in the yml template file." From d78557e8a6667f630d91b25efd9f799fa78bda2c Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Thu, 22 Jan 2026 15:53:54 +0100 Subject: [PATCH 2/7] Have list trails command query the new api endpoint --- cmd/kosli/listTrails.go | 24 ++++++++++++++++++--- cmd/kosli/listTrails_test.go | 41 ++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index b8f47c991..fd58f6293 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -94,13 +94,19 @@ func newListTrailsCmd(out io.Writer) *cobra.Command { cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlagOptional) cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", fingerprintInTrailsFlag) // We set the defauly page limit to 0 so that all results are returned if the flag is not provided - addListFlags(cmd, &o.listOptions, 0) + addListFlags(cmd, &o.listOptions, 20) return cmd } func (o *listTrailsOptions) run(out io.Writer) error { - url := fmt.Sprintf("%s/api/v2/trails/%s/?flow_name=%s&fingerprint=%s&per_page=%d&page=%d", global.Host, global.Org, o.flowName, o.fingerprint, o.pageLimit, o.pageNumber) + url := fmt.Sprintf("%s/api/v2/trails/%s?per_page=%d&page=%d", global.Host, global.Org, o.pageLimit, o.pageNumber) + if o.flowName != "" { + url += fmt.Sprintf("&flow=%s", o.flowName) + } + if o.fingerprint != "" { + url += fmt.Sprintf("&fingerprint=%s", o.fingerprint) + } reqParams := &requests.RequestParams{ Method: http.MethodGet, @@ -112,7 +118,19 @@ func (o *listTrailsOptions) run(out io.Writer) error { return err } - return output.FormattedPrint(response.Body, o.output, out, o.pageNumber, + // Extract only the "data" field from the response + var fullResponse listTrailsResponse + err = json.Unmarshal([]byte(response.Body), &fullResponse) + if err != nil { + return err + } + + dataOnly, err := json.Marshal(fullResponse.Data) + if err != nil { + return err + } + + return output.FormattedPrint(string(dataOnly), o.output, out, o.pageNumber, map[string]output.FormatOutputFunc{ "table": printTrailsListAsTable, "json": output.PrintJson, diff --git a/cmd/kosli/listTrails_test.go b/cmd/kosli/listTrails_test.go index 39380a156..b54f86bdb 100644 --- a/cmd/kosli/listTrails_test.go +++ b/cmd/kosli/listTrails_test.go @@ -29,66 +29,67 @@ func (suite *ListTrailsCommandTestSuite) SetupTest() { suite.flowName = "list-trails" suite.trailName = "trail-name" suite.fingerprint = "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9" - suite.defaultKosliArguments = fmt.Sprintf(" --flow %s --host %s --org %s --api-token %s", suite.flowName, global.Host, global.Org, global.ApiToken) + suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) BeginTrail(suite.trailName, suite.flowName, "", suite.Suite.T()) + CreateArtifactOnTrail(suite.flowName, suite.trailName, "artifact", suite.fingerprint, "artifact-name", suite.Suite.T()) global.Org = "acme-org" global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) - suite.acmeOrgKosliArguments = fmt.Sprintf(" --flow %s --host %s --org %s --api-token %s", suite.flowName, global.Host, global.Org, global.ApiToken) + suite.acmeOrgKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) - CreateArtifactOnTrail(suite.flowName, suite.trailName, "artifact", suite.fingerprint, "artifact-name", suite.Suite.T()) } func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { tests := []cmdTestCase{ { - name: "listing trails works when there are trails", - cmd: fmt.Sprintf(`list trails %s`, suite.defaultKosliArguments), + name: "1 listing trails works when there are trails", + cmd: fmt.Sprintf(`list trails --flow %s %s`, suite.flowName, suite.defaultKosliArguments), golden: "", }, { - name: "listing trails works when there are no trails", - cmd: fmt.Sprintf(`list trails %s`, suite.acmeOrgKosliArguments), + name: "2 listing trails works when there are no trails", + cmd: fmt.Sprintf(`list trails --flow %s %s`, suite.flowName, suite.acmeOrgKosliArguments), golden: "No trails were found.\n", }, { - name: "listing trails with --output json works when there are trails", - cmd: fmt.Sprintf(`list trails --output json %s`, suite.defaultKosliArguments), + name: "3 listing trails with --output json works when there are trails", + cmd: fmt.Sprintf(`list trails --flow %s --output json %s`, suite.flowName, suite.defaultKosliArguments), goldenJson: []jsonCheck{{"", "non-empty"}}, }, { - name: "listing trails with --output json works when there are no trails", - cmd: fmt.Sprintf(`list trails --output json %s`, suite.acmeOrgKosliArguments), + name: "4 listing trails with --output json works when there are no trails", + cmd: fmt.Sprintf(`list trails --flow %s --output json %s`, suite.flowName, suite.acmeOrgKosliArguments), goldenJson: []jsonCheck{{"", "[]"}}, }, { wantError: true, - name: "providing an argument causes an error", + name: "5 providing an argument causes an error", cmd: fmt.Sprintf(`list trails xxx %s`, suite.defaultKosliArguments), golden: "Error: unknown command \"xxx\" for \"kosli list trails\"\n", }, { wantError: true, - name: "negative page limit causes an error", - cmd: fmt.Sprintf(`list trails --page-limit -1 %s`, suite.defaultKosliArguments), + name: "6 negative page limit causes an error", + cmd: fmt.Sprintf(`list trails --flow %s --page-limit -1 %s`, suite.flowName, suite.defaultKosliArguments), golden: "Error: flag '--page-limit' has value '-1' which is illegal\n", }, { wantError: true, - name: "negative page number causes an error", - cmd: fmt.Sprintf(`list trails --page -1 %s`, suite.defaultKosliArguments), + name: "7 negative page number causes an error", + cmd: fmt.Sprintf(`list trails --flow %s --page -1 %s`, suite.flowName, suite.defaultKosliArguments), golden: "Error: flag '--page' has value '-1' which is illegal\n", }, { - name: "can list trails with pagination", - cmd: fmt.Sprintf(`list trails --page-limit 15 --page 2 %s`, suite.defaultKosliArguments), + name: "8 can list trails with pagination", + cmd: fmt.Sprintf(`list trails --flow %s --page-limit 15 --page 2 %s`, suite.flowName, suite.defaultKosliArguments), golden: "", }, { - name: "can list trails that contain an artifact with the provided fingerprint", - cmd: fmt.Sprintf(`list trails --fingerprint %s %s`, suite.fingerprint, suite.defaultKosliArguments), + name: "9 can list trails that contain an artifact with the provided fingerprint", + cmd: fmt.Sprintf(`list trails --fingerprint %s --output json %s`, suite.fingerprint, suite.defaultKosliArguments), + goldenJson: []jsonCheck{{"", "non-empty"}}, }, } From 5c2c3988052be41e5b0317d448bb1256278b3712 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Fri, 23 Jan 2026 10:35:45 +0100 Subject: [PATCH 3/7] Update command and tests to always receive json object containing data and pagination fields --- cmd/kosli/listTrails.go | 17 +---------------- cmd/kosli/listTrails_test.go | 6 +++--- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index fd58f6293..ad6a8f050 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -118,19 +118,7 @@ func (o *listTrailsOptions) run(out io.Writer) error { return err } - // Extract only the "data" field from the response - var fullResponse listTrailsResponse - err = json.Unmarshal([]byte(response.Body), &fullResponse) - if err != nil { - return err - } - - dataOnly, err := json.Marshal(fullResponse.Data) - if err != nil { - return err - } - - return output.FormattedPrint(string(dataOnly), o.output, out, o.pageNumber, + return output.FormattedPrint(response.Body, o.output, out, o.pageNumber, map[string]output.FormatOutputFunc{ "table": printTrailsListAsTable, "json": output.PrintJson, @@ -141,9 +129,6 @@ func printTrailsListAsTable(raw string, out io.Writer, page int) error { response := &listTrailsResponse{} trails := []Trail{} - // If using pagination, the response will have the format {data: [], pagination: {}} - // and therefore will not unmarshal into an array of Trail structs; instead, we need - // to unmarshal into a listTrailsResponse struct and extract the data field. err := json.Unmarshal([]byte(raw), &trails) if err != nil { err = json.Unmarshal([]byte(raw), &response) diff --git a/cmd/kosli/listTrails_test.go b/cmd/kosli/listTrails_test.go index b54f86bdb..f29a43672 100644 --- a/cmd/kosli/listTrails_test.go +++ b/cmd/kosli/listTrails_test.go @@ -56,12 +56,12 @@ func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { { name: "3 listing trails with --output json works when there are trails", cmd: fmt.Sprintf(`list trails --flow %s --output json %s`, suite.flowName, suite.defaultKosliArguments), - goldenJson: []jsonCheck{{"", "non-empty"}}, + goldenJson: []jsonCheck{{"data", "non-empty"}}, }, { name: "4 listing trails with --output json works when there are no trails", cmd: fmt.Sprintf(`list trails --flow %s --output json %s`, suite.flowName, suite.acmeOrgKosliArguments), - goldenJson: []jsonCheck{{"", "[]"}}, + goldenJson: []jsonCheck{{"data", "[]"}}, }, { wantError: true, @@ -89,7 +89,7 @@ func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { { name: "9 can list trails that contain an artifact with the provided fingerprint", cmd: fmt.Sprintf(`list trails --fingerprint %s --output json %s`, suite.fingerprint, suite.defaultKosliArguments), - goldenJson: []jsonCheck{{"", "non-empty"}}, + goldenJson: []jsonCheck{{"data", "non-empty"}}, }, } From b1be1e7568005c5a31c2451e6e71d5527c1e562b Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Fri, 23 Jan 2026 10:44:19 +0100 Subject: [PATCH 4/7] Update documentation for updated command --- cmd/kosli/listTrails.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index ad6a8f050..55ad5c89a 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -11,12 +11,9 @@ import ( "github.com/spf13/cobra" ) -const listTrailsShortDesc = `List Trails for a Flow in an org.` +const listTrailsShortDesc = `List Trails of an org.` -const listTrailsLongDesc = listTrailsShortDesc + `The results are ordered from latest to oldest. -If the ^page-limit^ flag is provided, the results will be paginated, otherwise all results will be -returned. -If ^page-limit^ is set to 0, all results will be returned.` +const listTrailsLongDesc = listTrailsShortDesc + `The list can be filtered by flow and artifact fingerprint. The results are paginated and ordered from latest to oldest.` const listTrailsExample = ` # list all trails for a flow: @@ -46,6 +43,13 @@ kosli list trails \ --api-token yourAPIToken \ --org yourOrgName \ --output json + +# list all trails across all flows that contain an artifact with the provided fingerprint (in JSON): +kosli list trails \ + --fingerprint yourArtifactFingerprint \ + --api-token yourAPIToken \ + --org yourOrgName \ + --output json \ ` type listTrailsOptions struct { From babf5dd940b13bde703495e459be54977dba577d Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Fri, 23 Jan 2026 11:00:15 +0100 Subject: [PATCH 5/7] Fix linting issue in test --- cmd/kosli/listTrails_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/kosli/listTrails_test.go b/cmd/kosli/listTrails_test.go index 3ac43cf3c..839514f3a 100644 --- a/cmd/kosli/listTrails_test.go +++ b/cmd/kosli/listTrails_test.go @@ -30,13 +30,13 @@ func (suite *ListTrailsCommandTestSuite) SetupTest() { suite.trailName = "trail-name" suite.fingerprint = "7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9" suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) - CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) - BeginTrail(suite.trailName, suite.flowName, "", suite.Suite.T()) - CreateArtifactOnTrail(suite.flowName, suite.trailName, "artifact", suite.fingerprint, "artifact-name", suite.Suite.T()) + CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.T()) + BeginTrail(suite.trailName, suite.flowName, "", suite.T()) + CreateArtifactOnTrail(suite.flowName, suite.trailName, "artifact", suite.fingerprint, "artifact-name", suite.T()) global.Org = "acme-org" global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" - CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) + CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.T()) suite.acmeOrgKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) } From e4242897af3f79567b3c137b760b74cd1d686b93 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Fri, 23 Jan 2026 13:50:26 +0100 Subject: [PATCH 6/7] Remove unnecessary check of response format, since it is now always formatted the same --- cmd/kosli/listTrails.go | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index 55ad5c89a..5049ff181 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "io" "net/http" @@ -16,20 +15,20 @@ const listTrailsShortDesc = `List Trails of an org.` const listTrailsLongDesc = listTrailsShortDesc + `The list can be filtered by flow and artifact fingerprint. The results are paginated and ordered from latest to oldest.` const listTrailsExample = ` -# list all trails for a flow: +# get a paginated list of trails for a flow: kosli list trails \ --flow yourFlowName \ --api-token yourAPIToken \ --org yourOrgName -#list the most recent 30 trails for a flow: +# list the most recent 30 trails for a flow: kosli list trails \ --flow yourFlowName \ --page-limit 30 \ --api-token yourAPIToken \ --org yourOrgName -#show the second page of trails for a flow: +# show the second page of trails for a flow: kosli list trails \ --flow yourFlowName \ --page-limit 30 \ @@ -37,14 +36,14 @@ kosli list trails \ --api-token yourAPIToken \ --org yourOrgName -# list all trails for a flow (in JSON): +# get a paginated list of trails for a flow (in JSON): kosli list trails \ --flow yourFlowName \ --api-token yourAPIToken \ --org yourOrgName \ --output json -# list all trails across all flows that contain an artifact with the provided fingerprint (in JSON): +# get a paginated list of trails across all flows that contain an artifact with the provided fingerprint (in JSON): kosli list trails \ --fingerprint yourArtifactFingerprint \ --api-token yourAPIToken \ @@ -97,7 +96,6 @@ func newListTrailsCmd(out io.Writer) *cobra.Command { cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlagOptional) cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", fingerprintInTrailsFlag) - // We set the defauly page limit to 0 so that all results are returned if the flag is not provided addListFlags(cmd, &o.listOptions, 20) return cmd @@ -133,15 +131,6 @@ func printTrailsListAsTable(raw string, out io.Writer, page int) error { response := &listTrailsResponse{} trails := []Trail{} - err := json.Unmarshal([]byte(raw), &trails) - if err != nil { - err = json.Unmarshal([]byte(raw), &response) - if err != nil { - return err - } - trails = response.Data - } - if len(trails) == 0 { msg := "No trails were found" if page != 1 { @@ -157,11 +146,9 @@ func printTrailsListAsTable(raw string, out io.Writer, page int) error { row := fmt.Sprintf("%s\t%s\t%s", trail.Name, trail.Description, trail.ComplianceState) rows = append(rows, row) } - if len(response.Data) > 0 { - pagination := response.Pagination - paginationInfo := fmt.Sprintf("\nShowing page %.0f of %.0f, total %.0f items", pagination.Page, pagination.PageCount, pagination.Total) - rows = append(rows, paginationInfo) - } + pagination := response.Pagination + paginationInfo := fmt.Sprintf("\nShowing page %.0f of %.0f, total %.0f items", pagination.Page, pagination.PageCount, pagination.Total) + rows = append(rows, paginationInfo) tabFormattedPrint(out, header, rows) From 729d4c06e4702ea945da1e5f8e46230a0354f900 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 26 Jan 2026 09:29:37 +0100 Subject: [PATCH 7/7] Fix text response of list trails, and add goldenfile to test to ensure output is correct --- cmd/kosli/listTrails.go | 7 ++++++- cmd/kosli/listTrails_test.go | 6 +++--- cmd/kosli/testdata/output/list/list-trails.txt | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 cmd/kosli/testdata/output/list/list-trails.txt diff --git a/cmd/kosli/listTrails.go b/cmd/kosli/listTrails.go index 5049ff181..215d0683e 100644 --- a/cmd/kosli/listTrails.go +++ b/cmd/kosli/listTrails.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io" "net/http" @@ -129,7 +130,11 @@ func (o *listTrailsOptions) run(out io.Writer) error { func printTrailsListAsTable(raw string, out io.Writer, page int) error { response := &listTrailsResponse{} - trails := []Trail{} + err := json.Unmarshal([]byte(raw), response) + if err != nil { + return err + } + trails := response.Data if len(trails) == 0 { msg := "No trails were found" diff --git a/cmd/kosli/listTrails_test.go b/cmd/kosli/listTrails_test.go index 839514f3a..06c5ec079 100644 --- a/cmd/kosli/listTrails_test.go +++ b/cmd/kosli/listTrails_test.go @@ -44,9 +44,9 @@ func (suite *ListTrailsCommandTestSuite) SetupTest() { func (suite *ListTrailsCommandTestSuite) TestListTrailsCmd() { tests := []cmdTestCase{ { - name: "1 listing trails works when there are trails", - cmd: fmt.Sprintf(`list trails --flow %s %s`, suite.flowName, suite.defaultKosliArguments), - golden: "", + name: "1 listing trails works when there are trails", + cmd: fmt.Sprintf(`list trails --flow %s %s`, suite.flowName, suite.defaultKosliArguments), + goldenFile: "output/list/list-trails.txt", }, { name: "2 listing trails works when there are no trails", diff --git a/cmd/kosli/testdata/output/list/list-trails.txt b/cmd/kosli/testdata/output/list/list-trails.txt new file mode 100644 index 000000000..b5527f3cf --- /dev/null +++ b/cmd/kosli/testdata/output/list/list-trails.txt @@ -0,0 +1,4 @@ +NAME DESCRIPTION COMPLIANCE +trail-name test trail INCOMPLETE + +Showing page 1 of 1, total 1 items