c99a4a2d95
- Add filter.go: Parse filters (+tag, -tag, attribute:value, IDs) - Implement Filter.ToSQL() for WHERE clause generation - Add modifier.go: Parse modifiers (set/clear attributes, add/remove tags) - Implement Modifier.Apply() to update existing tasks - Add dateparse.go: Smart date parsing (ISO, today, tomorrow, weekdays) - Implement nextWeekday logic (smart Sunday interpretation) - Update GetTasks() to accept Filter parameter - Add CreateTaskWithModifier() for task creation with modifiers - Add comprehensive test suite (13 new tests, all passing) - Support filtering by status, project, priority, tags, UUIDs, display IDs - Support modifying priority, project, dates, recurrence, tags
291 lines
5.9 KiB
Go
291 lines
5.9 KiB
Go
package engine
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
// Setup test database
|
|
os.Setenv("HOME", "/tmp/opal-test")
|
|
|
|
// Ensure config directory exists
|
|
configDir := "/tmp/opal-test/.config/jade"
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := InitDB(); err != nil {
|
|
panic(err)
|
|
}
|
|
defer CloseDB()
|
|
|
|
// Run tests
|
|
code := m.Run()
|
|
|
|
// Cleanup
|
|
os.RemoveAll("/tmp/opal-test/.config")
|
|
os.Exit(code)
|
|
}
|
|
|
|
func TestCreateTask(t *testing.T) {
|
|
task, err := CreateTask("Test task")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
if task.UUID == uuid.Nil {
|
|
t.Error("Task UUID should not be nil")
|
|
}
|
|
|
|
if task.Description != "Test task" {
|
|
t.Errorf("Expected description 'Test task', got '%s'", task.Description)
|
|
}
|
|
|
|
if task.Status != StatusPending {
|
|
t.Errorf("Expected status Pending, got %v", task.Status)
|
|
}
|
|
|
|
if task.Priority != PriorityDefault {
|
|
t.Errorf("Expected priority Default, got %v", task.Priority)
|
|
}
|
|
}
|
|
|
|
func TestGetTask(t *testing.T) {
|
|
// Create a task
|
|
created, err := CreateTask("Test get task")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
// Retrieve it
|
|
retrieved, err := GetTask(created.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if retrieved.UUID != created.UUID {
|
|
t.Error("UUIDs don't match")
|
|
}
|
|
|
|
if retrieved.Description != created.Description {
|
|
t.Error("Descriptions don't match")
|
|
}
|
|
}
|
|
|
|
func TestTaskSave(t *testing.T) {
|
|
task, err := CreateTask("Test save")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
// Modify task
|
|
task.Description = "Modified description"
|
|
task.Priority = PriorityHigh
|
|
project := "test-project"
|
|
task.Project = &project
|
|
|
|
if err := task.Save(); err != nil {
|
|
t.Fatalf("Failed to save task: %v", err)
|
|
}
|
|
|
|
// Retrieve and verify
|
|
retrieved, err := GetTask(task.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if retrieved.Description != "Modified description" {
|
|
t.Error("Description not updated")
|
|
}
|
|
|
|
if retrieved.Priority != PriorityHigh {
|
|
t.Error("Priority not updated")
|
|
}
|
|
|
|
if retrieved.Project == nil || *retrieved.Project != "test-project" {
|
|
t.Error("Project not updated")
|
|
}
|
|
}
|
|
|
|
func TestTaskTags(t *testing.T) {
|
|
task, err := CreateTask("Test tags")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
// Add tags
|
|
if err := task.AddTag("urgent"); err != nil {
|
|
t.Fatalf("Failed to add tag: %v", err)
|
|
}
|
|
|
|
if err := task.AddTag("work"); err != nil {
|
|
t.Fatalf("Failed to add tag: %v", err)
|
|
}
|
|
|
|
// Retrieve and verify tags
|
|
retrieved, err := GetTask(task.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if len(retrieved.Tags) != 2 {
|
|
t.Errorf("Expected 2 tags, got %d", len(retrieved.Tags))
|
|
}
|
|
|
|
// Remove a tag
|
|
if err := retrieved.RemoveTag("urgent"); err != nil {
|
|
t.Fatalf("Failed to remove tag: %v", err)
|
|
}
|
|
|
|
// Verify removal
|
|
retrieved2, err := GetTask(task.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if len(retrieved2.Tags) != 1 {
|
|
t.Errorf("Expected 1 tag after removal, got %d", len(retrieved2.Tags))
|
|
}
|
|
|
|
if retrieved2.Tags[0] != "work" {
|
|
t.Errorf("Expected remaining tag 'work', got '%s'", retrieved2.Tags[0])
|
|
}
|
|
}
|
|
|
|
func TestTaskComplete(t *testing.T) {
|
|
task, err := CreateTask("Test complete")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
if err := task.Complete(); err != nil {
|
|
t.Fatalf("Failed to complete task: %v", err)
|
|
}
|
|
|
|
if task.Status != StatusCompleted {
|
|
t.Error("Status should be Completed")
|
|
}
|
|
|
|
if task.End == nil {
|
|
t.Error("End time should be set")
|
|
}
|
|
|
|
// Verify in database
|
|
retrieved, err := GetTask(task.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if retrieved.Status != StatusCompleted {
|
|
t.Error("Retrieved task should be completed")
|
|
}
|
|
}
|
|
|
|
func TestTaskDelete(t *testing.T) {
|
|
// Test soft delete
|
|
task1, err := CreateTask("Test soft delete")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
if err := task1.Delete(false); err != nil {
|
|
t.Fatalf("Failed to soft delete: %v", err)
|
|
}
|
|
|
|
retrieved, err := GetTask(task1.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get deleted task: %v", err)
|
|
}
|
|
|
|
if retrieved.Status != StatusDeleted {
|
|
t.Error("Task should be marked as deleted")
|
|
}
|
|
|
|
// Test hard delete
|
|
task2, err := CreateTask("Test hard delete")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
if err := task2.Delete(true); err != nil {
|
|
t.Fatalf("Failed to hard delete: %v", err)
|
|
}
|
|
|
|
_, err = GetTask(task2.UUID)
|
|
if err == nil {
|
|
t.Error("Should not be able to retrieve hard-deleted task")
|
|
}
|
|
}
|
|
|
|
func TestTaskStartStop(t *testing.T) {
|
|
task, err := CreateTask("Test start/stop")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
// Start task
|
|
if err := task.StartTask(); err != nil {
|
|
t.Fatalf("Failed to start task: %v", err)
|
|
}
|
|
|
|
if task.Start == nil {
|
|
t.Error("Start time should be set")
|
|
}
|
|
|
|
// Stop task
|
|
if err := task.StopTask(); err != nil {
|
|
t.Fatalf("Failed to stop task: %v", err)
|
|
}
|
|
|
|
if task.Start != nil {
|
|
t.Error("Start time should be nil after stop")
|
|
}
|
|
}
|
|
|
|
func TestGetTasks(t *testing.T) {
|
|
// Create multiple tasks
|
|
CreateTask("Task 1")
|
|
CreateTask("Task 2")
|
|
CreateTask("Task 3")
|
|
|
|
tasks, err := GetTasks(nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get tasks: %v", err)
|
|
}
|
|
|
|
if len(tasks) < 3 {
|
|
t.Errorf("Expected at least 3 tasks, got %d", len(tasks))
|
|
}
|
|
}
|
|
|
|
func TestTaskWithDueDate(t *testing.T) {
|
|
task, err := CreateTask("Task with due date")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create task: %v", err)
|
|
}
|
|
|
|
dueDate := time.Now().Add(24 * time.Hour)
|
|
task.Due = &dueDate
|
|
|
|
if err := task.Save(); err != nil {
|
|
t.Fatalf("Failed to save task with due date: %v", err)
|
|
}
|
|
|
|
retrieved, err := GetTask(task.UUID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get task: %v", err)
|
|
}
|
|
|
|
if retrieved.Due == nil {
|
|
t.Error("Due date should be set")
|
|
}
|
|
|
|
if retrieved.Due.Unix() != dueDate.Unix() {
|
|
t.Error("Due dates don't match")
|
|
}
|
|
}
|