mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
648 lines
17 KiB
Go
648 lines
17 KiB
Go
package plugin
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/boolean-maybe/tiki/task"
|
|
)
|
|
|
|
func TestSplitTopLevelCommas(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "simple split",
|
|
input: "status=todo, type=bug",
|
|
want: []string{"status=todo", "type=bug"},
|
|
},
|
|
{
|
|
name: "comma in quotes",
|
|
input: "assignee='O,Brien', status=done",
|
|
want: []string{"assignee='O,Brien'", "status=done"},
|
|
},
|
|
{
|
|
name: "comma in brackets",
|
|
input: "tags+=[one,two], status=done",
|
|
want: []string{"tags+=[one,two]", "status=done"},
|
|
},
|
|
{
|
|
name: "mixed quotes and brackets",
|
|
input: `tags+=[one,"two,three"], status=done`,
|
|
want: []string{`tags+=[one,"two,three"]`, "status=done"},
|
|
},
|
|
{
|
|
name: "unterminated quote",
|
|
input: "status='todo, type=bug",
|
|
wantErr: "unterminated quotes or brackets",
|
|
},
|
|
{
|
|
name: "unterminated brackets",
|
|
input: "tags+=[one,two, status=done",
|
|
wantErr: "unterminated quotes or brackets",
|
|
},
|
|
{
|
|
name: "unexpected closing bracket",
|
|
input: "status=todo], type=bug",
|
|
wantErr: "unexpected ']'",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := splitTopLevelCommas(tc.input)
|
|
if tc.wantErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Fatalf("expected %v, got %v", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseLaneAction(t *testing.T) {
|
|
action, err := ParseLaneAction("status=done, type=bug, priority=2, points=3, assignee='Alice', tags+=[frontend,'needs review'], dependsOn+=[TIKI-ABC123]")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(action.Ops) != 7 {
|
|
t.Fatalf("expected 7 ops, got %d", len(action.Ops))
|
|
}
|
|
|
|
gotFields := []ActionField{
|
|
action.Ops[0].Field,
|
|
action.Ops[1].Field,
|
|
action.Ops[2].Field,
|
|
action.Ops[3].Field,
|
|
action.Ops[4].Field,
|
|
action.Ops[5].Field,
|
|
action.Ops[6].Field,
|
|
}
|
|
wantFields := []ActionField{
|
|
ActionFieldStatus,
|
|
ActionFieldType,
|
|
ActionFieldPriority,
|
|
ActionFieldPoints,
|
|
ActionFieldAssignee,
|
|
ActionFieldTags,
|
|
ActionFieldDependsOn,
|
|
}
|
|
if !reflect.DeepEqual(gotFields, wantFields) {
|
|
t.Fatalf("expected fields %v, got %v", wantFields, gotFields)
|
|
}
|
|
|
|
if action.Ops[0].StrValue != "done" {
|
|
t.Fatalf("expected status value 'done', got %q", action.Ops[0].StrValue)
|
|
}
|
|
if action.Ops[1].StrValue != "bug" {
|
|
t.Fatalf("expected type value 'bug', got %q", action.Ops[1].StrValue)
|
|
}
|
|
if action.Ops[2].IntValue != 2 {
|
|
t.Fatalf("expected priority 2, got %d", action.Ops[2].IntValue)
|
|
}
|
|
if action.Ops[3].IntValue != 3 {
|
|
t.Fatalf("expected points 3, got %d", action.Ops[3].IntValue)
|
|
}
|
|
if action.Ops[4].StrValue != "Alice" {
|
|
t.Fatalf("expected assignee Alice, got %q", action.Ops[4].StrValue)
|
|
}
|
|
if !reflect.DeepEqual(action.Ops[5].Tags, []string{"frontend", "needs review"}) {
|
|
t.Fatalf("expected tags [frontend needs review], got %v", action.Ops[5].Tags)
|
|
}
|
|
if !reflect.DeepEqual(action.Ops[6].DependsOn, []string{"TIKI-ABC123"}) {
|
|
t.Fatalf("expected dependsOn [TIKI-ABC123], got %v", action.Ops[6].DependsOn)
|
|
}
|
|
}
|
|
|
|
func TestParseLaneAction_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "empty segment",
|
|
input: "status=done,,type=bug",
|
|
wantErr: "empty action segment",
|
|
},
|
|
{
|
|
name: "missing operator",
|
|
input: "statusdone",
|
|
wantErr: "missing operator",
|
|
},
|
|
{
|
|
name: "tags assign not allowed",
|
|
input: "tags=[one]",
|
|
wantErr: "tags action only supports",
|
|
},
|
|
{
|
|
name: "status add not allowed",
|
|
input: "status+=done",
|
|
wantErr: "status action only supports",
|
|
},
|
|
{
|
|
name: "unknown field",
|
|
input: "owner=me",
|
|
wantErr: "unknown action field",
|
|
},
|
|
{
|
|
name: "invalid status",
|
|
input: "status=unknown",
|
|
wantErr: "invalid status value",
|
|
},
|
|
{
|
|
name: "invalid type",
|
|
input: "type=unknown",
|
|
wantErr: "invalid type value",
|
|
},
|
|
{
|
|
name: "priority out of range",
|
|
input: "priority=10",
|
|
wantErr: "priority value out of range",
|
|
},
|
|
{
|
|
name: "points out of range",
|
|
input: "points=-1",
|
|
wantErr: "points value out of range",
|
|
},
|
|
{
|
|
name: "tags missing brackets",
|
|
input: "tags+={one}",
|
|
wantErr: "tags value must be in brackets",
|
|
},
|
|
{
|
|
name: "dependsOn assign not allowed",
|
|
input: "dependsOn=[TIKI-ABC123]",
|
|
wantErr: "dependsOn action only supports",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := ParseLaneAction(tc.input)
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction(t *testing.T) {
|
|
base := &task.Task{
|
|
ID: "TASK-1",
|
|
Title: "Task",
|
|
Status: task.StatusBacklog,
|
|
Type: task.TypeStory,
|
|
Priority: task.PriorityMedium,
|
|
Points: 1,
|
|
Tags: []string{"existing"},
|
|
DependsOn: []string{"TIKI-AAA111"},
|
|
Assignee: "Bob",
|
|
}
|
|
|
|
action, err := ParseLaneAction("status=done, type=bug, priority=2, points=3, assignee=Alice, tags+=[moved], dependsOn+=[TIKI-BBB222]")
|
|
if err != nil {
|
|
t.Fatalf("unexpected parse error: %v", err)
|
|
}
|
|
|
|
updated, err := ApplyLaneAction(base, action, "")
|
|
if err != nil {
|
|
t.Fatalf("unexpected apply error: %v", err)
|
|
}
|
|
|
|
if updated.Status != task.StatusDone {
|
|
t.Fatalf("expected status done, got %v", updated.Status)
|
|
}
|
|
if updated.Type != task.TypeBug {
|
|
t.Fatalf("expected type bug, got %v", updated.Type)
|
|
}
|
|
if updated.Priority != 2 {
|
|
t.Fatalf("expected priority 2, got %d", updated.Priority)
|
|
}
|
|
if updated.Points != 3 {
|
|
t.Fatalf("expected points 3, got %d", updated.Points)
|
|
}
|
|
if updated.Assignee != "Alice" {
|
|
t.Fatalf("expected assignee Alice, got %q", updated.Assignee)
|
|
}
|
|
if !reflect.DeepEqual(updated.Tags, []string{"existing", "moved"}) {
|
|
t.Fatalf("expected tags [existing moved], got %v", updated.Tags)
|
|
}
|
|
if !reflect.DeepEqual(updated.DependsOn, []string{"TIKI-AAA111", "TIKI-BBB222"}) {
|
|
t.Fatalf("expected dependsOn [TIKI-AAA111 TIKI-BBB222], got %v", updated.DependsOn)
|
|
}
|
|
if base.Status != task.StatusBacklog {
|
|
t.Fatalf("expected base task unchanged, got %v", base.Status)
|
|
}
|
|
if !reflect.DeepEqual(base.Tags, []string{"existing"}) {
|
|
t.Fatalf("expected base tags unchanged, got %v", base.Tags)
|
|
}
|
|
if !reflect.DeepEqual(base.DependsOn, []string{"TIKI-AAA111"}) {
|
|
t.Fatalf("expected base dependsOn unchanged, got %v", base.DependsOn)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_InvalidResult(t *testing.T) {
|
|
// ApplyLaneAction no longer validates the result — the gate does that
|
|
// at persistence time. This test verifies the action is applied as-is.
|
|
base := &task.Task{
|
|
ID: "TASK-1",
|
|
Title: "Task",
|
|
Status: task.StatusBacklog,
|
|
Type: task.TypeStory,
|
|
Priority: task.PriorityMedium,
|
|
Points: 1,
|
|
}
|
|
|
|
action := LaneAction{
|
|
Ops: []LaneActionOp{
|
|
{
|
|
Field: ActionFieldPriority,
|
|
Operator: ActionOperatorAssign,
|
|
IntValue: 99,
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := ApplyLaneAction(base, action, "")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.Priority != 99 {
|
|
t.Errorf("expected priority 99, got %d", result.Priority)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_AssigneeCurrentUser(t *testing.T) {
|
|
base := &task.Task{
|
|
ID: "TASK-1",
|
|
Title: "Task",
|
|
Status: task.StatusBacklog,
|
|
Type: task.TypeStory,
|
|
Priority: task.PriorityMedium,
|
|
Points: 1,
|
|
Assignee: "Bob",
|
|
}
|
|
|
|
action, err := ParseLaneAction("assignee=CURRENT_USER")
|
|
if err != nil {
|
|
t.Fatalf("unexpected parse error: %v", err)
|
|
}
|
|
|
|
updated, err := ApplyLaneAction(base, action, "Alex")
|
|
if err != nil {
|
|
t.Fatalf("unexpected apply error: %v", err)
|
|
}
|
|
if updated.Assignee != "Alex" {
|
|
t.Fatalf("expected assignee Alex, got %q", updated.Assignee)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_AssigneeCurrentUserMissing(t *testing.T) {
|
|
base := &task.Task{
|
|
ID: "TASK-1",
|
|
Title: "Task",
|
|
Status: task.StatusBacklog,
|
|
Type: task.TypeStory,
|
|
Priority: task.PriorityMedium,
|
|
Points: 1,
|
|
}
|
|
|
|
action, err := ParseLaneAction("assignee=CURRENT_USER")
|
|
if err != nil {
|
|
t.Fatalf("unexpected parse error: %v", err)
|
|
}
|
|
|
|
_, err = ApplyLaneAction(base, action, "")
|
|
if err == nil {
|
|
t.Fatalf("expected error for missing current user")
|
|
}
|
|
if !strings.Contains(err.Error(), "current user") {
|
|
t.Fatalf("expected current user error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseLaneAction_Due(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
checkFunc func(*testing.T, LaneAction)
|
|
}{
|
|
{
|
|
name: "due with valid date",
|
|
input: "due=2026-03-16",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
if len(action.Ops) != 1 {
|
|
t.Fatalf("expected 1 op, got %d", len(action.Ops))
|
|
}
|
|
op := action.Ops[0]
|
|
if op.Field != ActionFieldDue {
|
|
t.Errorf("expected field due, got %v", op.Field)
|
|
}
|
|
if op.Operator != ActionOperatorAssign {
|
|
t.Errorf("expected operator =, got %v", op.Operator)
|
|
}
|
|
expectedDate := "2026-03-16"
|
|
gotDate := op.DueValue.Format(task.DateFormat)
|
|
if gotDate != expectedDate {
|
|
t.Errorf("expected date %v, got %v", expectedDate, gotDate)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "due with quoted date",
|
|
input: "due='2026-03-16'",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
if len(action.Ops) != 1 {
|
|
t.Fatalf("expected 1 op, got %d", len(action.Ops))
|
|
}
|
|
op := action.Ops[0]
|
|
expectedDate := "2026-03-16"
|
|
gotDate := op.DueValue.Format(task.DateFormat)
|
|
if gotDate != expectedDate {
|
|
t.Errorf("expected date %v, got %v", expectedDate, gotDate)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "due with empty string (clear)",
|
|
input: "due=''",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
if len(action.Ops) != 1 {
|
|
t.Fatalf("expected 1 op, got %d", len(action.Ops))
|
|
}
|
|
op := action.Ops[0]
|
|
if !op.DueValue.IsZero() {
|
|
t.Errorf("expected zero time for empty string, got %v", op.DueValue)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "due with invalid date format",
|
|
input: "due=03/16/2026",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "due with += operator",
|
|
input: "due+=2026-03-16",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "due with -= operator",
|
|
input: "due-=2026-03-16",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
action, err := ParseLaneAction(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ParseLaneAction() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err == nil && tt.checkFunc != nil {
|
|
tt.checkFunc(t, action)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseLaneAction_Recurrence(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
checkFunc func(*testing.T, LaneAction)
|
|
}{
|
|
{
|
|
name: "recurrence with cron pattern",
|
|
input: "recurrence='0 0 * * MON'",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
if len(action.Ops) != 1 {
|
|
t.Fatalf("expected 1 op, got %d", len(action.Ops))
|
|
}
|
|
op := action.Ops[0]
|
|
if op.Field != ActionFieldRecurrence {
|
|
t.Errorf("expected field recurrence, got %v", op.Field)
|
|
}
|
|
if op.RecurrenceValue != task.Recurrence("0 0 * * MON") {
|
|
t.Errorf("expected '0 0 * * MON', got %q", op.RecurrenceValue)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "recurrence with display name",
|
|
input: "recurrence=Daily",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
op := action.Ops[0]
|
|
if op.RecurrenceValue != task.RecurrenceDaily {
|
|
t.Errorf("expected daily cron, got %q", op.RecurrenceValue)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "recurrence clear with empty string",
|
|
input: "recurrence=''",
|
|
wantErr: false,
|
|
checkFunc: func(t *testing.T, action LaneAction) {
|
|
op := action.Ops[0]
|
|
if op.RecurrenceValue != task.RecurrenceNone {
|
|
t.Errorf("expected empty recurrence, got %q", op.RecurrenceValue)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "recurrence invalid value",
|
|
input: "recurrence=biweekly",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "recurrence += not allowed",
|
|
input: "recurrence+=Daily",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
action, err := ParseLaneAction(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ParseLaneAction() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err == nil && tt.checkFunc != nil {
|
|
tt.checkFunc(t, action)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_Recurrence(t *testing.T) {
|
|
base := &task.Task{
|
|
ID: "TIKI-TEST01",
|
|
Title: "Test Task",
|
|
Type: task.TypeStory,
|
|
Status: "backlog",
|
|
Priority: 3,
|
|
}
|
|
|
|
t.Run("set recurrence", func(t *testing.T) {
|
|
action, err := ParseLaneAction("recurrence='0 0 * * MON'")
|
|
if err != nil {
|
|
t.Fatalf("ParseLaneAction() error = %v", err)
|
|
}
|
|
result, err := ApplyLaneAction(base, action, "")
|
|
if err != nil {
|
|
t.Fatalf("ApplyLaneAction() error = %v", err)
|
|
}
|
|
if result.Recurrence != task.Recurrence("0 0 * * MON") {
|
|
t.Errorf("expected '0 0 * * MON', got %q", result.Recurrence)
|
|
}
|
|
})
|
|
|
|
t.Run("clear recurrence", func(t *testing.T) {
|
|
baseWithRec := base.Clone()
|
|
baseWithRec.Recurrence = "0 0 * * MON"
|
|
|
|
action, err := ParseLaneAction("recurrence=''")
|
|
if err != nil {
|
|
t.Fatalf("ParseLaneAction() error = %v", err)
|
|
}
|
|
result, err := ApplyLaneAction(baseWithRec, action, "")
|
|
if err != nil {
|
|
t.Fatalf("ApplyLaneAction() error = %v", err)
|
|
}
|
|
if result.Recurrence != task.RecurrenceNone {
|
|
t.Errorf("expected empty recurrence, got %q", result.Recurrence)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestApplyLaneAction_Due(t *testing.T) {
|
|
base := &task.Task{
|
|
ID: "TIKI-TEST01",
|
|
Title: "Test Task",
|
|
Type: task.TypeStory,
|
|
Status: "backlog",
|
|
Priority: 3,
|
|
}
|
|
|
|
t.Run("set due date", func(t *testing.T) {
|
|
action, err := ParseLaneAction("due=2026-03-16")
|
|
if err != nil {
|
|
t.Fatalf("ParseLaneAction() error = %v", err)
|
|
}
|
|
|
|
result, err := ApplyLaneAction(base, action, "")
|
|
if err != nil {
|
|
t.Fatalf("ApplyLaneAction() error = %v", err)
|
|
}
|
|
|
|
expectedDate := "2026-03-16"
|
|
gotDate := result.Due.Format(task.DateFormat)
|
|
if gotDate != expectedDate {
|
|
t.Errorf("expected due date %v, got %v", expectedDate, gotDate)
|
|
}
|
|
})
|
|
|
|
t.Run("clear due date", func(t *testing.T) {
|
|
baseWithDue := base.Clone()
|
|
baseWithDue.Due, _ = time.Parse(task.DateFormat, "2026-03-16")
|
|
|
|
action, err := ParseLaneAction("due=''")
|
|
if err != nil {
|
|
t.Fatalf("ParseLaneAction() error = %v", err)
|
|
}
|
|
|
|
result, err := ApplyLaneAction(baseWithDue, action, "")
|
|
if err != nil {
|
|
t.Fatalf("ApplyLaneAction() error = %v", err)
|
|
}
|
|
|
|
if !result.Due.IsZero() {
|
|
t.Errorf("expected zero due date, got %v", result.Due)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParseLaneAction_EmptyString(t *testing.T) {
|
|
action, err := ParseLaneAction("")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(action.Ops) != 0 {
|
|
t.Errorf("expected 0 ops for empty input, got %d", len(action.Ops))
|
|
}
|
|
}
|
|
|
|
func TestParseLaneAction_InvalidInteger(t *testing.T) {
|
|
_, err := ParseLaneAction("priority=abc")
|
|
if err == nil {
|
|
t.Fatal("expected error for non-integer priority")
|
|
}
|
|
if !strings.Contains(err.Error(), "invalid integer") {
|
|
t.Errorf("expected 'invalid integer' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_NilTask(t *testing.T) {
|
|
action := LaneAction{Ops: []LaneActionOp{{Field: ActionFieldStatus, Operator: ActionOperatorAssign, StrValue: "done"}}}
|
|
_, err := ApplyLaneAction(nil, action, "")
|
|
if err == nil {
|
|
t.Fatal("expected error for nil task")
|
|
}
|
|
if !strings.Contains(err.Error(), "task is nil") {
|
|
t.Errorf("expected 'task is nil' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_NoOps(t *testing.T) {
|
|
base := &task.Task{ID: "TASK-1", Title: "Task", Status: task.StatusBacklog}
|
|
result, err := ApplyLaneAction(base, LaneAction{}, "")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result == base {
|
|
t.Error("expected clone, not original pointer")
|
|
}
|
|
if result.Title != "Task" {
|
|
t.Errorf("expected title 'Task', got %q", result.Title)
|
|
}
|
|
}
|
|
|
|
func TestApplyLaneAction_UnsupportedField(t *testing.T) {
|
|
base := &task.Task{ID: "TASK-1", Title: "Task", Status: task.StatusBacklog}
|
|
action := LaneAction{Ops: []LaneActionOp{{Field: "bogus", Operator: ActionOperatorAssign, StrValue: "x"}}}
|
|
_, err := ApplyLaneAction(base, action, "")
|
|
if err == nil {
|
|
t.Fatal("expected error for unsupported field")
|
|
}
|
|
if !strings.Contains(err.Error(), "unsupported action field") {
|
|
t.Errorf("expected 'unsupported action field' error, got: %v", err)
|
|
}
|
|
}
|