218 lines
5.7 KiB
Go
218 lines
5.7 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
core "dappco.re/go/core"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPlanRetention_PlanCleanup_Good_DeletesExpiredArchivedPlans(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
s := newTestPrep(t)
|
|
|
|
oldPlan := &Plan{
|
|
ID: "old-plan-abc123",
|
|
Title: "Old Plan",
|
|
Status: "archived",
|
|
Objective: "Delete me",
|
|
ArchivedAt: time.Now().AddDate(0, 0, -100),
|
|
}
|
|
recentPlan := &Plan{
|
|
ID: "recent-plan-abc123",
|
|
Title: "Recent Plan",
|
|
Status: "archived",
|
|
Objective: "Keep me",
|
|
ArchivedAt: time.Now().AddDate(0, 0, -10),
|
|
}
|
|
activePlan := &Plan{
|
|
ID: "active-plan-abc123",
|
|
Title: "Active Plan",
|
|
Status: "ready",
|
|
Objective: "Keep me too",
|
|
}
|
|
|
|
_, err := writePlan(PlansRoot(), oldPlan)
|
|
require.NoError(t, err)
|
|
_, err = writePlan(PlansRoot(), recentPlan)
|
|
require.NoError(t, err)
|
|
_, err = writePlan(PlansRoot(), activePlan)
|
|
require.NoError(t, err)
|
|
|
|
result := s.planCleanup(core.NewOptions(core.Option{Key: "days", Value: 90}))
|
|
require.True(t, result.OK)
|
|
|
|
output, ok := result.Value.(PlanCleanupOutput)
|
|
require.True(t, ok)
|
|
assert.True(t, output.Success)
|
|
assert.Equal(t, 1, output.Deleted)
|
|
assert.Equal(t, 1, output.Matched)
|
|
assert.False(t, fs.Exists(core.JoinPath(PlansRoot(), "old-plan-abc123.json")))
|
|
assert.True(t, fs.Exists(core.JoinPath(PlansRoot(), "recent-plan-abc123.json")))
|
|
assert.True(t, fs.Exists(core.JoinPath(PlansRoot(), "active-plan-abc123.json")))
|
|
}
|
|
|
|
func TestPlanRetention_PlanCleanup_Good_ArchivesExpiredCompletedPlans(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
s := newTestPrep(t)
|
|
|
|
plan := &Plan{
|
|
ID: "completed-plan-abc123",
|
|
Title: "Completed Plan",
|
|
Status: "approved",
|
|
Objective: "Archive me",
|
|
UpdatedAt: time.Now().AddDate(0, 0, -100),
|
|
}
|
|
|
|
_, err := writePlan(PlansRoot(), plan)
|
|
require.NoError(t, err)
|
|
|
|
result := s.planCleanup(core.NewOptions(core.Option{Key: "days", Value: 90}))
|
|
require.True(t, result.OK)
|
|
|
|
output, ok := result.Value.(PlanCleanupOutput)
|
|
require.True(t, ok)
|
|
assert.True(t, output.Success)
|
|
assert.Equal(t, 1, output.Archived)
|
|
assert.Equal(t, 0, output.Deleted)
|
|
assert.Equal(t, 1, output.Matched)
|
|
|
|
updated, err := readPlan(PlansRoot(), plan.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "archived", updated.Status)
|
|
assert.False(t, updated.ArchivedAt.IsZero())
|
|
assert.True(t, fs.Exists(core.JoinPath(PlansRoot(), "completed-plan-abc123.json")))
|
|
}
|
|
|
|
func TestPlanRetention_PlanCleanup_Bad_DryRunKeepsFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
s := newTestPrep(t)
|
|
|
|
plan := &Plan{
|
|
ID: "dry-run-plan-abc123",
|
|
Title: "Dry Run Plan",
|
|
Status: "archived",
|
|
Objective: "Keep me for now",
|
|
ArchivedAt: time.Now().AddDate(0, 0, -100),
|
|
}
|
|
|
|
_, err := writePlan(PlansRoot(), plan)
|
|
require.NoError(t, err)
|
|
|
|
result := s.planCleanup(core.NewOptions(
|
|
core.Option{Key: "days", Value: 90},
|
|
core.Option{Key: "dry-run", Value: true},
|
|
))
|
|
require.True(t, result.OK)
|
|
|
|
output, ok := result.Value.(PlanCleanupOutput)
|
|
require.True(t, ok)
|
|
assert.True(t, output.Success)
|
|
assert.True(t, output.DryRun)
|
|
assert.Equal(t, 1, output.Matched)
|
|
assert.Equal(t, 0, output.Deleted)
|
|
assert.True(t, fs.Exists(core.JoinPath(PlansRoot(), "dry-run-plan-abc123.json")))
|
|
}
|
|
|
|
func TestPlanRetention_PlanCleanup_Ugly_DisabledCleanupKeepsFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
s := newTestPrep(t)
|
|
|
|
plan := &Plan{
|
|
ID: "disabled-plan-abc123",
|
|
Title: "Disabled Plan",
|
|
Status: "archived",
|
|
Objective: "Should remain",
|
|
ArchivedAt: time.Now().AddDate(0, 0, -100),
|
|
}
|
|
|
|
_, err := writePlan(PlansRoot(), plan)
|
|
require.NoError(t, err)
|
|
|
|
result := s.planCleanup(core.NewOptions(core.Option{Key: "days", Value: 0}))
|
|
require.True(t, result.OK)
|
|
|
|
output, ok := result.Value.(PlanCleanupOutput)
|
|
require.True(t, ok)
|
|
assert.True(t, output.Success)
|
|
assert.True(t, output.Disabled)
|
|
assert.Equal(t, 0, output.Deleted)
|
|
assert.True(t, fs.Exists(core.JoinPath(PlansRoot(), "disabled-plan-abc123.json")))
|
|
}
|
|
|
|
func TestPlanRetention_PlanArchivedAt_Good_FallsBackToFileModifiedTime(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
path := core.JoinPath(PlansRoot(), "fallback-plan-abc123.json")
|
|
require.True(t, fs.Write(path, `{"id":"fallback-plan-abc123","title":"Fallback","status":"archived","objective":"Fallback"}`).OK)
|
|
|
|
stat := fs.Stat(path)
|
|
require.True(t, stat.OK)
|
|
|
|
plan := &Plan{
|
|
ID: "fallback-plan-abc123",
|
|
Title: "Fallback",
|
|
Status: "archived",
|
|
Objective: "Fallback",
|
|
}
|
|
|
|
archivedAt := planArchivedAt(path, plan)
|
|
assert.False(t, archivedAt.IsZero())
|
|
|
|
_, ok := stat.Value.(interface{ ModTime() time.Time })
|
|
assert.True(t, ok)
|
|
}
|
|
|
|
func TestPlanRetention_RunPlanCleanupLoop_Good_DeletesExpiredPlans(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("CORE_WORKSPACE", dir)
|
|
|
|
s := newTestPrep(t)
|
|
|
|
plan := &Plan{
|
|
ID: "scheduled-plan-abc123",
|
|
Title: "Scheduled Plan",
|
|
Status: "archived",
|
|
Objective: "Remove me on the next retention pass",
|
|
ArchivedAt: time.Now().AddDate(0, 0, -100),
|
|
}
|
|
|
|
_, err := writePlan(PlansRoot(), plan)
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
s.runPlanCleanupLoop(ctx, time.Millisecond)
|
|
close(done)
|
|
}()
|
|
|
|
require.Eventually(t, func() bool {
|
|
return !fs.Exists(core.JoinPath(PlansRoot(), "scheduled-plan-abc123.json"))
|
|
}, time.Second, 5*time.Millisecond)
|
|
|
|
cancel()
|
|
require.Eventually(t, func() bool {
|
|
select {
|
|
case <-done:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}, time.Second, 5*time.Millisecond)
|
|
}
|