f86ab2312f
* feat(workflow): ship explicit completion signal toggle + YAML round-trip ADR 0015's "Wait for agent completion signal" UI shipped as a deferral comment in PR #1276 instead of a working toggle, and the auto_advance_requires_signal flag never reached the workflow export format. Add the pipeline-editor sub-toggle and propagate the field through StepPortable so the setting survives workflow export/import. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(workflow): guard completion-signal toggle + cover import in tests Address PR review: - hide ExplicitCompletionToggle when on_turn_complete has no transition (flag has no effect there; visible checkbox was misleading) - add service-level import test for auto_advance_requires_signal so the field has explicit ImportWorkflows coverage, mirroring the existing show_in_command_panel pattern Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(workflow): drop omitempty from AutoAdvanceRequiresSignal tags Match the sibling bool fields (IsStartStep, ShowInCommandPanel, AllowManualMove) so a `false` value still appears in exported YAML/JSON. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
509 lines
16 KiB
Go
509 lines
16 KiB
Go
package models
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
taskmodels "github.com/kandev/kandev/internal/task/models"
|
|
)
|
|
|
|
func TestBuildWorkflowExport(t *testing.T) {
|
|
t.Run("converts step IDs to positions", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "My Workflow", Description: "desc"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "step-a", Name: "Todo", Position: 0, Color: "blue"},
|
|
{
|
|
ID: "step-b", Name: "In Progress", Position: 1, Color: "yellow",
|
|
Events: StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_id": "step-a"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
stepMap := map[string][]*WorkflowStep{"wf-1": steps}
|
|
|
|
export := BuildWorkflowExport([]*taskmodels.Workflow{wf}, stepMap, nil)
|
|
|
|
require.Equal(t, ExportVersion, export.Version)
|
|
require.Equal(t, ExportType, export.Type)
|
|
require.Len(t, export.Workflows, 1)
|
|
|
|
pw := export.Workflows[0]
|
|
assert.Equal(t, "My Workflow", pw.Name)
|
|
assert.Equal(t, "desc", pw.Description)
|
|
require.Len(t, pw.Steps, 2)
|
|
|
|
// The move_to_step should reference position 0 (step-a's position), not step-a ID.
|
|
action := pw.Steps[1].Events.OnTurnComplete[0]
|
|
assert.Equal(t, OnTurnCompleteMoveToStep, action.Type)
|
|
assert.Equal(t, 0, action.Config["step_position"])
|
|
assert.Nil(t, action.Config["step_id"])
|
|
})
|
|
|
|
t.Run("preserves non-move events", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "WF"}
|
|
steps := []*WorkflowStep{
|
|
{
|
|
ID: "step-a", Name: "Step", Position: 0,
|
|
Events: StepEvents{
|
|
OnEnter: []OnEnterAction{{Type: OnEnterAutoStartAgent}},
|
|
OnExit: []OnExitAction{{Type: OnExitDisablePlanMode}},
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToNext},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
export := BuildWorkflowExport([]*taskmodels.Workflow{wf}, map[string][]*WorkflowStep{"wf-1": steps}, nil)
|
|
|
|
sp := export.Workflows[0].Steps[0]
|
|
assert.Len(t, sp.Events.OnEnter, 1)
|
|
assert.Equal(t, OnEnterAutoStartAgent, sp.Events.OnEnter[0].Type)
|
|
assert.Len(t, sp.Events.OnExit, 1)
|
|
assert.Len(t, sp.Events.OnTurnStart, 1)
|
|
assert.Equal(t, OnTurnStartMoveToNext, sp.Events.OnTurnStart[0].Type)
|
|
})
|
|
}
|
|
|
|
func TestValidate(t *testing.T) {
|
|
validExport := func() *WorkflowExport {
|
|
return &WorkflowExport{
|
|
Version: ExportVersion,
|
|
Type: ExportType,
|
|
Workflows: []WorkflowPortable{
|
|
{
|
|
Name: "Test",
|
|
Steps: []StepPortable{
|
|
{Name: "Todo", Position: 0, Color: "blue"},
|
|
{Name: "Done", Position: 1, Color: "green"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
t.Run("valid export passes", func(t *testing.T) {
|
|
assert.NoError(t, validExport().Validate())
|
|
})
|
|
|
|
t.Run("wrong version fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Version = 99
|
|
err := e.Validate()
|
|
assert.ErrorContains(t, err, "unsupported export version")
|
|
})
|
|
|
|
t.Run("wrong type fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Type = "wrong"
|
|
err := e.Validate()
|
|
assert.ErrorContains(t, err, "unsupported export type")
|
|
})
|
|
|
|
t.Run("empty workflows fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows = nil
|
|
assert.ErrorContains(t, e.Validate(), "no workflows")
|
|
})
|
|
|
|
t.Run("empty workflow name fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Name = ""
|
|
assert.ErrorContains(t, e.Validate(), "name is required")
|
|
})
|
|
|
|
t.Run("empty step name fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Name = ""
|
|
assert.ErrorContains(t, e.Validate(), "step 0: name is required")
|
|
})
|
|
|
|
t.Run("duplicate positions fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[1].Position = 0
|
|
assert.ErrorContains(t, e.Validate(), "duplicate step position 0")
|
|
})
|
|
|
|
t.Run("valid move_to_step position ref passes", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": 1}},
|
|
},
|
|
}
|
|
assert.NoError(t, e.Validate())
|
|
})
|
|
|
|
t.Run("set_session_mode with mode passes", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnEnter: []OnEnterAction{
|
|
{Type: OnEnterSetSessionMode, Config: map[string]any{"mode": "acceptEdits"}},
|
|
},
|
|
}
|
|
assert.NoError(t, e.Validate())
|
|
})
|
|
|
|
t.Run("set_session_mode without mode fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnEnter: []OnEnterAction{{Type: OnEnterSetSessionMode}},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "set_session_mode requires a non-empty string")
|
|
})
|
|
|
|
t.Run("set_session_mode with non-string mode fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnEnter: []OnEnterAction{
|
|
{Type: OnEnterSetSessionMode, Config: map[string]any{"mode": 3}},
|
|
},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "set_session_mode requires a non-empty string")
|
|
})
|
|
|
|
t.Run("invalid move_to_step position ref fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": 99}},
|
|
},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "does not match any step")
|
|
})
|
|
|
|
t.Run("missing config on move_to_step fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: nil},
|
|
},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "missing config")
|
|
})
|
|
|
|
t.Run("missing step_position key fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"other": "val"}},
|
|
},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "missing step_position")
|
|
})
|
|
|
|
t.Run("float64 position ref passes (JSON unmarshal)", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": float64(1)}},
|
|
},
|
|
}
|
|
assert.NoError(t, e.Validate())
|
|
})
|
|
|
|
t.Run("invalid position type fails", func(t *testing.T) {
|
|
e := validExport()
|
|
e.Workflows[0].Steps[0].Events = StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": "not-a-number"}},
|
|
},
|
|
}
|
|
assert.ErrorContains(t, e.Validate(), "unexpected type")
|
|
})
|
|
}
|
|
|
|
func TestConvertStepIDToPosition(t *testing.T) {
|
|
idToPos := map[string]int{"step-a": 0, "step-b": 1}
|
|
|
|
t.Run("rewrites step_id to step_position in OnTurnStart", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"step_id": "step-b"}},
|
|
},
|
|
}
|
|
result := convertStepIDToPosition(events, idToPos)
|
|
require.Len(t, result.OnTurnStart, 1)
|
|
assert.Equal(t, 1, result.OnTurnStart[0].Config["step_position"])
|
|
assert.Nil(t, result.OnTurnStart[0].Config["step_id"])
|
|
})
|
|
|
|
t.Run("rewrites step_id to step_position in OnTurnComplete", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_id": "step-a"}},
|
|
},
|
|
}
|
|
result := convertStepIDToPosition(events, idToPos)
|
|
require.Len(t, result.OnTurnComplete, 1)
|
|
assert.Equal(t, 0, result.OnTurnComplete[0].Config["step_position"])
|
|
assert.Nil(t, result.OnTurnComplete[0].Config["step_id"])
|
|
})
|
|
|
|
t.Run("preserves non-move actions unchanged", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{{Type: OnTurnStartMoveToNext}},
|
|
OnTurnComplete: []OnTurnCompleteAction{{Type: OnTurnCompleteMoveToNext}},
|
|
OnEnter: []OnEnterAction{{Type: OnEnterAutoStartAgent}},
|
|
OnExit: []OnExitAction{{Type: OnExitDisablePlanMode}},
|
|
}
|
|
result := convertStepIDToPosition(events, idToPos)
|
|
assert.Len(t, result.OnTurnStart, 1)
|
|
assert.Equal(t, OnTurnStartMoveToNext, result.OnTurnStart[0].Type)
|
|
assert.Len(t, result.OnTurnComplete, 1)
|
|
assert.Len(t, result.OnEnter, 1)
|
|
assert.Len(t, result.OnExit, 1)
|
|
})
|
|
|
|
t.Run("unknown step_id left unchanged", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"step_id": "unknown"}},
|
|
},
|
|
}
|
|
result := convertStepIDToPosition(events, idToPos)
|
|
assert.Equal(t, "unknown", result.OnTurnStart[0].Config["step_id"])
|
|
})
|
|
}
|
|
|
|
func TestConvertPositionToStepID(t *testing.T) {
|
|
posToID := map[int]string{0: "new-a", 1: "new-b"}
|
|
|
|
t.Run("rewrites step_position to step_id in OnTurnStart", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"step_position": 1}},
|
|
},
|
|
}
|
|
result := ConvertPositionToStepID(events, posToID)
|
|
require.Len(t, result.OnTurnStart, 1)
|
|
assert.Equal(t, "new-b", result.OnTurnStart[0].Config["step_id"])
|
|
assert.Nil(t, result.OnTurnStart[0].Config["step_position"])
|
|
})
|
|
|
|
t.Run("rewrites step_position to step_id in OnTurnComplete", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": 0}},
|
|
},
|
|
}
|
|
result := ConvertPositionToStepID(events, posToID)
|
|
require.Len(t, result.OnTurnComplete, 1)
|
|
assert.Equal(t, "new-a", result.OnTurnComplete[0].Config["step_id"])
|
|
assert.Nil(t, result.OnTurnComplete[0].Config["step_position"])
|
|
})
|
|
|
|
t.Run("handles float64 position (JSON unmarshal)", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"step_position": float64(0)}},
|
|
},
|
|
}
|
|
result := ConvertPositionToStepID(events, posToID)
|
|
assert.Equal(t, "new-a", result.OnTurnStart[0].Config["step_id"])
|
|
})
|
|
|
|
t.Run("unknown position left unchanged", func(t *testing.T) {
|
|
events := StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_position": 99}},
|
|
},
|
|
}
|
|
result := ConvertPositionToStepID(events, posToID)
|
|
assert.Equal(t, 99, result.OnTurnComplete[0].Config["step_position"])
|
|
})
|
|
}
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
t.Run("export then import preserves events", func(t *testing.T) {
|
|
// Build domain steps with step_id references.
|
|
steps := []*WorkflowStep{
|
|
{ID: "orig-a", Name: "Backlog", Position: 0, Color: "gray"},
|
|
{
|
|
ID: "orig-b", Name: "In Progress", Position: 1, Color: "blue",
|
|
Events: StepEvents{
|
|
OnTurnComplete: []OnTurnCompleteAction{
|
|
{Type: OnTurnCompleteMoveToStep, Config: map[string]any{"step_id": "orig-c"}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ID: "orig-c", Name: "Done", Position: 2, Color: "green",
|
|
Events: StepEvents{
|
|
OnTurnStart: []OnTurnStartAction{
|
|
{Type: OnTurnStartMoveToStep, Config: map[string]any{"step_id": "orig-b"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "Pipeline"}
|
|
export := BuildWorkflowExport([]*taskmodels.Workflow{wf}, map[string][]*WorkflowStep{"wf-1": steps}, nil)
|
|
|
|
require.NoError(t, export.Validate())
|
|
|
|
// Simulate import: assign new IDs by position.
|
|
posToID := map[int]string{0: "new-a", 1: "new-b", 2: "new-c"}
|
|
for _, sp := range export.Workflows[0].Steps {
|
|
imported := ConvertPositionToStepID(sp.Events, posToID)
|
|
|
|
switch sp.Name {
|
|
case "In Progress":
|
|
require.Len(t, imported.OnTurnComplete, 1)
|
|
assert.Equal(t, "new-c", imported.OnTurnComplete[0].Config["step_id"],
|
|
"In Progress should now reference new Done ID")
|
|
case "Done":
|
|
require.Len(t, imported.OnTurnStart, 1)
|
|
assert.Equal(t, "new-b", imported.OnTurnStart[0].Config["step_id"],
|
|
"Done should now reference new In Progress ID")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAutoAdvanceRequiresSignalExport(t *testing.T) {
|
|
t.Run("preserves auto_advance_requires_signal in export", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "WF"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Legacy", Position: 0, Color: "gray", AutoAdvanceRequiresSignal: false},
|
|
{ID: "s2", Name: "Gated", Position: 1, Color: "blue", AutoAdvanceRequiresSignal: true},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
nil,
|
|
)
|
|
|
|
require.Len(t, export.Workflows[0].Steps, 2)
|
|
assert.False(t, export.Workflows[0].Steps[0].AutoAdvanceRequiresSignal)
|
|
assert.True(t, export.Workflows[0].Steps[1].AutoAdvanceRequiresSignal)
|
|
})
|
|
}
|
|
|
|
func TestShowInCommandPanelExport(t *testing.T) {
|
|
t.Run("preserves show_in_command_panel in export", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "Test"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Backlog", Position: 0, ShowInCommandPanel: false},
|
|
{ID: "s2", Name: "Active", Position: 1, ShowInCommandPanel: true},
|
|
{ID: "s3", Name: "Done", Position: 2, ShowInCommandPanel: false},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
nil,
|
|
)
|
|
|
|
require.Len(t, export.Workflows[0].Steps, 3)
|
|
assert.False(t, export.Workflows[0].Steps[0].ShowInCommandPanel)
|
|
assert.True(t, export.Workflows[0].Steps[1].ShowInCommandPanel)
|
|
assert.False(t, export.Workflows[0].Steps[2].ShowInCommandPanel)
|
|
})
|
|
}
|
|
|
|
func TestAgentProfileExport(t *testing.T) {
|
|
resolver := func(profileID string) *AgentProfilePortable {
|
|
profiles := map[string]*AgentProfilePortable{
|
|
"prof-1": {AgentName: "Claude Code", Model: "opus", Mode: "code"},
|
|
"prof-2": {AgentName: "Codex", Model: "o3"},
|
|
}
|
|
return profiles[profileID]
|
|
}
|
|
|
|
t.Run("includes agent profile on workflow and steps", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "WithProfiles", AgentProfileID: "prof-1"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Dev", Position: 0, Color: "blue", AgentProfileID: "prof-2"},
|
|
{ID: "s2", Name: "Review", Position: 1, Color: "green"},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
resolver,
|
|
)
|
|
|
|
pw := export.Workflows[0]
|
|
require.NotNil(t, pw.AgentProfile)
|
|
assert.Equal(t, "Claude Code", pw.AgentProfile.AgentName)
|
|
assert.Equal(t, "opus", pw.AgentProfile.Model)
|
|
assert.Equal(t, "code", pw.AgentProfile.Mode)
|
|
|
|
require.NotNil(t, pw.Steps[0].AgentProfile)
|
|
assert.Equal(t, "Codex", pw.Steps[0].AgentProfile.AgentName)
|
|
assert.Equal(t, "o3", pw.Steps[0].AgentProfile.Model)
|
|
assert.Empty(t, pw.Steps[0].AgentProfile.Mode)
|
|
|
|
assert.Nil(t, pw.Steps[1].AgentProfile, "step without profile should be nil")
|
|
})
|
|
|
|
t.Run("omits agent profile when resolver is nil", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "NoResolver", AgentProfileID: "prof-1"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Step", Position: 0, Color: "gray", AgentProfileID: "prof-2"},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
nil,
|
|
)
|
|
|
|
pw := export.Workflows[0]
|
|
assert.Nil(t, pw.AgentProfile)
|
|
assert.Nil(t, pw.Steps[0].AgentProfile)
|
|
})
|
|
|
|
t.Run("omits agent profile when IDs are empty", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "EmptyIDs"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Step", Position: 0, Color: "gray"},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
resolver,
|
|
)
|
|
|
|
pw := export.Workflows[0]
|
|
assert.Nil(t, pw.AgentProfile)
|
|
assert.Nil(t, pw.Steps[0].AgentProfile)
|
|
})
|
|
|
|
t.Run("handles unknown profile ID gracefully", func(t *testing.T) {
|
|
wf := &taskmodels.Workflow{ID: "wf-1", Name: "Unknown", AgentProfileID: "prof-unknown"}
|
|
steps := []*WorkflowStep{
|
|
{ID: "s1", Name: "Step", Position: 0, Color: "gray"},
|
|
}
|
|
export := BuildWorkflowExport(
|
|
[]*taskmodels.Workflow{wf},
|
|
map[string][]*WorkflowStep{"wf-1": steps},
|
|
resolver,
|
|
)
|
|
|
|
pw := export.Workflows[0]
|
|
assert.Nil(t, pw.AgentProfile, "unknown profile should resolve to nil")
|
|
})
|
|
}
|
|
|
|
func TestToInt(t *testing.T) {
|
|
t.Run("float64", func(t *testing.T) {
|
|
v, ok := toInt(float64(42))
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 42, v)
|
|
})
|
|
t.Run("int", func(t *testing.T) {
|
|
v, ok := toInt(7)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 7, v)
|
|
})
|
|
t.Run("string returns false", func(t *testing.T) {
|
|
_, ok := toInt("nope")
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("nil returns false", func(t *testing.T) {
|
|
_, ok := toInt(nil)
|
|
assert.False(t, ok)
|
|
})
|
|
}
|