mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
451 lines
11 KiB
Go
451 lines
11 KiB
Go
package model
|
|
|
|
import (
|
|
"testing"
|
|
|
|
taskpkg "github.com/boolean-maybe/tiki/task"
|
|
)
|
|
|
|
func TestTaskDetailParams_EncodeDecodeRoundTrip(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params TaskDetailParams
|
|
}{
|
|
{
|
|
name: "simple task ID",
|
|
params: TaskDetailParams{TaskID: "TIKI-1"},
|
|
},
|
|
{
|
|
name: "task ID with hyphen",
|
|
params: TaskDetailParams{TaskID: "TIKI-123"},
|
|
},
|
|
{
|
|
name: "task ID with special format",
|
|
params: TaskDetailParams{TaskID: "PROJECT-999"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Encode
|
|
encoded := EncodeTaskDetailParams(tt.params)
|
|
|
|
// Decode
|
|
decoded := DecodeTaskDetailParams(encoded)
|
|
|
|
// Verify round-trip
|
|
if decoded.TaskID != tt.params.TaskID {
|
|
t.Errorf("round-trip failed: TaskID = %q, want %q", decoded.TaskID, tt.params.TaskID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskDetailParams_EmptyTaskID(t *testing.T) {
|
|
// Empty task ID should encode to nil
|
|
params := TaskDetailParams{TaskID: ""}
|
|
encoded := EncodeTaskDetailParams(params)
|
|
|
|
if encoded != nil {
|
|
t.Errorf("EncodeTaskDetailParams with empty TaskID = %v, want nil", encoded)
|
|
}
|
|
|
|
// Decoding nil should return zero value
|
|
decoded := DecodeTaskDetailParams(nil)
|
|
if decoded.TaskID != "" {
|
|
t.Errorf("DecodeTaskDetailParams(nil) TaskID = %q, want empty", decoded.TaskID)
|
|
}
|
|
}
|
|
|
|
func TestTaskDetailParams_DecodeInvalidParams(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params map[string]interface{}
|
|
want TaskDetailParams
|
|
}{
|
|
{
|
|
name: "nil params",
|
|
params: nil,
|
|
want: TaskDetailParams{},
|
|
},
|
|
{
|
|
name: "empty params",
|
|
params: map[string]interface{}{},
|
|
want: TaskDetailParams{},
|
|
},
|
|
{
|
|
name: "wrong type for taskID",
|
|
params: map[string]interface{}{"taskID": 123},
|
|
want: TaskDetailParams{},
|
|
},
|
|
{
|
|
name: "missing taskID",
|
|
params: map[string]interface{}{"other": "value"},
|
|
want: TaskDetailParams{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
decoded := DecodeTaskDetailParams(tt.params)
|
|
if decoded.TaskID != tt.want.TaskID {
|
|
t.Errorf("DecodeTaskDetailParams() TaskID = %q, want %q", decoded.TaskID, tt.want.TaskID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskDetailParams_ReadOnlyRoundTrip(t *testing.T) {
|
|
// ReadOnly=true should survive encode/decode
|
|
params := TaskDetailParams{
|
|
TaskID: "TIKI-1",
|
|
ReadOnly: true,
|
|
}
|
|
|
|
encoded := EncodeTaskDetailParams(params)
|
|
decoded := DecodeTaskDetailParams(encoded)
|
|
|
|
if !decoded.ReadOnly {
|
|
t.Error("round-trip failed: ReadOnly = false, want true")
|
|
}
|
|
|
|
// ReadOnly=false should not be stored (keeps params map clean)
|
|
paramsNoRO := TaskDetailParams{
|
|
TaskID: "TIKI-2",
|
|
}
|
|
encodedNoRO := EncodeTaskDetailParams(paramsNoRO)
|
|
if _, exists := encodedNoRO[paramReadOnly]; exists {
|
|
t.Error("ReadOnly=false should not be stored in encoded params")
|
|
}
|
|
|
|
decodedNoRO := DecodeTaskDetailParams(encodedNoRO)
|
|
if decodedNoRO.ReadOnly {
|
|
t.Error("ReadOnly should default to false")
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_EncodeDecodeRoundTrip(t *testing.T) {
|
|
draftTask := &taskpkg.Task{
|
|
ID: "TIKI-42",
|
|
Title: "Test Task",
|
|
Status: taskpkg.StatusReady,
|
|
Type: taskpkg.TypeStory,
|
|
Priority: 3,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
params TaskEditParams
|
|
}{
|
|
{
|
|
name: "task ID only",
|
|
params: TaskEditParams{
|
|
TaskID: "TIKI-1",
|
|
},
|
|
},
|
|
{
|
|
name: "task ID with draft",
|
|
params: TaskEditParams{
|
|
TaskID: "TIKI-42",
|
|
Draft: draftTask,
|
|
},
|
|
},
|
|
{
|
|
name: "task ID with focus",
|
|
params: TaskEditParams{
|
|
TaskID: "TIKI-1",
|
|
Focus: EditFieldTitle,
|
|
},
|
|
},
|
|
{
|
|
name: "all fields",
|
|
params: TaskEditParams{
|
|
TaskID: "TIKI-42",
|
|
Draft: draftTask,
|
|
Focus: EditFieldDescription,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Encode
|
|
encoded := EncodeTaskEditParams(tt.params)
|
|
|
|
// Decode
|
|
decoded := DecodeTaskEditParams(encoded)
|
|
|
|
// Verify round-trip
|
|
if decoded.TaskID != tt.params.TaskID {
|
|
t.Errorf("round-trip failed: TaskID = %q, want %q", decoded.TaskID, tt.params.TaskID)
|
|
}
|
|
|
|
if tt.params.Draft != nil {
|
|
if decoded.Draft == nil {
|
|
t.Error("round-trip failed: Draft = nil, want non-nil")
|
|
} else if decoded.Draft.ID != tt.params.Draft.ID {
|
|
t.Errorf("round-trip failed: Draft.ID = %q, want %q",
|
|
decoded.Draft.ID, tt.params.Draft.ID)
|
|
}
|
|
} else if decoded.Draft != nil {
|
|
t.Error("round-trip failed: Draft != nil, want nil")
|
|
}
|
|
|
|
if decoded.Focus != tt.params.Focus {
|
|
t.Errorf("round-trip failed: Focus = %v, want %v", decoded.Focus, tt.params.Focus)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_DescOnlyRoundTrip(t *testing.T) {
|
|
params := TaskEditParams{
|
|
TaskID: "TIKI-1",
|
|
Focus: EditFieldDescription,
|
|
DescOnly: true,
|
|
}
|
|
|
|
encoded := EncodeTaskEditParams(params)
|
|
decoded := DecodeTaskEditParams(encoded)
|
|
|
|
if !decoded.DescOnly {
|
|
t.Error("round-trip failed: DescOnly = false, want true")
|
|
}
|
|
if decoded.Focus != EditFieldDescription {
|
|
t.Errorf("round-trip failed: Focus = %v, want %v", decoded.Focus, EditFieldDescription)
|
|
}
|
|
|
|
// verify DescOnly=false is not stored (keeps params map clean)
|
|
paramsNoDesc := TaskEditParams{
|
|
TaskID: "TIKI-2",
|
|
}
|
|
encodedNoDesc := EncodeTaskEditParams(paramsNoDesc)
|
|
if _, exists := encodedNoDesc[paramDescOnly]; exists {
|
|
t.Error("DescOnly=false should not be stored in encoded params")
|
|
}
|
|
|
|
decodedNoDesc := DecodeTaskEditParams(encodedNoDesc)
|
|
if decodedNoDesc.DescOnly {
|
|
t.Error("DescOnly should default to false")
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_DraftWithoutTaskID(t *testing.T) {
|
|
// When Draft is present but TaskID is empty, TaskID should be inferred from Draft
|
|
draftTask := &taskpkg.Task{
|
|
ID: "TIKI-100",
|
|
Title: "Draft Task",
|
|
}
|
|
|
|
params := TaskEditParams{
|
|
TaskID: "",
|
|
Draft: draftTask,
|
|
}
|
|
|
|
encoded := EncodeTaskEditParams(params)
|
|
|
|
// TaskID should be inferred
|
|
if encoded == nil {
|
|
t.Fatal("EncodeTaskEditParams returned nil")
|
|
}
|
|
|
|
if encoded["taskID"] != "TIKI-100" {
|
|
t.Errorf("taskID = %v, want TIKI-100", encoded["taskID"])
|
|
}
|
|
|
|
// Decoding should preserve the inference
|
|
decoded := DecodeTaskEditParams(encoded)
|
|
if decoded.TaskID != "TIKI-100" {
|
|
t.Errorf("decoded TaskID = %q, want TIKI-100", decoded.TaskID)
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_EmptyTaskID(t *testing.T) {
|
|
// Empty task ID and nil draft should encode to nil
|
|
params := TaskEditParams{
|
|
TaskID: "",
|
|
Draft: nil,
|
|
}
|
|
|
|
encoded := EncodeTaskEditParams(params)
|
|
|
|
if encoded != nil {
|
|
t.Errorf("EncodeTaskEditParams with empty TaskID and nil Draft = %v, want nil", encoded)
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_FocusStringEncoding(t *testing.T) {
|
|
// Focus should be encoded as string for interop
|
|
params := TaskEditParams{
|
|
TaskID: "TIKI-1",
|
|
Focus: EditFieldTitle,
|
|
}
|
|
|
|
encoded := EncodeTaskEditParams(params)
|
|
|
|
// Verify focus is stored as string
|
|
focusVal, ok := encoded["focus"]
|
|
if !ok {
|
|
t.Fatal("focus not in encoded params")
|
|
}
|
|
|
|
focusStr, ok := focusVal.(string)
|
|
if !ok {
|
|
t.Errorf("focus type = %T, want string", focusVal)
|
|
}
|
|
|
|
if focusStr != string(EditFieldTitle) {
|
|
t.Errorf("focus string = %q, want %q", focusStr, string(EditFieldTitle))
|
|
}
|
|
|
|
// Decoding string focus should work
|
|
decoded := DecodeTaskEditParams(encoded)
|
|
if decoded.Focus != EditFieldTitle {
|
|
t.Errorf("decoded Focus = %v, want %v", decoded.Focus, EditFieldTitle)
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_FocusEditFieldType(t *testing.T) {
|
|
// Decode should handle focus as EditField type too
|
|
params := map[string]interface{}{
|
|
"taskID": "TIKI-1",
|
|
"focus": EditFieldDescription, // EditField type, not string
|
|
}
|
|
|
|
decoded := DecodeTaskEditParams(params)
|
|
|
|
if decoded.Focus != EditFieldDescription {
|
|
t.Errorf("Focus = %v, want %v", decoded.Focus, EditFieldDescription)
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_DecodeInvalidParams(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params map[string]interface{}
|
|
want TaskEditParams
|
|
}{
|
|
{
|
|
name: "nil params",
|
|
params: nil,
|
|
want: TaskEditParams{},
|
|
},
|
|
{
|
|
name: "empty params",
|
|
params: map[string]interface{}{},
|
|
want: TaskEditParams{},
|
|
},
|
|
{
|
|
name: "wrong type for taskID",
|
|
params: map[string]interface{}{"taskID": 123},
|
|
want: TaskEditParams{},
|
|
},
|
|
{
|
|
name: "wrong type for draft",
|
|
params: map[string]interface{}{
|
|
"taskID": "TIKI-1",
|
|
"draftTask": "not a task",
|
|
},
|
|
want: TaskEditParams{TaskID: "TIKI-1"},
|
|
},
|
|
{
|
|
name: "wrong type for focus",
|
|
params: map[string]interface{}{
|
|
"taskID": "TIKI-1",
|
|
"focus": 123,
|
|
},
|
|
want: TaskEditParams{TaskID: "TIKI-1"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
decoded := DecodeTaskEditParams(tt.params)
|
|
|
|
if decoded.TaskID != tt.want.TaskID {
|
|
t.Errorf("TaskID = %q, want %q", decoded.TaskID, tt.want.TaskID)
|
|
}
|
|
|
|
if tt.want.Draft == nil && decoded.Draft != nil {
|
|
t.Error("Draft != nil, want nil")
|
|
}
|
|
|
|
if tt.want.Focus == "" && decoded.Focus != "" {
|
|
t.Errorf("Focus = %v, want empty", decoded.Focus)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_DraftTaskIDInference(t *testing.T) {
|
|
// When Draft has an ID but TaskID param is empty, it should be inferred
|
|
draftTask := &taskpkg.Task{
|
|
ID: "TIKI-999",
|
|
Title: "Draft",
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"taskID": "",
|
|
"draftTask": draftTask,
|
|
}
|
|
|
|
decoded := DecodeTaskEditParams(params)
|
|
|
|
// TaskID should be inferred from Draft
|
|
if decoded.TaskID != "TIKI-999" {
|
|
t.Errorf("TaskID = %q, want TIKI-999 (inferred from Draft)", decoded.TaskID)
|
|
}
|
|
|
|
if decoded.Draft == nil {
|
|
t.Error("Draft = nil, want non-nil")
|
|
}
|
|
}
|
|
|
|
func TestTaskEditParams_NilDraftNoInference(t *testing.T) {
|
|
// When Draft is nil, TaskID should not be inferred
|
|
params := map[string]interface{}{
|
|
"taskID": "",
|
|
"draftTask": (*taskpkg.Task)(nil),
|
|
}
|
|
|
|
decoded := DecodeTaskEditParams(params)
|
|
|
|
if decoded.TaskID != "" {
|
|
t.Errorf("TaskID = %q, want empty (no draft to infer from)", decoded.TaskID)
|
|
}
|
|
|
|
if decoded.Draft != nil {
|
|
t.Error("Draft != nil, want nil")
|
|
}
|
|
}
|
|
|
|
func TestViewParams_ParamKeyConstants(t *testing.T) {
|
|
// Verify that the param keys used internally match expectations
|
|
// This is more of a documentation test
|
|
|
|
detailParams := EncodeTaskDetailParams(TaskDetailParams{TaskID: "TIKI-1"})
|
|
if _, ok := detailParams["taskID"]; !ok {
|
|
t.Error("TaskDetailParams should use 'taskID' key")
|
|
}
|
|
|
|
editParams := EncodeTaskEditParams(TaskEditParams{
|
|
TaskID: "TIKI-1",
|
|
Draft: &taskpkg.Task{
|
|
ID: "TIKI-1",
|
|
Title: "Test",
|
|
},
|
|
Focus: EditFieldTitle,
|
|
})
|
|
|
|
if _, ok := editParams["taskID"]; !ok {
|
|
t.Error("TaskEditParams should use 'taskID' key")
|
|
}
|
|
|
|
if _, ok := editParams["draftTask"]; !ok {
|
|
t.Error("TaskEditParams should use 'draftTask' key for Draft")
|
|
}
|
|
|
|
if _, ok := editParams["focus"]; !ok {
|
|
t.Error("TaskEditParams should use 'focus' key for Focus")
|
|
}
|
|
}
|