From b16fe58199b8b8407f21225cd9cc45d86fb4ec09 Mon Sep 17 00:00:00 2001 From: Thomas Kooi Date: Tue, 20 Oct 2026 13:32:47 +0200 Subject: [PATCH] fix(context): clear project when switching organisation Projects are organisation-scoped; leaving a stale project after an org switch caused API errors in the new organisation. --- cmd/context/apply_organisation.go | 19 ++++ cmd/context/apply_organisation_test.go | 100 +++++++++++++++++++++ cmd/context/create.go | 2 +- cmd/context/organisation.go | 4 +- docs/tcloud/context/organisation/_index.md | 2 +- 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 cmd/context/apply_organisation.go create mode 100644 cmd/context/apply_organisation_test.go diff --git a/cmd/context/apply_organisation.go b/cmd/context/apply_organisation.go new file mode 100644 index 0000000..3e9c491 --- /dev/null +++ b/cmd/context/apply_organisation.go @@ -0,0 +1,19 @@ +package context + +import "github.com/thalassa-cloud/cli/internal/config/contextstate" + +// applyOrganisationToContext sets the organisation on ctx. When the organisation +// changes to a non-empty value, the project is cleared because projects are +// organisation-scoped. Returns the previous project when it was cleared. +func applyOrganisationToContext(ctx *contextstate.Context, organisation string) (clearedProject string) { + if ctx == nil || organisation == "" { + return "" + } + if ctx.Organisation == organisation { + return "" + } + clearedProject = ctx.Project + ctx.Organisation = organisation + ctx.Project = "" + return clearedProject +} diff --git a/cmd/context/apply_organisation_test.go b/cmd/context/apply_organisation_test.go new file mode 100644 index 0000000..59d6117 --- /dev/null +++ b/cmd/context/apply_organisation_test.go @@ -0,0 +1,100 @@ +package context + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/thalassa-cloud/cli/internal/config/contextstate" +) + +func TestApplyOrganisationToContext(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + ctx *contextstate.Context + organisation string + wantClearedProject string + wantOrganisation string + wantProject string + }{ + { + name: "same organisation keeps project", + ctx: &contextstate.Context{ + Organisation: "acme", + Project: "proj-1", + }, + organisation: "acme", + wantClearedProject: "", + wantOrganisation: "acme", + wantProject: "proj-1", + }, + { + name: "different organisation clears project", + ctx: &contextstate.Context{ + Organisation: "acme", + Project: "proj-1", + }, + organisation: "other", + wantClearedProject: "proj-1", + wantOrganisation: "other", + wantProject: "", + }, + { + name: "different organisation with empty project", + ctx: &contextstate.Context{ + Organisation: "acme", + Project: "", + }, + organisation: "other", + wantClearedProject: "", + wantOrganisation: "other", + wantProject: "", + }, + { + name: "empty organisation leaves context unchanged", + ctx: &contextstate.Context{ + Organisation: "acme", + Project: "proj-1", + }, + organisation: "", + wantClearedProject: "", + wantOrganisation: "acme", + wantProject: "proj-1", + }, + { + name: "nil context is a no-op", + ctx: nil, + organisation: "other", + wantClearedProject: "", + }, + { + name: "sets organisation when previously empty", + ctx: &contextstate.Context{ + Organisation: "", + Project: "proj-1", + }, + organisation: "acme", + wantClearedProject: "proj-1", + wantOrganisation: "acme", + wantProject: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cleared := applyOrganisationToContext(tt.ctx, tt.organisation) + assert.Equal(t, tt.wantClearedProject, cleared) + if tt.ctx == nil { + return + } + require.NotNil(t, tt.ctx) + assert.Equal(t, tt.wantOrganisation, tt.ctx.Organisation) + assert.Equal(t, tt.wantProject, tt.ctx.Project) + }) + } +} diff --git a/cmd/context/create.go b/cmd/context/create.go index 13fe1b5..9f1a8ca 100644 --- a/cmd/context/create.go +++ b/cmd/context/create.go @@ -98,7 +98,7 @@ var createCmd = &cobra.Command{ if err != nil { return err } - currentContext.Organisation = organisation + applyOrganisationToContext(¤tContext, organisation) err = contextstate.CombineConfigContext(currentContext) if err != nil { return err diff --git a/cmd/context/organisation.go b/cmd/context/organisation.go index d6d23fb..1095be1 100644 --- a/cmd/context/organisation.go +++ b/cmd/context/organisation.go @@ -16,7 +16,7 @@ var organisationCmd = &cobra.Command{ Aliases: []string{"set-organisation", "use-organisation", "organisation", "org"}, Args: cobra.RangeArgs(0, 1), Short: "Set the organisation in the current-context", - Long: "Set the organisation in the current-context", + Long: "Set the organisation in the current-context. Changing the organisation clears the project.", Example: "tcloud context use-organisation ", RunE: func(cmd *cobra.Command, args []string) error { currentContext, err := contextstate.GetContextConfiguration() @@ -27,7 +27,7 @@ var organisationCmd = &cobra.Command{ if err != nil { return err } - currentContext.Organisation = selectedOrganisation + applyOrganisationToContext(¤tContext, selectedOrganisation) err = contextstate.CombineConfigContext(currentContext) if err != nil { return err diff --git a/docs/tcloud/context/organisation/_index.md b/docs/tcloud/context/organisation/_index.md index f57cec3..c030f6a 100644 --- a/docs/tcloud/context/organisation/_index.md +++ b/docs/tcloud/context/organisation/_index.md @@ -13,7 +13,7 @@ Set the organisation in the current-context ### Synopsis -Set the organisation in the current-context +Set the organisation in the current-context. ``` tcloud context organisation [flags]