From 853fd4abe46e54ce66100b3ab27e358e84710287 Mon Sep 17 00:00:00 2001 From: Jakub Nowak Date: Fri, 17 Apr 2026 13:49:20 +0200 Subject: [PATCH 1/2] initial impl --- cmd/workflow/pause/pause.go | 194 +++-------------- cmd/workflow/pause/pause_test.go | 6 - cmd/workflow/pause/registry_pause_strategy.go | 18 ++ .../pause/registry_pause_strategy_onchain.go | 196 ++++++++++++++++++ .../pause/registry_pause_strategy_private.go | 64 ++++++ .../workflow_private_registry.go | 176 ++++++++++++++++ test/multi_command_test.go | 1 + 7 files changed, 479 insertions(+), 176 deletions(-) create mode 100644 cmd/workflow/pause/registry_pause_strategy.go create mode 100644 cmd/workflow/pause/registry_pause_strategy_onchain.go create mode 100644 cmd/workflow/pause/registry_pause_strategy_private.go diff --git a/cmd/workflow/pause/pause.go b/cmd/workflow/pause/pause.go index 4f7ed8de..1180910d 100644 --- a/cmd/workflow/pause/pause.go +++ b/cmd/workflow/pause/pause.go @@ -1,25 +1,16 @@ package pause import ( - "encoding/hex" "fmt" - "math/big" - "sync" - "time" - "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/spf13/viper" - workflow_registry_v2_wrapper "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/workflow_registry_wrapper_v2" - "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" ) @@ -29,10 +20,8 @@ const ( ) type Inputs struct { - WorkflowName string `validate:"workflow_name"` - WorkflowOwner string `validate:"workflow_owner"` - WorkflowRegistryContractAddress string `validate:"required"` - WorkflowRegistryContractChainName string `validate:"required"` + WorkflowName string `validate:"workflow_name"` + WorkflowOwner string `validate:"workflow_owner"` } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -69,13 +58,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 { @@ -86,33 +71,20 @@ 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, "pause") + 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, - WorkflowRegistryContractChainName: oc.ChainName(), - WorkflowRegistryContractAddress: oc.Address(), + WorkflowName: h.settings.Workflow.UserWorkflowSettings.WorkflowName, + WorkflowOwner: resolvedWorkflowOwner, }, nil } @@ -135,158 +107,40 @@ func (h *handler) Execute() error { return fmt.Errorf("handler inputs not validated") } - workflowName := h.inputs.WorkflowName - workflowOwner := common.HexToAddress(h.inputs.WorkflowOwner) - h.displayWorkflowDetails() - h.wg.Wait() - if h.wrcErr != nil { - return h.wrcErr - } - - ui.Dim(fmt.Sprintf("Fetching workflows to pause... Name=%s, Owner=%s", workflowName, workflowOwner.Hex())) - - workflows, err := fetchAllWorkflows(h.wrc, workflowOwner, workflowName) + strategy, err := newRegistryPauseStrategy(h.runtimeContext.ResolvedRegistry, h) if err != nil { - return fmt.Errorf("failed to list workflows: %w", err) - } - if len(workflows) == 0 { - return fmt.Errorf("no workflows found for name %q and owner %q", workflowName, workflowOwner.Hex()) + return err } - // Validate precondition: only pause workflows that are currently active - var activeWorkflowIDs [][32]byte - for _, workflow := range workflows { - if workflow.Status == WorkflowStatusActive { - activeWorkflowIDs = append(activeWorkflowIDs, workflow.WorkflowId) - } - } + return strategy.Pause() +} - if len(activeWorkflowIDs) == 0 { - return fmt.Errorf("workflow is already paused, 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 } - // Note: The way deploy is set up, there will only ever be one workflow in the command for now - h.runtimeContext.Workflow.ID = hex.EncodeToString(activeWorkflowIDs[0][:]) - - ui.Dim(fmt.Sprintf("Processing batch pause... count=%d", len(activeWorkflowIDs))) - - txOut, err := h.wrc.BatchPauseWorkflows(activeWorkflowIDs) - if err != nil { - return fmt.Errorf("failed to batch pause workflows: %w", err) + owner := h.runtimeContext.DerivedWorkflowOwner + if owner == "" { + return "", fmt.Errorf("derived workflow owner is not available; ensure authentication succeeded") } - oc, _ := h.runtimeContext.ResolvedRegistry.(*settings.OnChainRegistry) - - switch txOut.Type { - case client.Regular: - ui.Success("Transaction confirmed") - ui.URL(fmt.Sprintf("%s/tx/%s", oc.ExplorerURL(), txOut.Hash)) - ui.Success("Workflows paused successfully") - ui.Line() - ui.Bold("Details:") - 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)) - for _, w := range activeWorkflowIDs { - ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(w[:]))) - } - - case client.Raw: - ui.Line() - ui.Success("MSIG workflow pause transaction prepared!") - ui.Dim(fmt.Sprintf("To Pause %s", workflowName)) - 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{ - { - BatchPauseWorkflow: &types.BatchPauseWorkflow{ - Payload: types.UserWorkflowBatchPauseInput{ - WorkflowIDs: h.runtimeContext.Workflow.ID, // Note: The way deploy is set up, there will only ever be one workflow in the command for now - - 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("BatchPauseWorkflow_%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 -} - -func fetchAllWorkflows( - wrc interface { - GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) - }, - owner common.Address, - name string, -) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { - const pageSize = int64(200) - var ( - start = big.NewInt(0) - limit = big.NewInt(pageSize) - workflows = make([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, 0, pageSize) - ) - - for { - list, err := wrc.GetWorkflowListByOwnerAndName(owner, name, start, limit) - if err != nil { - return nil, err - } - if len(list) == 0 { - break - } - - workflows = append(workflows, list...) - - start = big.NewInt(start.Int64() + int64(len(list))) - if int64(len(list)) < pageSize { - break - } + if len(owner) >= 2 && owner[:2] != "0x" { + owner = "0x" + owner } - return workflows, nil + return owner, nil } func (h *handler) displayWorkflowDetails() { ui.Line() ui.Title(fmt.Sprintf("Pausing 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/pause/pause_test.go b/cmd/workflow/pause/pause_test.go index e2022216..3cca5e27 100644 --- a/cmd/workflow/pause/pause_test.go +++ b/cmd/workflow/pause/pause_test.go @@ -18,12 +18,6 @@ func TestWorkflowPauseCommand(t *testing.T) { t.Parallel() validRequired := 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/pause/registry_pause_strategy.go b/cmd/workflow/pause/registry_pause_strategy.go new file mode 100644 index 00000000..cc80c842 --- /dev/null +++ b/cmd/workflow/pause/registry_pause_strategy.go @@ -0,0 +1,18 @@ +package pause + +import ( + "github.com/smartcontractkit/cre-cli/internal/settings" +) + +// registryPauseStrategy encapsulates target-specific pause logic. +type registryPauseStrategy interface { + Pause() error +} + +// newRegistryPauseStrategy returns the appropriate strategy for the given target. +func newRegistryPauseStrategy(resolvedRegistry settings.ResolvedRegistry, h *handler) (registryPauseStrategy, error) { + if resolvedRegistry.Type() == settings.RegistryTypeOffChain { + return newPrivateRegistryPauseStrategy(h), nil + } + return newOnchainRegistryPauseStrategy(h) +} diff --git a/cmd/workflow/pause/registry_pause_strategy_onchain.go b/cmd/workflow/pause/registry_pause_strategy_onchain.go new file mode 100644 index 00000000..c52e793b --- /dev/null +++ b/cmd/workflow/pause/registry_pause_strategy_onchain.go @@ -0,0 +1,196 @@ +package pause + +import ( + "encoding/hex" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + + workflow_registry_v2_wrapper "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/workflow_registry_wrapper_v2" + + "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 onchainRegistryPauseStrategy struct { + h *handler + wrc *client.WorkflowRegistryV2Client + onChain *settings.OnChainRegistry + wg sync.WaitGroup + initErr error +} + +func newOnchainRegistryPauseStrategy(h *handler) (*onchainRegistryPauseStrategy, error) { + onChain, err := settings.AsOnChain(h.runtimeContext.ResolvedRegistry, "pause") + if err != nil { + return nil, err + } + + a := &onchainRegistryPauseStrategy{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 *onchainRegistryPauseStrategy) Pause() error { + h := a.h + + a.wg.Wait() + if a.initErr != nil { + return a.initErr + } + + workflowName := h.inputs.WorkflowName + workflowOwner := common.HexToAddress(h.inputs.WorkflowOwner) + + ui.Dim(fmt.Sprintf("Fetching workflows to pause... Name=%s, Owner=%s", workflowName, workflowOwner.Hex())) + + workflows, err := fetchAllWorkflows(a.wrc, workflowOwner, workflowName) + if err != nil { + return fmt.Errorf("failed to list workflows: %w", err) + } + if len(workflows) == 0 { + return fmt.Errorf("no workflows found for name %q and owner %q", workflowName, workflowOwner.Hex()) + } + + // Validate precondition: only pause workflows that are currently active + var activeWorkflowIDs [][32]byte + for _, workflow := range workflows { + if workflow.Status == WorkflowStatusActive { + activeWorkflowIDs = append(activeWorkflowIDs, workflow.WorkflowId) + } + } + + if len(activeWorkflowIDs) == 0 { + return fmt.Errorf("workflow is already paused, cancelling transaction") + } + + // Note: The way deploy is set up, there will only ever be one workflow in the command for now + h.runtimeContext.Workflow.ID = hex.EncodeToString(activeWorkflowIDs[0][:]) + + ui.Dim(fmt.Sprintf("Processing batch pause... count=%d", len(activeWorkflowIDs))) + + txOut, err := a.wrc.BatchPauseWorkflows(activeWorkflowIDs) + if err != nil { + return fmt.Errorf("failed to batch pause workflows: %w", err) + } + + oc := a.onChain + + switch txOut.Type { + case client.Regular: + ui.Success("Transaction confirmed") + ui.URL(fmt.Sprintf("%s/tx/%s", oc.ExplorerURL(), txOut.Hash)) + ui.Success("Workflows paused successfully") + ui.Line() + ui.Bold("Details:") + 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)) + for _, w := range activeWorkflowIDs { + ui.Dim(fmt.Sprintf(" Workflow ID: %s", hex.EncodeToString(w[:]))) + } + + case client.Raw: + ui.Line() + ui.Success("MSIG workflow pause transaction prepared!") + ui.Dim(fmt.Sprintf("To Pause %s", workflowName)) + 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{ + { + BatchPauseWorkflow: &types.BatchPauseWorkflow{ + Payload: types.UserWorkflowBatchPauseInput{ + WorkflowIDs: h.runtimeContext.Workflow.ID, // Note: The way deploy is set up, there will only ever be one workflow in the command for now + + 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("BatchPauseWorkflow_%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 +} + +func fetchAllWorkflows( + wrc interface { + GetWorkflowListByOwnerAndName(owner common.Address, workflowName string, start, limit *big.Int) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) + }, + owner common.Address, + name string, +) ([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, error) { + const pageSize = int64(200) + var ( + start = big.NewInt(0) + limit = big.NewInt(pageSize) + workflows = make([]workflow_registry_v2_wrapper.WorkflowRegistryWorkflowMetadataView, 0, pageSize) + ) + + for { + list, err := wrc.GetWorkflowListByOwnerAndName(owner, name, start, limit) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + + workflows = append(workflows, list...) + + start = big.NewInt(start.Int64() + int64(len(list))) + if int64(len(list)) < pageSize { + break + } + } + + return workflows, nil +} diff --git a/cmd/workflow/pause/registry_pause_strategy_private.go b/cmd/workflow/pause/registry_pause_strategy_private.go new file mode 100644 index 00000000..25ba0602 --- /dev/null +++ b/cmd/workflow/pause/registry_pause_strategy_private.go @@ -0,0 +1,64 @@ +package pause + +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 privateRegistryPauseStrategy struct { + h *handler + prc *privateregistryclient.Client +} + +func newPrivateRegistryPauseStrategy(h *handler) *privateRegistryPauseStrategy { + return &privateRegistryPauseStrategy{h: h} +} + +func (a *privateRegistryPauseStrategy) 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 *privateRegistryPauseStrategy) Pause() error { + a.ensureClient() + + h := a.h + workflowName := h.inputs.WorkflowName + + ui.Dim(fmt.Sprintf("Fetching workflow to pause... 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.WorkflowStatusPaused { + return fmt.Errorf("workflow is already paused, cancelling transaction") + } + + h.runtimeContext.Workflow.ID = workflow.WorkflowID + + ui.Dim(fmt.Sprintf("Processing pause for workflow ID %s...", workflow.WorkflowID)) + + result, err := a.prc.PauseWorkflowInRegistry(workflow.WorkflowID) + if err != nil { + return fmt.Errorf("failed to pause workflow in private registry: %w", err) + } + + ui.Success("Workflow paused 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 5b25ae7a..841dc93d 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -277,3 +277,179 @@ func RunWorkflowPrivateRegistryHappyPath(t *testing.T, tc TestConfig) { require.Contains(t, out, "Binary URL:", "expected binary URL in details.\nCLI OUTPUT:\n%s", out) require.Contains(t, out, "Owner: "+privateRegistryOwnerAddress, "expected owner in details.\nCLI OUTPUT:\n%s", out) } + +// workflowPausePrivateRegistry pauses a workflow in the private registry via CLI +// using a mock GraphQL server. +func workflowPausePrivateRegistry(t *testing.T, tc TestConfig) string { + t.Helper() + + var getWorkflowCalled atomic.Bool + var pauseWorkflowCalled 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_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 + } + + if strings.Contains(req.Query, "PauseOffchainWorkflow") { + pauseWorkflowCalled.Store(true) + _ = json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "pauseOffchainWorkflow": 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 + } + + 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", "pause", + "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 pause failed:\nSTDOUT:\n%s\nSTDERR:\n%s", + stdout.String(), + stderr.String(), + ) + require.True(t, getWorkflowCalled.Load(), "expected GetOffchainWorkflowByName to be called") + require.True(t, pauseWorkflowCalled.Load(), "expected PauseOffchainWorkflow to be called") + + return StripANSI(stdout.String() + stderr.String()) +} + +// RunWorkflowPausePrivateRegistryHappyPath runs the workflow pause happy path for private registry. +func RunWorkflowPausePrivateRegistryHappyPath(t *testing.T, tc TestConfig) { + t.Helper() + + out := workflowPausePrivateRegistry(t, tc) + require.Contains(t, out, "Workflow paused successfully", "expected private registry pause 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_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) +} + diff --git a/test/multi_command_test.go b/test/multi_command_test.go index 062fdea0..8d0a034b 100644 --- a/test/multi_command_test.go +++ b/test/multi_command_test.go @@ -142,6 +142,7 @@ func TestMultiCommandHappyPaths(t *testing.T) { t.Cleanup(tc.Cleanup(t)) multi_command_flows.RunWorkflowPrivateRegistryHappyPath(t, tc) + multi_command_flows.RunWorkflowPausePrivateRegistryHappyPath(t, tc) }) // Run Account Happy Path: Link -> List -> Unlink -> List (verify unlinked) From 2270fd3267ee286276d8c15855c2e80c1d7b4c14 Mon Sep 17 00:00:00 2001 From: Jakub Nowak Date: Fri, 17 Apr 2026 13:49:38 +0200 Subject: [PATCH 2/2] lint --- test/multi_command_flows/workflow_private_registry.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/multi_command_flows/workflow_private_registry.go b/test/multi_command_flows/workflow_private_registry.go index 841dc93d..0fd0ed80 100644 --- a/test/multi_command_flows/workflow_private_registry.go +++ b/test/multi_command_flows/workflow_private_registry.go @@ -452,4 +452,3 @@ 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) } -