From 7d2d2c095458d23f1179c2448a417cf976052bb8 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:35:37 +0200 Subject: [PATCH 1/6] feat: Add integration tests for API key lifecycle and update workflows --- .github/workflows/integration-test.yml | 37 ++++++ .github/workflows/tag-release.yml | 5 + Makefile | 4 + cli/cmd/api_key_integration_test.go | 170 +++++++++++++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 .github/workflows/integration-test.yml create mode 100644 cli/cmd/api_key_integration_test.go diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000..2327eda2 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,37 @@ +name: Integration Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_call: + +jobs: + integration-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Run Integration Tests + env: + OMS_PORTAL_API_KEY: ${{ secrets.OMS_PORTAL_API_KEY }} + OMS_PORTAL_API: ${{ secrets.OMS_PORTAL_API }} + run: make test-integration + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: integration-test-results + path: | + **/*test*.xml + **/*test*.json + retention-days: 30 diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index d17de3a6..95331ff8 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -6,8 +6,13 @@ on: - main jobs: + integration-tests: + uses: ./.github/workflows/integration-test.yml + secrets: inherit + tag: runs-on: ubuntu-latest + needs: integration-tests steps: - uses: actions/checkout@v4 with: diff --git a/Makefile b/Makefile index 23663575..f4361519 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,10 @@ test-cli: # -count=1 to disable caching test results go test -count=1 -v ./cli/... +test-integration: + # Run integration tests with build tag + go test -count=1 -v -tags=integration ./cli/... + test-service: go test -count=1 -v ./service/... diff --git a/cli/cmd/api_key_integration_test.go b/cli/cmd/api_key_integration_test.go new file mode 100644 index 00000000..61548279 --- /dev/null +++ b/cli/cmd/api_key_integration_test.go @@ -0,0 +1,170 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +//go:build integration +// +build integration + +package cmd_test + +import ( + "fmt" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/codesphere-cloud/oms/cli/cmd" + "github.com/codesphere-cloud/oms/internal/portal" +) + +var _ = Describe("API Key Integration Tests", func() { + var ( + portalClient portal.Portal + testOwner string + testOrg string + testRole string + registeredKey *portal.ApiKey + expiresAt time.Time + extendedExpiry time.Time + ) + + BeforeEach(func() { + apiKey := os.Getenv("OMS_PORTAL_API_KEY") + Expect(apiKey).NotTo(BeEmpty(), "OMS_PORTAL_API_KEY must be set for integration tests") + + apiURL := os.Getenv("OMS_PORTAL_API") + Expect(apiURL).NotTo(BeEmpty(), "OMS_PORTAL_API must be set for integration tests") + + portalClient = portal.NewPortalClient() + + // test data + testOwner = fmt.Sprintf("integration-test-%d@test.com", time.Now().Unix()) + testOrg = "IntegrationTestOrg" + testRole = "Ext" + expiresAt = time.Now().Add(24 * time.Hour) + extendedExpiry = time.Now().Add(48 * time.Hour) + }) + + Describe("Complete API Key Flow", func() { + It("should successfully complete the full API key lifecycle", func() { + By("Registering a new customer API key") + registerCmd := cmd.RegisterCmd{ + Opts: cmd.RegisterOpts{ + Owner: testOwner, + Organization: testOrg, + Role: testRole, + ExpiresAt: expiresAt.Format(time.RFC3339), + }, + } + + err := registerCmd.Register(portalClient) + Expect(err).To(BeNil(), "API key registration should succeed") + + By("Listing API keys to get the newly registered key") + keys, err := portalClient.ListAPIKeys() + Expect(err).To(BeNil(), "Listing API keys should succeed") + Expect(keys).NotTo(BeEmpty(), "Should have at least one API key") + + // Find the new key + for i := range keys { + if keys[i].Owner == testOwner { + registeredKey = &keys[i] + break + } + } + Expect(registeredKey).NotTo(BeNil(), "Should find the registered API key") + Expect(registeredKey.Owner).To(Equal(testOwner)) + Expect(registeredKey.Organization).To(Equal(testOrg)) + Expect(registeredKey.Role).To(Equal(testRole)) + + By("Ensuring the customer can see builds") + // This test uses the admin API key from env + builds, err := portalClient.ListBuilds(portal.CodesphereProduct) + Expect(err).To(BeNil(), "Listing builds should succeed") + Expect(builds.Builds).NotTo(BeEmpty(), "Should have at least one build available") + + By("Extending the API Key to a future date") + updateCmd := cmd.UpdateAPIKeyCmd{ + Opts: cmd.UpdateAPIKeyOpts{ + APIKeyID: registeredKey.KeyID, + ExpiresAtStr: extendedExpiry.Format(time.RFC3339), + }, + } + + err = updateCmd.UpdateAPIKey(portalClient) + Expect(err).To(BeNil(), "API key update should succeed") + + By("Verifying the API key was updated") + keys, err = portalClient.ListAPIKeys() + Expect(err).To(BeNil(), "Listing API keys should succeed") + + // Find the updated key + var updatedKey *portal.ApiKey + for i := range keys { + if keys[i].KeyID == registeredKey.KeyID { + updatedKey = &keys[i] + break + } + } + Expect(updatedKey).NotTo(BeNil(), "Should find the updated API key") + Expect(updatedKey.ExpiresAt).To(BeTemporally("~", extendedExpiry, 5*time.Second)) + + By("Revoking the API Key") + revokeCmd := cmd.RevokeAPIKeyCmd{ + Opts: cmd.RevokeAPIKeyOpts{ + ID: registeredKey.KeyID, + }, + } + + err = revokeCmd.Revoke(portalClient) + Expect(err).To(BeNil(), "API key revocation should succeed") + + By("Ensuring the API Key is not valid anymore") + keys, err = portalClient.ListAPIKeys() + Expect(err).To(BeNil(), "Listing API keys should succeed") + + // The key should be no longer in the list + keyFound := false + for i := range keys { + if keys[i].KeyID == registeredKey.KeyID { + keyFound = true + break + } + } + Expect(keyFound).To(BeFalse(), "Revoked API key should not be in the list") + }) + }) + + Describe("API Key Registration Edge Cases", func() { + It("should handle registration with invalid expiration date", func() { + registerCmd := cmd.RegisterCmd{ + Opts: cmd.RegisterOpts{ + Owner: testOwner, + Organization: testOrg, + Role: testRole, + ExpiresAt: "invalid-date", + }, + } + + err := registerCmd.Register(portalClient) + Expect(err).NotTo(BeNil(), "Should fail with invalid date format") + Expect(err.Error()).To(ContainSubstring("failed to parse expiration date")) + }) + }) + + Describe("API Key Update With Wrong Input", func() { + It("should handle update with invalid date format", func() { + updateCmd := cmd.UpdateAPIKeyCmd{ + Opts: cmd.UpdateAPIKeyOpts{ + APIKeyID: "test-key-id", + ExpiresAtStr: "invalid-date", + }, + } + + err := updateCmd.UpdateAPIKey(portalClient) + Expect(err).NotTo(BeNil(), "Should fail with invalid date format") + Expect(err.Error()).To(ContainSubstring("invalid date format")) + }) + }) +}) From 518eefa078672ca5abde07e594f5b5538679012c Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:32:54 +0200 Subject: [PATCH 2/6] feat: Enhance API key registration and integration tests --- cli/cmd/api_key_integration_test.go | 113 +++++++++++++++++++++------ cli/cmd/api_key_test_helpers_test.go | 41 ++++++++++ cli/cmd/download_package.go | 2 +- cli/cmd/register.go | 13 +-- cli/cmd/register_test.go | 13 +-- internal/portal/http.go | 12 +-- internal/portal/mocks.go | 27 +++++-- internal/tmpl/tmpl_suite_test.go | 2 +- internal/util/mocks.go | 3 +- 9 files changed, 174 insertions(+), 52 deletions(-) create mode 100644 cli/cmd/api_key_test_helpers_test.go diff --git a/cli/cmd/api_key_integration_test.go b/cli/cmd/api_key_integration_test.go index 61548279..29dd22ca 100644 --- a/cli/cmd/api_key_integration_test.go +++ b/cli/cmd/api_key_integration_test.go @@ -20,23 +20,28 @@ import ( var _ = Describe("API Key Integration Tests", func() { var ( - portalClient portal.Portal - testOwner string - testOrg string - testRole string - registeredKey *portal.ApiKey - expiresAt time.Time - extendedExpiry time.Time + portalClient portal.Portal + testOwner string + testOrg string + testRole string + registeredKey *portal.ApiKey + originalAdminKey string + expiresAt time.Time + extendedExpiry time.Time ) BeforeEach(func() { apiKey := os.Getenv("OMS_PORTAL_API_KEY") - Expect(apiKey).NotTo(BeEmpty(), "OMS_PORTAL_API_KEY must be set for integration tests") - apiURL := os.Getenv("OMS_PORTAL_API") - Expect(apiURL).NotTo(BeEmpty(), "OMS_PORTAL_API must be set for integration tests") + if apiKey == "" || apiURL == "" { + Skip("Integration tests require OMS_PORTAL_API_KEY and OMS_PORTAL_API environment variables") + } + + originalAdminKey = apiKey portalClient = portal.NewPortalClient() + // test env wrapper + portalClient.(*portal.PortalClient).Env = NewTestEnv(apiKey, os.Getenv("OMS_PORTAL_API"), "") // test data testOwner = fmt.Sprintf("integration-test-%d@test.com", time.Now().Unix()) @@ -46,6 +51,43 @@ var _ = Describe("API Key Integration Tests", func() { extendedExpiry = time.Now().Add(48 * time.Hour) }) + Describe("Standalone created-key behavior", func() { + It("created API key can list builds when used", func() { + registerCmd := cmd.RegisterCmd{ + Opts: cmd.RegisterOpts{ + Owner: fmt.Sprintf("standalone-test-%d@test.com", time.Now().Unix()), + Organization: "StandaloneTestOrg", + Role: "Ext", + ExpiresAt: time.Now().Add(1 * time.Hour).Format(time.RFC3339), + }, + } + + newKey, err := registerCmd.Register(portalClient) + Expect(err).To(BeNil(), "API key registration should succeed") + Expect(newKey).NotTo(BeNil(), "Register should return the created API key") + + keys, err := portalClient.ListAPIKeys() + Expect(err).To(BeNil(), "Listing API keys should succeed") + + var created *portal.ApiKey + for i := range keys { + if keys[i].Owner == registerCmd.Opts.Owner { + created = &keys[i] + break + } + } + Expect(created).NotTo(BeNil(), "Should find the created API key") + Expect(newKey.ApiKey).NotTo(BeEmpty(), "Created API key must include secret value") + + client := portal.NewPortalClient() + client.Env = NewTestEnv(newKey.ApiKey, os.Getenv("OMS_PORTAL_API"), "") + + builds, err := client.ListBuilds(portal.CodesphereProduct) + Expect(err).To(BeNil(), "Listing builds with created key should succeed") + Expect(builds.Builds).NotTo(BeEmpty(), "Created key should be able to see builds") + }) + }) + Describe("Complete API Key Flow", func() { It("should successfully complete the full API key lifecycle", func() { By("Registering a new customer API key") @@ -58,8 +100,9 @@ var _ = Describe("API Key Integration Tests", func() { }, } - err := registerCmd.Register(portalClient) + newKey, err := registerCmd.Register(portalClient) Expect(err).To(BeNil(), "API key registration should succeed") + Expect(newKey).NotTo(BeNil(), "Register should return the created API key") By("Listing API keys to get the newly registered key") keys, err := portalClient.ListAPIKeys() @@ -79,11 +122,19 @@ var _ = Describe("API Key Integration Tests", func() { Expect(registeredKey.Role).To(Equal(testRole)) By("Ensuring the customer can see builds") - // This test uses the admin API key from env - builds, err := portalClient.ListBuilds(portal.CodesphereProduct) - Expect(err).To(BeNil(), "Listing builds should succeed") + Expect(newKey.ApiKey).NotTo(BeEmpty(), "Registered key must include the API key value") + + p := portal.NewPortalClient() + // switch to the new key + p.Env = NewTestEnv(newKey.ApiKey, os.Getenv("OMS_PORTAL_API"), "") + + builds, err := p.ListBuilds(portal.CodesphereProduct) + Expect(err).To(BeNil(), "Listing builds with new key should succeed") Expect(builds.Builds).NotTo(BeEmpty(), "Should have at least one build available") + // restore admin key + portalClient.(*portal.PortalClient).Env = NewTestEnv(originalAdminKey, os.Getenv("OMS_PORTAL_API"), "") + By("Extending the API Key to a future date") updateCmd := cmd.UpdateAPIKeyCmd{ Opts: cmd.UpdateAPIKeyOpts{ @@ -121,18 +172,34 @@ var _ = Describe("API Key Integration Tests", func() { Expect(err).To(BeNil(), "API key revocation should succeed") By("Ensuring the API Key is not valid anymore") - keys, err = portalClient.ListAPIKeys() - Expect(err).To(BeNil(), "Listing API keys should succeed") - // The key should be no longer in the list - keyFound := false - for i := range keys { - if keys[i].KeyID == registeredKey.KeyID { - keyFound = true + keyFound := true + for attempt := 0; attempt < 5; attempt++ { + keys, err = portalClient.ListAPIKeys() + Expect(err).To(BeNil(), "Listing API keys should succeed") + + keyFound = false + for i := range keys { + if keys[i].KeyID == registeredKey.KeyID { + keyFound = true + break + } + } + + if !keyFound { break } + time.Sleep(1 * time.Second) + } + + if keyFound { + revokedClient := portal.NewPortalClient() + revokedClient.Env = NewTestEnv(newKey.ApiKey, os.Getenv("OMS_PORTAL_API"), "") + _, useErr := revokedClient.ListBuilds(portal.CodesphereProduct) + Expect(useErr).NotTo(BeNil(), "Using a revoked API key should fail") + } else { + Expect(keyFound).To(BeFalse(), "Revoked API key should not be in the list") } - Expect(keyFound).To(BeFalse(), "Revoked API key should not be in the list") }) }) @@ -147,7 +214,7 @@ var _ = Describe("API Key Integration Tests", func() { }, } - err := registerCmd.Register(portalClient) + _, err := registerCmd.Register(portalClient) Expect(err).NotTo(BeNil(), "Should fail with invalid date format") Expect(err.Error()).To(ContainSubstring("failed to parse expiration date")) }) diff --git a/cli/cmd/api_key_test_helpers_test.go b/cli/cmd/api_key_test_helpers_test.go new file mode 100644 index 00000000..e2e8c277 --- /dev/null +++ b/cli/cmd/api_key_test_helpers_test.go @@ -0,0 +1,41 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +//go:build integration +// +build integration + +package cmd_test + +import "errors" + +// interface for tests and allows injecting a specific API key and API URL without modifying process env +type testEnv struct { + apiKey string + apiURL string + workdir string +} + +func NewTestEnv(apiKey, apiURL, workdir string) *testEnv { + return &testEnv{apiKey: apiKey, apiURL: apiURL, workdir: workdir} +} + +func (e *testEnv) GetOmsPortalApiKey() (string, error) { + if e.apiKey == "" { + return "", errors.New("OMS_PORTAL_API_KEY not set in test env") + } + return e.apiKey, nil +} + +func (e *testEnv) GetOmsPortalApi() string { + if e.apiURL == "" { + return "https://oms-portal.codesphere.com/api" + } + return e.apiURL +} + +func (e *testEnv) GetOmsWorkdir() string { + if e.workdir == "" { + return "./oms-workdir" + } + return e.workdir +} diff --git a/cli/cmd/download_package.go b/cli/cmd/download_package.go index 7f5839f8..28fe142b 100644 --- a/cli/cmd/download_package.go +++ b/cli/cmd/download_package.go @@ -29,7 +29,7 @@ type DownloadPackageOpts struct { } func (c *DownloadPackageCmd) RunE(_ *cobra.Command, args []string) error { - if c.Opts.Hash != "" { + if c.Opts.Hash != "" { log.Printf("Downloading package '%s' with hash '%s'\n", c.Opts.Version, c.Opts.Hash) } else { log.Printf("Downloading package '%s'\n", c.Opts.Version) diff --git a/cli/cmd/register.go b/cli/cmd/register.go index 93750cd1..f810a2d7 100644 --- a/cli/cmd/register.go +++ b/cli/cmd/register.go @@ -27,25 +27,26 @@ type RegisterOpts struct { func (c *RegisterCmd) RunE(_ *cobra.Command, args []string) error { p := portal.NewPortalClient() - return c.Register(p) + _, err := c.Register(p) + return err } -func (c *RegisterCmd) Register(p portal.Portal) error { +func (c *RegisterCmd) Register(p portal.Portal) (*portal.ApiKey, error) { var err error var expiresAt time.Time if c.Opts.ExpiresAt != "" { expiresAt, err = time.Parse(time.RFC3339, c.Opts.ExpiresAt) if err != nil { - return fmt.Errorf("failed to parse expiration date: %w", err) + return nil, fmt.Errorf("failed to parse expiration date: %w", err) } } - err = p.RegisterAPIKey(c.Opts.Owner, c.Opts.Organization, c.Opts.Role, expiresAt) + newKey, err := p.RegisterAPIKey(c.Opts.Owner, c.Opts.Organization, c.Opts.Role, expiresAt) if err != nil { - return fmt.Errorf("failed to register API key: %w", err) + return nil, fmt.Errorf("failed to register API key: %w", err) } - return nil + return newKey, nil } func AddRegisterCmd(list *cobra.Command, opts GlobalOptions) { diff --git a/cli/cmd/register_test.go b/cli/cmd/register_test.go index 05c31a39..11713077 100644 --- a/cli/cmd/register_test.go +++ b/cli/cmd/register_test.go @@ -44,15 +44,17 @@ var _ = Describe("RegisterCmd", func() { Context("when expiration date is valid", func() { It("registers the API key successfully", func() { parsedTime, _ := time.Parse(time.RFC3339, expiresAt) - mockPortal.EXPECT().RegisterAPIKey(owner, organization, role, parsedTime).Return(nil) - err := c.Register(mockPortal) + mockPortal.EXPECT().RegisterAPIKey(owner, organization, role, parsedTime).Return(&portal.ApiKey{}, nil) + ak, err := c.Register(mockPortal) Expect(err).To(BeNil()) + Expect(ak).NotTo(BeNil()) }) It("returns error if Register fails", func() { parsedTime, _ := time.Parse(time.RFC3339, expiresAt) - mockPortal.EXPECT().RegisterAPIKey(owner, organization, role, parsedTime).Return(fmt.Errorf("some error")) - err := c.Register(mockPortal) + mockPortal.EXPECT().RegisterAPIKey(owner, organization, role, parsedTime).Return((*portal.ApiKey)(nil), fmt.Errorf("some error")) + ak, err := c.Register(mockPortal) + Expect(ak).To(BeNil()) Expect(err).To(MatchError(ContainSubstring("failed to register API key"))) }) }) @@ -62,7 +64,8 @@ var _ = Describe("RegisterCmd", func() { c.Opts.ExpiresAt = "invalid-date" }) It("returns error for invalid expiration date", func() { - err := c.Register(mockPortal) + ak, err := c.Register(mockPortal) + Expect(ak).To(BeNil()) Expect(err).To(MatchError(ContainSubstring("failed to parse expiration date"))) }) }) diff --git a/internal/portal/http.go b/internal/portal/http.go index 8f1a383f..78164e13 100644 --- a/internal/portal/http.go +++ b/internal/portal/http.go @@ -23,7 +23,7 @@ type Portal interface { ListBuilds(product Product) (availablePackages Builds, err error) GetBuild(product Product, version string, hash string) (Build, error) DownloadBuildArtifact(product Product, build Build, file io.Writer) error - RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) error + RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) (*ApiKey, error) RevokeAPIKey(key string) error UpdateAPIKey(key string, expiresAt time.Time) error ListAPIKeys() ([]ApiKey, error) @@ -200,7 +200,7 @@ func (c *PortalClient) DownloadBuildArtifact(product Product, build Build, file return nil } -func (c *PortalClient) RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) error { +func (c *PortalClient) RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) (*ApiKey, error) { req := struct { Owner string `json:"owner"` Organization string `json:"organization"` @@ -215,25 +215,25 @@ func (c *PortalClient) RegisterAPIKey(owner string, organization string, role st reqBody, err := json.Marshal(req) if err != nil { - return fmt.Errorf("failed to generate request body: %w", err) + return nil, fmt.Errorf("failed to generate request body: %w", err) } resp, err := c.HttpRequest(http.MethodPost, "/key/register", reqBody) if err != nil { - return fmt.Errorf("POST request to register API key failed: %w", err) + return nil, fmt.Errorf("POST request to register API key failed: %w", err) } defer func() { _ = resp.Body.Close() }() newKey := &ApiKey{} err = json.NewDecoder(resp.Body).Decode(newKey) if err != nil { - return fmt.Errorf("failed to decode response body: %w", err) + return nil, fmt.Errorf("failed to decode response body: %w", err) } log.Println("API key registered successfully!") log.Printf("Owner: %s\nOrganisation: %s\nKey: %s\n", newKey.Owner, newKey.Organization, newKey.ApiKey) - return nil + return newKey, nil } func (c *PortalClient) RevokeAPIKey(keyId string) error { diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index 6b85a82b..156c8ef8 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -251,20 +251,31 @@ func (_c *MockPortal_ListBuilds_Call) RunAndReturn(run func(product Product) (Bu } // RegisterAPIKey provides a mock function for the type MockPortal -func (_mock *MockPortal) RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) error { +func (_mock *MockPortal) RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) (*ApiKey, error) { ret := _mock.Called(owner, organization, role, expiresAt) if len(ret) == 0 { panic("no return value specified for RegisterAPIKey") } - var r0 error - if returnFunc, ok := ret.Get(0).(func(string, string, string, time.Time) error); ok { + var r0 *ApiKey + var r1 error + if returnFunc, ok := ret.Get(0).(func(string, string, string, time.Time) (*ApiKey, error)); ok { + return returnFunc(owner, organization, role, expiresAt) + } + if returnFunc, ok := ret.Get(0).(func(string, string, string, time.Time) *ApiKey); ok { r0 = returnFunc(owner, organization, role, expiresAt) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ApiKey) + } } - return r0 + if returnFunc, ok := ret.Get(1).(func(string, string, string, time.Time) error); ok { + r1 = returnFunc(owner, organization, role, expiresAt) + } else { + r1 = ret.Error(1) + } + return r0, r1 } // MockPortal_RegisterAPIKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterAPIKey' @@ -288,12 +299,12 @@ func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organizatio return _c } -func (_c *MockPortal_RegisterAPIKey_Call) Return(err error) *MockPortal_RegisterAPIKey_Call { - _c.Call.Return(err) +func (_c *MockPortal_RegisterAPIKey_Call) Return(apiKey *ApiKey, err error) *MockPortal_RegisterAPIKey_Call { + _c.Call.Return(apiKey, err) return _c } -func (_c *MockPortal_RegisterAPIKey_Call) RunAndReturn(run func(owner string, organization string, role string, expiresAt time.Time) error) *MockPortal_RegisterAPIKey_Call { +func (_c *MockPortal_RegisterAPIKey_Call) RunAndReturn(run func(owner string, organization string, role string, expiresAt time.Time) (*ApiKey, error)) *MockPortal_RegisterAPIKey_Call { _c.Call.Return(run) return _c } diff --git a/internal/tmpl/tmpl_suite_test.go b/internal/tmpl/tmpl_suite_test.go index 53592b2c..09cd81ee 100644 --- a/internal/tmpl/tmpl_suite_test.go +++ b/internal/tmpl/tmpl_suite_test.go @@ -13,4 +13,4 @@ import ( func TestTmpl(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Tmpl Suite") -} \ No newline at end of file +} diff --git a/internal/util/mocks.go b/internal/util/mocks.go index c9fcabf6..23487339 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -5,10 +5,9 @@ package util import ( - "os" - "github.com/jedib0t/go-pretty/v6/table" mock "github.com/stretchr/testify/mock" + "os" ) // NewMockFileIO creates a new instance of MockFileIO. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. From 8749c8256cdee07520468c6d136d8f9241413c93 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:58:40 +0200 Subject: [PATCH 3/6] refactor: Remove redundant logging from API key registration and enhance output in command --- cli/cmd/api_key_integration_test.go | 17 ----------------- cli/cmd/register.go | 12 ++++++++++-- internal/portal/http.go | 3 --- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/cli/cmd/api_key_integration_test.go b/cli/cmd/api_key_integration_test.go index 29dd22ca..b90d100d 100644 --- a/cli/cmd/api_key_integration_test.go +++ b/cli/cmd/api_key_integration_test.go @@ -203,23 +203,6 @@ var _ = Describe("API Key Integration Tests", func() { }) }) - Describe("API Key Registration Edge Cases", func() { - It("should handle registration with invalid expiration date", func() { - registerCmd := cmd.RegisterCmd{ - Opts: cmd.RegisterOpts{ - Owner: testOwner, - Organization: testOrg, - Role: testRole, - ExpiresAt: "invalid-date", - }, - } - - _, err := registerCmd.Register(portalClient) - Expect(err).NotTo(BeNil(), "Should fail with invalid date format") - Expect(err.Error()).To(ContainSubstring("failed to parse expiration date")) - }) - }) - Describe("API Key Update With Wrong Input", func() { It("should handle update with invalid date format", func() { updateCmd := cmd.UpdateAPIKeyCmd{ diff --git a/cli/cmd/register.go b/cli/cmd/register.go index f810a2d7..e621a604 100644 --- a/cli/cmd/register.go +++ b/cli/cmd/register.go @@ -27,8 +27,16 @@ type RegisterOpts struct { func (c *RegisterCmd) RunE(_ *cobra.Command, args []string) error { p := portal.NewPortalClient() - _, err := c.Register(p) - return err + newKey, err := c.Register(p) + if err != nil { + return err + } + + if newKey != nil { + fmt.Printf("API key registered successfully!\nOwner: %s\nOrganisation: %s\nKey: %s\n", newKey.Owner, newKey.Organization, newKey.ApiKey) + } + + return nil } func (c *RegisterCmd) Register(p portal.Portal) (*portal.ApiKey, error) { diff --git a/internal/portal/http.go b/internal/portal/http.go index 78164e13..28bc8d92 100644 --- a/internal/portal/http.go +++ b/internal/portal/http.go @@ -230,9 +230,6 @@ func (c *PortalClient) RegisterAPIKey(owner string, organization string, role st return nil, fmt.Errorf("failed to decode response body: %w", err) } - log.Println("API key registered successfully!") - log.Printf("Owner: %s\nOrganisation: %s\nKey: %s\n", newKey.Owner, newKey.Organization, newKey.ApiKey) - return newKey, nil } From 3c0ab1e8337c3a0073961785577737d34cf3273d Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:37:59 +0200 Subject: [PATCH 4/6] fix: remove upload artifact step from integration tests --- .github/workflows/integration-test.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 2327eda2..382e64c0 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -25,13 +25,3 @@ jobs: OMS_PORTAL_API_KEY: ${{ secrets.OMS_PORTAL_API_KEY }} OMS_PORTAL_API: ${{ secrets.OMS_PORTAL_API }} run: make test-integration - - - name: Upload Test Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: integration-test-results - path: | - **/*test*.xml - **/*test*.json - retention-days: 30 From 90349b7a92f6926b4aec95e454c168ef41b71aa2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:44:40 +0200 Subject: [PATCH 5/6] fix: update Go version setup to use go.mod file across workflows --- .github/workflows/cli-build_test.yml | 2 +- .github/workflows/integration-test.yml | 2 +- .github/workflows/service-build_test.yml | 2 +- .github/workflows/tag-release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cli-build_test.yml b/.github/workflows/cli-build_test.yml index 8006aa3f..2c7cf2ca 100644 --- a/.github/workflows/cli-build_test.yml +++ b/.github/workflows/cli-build_test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: 'go.mod' - name: Build run: make build-cli diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 382e64c0..d7858371 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version-file: 'go.mod' - name: Run Integration Tests env: diff --git a/.github/workflows/service-build_test.yml b/.github/workflows/service-build_test.yml index d703121f..0640adf4 100644 --- a/.github/workflows/service-build_test.yml +++ b/.github/workflows/service-build_test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: 'go.mod' - name: Build run: make build-service diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 6ceb8efa..7305b396 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: '1.25' + go-version-file: 'go.mod' - name: Tag run: hack/tag-release.sh From bd3b2f4090032a85fd12c8a00be20cec97a9f300 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:10:51 +0200 Subject: [PATCH 6/6] fix: replace fmt.Printf with log.Printf for download progress logging --- internal/portal/write_counter.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/portal/write_counter.go b/internal/portal/write_counter.go index 560bd90b..3df3b3bc 100644 --- a/internal/portal/write_counter.go +++ b/internal/portal/write_counter.go @@ -6,6 +6,7 @@ package portal import ( "fmt" "io" + "log" "time" ) @@ -37,7 +38,8 @@ func (wc *WriteCounter) Write(p []byte) (int, error) { wc.Written += int64(n) if time.Since(wc.LastUpdate) >= 100*time.Millisecond { - fmt.Printf("\rDownloading... %s transferred %c \033[K", byteCountToHumanReadable(wc.Written), wc.animate()) + // We need to use the log package so callers/tests that redirect log output will capture progress messages correctly. + log.Printf("Downloading... %s transferred %c", byteCountToHumanReadable(wc.Written), wc.animate()) wc.LastUpdate = time.Now() }