test(v0.3): add MostRecentActive helper with focused unit coverage

Extracts the cross-type "most recently updated active workspace"
selector that previously lived inline in cmd/last and cmd/delete.
The helper takes only a root path and returns (nil, nil) when
nothing matches, which keeps the call sites trivial.

Eight focused tests cover:
  - tasks-only fixture
  - projects-only fixture
  - project most-recent vs older task
  - task most-recent vs older project
  - legacy v0.2 workspace (no Type field) winning, treated as task
  - newest workspace archived, older active project wins
  - empty root returns (nil, nil)
  - all archived returns (nil, nil)

A new createTestWorkspaceFull helper takes an explicit UpdatedAt
timestamp so the "which is newest" assertions don't depend on
wall-clock ordering.

cmd/last and cmd/delete are migrated onto the helper in the next
commit.
This commit is contained in:
2026-04-10 17:00:05 -04:00
parent bd1cff5b26
commit 3a4a8d28f2
3 changed files with 178 additions and 3 deletions
+30
View File
@@ -0,0 +1,30 @@
package workspace
// MostRecentActive returns the active workspace with the latest UpdatedAt
// timestamp under root, considering both tasks and projects.
//
// Returns (nil, nil) if no active workspaces exist (this is not an error
// condition; callers decide how to surface "nothing to do").
//
// Legacy v0.2 workspaces (no Type field) are included as tasks via
// EffectiveType. Archived workspaces are always excluded.
func MostRecentActive(root string) (*QueryResult, error) {
results, err := ListWorkspaces(root, ListOpts{
IncludeArchived: false,
Limit: 0,
Type: TypeAny,
})
if err != nil {
return nil, err
}
if len(results) == 0 {
return nil, nil
}
best := results[0]
for _, r := range results[1:] {
if r.Meta.UpdatedAt.After(best.Meta.UpdatedAt) {
best = r
}
}
return &best, nil
}
+138
View File
@@ -0,0 +1,138 @@
package workspace
import (
"testing"
"time"
)
// All test timestamps are anchored to a fixed base date so the assertions
// don't depend on wall-clock time.
var baseT = time.Date(2026, 4, 1, 12, 0, 0, 0, time.UTC)
func TestMostRecentActiveTasksOnly(t *testing.T) {
root := t.TempDir()
createTestWorkspaceFull(t, root, "general", "2026-04-01_old", "active", "task", baseT)
createTestWorkspaceFull(t, root, "general", "2026-04-02_new", "active", "task", baseT.Add(2*time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil {
t.Fatal("expected a result, got nil")
}
if got.Meta.Slug != "new" {
t.Errorf("slug: got %q, want \"new\"", got.Meta.Slug)
}
}
func TestMostRecentActiveProjectsOnly(t *testing.T) {
root := t.TempDir()
createTestWorkspaceFull(t, root, "projects", "2026-04-01_old-proj", "active", "project", baseT)
createTestWorkspaceFull(t, root, "projects", "2026-04-02_new-proj", "active", "project", baseT.Add(2*time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil {
t.Fatal("expected a result, got nil")
}
if got.Meta.Slug != "new-proj" {
t.Errorf("slug: got %q, want \"new-proj\"", got.Meta.Slug)
}
}
func TestMostRecentActiveProjectWinsOverOlderTask(t *testing.T) {
root := t.TempDir()
createTestWorkspaceFull(t, root, "general", "2026-04-01_old-task", "active", "task", baseT)
createTestWorkspaceFull(t, root, "projects", "2026-04-02_new-proj", "active", "project", baseT.Add(2*time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil || got.Meta.Slug != "new-proj" {
t.Fatalf("expected new-proj, got %+v", got)
}
if EffectiveType(got.Meta) != "project" {
t.Errorf("EffectiveType: got %q, want \"project\"", EffectiveType(got.Meta))
}
}
func TestMostRecentActiveTaskWinsOverOlderProject(t *testing.T) {
root := t.TempDir()
createTestWorkspaceFull(t, root, "projects", "2026-04-01_old-proj", "active", "project", baseT)
createTestWorkspaceFull(t, root, "general", "2026-04-02_new-task", "active", "task", baseT.Add(2*time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil || got.Meta.Slug != "new-task" {
t.Fatalf("expected new-task, got %+v", got)
}
if EffectiveType(got.Meta) != "task" {
t.Errorf("EffectiveType: got %q, want \"task\"", EffectiveType(got.Meta))
}
}
func TestMostRecentActiveLegacyWorkspaceCounts(t *testing.T) {
root := t.TempDir()
// Most recent has no Type field at all (v0.2 era).
createTestWorkspaceFull(t, root, "projects", "2026-04-01_old-proj", "active", "project", baseT)
createTestWorkspaceFull(t, root, "general", "2026-04-02_legacy", "active", "", baseT.Add(2*time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil || got.Meta.Slug != "legacy" {
t.Fatalf("expected legacy, got %+v", got)
}
// EffectiveType should fall back to "task".
if EffectiveType(got.Meta) != "task" {
t.Errorf("legacy EffectiveType: got %q, want \"task\"", EffectiveType(got.Meta))
}
}
func TestMostRecentActiveExcludesArchived(t *testing.T) {
root := t.TempDir()
// Newest is archived; should be skipped in favor of the older active project.
createTestWorkspaceFull(t, root, "general", "2026-04-03_newest-archived", "archived", "task", baseT.Add(3*time.Hour))
createTestWorkspaceFull(t, root, "projects", "2026-04-02_active-proj", "active", "project", baseT.Add(2*time.Hour))
createTestWorkspaceFull(t, root, "general", "2026-04-01_old-task", "active", "task", baseT)
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got == nil || got.Meta.Slug != "active-proj" {
t.Fatalf("expected active-proj (newest non-archived), got %+v", got)
}
}
func TestMostRecentActiveReturnsNilWhenEmpty(t *testing.T) {
root := t.TempDir()
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got != nil {
t.Errorf("expected nil for empty root, got %+v", got)
}
}
func TestMostRecentActiveReturnsNilWhenAllArchived(t *testing.T) {
root := t.TempDir()
createTestWorkspaceFull(t, root, "general", "2026-04-01_arch-task", "archived", "task", baseT)
createTestWorkspaceFull(t, root, "projects", "2026-04-02_arch-proj", "archived", "project", baseT.Add(time.Hour))
got, err := MostRecentActive(root)
if err != nil {
t.Fatalf("MostRecentActive: %v", err)
}
if got != nil {
t.Errorf("expected nil when all workspaces archived, got %+v", got)
}
}
+10 -3
View File
@@ -16,18 +16,25 @@ func createTestWorkspace(t *testing.T, root, category, dirName string, status st
// createTestWorkspaceTyped creates a minimal workspace of an explicit type
// (use "" to simulate a v0.2 workspace with no type field).
func createTestWorkspaceTyped(t *testing.T, root, category, dirName, status, taskType string) {
t.Helper()
now := time.Now().UTC().Truncate(time.Second)
createTestWorkspaceFull(t, root, category, dirName, status, taskType, now)
}
// createTestWorkspaceFull creates a workspace with an explicit UpdatedAt
// timestamp -- used by tests that exercise "most recently updated" logic.
func createTestWorkspaceFull(t *testing.T, root, category, dirName, status, taskType string, updatedAt time.Time) {
t.Helper()
dir := filepath.Join(root, category, dirName)
os.MkdirAll(dir, 0755)
now := time.Now().UTC().Truncate(time.Second)
// Extract slug from dirName (skip "YYYY-MM-DD_")
slug := dirName[11:]
meta := &TaskMeta{
ID: "test",
Slug: slug,
Title: slug,
CreatedAt: now,
UpdatedAt: now,
CreatedAt: updatedAt,
UpdatedAt: updatedAt,
Status: status,
Category: category,
Type: taskType,