mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
test(ai): prove recovery storage tool fallbacks through service
This commit is contained in:
@@ -2,14 +2,17 @@ package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/providers"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/tools"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
|
||||
ur "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
)
|
||||
|
||||
@@ -48,6 +51,41 @@ func (f *fakeCanonicalReadState) K8sDeployments() []*ur.K8sDeploymentView {
|
||||
func (f *fakeCanonicalReadState) Workloads() []*ur.WorkloadView { return nil }
|
||||
func (f *fakeCanonicalReadState) Infrastructure() []*ur.InfrastructureView { return nil }
|
||||
|
||||
type fakeRecoveryPointsProvider struct {
|
||||
points []recovery.RecoveryPoint
|
||||
}
|
||||
|
||||
func (f *fakeRecoveryPointsProvider) ListPoints(_ context.Context, opts recovery.ListPointsOptions) ([]recovery.RecoveryPoint, int, error) {
|
||||
filtered := make([]recovery.RecoveryPoint, 0, len(f.points))
|
||||
for _, point := range f.points {
|
||||
if opts.Provider != "" && point.Provider != opts.Provider {
|
||||
continue
|
||||
}
|
||||
if opts.Kind != "" && point.Kind != opts.Kind {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, point)
|
||||
}
|
||||
|
||||
total := len(filtered)
|
||||
if opts.Limit <= 0 {
|
||||
return filtered, total, nil
|
||||
}
|
||||
page := opts.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
start := (page - 1) * opts.Limit
|
||||
if start >= total {
|
||||
return []recovery.RecoveryPoint{}, total, nil
|
||||
}
|
||||
end := start + opts.Limit
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
return filtered[start:end], total, nil
|
||||
}
|
||||
|
||||
func toolNameSet(list []providers.Tool) map[string]bool {
|
||||
set := make(map[string]bool, len(list))
|
||||
for _, tool := range list {
|
||||
@@ -227,6 +265,125 @@ func TestExecuteMCPTool_ErrorsAndSuccess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteMCPTool_PulseStorageSnapshotsToleratesMalformedRecoveryMetadata(t *testing.T) {
|
||||
completedAt := time.Date(2026, 2, 24, 10, 30, 0, 0, time.UTC)
|
||||
svc := &Service{
|
||||
executor: tools.NewPulseToolExecutor(tools.ExecutorConfig{
|
||||
StateProvider: fakeStateProvider{},
|
||||
ReadState: &fakeCanonicalReadState{},
|
||||
RecoveryPointsProvider: &fakeRecoveryPointsProvider{points: []recovery.RecoveryPoint{
|
||||
{
|
||||
ID: "pve-snapshot:snap-100-before-upgrade",
|
||||
Provider: recovery.ProviderProxmoxPVE,
|
||||
Kind: recovery.KindSnapshot,
|
||||
Mode: recovery.ModeLocal,
|
||||
Outcome: recovery.OutcomeSuccess,
|
||||
SubjectRef: &recovery.ExternalRef{
|
||||
Type: "vm",
|
||||
Name: "100",
|
||||
ID: "100",
|
||||
Namespace: "pve1",
|
||||
Class: "node1",
|
||||
},
|
||||
Display: &recovery.RecoveryPointDisplay{
|
||||
SubjectLabel: "100",
|
||||
ItemType: "vm",
|
||||
ClusterLabel: "pve1",
|
||||
NodeHostLabel: "node1",
|
||||
EntityIDLabel: "100",
|
||||
DetailsSummary: "before-upgrade",
|
||||
},
|
||||
CompletedAt: &completedAt,
|
||||
},
|
||||
}},
|
||||
}),
|
||||
}
|
||||
|
||||
output, err := svc.ExecuteMCPTool(context.Background(), "pulse_storage", map[string]interface{}{
|
||||
"type": "snapshots",
|
||||
"guest_id": "100",
|
||||
"instance": "pve1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteMCPTool(pulse_storage snapshots): %v", err)
|
||||
}
|
||||
|
||||
var resp tools.SnapshotsResponse
|
||||
if err := json.Unmarshal([]byte(output), &resp); err != nil {
|
||||
t.Fatalf("unmarshal snapshots response: %v\noutput=%s", err, output)
|
||||
}
|
||||
if resp.Total != 1 || resp.Filtered != 1 || len(resp.Snapshots) != 1 {
|
||||
t.Fatalf("unexpected snapshots counts: total=%d filtered=%d len=%d", resp.Total, resp.Filtered, len(resp.Snapshots))
|
||||
}
|
||||
if resp.Snapshots[0].VMID != 100 || resp.Snapshots[0].Instance != "pve1" || resp.Snapshots[0].Node != "node1" {
|
||||
t.Fatalf("unexpected snapshot identity: %#v", resp.Snapshots[0])
|
||||
}
|
||||
if resp.Snapshots[0].Type != "vm" || resp.Snapshots[0].SnapshotName != "before-upgrade" {
|
||||
t.Fatalf("unexpected snapshot canonical fields: %#v", resp.Snapshots[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteMCPTool_PulseStorageBackupTasksToleratesMalformedRecoveryMetadata(t *testing.T) {
|
||||
startedAt := time.Date(2026, 2, 24, 11, 0, 0, 0, time.UTC)
|
||||
completedAt := time.Date(2026, 2, 24, 11, 15, 0, 0, time.UTC)
|
||||
svc := &Service{
|
||||
executor: tools.NewPulseToolExecutor(tools.ExecutorConfig{
|
||||
StateProvider: fakeStateProvider{},
|
||||
ReadState: &fakeCanonicalReadState{},
|
||||
RecoveryPointsProvider: &fakeRecoveryPointsProvider{points: []recovery.RecoveryPoint{
|
||||
{
|
||||
ID: "pve-task:task-101-backup",
|
||||
Provider: recovery.ProviderProxmoxPVE,
|
||||
Kind: recovery.KindBackup,
|
||||
Mode: recovery.ModeLocal,
|
||||
Outcome: recovery.OutcomeSuccess,
|
||||
SubjectRef: &recovery.ExternalRef{
|
||||
Type: "vm",
|
||||
Name: "101",
|
||||
ID: "101",
|
||||
Namespace: "pve1",
|
||||
Class: "node1",
|
||||
},
|
||||
Display: &recovery.RecoveryPointDisplay{
|
||||
SubjectLabel: "101",
|
||||
ItemType: "vm",
|
||||
ClusterLabel: "pve1",
|
||||
NodeHostLabel: "node1",
|
||||
EntityIDLabel: "101",
|
||||
DetailsSummary: "completed successfully",
|
||||
},
|
||||
StartedAt: &startedAt,
|
||||
CompletedAt: &completedAt,
|
||||
},
|
||||
}},
|
||||
}),
|
||||
}
|
||||
|
||||
output, err := svc.ExecuteMCPTool(context.Background(), "pulse_storage", map[string]interface{}{
|
||||
"type": "backup_tasks",
|
||||
"guest_id": "101",
|
||||
"instance": "pve1",
|
||||
"status": "OK",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteMCPTool(pulse_storage backup_tasks): %v", err)
|
||||
}
|
||||
|
||||
var resp tools.BackupTasksListResponse
|
||||
if err := json.Unmarshal([]byte(output), &resp); err != nil {
|
||||
t.Fatalf("unmarshal backup tasks response: %v\noutput=%s", err, output)
|
||||
}
|
||||
if resp.Total != 1 || resp.Filtered != 1 || len(resp.Tasks) != 1 {
|
||||
t.Fatalf("unexpected backup task counts: total=%d filtered=%d len=%d", resp.Total, resp.Filtered, len(resp.Tasks))
|
||||
}
|
||||
if resp.Tasks[0].VMID != 101 || resp.Tasks[0].Instance != "pve1" || resp.Tasks[0].Node != "node1" {
|
||||
t.Fatalf("unexpected backup task identity: %#v", resp.Tasks[0])
|
||||
}
|
||||
if resp.Tasks[0].Type != "vm" || resp.Tasks[0].Status != "OK" {
|
||||
t.Fatalf("unexpected backup task canonical fields: %#v", resp.Tasks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_StartInitializesActionAuditStore(t *testing.T) {
|
||||
svc := NewService(Config{
|
||||
AIConfig: &config.AIConfig{
|
||||
|
||||
Reference in New Issue
Block a user