diff --git a/cmd/workflow/activate/activate.go b/cmd/workflow/activate/activate.go index c30837be..e5d4e40d 100644 --- a/cmd/workflow/activate/activate.go +++ b/cmd/workflow/activate/activate.go @@ -1,24 +1,16 @@ package activate import ( - "encoding/hex" "fmt" - "math/big" - "sort" - "sync" - "time" - "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/smartcontractkit/cre-cli/cmd/client" - cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" "github.com/smartcontractkit/cre-cli/internal/environments" "github.com/smartcontractkit/cre-cli/internal/runtime" "github.com/smartcontractkit/cre-cli/internal/settings" - "github.com/smartcontractkit/cre-cli/internal/types" "github.com/smartcontractkit/cre-cli/internal/ui" "github.com/smartcontractkit/cre-cli/internal/validation" ) @@ -28,11 +20,9 @@ const ( ) type Inputs struct { - WorkflowName string `validate:"workflow_name"` - WorkflowOwner string `validate:"workflow_owner"` - DonFamily string `validate:"required"` - WorkflowRegistryContractAddress string `validate:"required"` - WorkflowRegistryContractChainName string `validate:"required"` + WorkflowName string `validate:"workflow_name"` + WorkflowOwner string `validate:"workflow_owner"` + DonFamily string `validate:"required"` } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -70,13 +60,9 @@ type handler struct { settings *settings.Settings environmentSet *environments.EnvironmentSet inputs Inputs - wrc *client.WorkflowRegistryV2Client runtimeContext *runtime.Context validated bool - - wg sync.WaitGroup - wrcErr error } func newHandler(ctx *runtime.Context) *handler { @@ -87,34 +73,21 @@ func newHandler(ctx *runtime.Context) *handler { environmentSet: ctx.EnvironmentSet, runtimeContext: ctx, validated: false, - wg: sync.WaitGroup{}, - wrcErr: nil, } - h.wg.Add(1) - go func() { - defer h.wg.Done() - wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() - if err != nil { - h.wrcErr = fmt.Errorf("failed to create workflow registry client: %w", err) - return - } - h.wrc = wrc - }() return &h } func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { - oc, err := settings.AsOnChain(h.runtimeContext.ResolvedRegistry, "activate") + resolvedWorkflowOwner, err := h.resolveWorkflowOwner(h.runtimeContext.ResolvedRegistry.Type()) if err != nil { - return Inputs{}, err + return Inputs{}, fmt.Errorf("failed to resolve workflow owner: %w", err) } + return Inputs{ - WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, - WorkflowOwner: h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, - DonFamily: oc.DonFamily(), - WorkflowRegistryContractAddress: oc.Address(), - WorkflowRegistryContractChainName: oc.ChainName(), + WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, + WorkflowOwner: resolvedWorkflowOwner, + DonFamily: h.runtimeContext.ResolvedRegistry.DonFamily(), }, nil } @@ -137,126 +110,40 @@ func (h *handler) Execute() error { return fmt.Errorf("handler inputs not validated") } - workflowName := h.inputs.WorkflowName - workflowOwner := h.inputs.WorkflowOwner - h.displayWorkflowDetails() - h.wg.Wait() - if h.wrcErr != nil { - return h.wrcErr - } - - ownerAddr := common.HexToAddress(workflowOwner) - - const pageLimit = 200 - workflows, err := h.wrc.GetWorkflowListByOwnerAndName(ownerAddr, workflowName, big.NewInt(0), big.NewInt(pageLimit)) + strategy, err := newRegistryActivateStrategy(h.runtimeContext.ResolvedRegistry, h) if err != nil { - return fmt.Errorf("failed to get workflow list: %w", err) - } - if len(workflows) == 0 { - return fmt.Errorf("no workflows found for owner=%s name=%s", workflowOwner, workflowName) + return err } - // Sort by CreatedAt descending - sort.Slice(workflows, func(i, j int) bool { - return workflows[i].CreatedAt > workflows[j].CreatedAt - }) - - latest := workflows[0] - - h.runtimeContext.Workflow.ID = hex.EncodeToString(latest.WorkflowId[:]) + return strategy.Activate() +} - // Validate precondition: workflow must be in paused state - if latest.Status != WorkflowStatusPaused { - return fmt.Errorf("workflow is already active, cancelling transaction") +// resolveWorkflowOwner returns the effective owner address for workflow ID computation. +// For private registry deploys, the derived workflow owner from the runtime context is used. +// For onchain deploys, the configured WorkflowOwner address is used directly. +func (h *handler) resolveWorkflowOwner(registryType settings.RegistryType) (string, error) { + if registryType != settings.RegistryTypeOffChain { + return h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, nil } - if err := h.wrc.CheckUserDonLimit(ownerAddr, h.inputs.DonFamily, 1); err != nil { - return err + owner := h.runtimeContext.DerivedWorkflowOwner + if owner == "" { + return "", fmt.Errorf("derived workflow owner is not available; ensure authentication succeeded") } - ui.Dim(fmt.Sprintf("Activating workflow: Name=%s, Owner=%s, WorkflowID=%s", workflowName, workflowOwner, hex.EncodeToString(latest.WorkflowId[:]))) - - txOut, err := h.wrc.ActivateWorkflow(latest.WorkflowId, h.inputs.DonFamily) - if err != nil { - return fmt.Errorf("failed to activate workflow: %w", err) + if len(owner) >= 2 && owner[:2] != "0x" { + owner = "0x" + owner } - oc, _ := h.runtimeContext.ResolvedRegistry.(*settings.OnChainRegistry) - - switch txOut.Type { - case client.Regular: - ui.Success(fmt.Sprintf("Transaction confirmed: %s", txOut.Hash)) - ui.URL(fmt.Sprintf("%s/tx/%s", oc.ExplorerURL(), txOut.Hash)) - ui.Line() - ui.Success("Workflow activated successfully") - ui.Dim(fmt.Sprintf(" Contract address: %s", oc.Address())) - ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash)) - ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName)) - ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(latest.WorkflowId[:]))) - - case client.Raw: - ui.Line() - ui.Success("MSIG workflow activation transaction prepared!") - ui.Dim(fmt.Sprintf("To Activate %s with workflowID: %s", workflowName, hex.EncodeToString(latest.WorkflowId[:]))) - ui.Line() - ui.Bold("Next steps:") - ui.Line() - ui.Print(" 1. Submit the following transaction on the target chain:") - ui.Dim(fmt.Sprintf(" Chain: %s", h.inputs.WorkflowRegistryContractChainName)) - ui.Dim(fmt.Sprintf(" Contract Address: %s", txOut.RawTx.To)) - ui.Line() - ui.Print(" 2. Use the following transaction data:") - ui.Line() - ui.Code(fmt.Sprintf(" %x", txOut.RawTx.Data)) - ui.Line() - - case client.Changeset: - chainSelector, err := settings.GetChainSelectorByChainName(oc.ChainName()) - if err != nil { - return fmt.Errorf("failed to get chain selector for chain %q: %w", oc.ChainName(), err) - } - mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) - if err != nil { - ui.Warning("MCMS config not found or is incorrect, skipping MCMS config in changeset") - } - cldSettings := h.settings.CLDSettings - changesets := []types.Changeset{ - { - ActivateWorkflow: &types.ActivateWorkflow{ - Payload: types.UserWorkflowActivateInput{ - WorkflowID: h.runtimeContext.Workflow.ID, - DonFamily: h.inputs.DonFamily, - - ChainSelector: chainSelector, - MCMSConfig: mcmsConfig, - WorkflowRegistryQualifier: cldSettings.WorkflowRegistryQualifier, - }, - }, - }, - } - csFile := types.NewChangesetFile(cldSettings.Environment, cldSettings.Domain, cldSettings.MergeProposals, changesets) - - var fileName string - if cldSettings.ChangesetFile != "" { - fileName = cldSettings.ChangesetFile - } else { - fileName = fmt.Sprintf("ActivateWorkflow_%s_%s.yaml", workflowName, time.Now().Format("20060102_150405")) - } - - return cmdCommon.WriteChangesetFile(fileName, csFile, h.settings) - - default: - h.log.Warn().Msgf("Unsupported transaction type: %s", txOut.Type) - } - return nil + return owner, nil } func (h *handler) displayWorkflowDetails() { ui.Line() ui.Title(fmt.Sprintf("Activating Workflow: %s", h.inputs.WorkflowName)) ui.Dim(fmt.Sprintf("Target: %s", h.settings.User.TargetName)) - ui.Dim(fmt.Sprintf("Owner Address: %s", h.settings.Workflow.UserWorkflowSettings.WorkflowOwnerAddress)) + ui.Dim(fmt.Sprintf("Owner Address: %s", h.inputs.WorkflowOwner)) ui.Line() } diff --git a/cmd/workflow/activate/activate_test.go b/cmd/workflow/activate/activate_test.go index 07f32e7d..2b06bf00 100644 --- a/cmd/workflow/activate/activate_test.go +++ b/cmd/workflow/activate/activate_test.go @@ -18,12 +18,6 @@ func TestWorkflowActivateCommand(t *testing.T) { t.Parallel() fillRequired := func(in Inputs) Inputs { - if in.WorkflowRegistryContractAddress == "" { - in.WorkflowRegistryContractAddress = "0x0000000000000000000000000000000000000000" - } - if in.WorkflowRegistryContractChainName == "" { - in.WorkflowRegistryContractChainName = "ethereum-testnet-sepolia" - } return in } diff --git a/cmd/workflow/activate/registry_activate_strategy.go b/cmd/workflow/activate/registry_activate_strategy.go new file mode 100644 index 00000000..038241f6 --- /dev/null +++ b/cmd/workflow/activate/registry_activate_strategy.go @@ -0,0 +1,18 @@ +package activate + +import ( + "github.com/smartcontractkit/cre-cli/internal/settings" +) + +// registryActivateStrategy encapsulates target-specific activate logic. +type registryActivateStrategy interface { + Activate() error +} + +// newRegistryActivateStrategy returns the appropriate strategy for the given target. +func newRegistryActivateStrategy(resolvedRegistry settings.ResolvedRegistry, h *handler) (registryActivateStrategy, error) { + if resolvedRegistry.Type() == settings.RegistryTypeOffChain { + return newPrivateRegistryActivateStrategy(h), nil + } + return newOnchainRegistryActivateStrategy(h) +} diff --git a/cmd/workflow/activate/registry_activate_strategy_onchain.go b/cmd/workflow/activate/registry_activate_strategy_onchain.go new file mode 100644 index 00000000..e1acd89b --- /dev/null +++ b/cmd/workflow/activate/registry_activate_strategy_onchain.go @@ -0,0 +1,163 @@ +package activate + +import ( + "encoding/hex" + "fmt" + "math/big" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/cre-cli/cmd/client" + cmdCommon "github.com/smartcontractkit/cre-cli/cmd/common" + "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/types" + "github.com/smartcontractkit/cre-cli/internal/ui" +) + +type onchainRegistryActivateStrategy struct { + h *handler + wrc *client.WorkflowRegistryV2Client + onChain *settings.OnChainRegistry + wg sync.WaitGroup + initErr error +} + +func newOnchainRegistryActivateStrategy(h *handler) (*onchainRegistryActivateStrategy, error) { + onChain, err := settings.AsOnChain(h.runtimeContext.ResolvedRegistry, "activate") + if err != nil { + return nil, err + } + + a := &onchainRegistryActivateStrategy{h: h, onChain: onChain} + a.wg.Add(1) + go func() { + defer a.wg.Done() + wrc, err := h.clientFactory.NewWorkflowRegistryV2Client() + if err != nil { + a.initErr = fmt.Errorf("failed to create workflow registry client: %w", err) + return + } + a.wrc = wrc + }() + return a, nil +} + +func (a *onchainRegistryActivateStrategy) Activate() error { + h := a.h + + a.wg.Wait() + if a.initErr != nil { + return a.initErr + } + + workflowName := h.inputs.WorkflowName + workflowOwner := h.inputs.WorkflowOwner + + ownerAddr := common.HexToAddress(workflowOwner) + + const pageLimit = 200 + workflows, err := a.wrc.GetWorkflowListByOwnerAndName(ownerAddr, workflowName, big.NewInt(0), big.NewInt(pageLimit)) + if err != nil { + return fmt.Errorf("failed to get workflow list: %w", err) + } + if len(workflows) == 0 { + return fmt.Errorf("no workflows found for owner=%s name=%s", workflowOwner, workflowName) + } + + // Sort by CreatedAt descending + sort.Slice(workflows, func(i, j int) bool { + return workflows[i].CreatedAt > workflows[j].CreatedAt + }) + + latest := workflows[0] + + h.runtimeContext.Workflow.ID = hex.EncodeToString(latest.WorkflowId[:]) + + // Validate precondition: workflow must be in paused state + if latest.Status != WorkflowStatusPaused { + return fmt.Errorf("workflow is already active, cancelling transaction") + } + + if err := a.wrc.CheckUserDonLimit(ownerAddr, h.inputs.DonFamily, 1); err != nil { + return err + } + + ui.Dim(fmt.Sprintf("Activating workflow: Name=%s, Owner=%s, WorkflowID=%s", workflowName, workflowOwner, hex.EncodeToString(latest.WorkflowId[:]))) + + txOut, err := a.wrc.ActivateWorkflow(latest.WorkflowId, h.inputs.DonFamily) + if err != nil { + return fmt.Errorf("failed to activate workflow: %w", err) + } + + oc := a.onChain + + switch txOut.Type { + case client.Regular: + ui.Success(fmt.Sprintf("Transaction confirmed: %s", txOut.Hash)) + ui.URL(fmt.Sprintf("%s/tx/%s", oc.ExplorerURL(), txOut.Hash)) + ui.Line() + ui.Success("Workflow activated successfully") + ui.Dim(fmt.Sprintf(" Contract address: %s", oc.Address())) + ui.Dim(fmt.Sprintf(" Transaction hash: %s", txOut.Hash)) + ui.Dim(fmt.Sprintf(" Workflow Name: %s", workflowName)) + ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(latest.WorkflowId[:]))) + + case client.Raw: + ui.Line() + ui.Success("MSIG workflow activation transaction prepared!") + ui.Dim(fmt.Sprintf("To Activate %s with workflowID: %s", workflowName, hex.EncodeToString(latest.WorkflowId[:]))) + ui.Line() + ui.Bold("Next steps:") + ui.Line() + ui.Print(" 1. Submit the following transaction on the target chain:") + ui.Dim(fmt.Sprintf(" Chain: %s", oc.ChainName())) + ui.Dim(fmt.Sprintf(" Contract Address: %s", txOut.RawTx.To)) + ui.Line() + ui.Print(" 2. Use the following transaction data:") + ui.Line() + ui.Code(fmt.Sprintf(" %x", txOut.RawTx.Data)) + ui.Line() + + case client.Changeset: + chainSelector, err := settings.GetChainSelectorByChainName(oc.ChainName()) + if err != nil { + return fmt.Errorf("failed to get chain selector for chain %q: %w", oc.ChainName(), err) + } + mcmsConfig, err := settings.GetMCMSConfig(h.settings, chainSelector) + if err != nil { + ui.Warning("MCMS config not found or is incorrect, skipping MCMS config in changeset") + } + cldSettings := h.settings.CLDSettings + changesets := []types.Changeset{ + { + ActivateWorkflow: &types.ActivateWorkflow{ + Payload: types.UserWorkflowActivateInput{ + WorkflowID: h.runtimeContext.Workflow.ID, + DonFamily: h.inputs.DonFamily, + + ChainSelector: chainSelector, + MCMSConfig: mcmsConfig, + WorkflowRegistryQualifier: cldSettings.WorkflowRegistryQualifier, + }, + }, + }, + } + csFile := types.NewChangesetFile(cldSettings.Environment, cldSettings.Domain, cldSettings.MergeProposals, changesets) + + var fileName string + if cldSettings.ChangesetFile != "" { + fileName = cldSettings.ChangesetFile + } else { + fileName = fmt.Sprintf("ActivateWorkflow_%s_%s.yaml", workflowName, time.Now().Format("20060102_150405")) + } + + return cmdCommon.WriteChangesetFile(fileName, csFile, h.settings) + + default: + h.log.Warn().Msgf("Unsupported transaction type: %s", txOut.Type) + } + return nil +} diff --git a/cmd/workflow/activate/registry_activate_strategy_private.go b/cmd/workflow/activate/registry_activate_strategy_private.go new file mode 100644 index 00000000..f63079e1 --- /dev/null +++ b/cmd/workflow/activate/registry_activate_strategy_private.go @@ -0,0 +1,64 @@ +package activate + +import ( + "fmt" + + "github.com/smartcontractkit/cre-cli/internal/client/graphqlclient" + "github.com/smartcontractkit/cre-cli/internal/client/privateregistryclient" + "github.com/smartcontractkit/cre-cli/internal/ui" +) + +type privateRegistryActivateStrategy struct { + h *handler + prc *privateregistryclient.Client +} + +func newPrivateRegistryActivateStrategy(h *handler) *privateRegistryActivateStrategy { + return &privateRegistryActivateStrategy{h: h} +} + +func (a *privateRegistryActivateStrategy) ensureClient() { + if a.prc == nil { + gql := graphqlclient.New(a.h.runtimeContext.Credentials, a.h.environmentSet, a.h.log) + a.prc = privateregistryclient.New(gql, a.h.log) + } +} + +func (a *privateRegistryActivateStrategy) Activate() error { + a.ensureClient() + + h := a.h + workflowName := h.inputs.WorkflowName + + ui.Dim(fmt.Sprintf("Fetching workflow to activate... Name=%s", workflowName)) + + workflow, err := a.prc.GetWorkflowByName(workflowName) + if err != nil { + return fmt.Errorf("failed to get workflow: %w", err) + } + + if workflow.Status == privateregistryclient.WorkflowStatusActive { + return fmt.Errorf("workflow is already active, cancelling transaction") + } + + h.runtimeContext.Workflow.ID = workflow.WorkflowID + + ui.Dim(fmt.Sprintf("Processing activation for workflow ID %s...", workflow.WorkflowID)) + + result, err := a.prc.ActivateWorkflowInRegistry(workflow.WorkflowID) + if err != nil { + return fmt.Errorf("failed to activate workflow in private registry: %w", err) + } + + ui.Success("Workflow activated successfully") + ui.Line() + ui.Bold("Details:") + ui.Dim(fmt.Sprintf(" Workflow Name: %s", result.WorkflowName)) + ui.Dim(fmt.Sprintf(" Workflow ID: %s", result.WorkflowID)) + ui.Dim(fmt.Sprintf(" Status: %s", result.Status)) + if result.Owner != "" { + ui.Dim(fmt.Sprintf(" Owner: %s", result.Owner)) + } + + return nil +} diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index 10f5c493..014081a2 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -451,3 +451,178 @@ func RunWorkflowPausePrivateRegistryHappyPath(t *testing.T, tc TestConfig) { require.Contains(t, out, "Status: WORKFLOW_STATUS_PAUSED", "expected paused status in details.\nCLI OUTPUT:\n%s", out) require.Contains(t, out, "Owner: "+privateRegistryOwnerAddress, "expected owner in details.\nCLI OUTPUT:\n%s", out) } + +// workflowActivatePrivateRegistry activates a workflow in the private registry via CLI +// using a mock GraphQL server. +func workflowActivatePrivateRegistry(t *testing.T, tc TestConfig) string { + t.Helper() + + var getWorkflowCalled atomic.Bool + var activateWorkflowCalled atomic.Bool + var srv *httptest.Server + srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasPrefix(r.URL.Path, "/graphql") && r.Method == http.MethodPost: + var req graphQLRequest + _ = json.NewDecoder(r.Body).Decode(&req) + + w.Header().Set("Content-Type", "application/json") + + if strings.Contains(req.Query, "getCreOrganizationInfo") { + _ = json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "getCreOrganizationInfo": map[string]any{ + "orgId": "test-org-id", + "derivedWorkflowOwners": []string{"ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12"}, + }, + }, + }) + return + } + + if strings.Contains(req.Query, "GetTenantConfig") || strings.Contains(req.Query, "getTenantConfig") { + _ = json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "getTenantConfig": map[string]any{ + "tenantId": "42", + "defaultDonFamily": "test-don", + "vaultGatewayUrl": "https://vault.example.test", + "registries": []map[string]any{ + { + "id": "reg-test", + "label": "reg-test", + "type": "OFF_CHAIN", + "chainSelector": "6433500567565415381", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "secretsAuthFlows": []string{"BROWSER"}, + }, + }, + }, + }, + }) + return + } + + if strings.Contains(req.Query, "GetOffchainWorkflowByName") { + getWorkflowCalled.Store(true) + _ = json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "getOffchainWorkflowByName": map[string]any{ + "workflow": map[string]any{ + "workflowId": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "owner": privateRegistryOwnerAddress, + "createdAt": "2025-01-01T00:00:00Z", + "status": "WORKFLOW_STATUS_PAUSED", + "workflowName": "private-registry-happy-path-workflow", + "binaryUrl": srv.URL + "/get/binary.wasm", + "configUrl": "", + "tag": "private-registry-happy-path-workflow", + "attributes": "", + "donFamily": "test-don", + "organizationId": "test-org-id", + }, + }, + }, + }) + return + } + + if strings.Contains(req.Query, "ActivateOffchainWorkflow") { + activateWorkflowCalled.Store(true) + _ = json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "activateOffchainWorkflow": map[string]any{ + "workflow": map[string]any{ + "workflowId": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "owner": privateRegistryOwnerAddress, + "createdAt": "2025-01-01T00:00:00Z", + "status": "WORKFLOW_STATUS_ACTIVE", + "workflowName": "private-registry-happy-path-workflow", + "binaryUrl": srv.URL + "/get/binary.wasm", + "configUrl": "", + "tag": "private-registry-happy-path-workflow", + "attributes": "", + "donFamily": "test-don", + "organizationId": "test-org-id", + }, + }, + }, + }) + return + } + + w.WriteHeader(http.StatusBadRequest) + _ = json.NewEncoder(w).Encode(map[string]any{ + "errors": []map[string]string{{"message": "Unsupported GraphQL query"}}, + }) + return + + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte("not found")) + return + } + })) + defer srv.Close() + + t.Setenv(environments.EnvVarGraphQLURL, srv.URL+"/graphql") + + args := []string{ + "workflow", "activate", + "blank_workflow", + tc.GetCliEnvFlag(), + tc.GetProjectRootFlag(), + "--" + settings.Flags.SkipConfirmation.Name, + } + + cmd := exec.Command(CLIPath, args...) + testHome := createTestBearerCredentialsHome(t) + + realHome, err := os.UserHomeDir() + require.NoError(t, err, "failed to get real home dir") + + childEnv := make([]string, 0, len(os.Environ())+3) + hasGOPATH := false + for _, entry := range os.Environ() { + if strings.HasPrefix(entry, "HOME=") || strings.HasPrefix(entry, "USERPROFILE=") { + continue + } + if strings.HasPrefix(entry, "GOPATH=") { + hasGOPATH = true + } + childEnv = append(childEnv, entry) + } + childEnv = append(childEnv, "HOME="+testHome, "USERPROFILE="+testHome) + if !hasGOPATH { + childEnv = append(childEnv, "GOPATH="+filepath.Join(realHome, "go")) + } + cmd.Env = childEnv + + var stdout, stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + + require.NoError( + t, + cmd.Run(), + "cre workflow activate failed:\nSTDOUT:\n%s\nSTDERR:\n%s", + stdout.String(), + stderr.String(), + ) + require.True(t, getWorkflowCalled.Load(), "expected GetOffchainWorkflowByName to be called") + require.True(t, activateWorkflowCalled.Load(), "expected ActivateOffchainWorkflow to be called") + + return StripANSI(stdout.String() + stderr.String()) +} + +// RunWorkflowActivatePrivateRegistryHappyPath runs the workflow activate happy path for private registry. +func RunWorkflowActivatePrivateRegistryHappyPath(t *testing.T, tc TestConfig) { + t.Helper() + + out := workflowActivatePrivateRegistry(t, tc) + require.Contains(t, out, "Workflow activated successfully", "expected private registry activate success.\nCLI OUTPUT:\n%s", out) + require.Contains(t, out, "Details:", "expected details block.\nCLI OUTPUT:\n%s", out) + require.Contains(t, out, "Workflow Name: private-registry-happy-path-workflow", "expected workflow name in details.\nCLI OUTPUT:\n%s", out) + require.Contains(t, out, "Workflow ID:", "expected workflow ID in details.\nCLI OUTPUT:\n%s", out) + require.Contains(t, out, "Status: WORKFLOW_STATUS_ACTIVE", "expected active status in details.\nCLI OUTPUT:\n%s", out) + require.Contains(t, out, "Owner: "+privateRegistryOwnerAddress, "expected owner in details.\nCLI OUTPUT:\n%s", out) +} diff --git a/test/multi_command_test.go b/test/multi_command_test.go index 130b1ca7..c90b630a 100644 --- a/test/multi_command_test.go +++ b/test/multi_command_test.go @@ -154,6 +154,7 @@ func TestMultiCommandHappyPaths(t *testing.T) { multi_command_flows.RunWorkflowPrivateRegistryHappyPath(t, tc) multi_command_flows.RunWorkflowPausePrivateRegistryHappyPath(t, tc) + multi_command_flows.RunWorkflowActivatePrivateRegistryHappyPath(t, tc) }) // Run Account Happy Path: Link -> List -> Unlink -> List (verify unlinked)