Files
osmedeus/internal/state/export_test.go
T
j3ssie f5840272c5 feat: add run cancellation, event enhancements, and performance optimizations
Major features:
- Add run registry for tracking active runs with PID management
- Add API-based run cancellation with process termination
- Add event trigger input vars syntax for multi-variable extraction
- Add filter_functions with utility function support in triggers
- Add event envelope injection for full event context in workflows
- Add write coordinator for batched database operations

API improvements:
- Add logout endpoint and diffs endpoints for assets/vulnerabilities
- Add step-results listing endpoint
- Update schedule model with target, workspace, params fields
- Change run_id to run_uuid across API responses

Performance:
- Add compiled JS program caching for 60-80% faster loop conditions
- Add parallel shard rendering for 20-40% faster workflow startup
- Add memory-mapped I/O for large file line counting
- Add efficient output buffer combining in runners
- Add mtime-based cache invalidation for workflow loader

Other changes:
- Rename trigger field from trigger to triggers in workflow YAML
- Disable pongo2 HTML autoescape for shell command templates
- Update JWT expiration default to 1440 minutes (1 day)
- Change CORS default to reflect-origin for credentials support
- Add source_type field to events (run, eval, api)
- Skip copying core Unix tools to external-binaries
2026-01-24 01:11:33 +08:00

162 lines
4.2 KiB
Go

package state
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
)
func TestExport_WithoutDatabase(t *testing.T) {
// Create temp directory for test
tmpDir, err := os.MkdirTemp("", "state-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
stateFile := filepath.Join(tmpDir, "run-state.json")
// Create context without database
now := time.Now()
ctx := &ExportContext{
RunUUID: "test-run-123",
WorkflowName: "test-workflow",
WorkflowKind: "module",
Target: "example.com",
WorkspacePath: "/tmp/workspace",
WorkspaceName: "example.com",
Status: "running",
StartedAt: &now,
TotalSteps: 5,
CompletedSteps: 2,
Artifacts: []string{"/tmp/workspace/output1.txt", "/tmp/workspace/output2.txt"},
}
// Export state
err = Export(stateFile, ctx)
if err != nil {
t.Fatalf("Export failed: %v", err)
}
// Read and verify the exported file
data, err := os.ReadFile(stateFile)
if err != nil {
t.Fatalf("Failed to read state file: %v", err)
}
var export StateExport
if err := json.Unmarshal(data, &export); err != nil {
t.Fatalf("Failed to unmarshal state file: %v", err)
}
// Verify run info
if export.Run == nil {
t.Fatal("Expected Run to be populated")
}
if export.Run.RunUUID != "test-run-123" {
t.Errorf("Expected RunID 'test-run-123', got '%s'", export.Run.RunUUID)
}
if export.Run.WorkflowName != "test-workflow" {
t.Errorf("Expected WorkflowName 'test-workflow', got '%s'", export.Run.WorkflowName)
}
if export.Run.WorkflowKind != "module" {
t.Errorf("Expected WorkflowKind 'module', got '%s'", export.Run.WorkflowKind)
}
if export.Run.Target != "example.com" {
t.Errorf("Expected Target 'example.com', got '%s'", export.Run.Target)
}
if export.Run.Status != "running" {
t.Errorf("Expected Status 'running', got '%s'", export.Run.Status)
}
if export.Run.TotalSteps != 5 {
t.Errorf("Expected TotalSteps 5, got %d", export.Run.TotalSteps)
}
if export.Run.CompletedSteps != 2 {
t.Errorf("Expected CompletedSteps 2, got %d", export.Run.CompletedSteps)
}
// Verify workspace info
if export.Workspace == nil {
t.Fatal("Expected Workspace to be populated")
}
if export.Workspace.Name != "example.com" {
t.Errorf("Expected Workspace Name 'example.com', got '%s'", export.Workspace.Name)
}
if export.Workspace.LocalPath != "/tmp/workspace" {
t.Errorf("Expected Workspace LocalPath '/tmp/workspace', got '%s'", export.Workspace.LocalPath)
}
// Verify artifacts
if len(export.Artifacts) != 2 {
t.Errorf("Expected 2 artifacts, got %d", len(export.Artifacts))
}
// Verify updated_at is set
if export.UpdatedAt.IsZero() {
t.Error("Expected UpdatedAt to be set")
}
}
func TestExport_MinimalContext(t *testing.T) {
// Create temp directory for test
tmpDir, err := os.MkdirTemp("", "state-test-minimal-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
stateFile := filepath.Join(tmpDir, "run-state.json")
// Create minimal context
ctx := &ExportContext{
RunUUID: "minimal-run",
WorkspaceName: "minimal-workspace",
}
// Export state
err = Export(stateFile, ctx)
if err != nil {
t.Fatalf("Export failed: %v", err)
}
// Read and verify the exported file
data, err := os.ReadFile(stateFile)
if err != nil {
t.Fatalf("Failed to read state file: %v", err)
}
var export StateExport
if err := json.Unmarshal(data, &export); err != nil {
t.Fatalf("Failed to unmarshal state file: %v", err)
}
// Verify run info is present even with minimal data
if export.Run == nil {
t.Fatal("Expected Run to be populated")
}
if export.Run.RunUUID != "minimal-run" {
t.Errorf("Expected RunID 'minimal-run', got '%s'", export.Run.RunUUID)
}
// Verify workspace info is present
if export.Workspace == nil {
t.Fatal("Expected Workspace to be populated")
}
if export.Workspace.Name != "minimal-workspace" {
t.Errorf("Expected Workspace Name 'minimal-workspace', got '%s'", export.Workspace.Name)
}
}
func TestExport_EmptyStateFilePath(t *testing.T) {
ctx := &ExportContext{
RunUUID: "test",
}
err := Export("", ctx)
if err == nil {
t.Error("Expected error for empty state file path")
}
}