vikunja/veans/e2e/tasks_test.go

162 lines
5.0 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package e2e
import (
"encoding/json"
"fmt"
"strings"
"testing"
"code.vikunja.io/veans/internal/client"
)
// TestCreateShowList_RoundTrip verifies the read+write path against a real
// Vikunja: provision a workspace via init, create a task, show it, list it
// (with --filter), and confirm the JSON shapes are unwrapped raw object/array.
func TestCreateShowList_RoundTrip(t *testing.T) {
ws, h := provisionWorkspace(t)
// Create a task with a description and a label.
out, errOut, code := h.Run(t, ws,
"create", "Test bug fix",
"-d", "## Repro\n- [ ] step 1\n- [ ] step 2",
"--label", "bug",
"--priority", "3",
)
if code != 0 {
t.Fatalf("create exit %d\n%s\n%s", code, out, errOut)
}
var created client.Task
if err := json.Unmarshal([]byte(out), &created); err != nil {
t.Fatalf("decode create: %v\n%s", err, out)
}
if created.Title != "Test bug fix" {
t.Fatalf("created title = %q", created.Title)
}
if created.Priority != 3 {
t.Fatalf("priority = %d", created.Priority)
}
if !taskHasLabelTitle(&created, "veans:bug") {
t.Fatalf("expected veans:bug label on created task; got %+v", created.Labels)
}
// Show with --json — should be a raw object, not enveloped.
id := fmt.Sprintf("%d", created.Index)
showOut, _, code := h.Run(t, ws, "show", id)
if code != 0 {
t.Fatalf("show exit %d", code)
}
var shown client.Task
if err := json.Unmarshal([]byte(showOut), &shown); err != nil {
t.Fatalf("decode show: %v\n%s", err, showOut)
}
if shown.ID != created.ID {
t.Fatalf("show returned wrong task: %d vs %d", shown.ID, created.ID)
}
// List with --json — should be a raw array.
listOut, _, code := h.Run(t, ws, "list")
if code != 0 {
t.Fatalf("list exit %d", code)
}
var listed []*client.Task
if err := json.Unmarshal([]byte(listOut), &listed); err != nil {
t.Fatalf("decode list: %v\n%s", err, listOut)
}
if len(listed) == 0 {
t.Fatalf("list returned empty array; expected at least our created task")
}
// --filter passthrough: only items with priority > 2.
filterOut, _, code := h.Run(t, ws, "list", "--filter", "priority > 2")
if code != 0 {
t.Fatalf("list --filter exit %d\n%s", code, filterOut)
}
var filtered []*client.Task
if err := json.Unmarshal([]byte(filterOut), &filtered); err != nil {
t.Fatalf("decode filter list: %v", err)
}
for _, ft := range filtered {
if ft.Priority <= 2 {
t.Fatalf("filter leaked priority=%d task into result", ft.Priority)
}
}
// Empty result set must encode as `[]`, not `null` — JSON-parsing agents
// can't reliably branch on the latter. Use a filter that matches nothing.
emptyOut, _, code := h.Run(t, ws, "list", "--filter", "priority > 10")
if code != 0 {
t.Fatalf("list (empty) exit %d\n%s", code, emptyOut)
}
if got := strings.TrimSpace(emptyOut); got != "[]" {
t.Fatalf("empty list should print []; got %q", got)
}
}
// TestUpdate_DescriptionReplaceUniqueness pins the agent-friendly Edit-tool
// behavior: the "old" string must match exactly once, otherwise the update
// errors and nothing changes.
func TestUpdate_DescriptionReplaceUniqueness(t *testing.T) {
ws, h := provisionWorkspace(t)
out, errOut, code := h.Run(t, ws, "create", "checkbox task",
"-d", "- [ ] step 1\n- [ ] step 1 (again)",
)
if code != 0 {
t.Fatalf("create exit %d\n%s\n%s", code, out, errOut)
}
var created client.Task
if err := json.Unmarshal([]byte(out), &created); err != nil {
t.Fatal(err)
}
id := fmt.Sprintf("%d", created.Index)
// Non-unique replace should fail with a validation error and a
// non-zero exit code.
_, stderr, code := h.Run(t, ws,
"update", id,
"--description-replace-old", "step 1",
"--description-replace-new", "step 1 [x]",
)
if code == 0 {
t.Fatalf("expected non-zero exit on non-unique replace")
}
if !strings.Contains(stderr, "VALIDATION_ERROR") {
t.Fatalf("expected VALIDATION_ERROR in stderr, got: %s", stderr)
}
// Disambiguate and try again — should succeed.
_, _, code = h.Run(t, ws,
"update", id,
"--description-replace-old", "- [ ] step 1\n",
"--description-replace-new", "- [x] step 1\n",
)
if code != 0 {
t.Fatalf("disambiguated update should succeed; exit %d", code)
}
}
func taskHasLabelTitle(t *client.Task, title string) bool {
for _, l := range t.Labels {
if l != nil && l.Title == title {
return true
}
}
return false
}