mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-30 11:49:44 +02:00
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
305 lines
8.5 KiB
Go
305 lines
8.5 KiB
Go
package distributed
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/j3ssie/osmedeus/v5/internal/core"
|
|
)
|
|
|
|
func TestDataEnvelopeSerialization(t *testing.T) {
|
|
testData := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
|
|
dataBytes, err := json.Marshal(testData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal test data: %v", err)
|
|
}
|
|
|
|
envelope := DataEnvelope{
|
|
Type: "test-type",
|
|
Data: dataBytes,
|
|
Timestamp: time.Now().Truncate(time.Second),
|
|
WorkerID: "worker-123",
|
|
}
|
|
|
|
// Serialize
|
|
serialized, err := json.Marshal(envelope)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal envelope: %v", err)
|
|
}
|
|
|
|
// Deserialize
|
|
var decoded DataEnvelope
|
|
if err := json.Unmarshal(serialized, &decoded); err != nil {
|
|
t.Fatalf("Failed to unmarshal envelope: %v", err)
|
|
}
|
|
|
|
// Verify fields
|
|
if decoded.Type != envelope.Type {
|
|
t.Errorf("Type mismatch: %s vs %s", decoded.Type, envelope.Type)
|
|
}
|
|
if decoded.WorkerID != envelope.WorkerID {
|
|
t.Errorf("WorkerID mismatch: %s vs %s", decoded.WorkerID, envelope.WorkerID)
|
|
}
|
|
if !decoded.Timestamp.Equal(envelope.Timestamp) {
|
|
t.Errorf("Timestamp mismatch: %v vs %v", decoded.Timestamp, envelope.Timestamp)
|
|
}
|
|
|
|
// Verify nested data can be decoded
|
|
var decodedData map[string]string
|
|
if err := json.Unmarshal(decoded.Data, &decodedData); err != nil {
|
|
t.Fatalf("Failed to unmarshal nested data: %v", err)
|
|
}
|
|
if decodedData["key1"] != "value1" {
|
|
t.Errorf("Nested data mismatch: expected value1, got %s", decodedData["key1"])
|
|
}
|
|
}
|
|
|
|
func TestEventSerialization(t *testing.T) {
|
|
event := &core.Event{
|
|
Topic: "test.event",
|
|
ID: "event-id-123",
|
|
Name: "test.name",
|
|
Source: "test-source",
|
|
DataType: "test-data-type",
|
|
Data: `{"nested": "value"}`,
|
|
Timestamp: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
// Serialize
|
|
serialized, err := json.Marshal(event)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal event: %v", err)
|
|
}
|
|
|
|
// Deserialize
|
|
var decoded core.Event
|
|
if err := json.Unmarshal(serialized, &decoded); err != nil {
|
|
t.Fatalf("Failed to unmarshal event: %v", err)
|
|
}
|
|
|
|
// Verify all fields
|
|
if decoded.Topic != event.Topic {
|
|
t.Errorf("Topic mismatch: %s vs %s", decoded.Topic, event.Topic)
|
|
}
|
|
if decoded.ID != event.ID {
|
|
t.Errorf("ID mismatch: %s vs %s", decoded.ID, event.ID)
|
|
}
|
|
if decoded.Name != event.Name {
|
|
t.Errorf("Name mismatch: %s vs %s", decoded.Name, event.Name)
|
|
}
|
|
if decoded.Source != event.Source {
|
|
t.Errorf("Source mismatch: %s vs %s", decoded.Source, event.Source)
|
|
}
|
|
if decoded.DataType != event.DataType {
|
|
t.Errorf("DataType mismatch: %s vs %s", decoded.DataType, event.DataType)
|
|
}
|
|
if decoded.Data != event.Data {
|
|
t.Errorf("Data mismatch: %s vs %s", decoded.Data, event.Data)
|
|
}
|
|
}
|
|
|
|
func TestKeyConstants(t *testing.T) {
|
|
// Verify key prefixes are properly formatted
|
|
expectedPrefix := "osm:"
|
|
|
|
keys := map[string]string{
|
|
"KeyPrefix": KeyPrefix,
|
|
"KeyEventsPrefix": KeyEventsPrefix,
|
|
"KeyDataRuns": KeyDataRuns,
|
|
"KeyDataSteps": KeyDataSteps,
|
|
"KeyDataEvents": KeyDataEvents,
|
|
"KeyDataArtifacts": KeyDataArtifacts,
|
|
}
|
|
|
|
for name, key := range keys {
|
|
if len(key) < len(expectedPrefix) {
|
|
t.Errorf("%s is too short: %s", name, key)
|
|
continue
|
|
}
|
|
if key[:len(expectedPrefix)] != expectedPrefix {
|
|
t.Errorf("%s doesn't start with %s: %s", name, expectedPrefix, key)
|
|
}
|
|
}
|
|
|
|
// Verify specific keys
|
|
if KeyEventsPrefix != "osm:events:" {
|
|
t.Errorf("KeyEventsPrefix mismatch: expected osm:events:, got %s", KeyEventsPrefix)
|
|
}
|
|
if KeyDataRuns != "osm:data:runs" {
|
|
t.Errorf("KeyDataRuns mismatch: expected osm:data:runs, got %s", KeyDataRuns)
|
|
}
|
|
if KeyDataSteps != "osm:data:steps" {
|
|
t.Errorf("KeyDataSteps mismatch: expected osm:data:steps, got %s", KeyDataSteps)
|
|
}
|
|
if KeyDataEvents != "osm:data:events" {
|
|
t.Errorf("KeyDataEvents mismatch: expected osm:data:events, got %s", KeyDataEvents)
|
|
}
|
|
if KeyDataArtifacts != "osm:data:artifacts" {
|
|
t.Errorf("KeyDataArtifacts mismatch: expected osm:data:artifacts, got %s", KeyDataArtifacts)
|
|
}
|
|
}
|
|
|
|
func TestTaskStatusConstants(t *testing.T) {
|
|
// Verify task status constants
|
|
if TaskStatusPending != "pending" {
|
|
t.Errorf("TaskStatusPending mismatch: expected pending, got %s", TaskStatusPending)
|
|
}
|
|
if TaskStatusRunning != "running" {
|
|
t.Errorf("TaskStatusRunning mismatch: expected running, got %s", TaskStatusRunning)
|
|
}
|
|
if TaskStatusCompleted != "completed" {
|
|
t.Errorf("TaskStatusCompleted mismatch: expected completed, got %s", TaskStatusCompleted)
|
|
}
|
|
if TaskStatusFailed != "failed" {
|
|
t.Errorf("TaskStatusFailed mismatch: expected failed, got %s", TaskStatusFailed)
|
|
}
|
|
}
|
|
|
|
func TestNewTask(t *testing.T) {
|
|
task := NewTask("task-123", "test-workflow", "module", "example.com", map[string]interface{}{
|
|
"param1": "value1",
|
|
})
|
|
|
|
if task.ID != "task-123" {
|
|
t.Errorf("ID mismatch: expected task-123, got %s", task.ID)
|
|
}
|
|
if task.WorkflowName != "test-workflow" {
|
|
t.Errorf("WorkflowName mismatch: expected test-workflow, got %s", task.WorkflowName)
|
|
}
|
|
if task.WorkflowKind != "module" {
|
|
t.Errorf("WorkflowKind mismatch: expected module, got %s", task.WorkflowKind)
|
|
}
|
|
if task.Target != "example.com" {
|
|
t.Errorf("Target mismatch: expected example.com, got %s", task.Target)
|
|
}
|
|
if task.Status != TaskStatusPending {
|
|
t.Errorf("Status mismatch: expected pending, got %s", task.Status)
|
|
}
|
|
if task.CreatedAt.IsZero() {
|
|
t.Error("CreatedAt should not be zero")
|
|
}
|
|
}
|
|
|
|
func TestTaskMarkRunning(t *testing.T) {
|
|
task := NewTask("task-123", "test-workflow", "module", "example.com", nil)
|
|
|
|
task.MarkRunning("worker-456")
|
|
|
|
if task.Status != TaskStatusRunning {
|
|
t.Errorf("Status mismatch: expected running, got %s", task.Status)
|
|
}
|
|
if task.WorkerID != "worker-456" {
|
|
t.Errorf("WorkerID mismatch: expected worker-456, got %s", task.WorkerID)
|
|
}
|
|
if task.StartedAt == nil {
|
|
t.Error("StartedAt should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestTaskMarkCompleted(t *testing.T) {
|
|
task := NewTask("task-123", "test-workflow", "module", "example.com", nil)
|
|
|
|
task.MarkCompleted()
|
|
|
|
if task.Status != TaskStatusCompleted {
|
|
t.Errorf("Status mismatch: expected completed, got %s", task.Status)
|
|
}
|
|
if task.CompletedAt == nil {
|
|
t.Error("CompletedAt should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestTaskMarkFailed(t *testing.T) {
|
|
task := NewTask("task-123", "test-workflow", "module", "example.com", nil)
|
|
|
|
task.MarkFailed("something went wrong")
|
|
|
|
if task.Status != TaskStatusFailed {
|
|
t.Errorf("Status mismatch: expected failed, got %s", task.Status)
|
|
}
|
|
if task.Error != "something went wrong" {
|
|
t.Errorf("Error mismatch: expected 'something went wrong', got %s", task.Error)
|
|
}
|
|
if task.CompletedAt == nil {
|
|
t.Error("CompletedAt should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestTaskResultSerialization(t *testing.T) {
|
|
result := &TaskResult{
|
|
TaskID: "task-123",
|
|
Status: TaskStatusCompleted,
|
|
Output: "task output",
|
|
Exports: map[string]interface{}{"key": "value"},
|
|
CompletedAt: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
// Serialize
|
|
data, err := result.MarshalJSON()
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal result: %v", err)
|
|
}
|
|
|
|
// Deserialize
|
|
decoded, err := UnmarshalTaskResult(data)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal result: %v", err)
|
|
}
|
|
|
|
if decoded.TaskID != result.TaskID {
|
|
t.Errorf("TaskID mismatch: %s vs %s", decoded.TaskID, result.TaskID)
|
|
}
|
|
if decoded.Status != result.Status {
|
|
t.Errorf("Status mismatch: %s vs %s", decoded.Status, result.Status)
|
|
}
|
|
if decoded.Output != result.Output {
|
|
t.Errorf("Output mismatch: %s vs %s", decoded.Output, result.Output)
|
|
}
|
|
}
|
|
|
|
func TestWorkerInfoSerialization(t *testing.T) {
|
|
info := &WorkerInfo{
|
|
ID: "worker-123",
|
|
Hostname: "test-host",
|
|
Status: "idle",
|
|
CurrentTaskID: "",
|
|
JoinedAt: time.Now().Truncate(time.Second),
|
|
LastHeartbeat: time.Now().Truncate(time.Second),
|
|
TasksComplete: 5,
|
|
TasksFailed: 1,
|
|
}
|
|
|
|
// Serialize
|
|
data, err := info.MarshalJSON()
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal worker info: %v", err)
|
|
}
|
|
|
|
// Deserialize
|
|
decoded, err := UnmarshalWorkerInfo(data)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal worker info: %v", err)
|
|
}
|
|
|
|
if decoded.ID != info.ID {
|
|
t.Errorf("ID mismatch: %s vs %s", decoded.ID, info.ID)
|
|
}
|
|
if decoded.Hostname != info.Hostname {
|
|
t.Errorf("Hostname mismatch: %s vs %s", decoded.Hostname, info.Hostname)
|
|
}
|
|
if decoded.Status != info.Status {
|
|
t.Errorf("Status mismatch: %s vs %s", decoded.Status, info.Status)
|
|
}
|
|
if decoded.TasksComplete != info.TasksComplete {
|
|
t.Errorf("TasksComplete mismatch: %d vs %d", decoded.TasksComplete, info.TasksComplete)
|
|
}
|
|
if decoded.TasksFailed != info.TasksFailed {
|
|
t.Errorf("TasksFailed mismatch: %d vs %d", decoded.TasksFailed, info.TasksFailed)
|
|
}
|
|
}
|