Files
gems/opal-task/internal/engine/task_test.go
T
joakim 5d01c9f564 refactor: implement configurable directory structure with XDG support
Separate configuration from data storage and make paths configurable
via environment variables and command-line flags. This improves
Unix/Linux compliance and supports both development and production
deployments.

Key changes:
- Separate config dir (opal.yml) from data dir (database, logs)
- Support XDG Base Directory specification
- Add --config-dir and --data-dir flags
- Environment variables: OPAL_CONFIG_DIR, OPAL_DATA_DIR, OPAL_DB_PATH
- Smart fallback: /etc/opal, /var/lib/opal -> ~/.config/opal, ~/.local/share/opal
- Server mode validates required OAuth/JWT environment variables
- Update naming from 'jade' to 'opal' throughout
- Update systemd service name to 'opal.service'
- Add migration guide in README

Default paths:
- Config: /etc/opal (fallback: ~/.config/opal)
- Data: /var/lib/opal (fallback: ~/.local/share/opal)

Files modified:
- internal/engine/config.go: New directory resolution logic
- internal/engine/database.go: Auto-create data directory
- cmd/root.go: Add global flags for directory overrides
- cmd/server.go: Add configuration validation
- cmd/sync.go, internal/sync/*: Use new path helper functions
- tests: Update to use directory overrides
- docs: Update deployment guide and README
2026-01-06 20:46:29 +01:00

294 lines
6.0 KiB
Go

package engine
import (
"os"
"testing"
"time"
"github.com/google/uuid"
)
func TestMain(m *testing.M) {
// Setup test database with explicit directory overrides
testDir := "/tmp/opal-test"
// Set directory overrides to use test directory
SetConfigDirOverride(testDir)
SetDataDirOverride(testDir)
// Ensure test directory exists
if err := os.MkdirAll(testDir, 0755); err != nil {
panic(err)
}
if err := InitDB(); err != nil {
panic(err)
}
defer CloseDB()
// Run tests
code := m.Run()
// Cleanup
os.RemoveAll(testDir)
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")
}
}