mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-24 08:32:27 +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
324 lines
6.9 KiB
Go
324 lines
6.9 KiB
Go
package core
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/goccy/go-yaml"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMatchesTopic(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
triggerTopic string
|
|
eventTopic string
|
|
triggerType TriggerType
|
|
want bool
|
|
}{
|
|
// Non-event triggers should not match
|
|
{
|
|
name: "cron trigger should not match",
|
|
triggerType: TriggerCron,
|
|
eventTopic: "test.topic",
|
|
want: false,
|
|
},
|
|
// Empty topic matches all
|
|
{
|
|
name: "empty topic matches all events",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "",
|
|
eventTopic: "any.topic",
|
|
want: true,
|
|
},
|
|
// Exact matching
|
|
{
|
|
name: "exact match succeeds",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "assets.new",
|
|
eventTopic: "assets.new",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "exact match fails on different topic",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "assets.new",
|
|
eventTopic: "assets.updated",
|
|
want: false,
|
|
},
|
|
// Wildcard patterns
|
|
{
|
|
name: "star matches everything",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "*",
|
|
eventTopic: "any.topic.here",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "prefix wildcard - matches",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test*",
|
|
eventTopic: "test.asset.new",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "prefix wildcard - does not match",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test*",
|
|
eventTopic: "other.topic",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "suffix wildcard - matches",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "*.new",
|
|
eventTopic: "assets.new",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "suffix wildcard - does not match",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "*.new",
|
|
eventTopic: "assets.updated",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "middle wildcard - matches",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "assets.*.created",
|
|
eventTopic: "assets.subdomain.created",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "middle wildcard - does not match different suffix",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "assets.*.created",
|
|
eventTopic: "assets.subdomain.updated",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "question mark wildcard - matches single char",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test?",
|
|
eventTopic: "test1",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "question mark wildcard - does not match multiple chars",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test?",
|
|
eventTopic: "test123",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "character class - matches",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test[abc]",
|
|
eventTopic: "testa",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "character class - does not match",
|
|
triggerType: TriggerEvent,
|
|
triggerTopic: "test[abc]",
|
|
eventTopic: "testd",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
trigger := &Trigger{
|
|
On: tt.triggerType,
|
|
}
|
|
if tt.triggerType == TriggerEvent {
|
|
trigger.Event = &EventConfig{
|
|
Topic: tt.triggerTopic,
|
|
}
|
|
}
|
|
|
|
got := trigger.MatchesTopic(tt.eventTopic)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchesTopic_NilEvent(t *testing.T) {
|
|
trigger := &Trigger{
|
|
On: TriggerEvent,
|
|
Event: nil,
|
|
}
|
|
|
|
got := trigger.MatchesTopic("any.topic")
|
|
assert.False(t, got, "nil event should not match")
|
|
}
|
|
|
|
func TestContainsWildcard(t *testing.T) {
|
|
tests := []struct {
|
|
pattern string
|
|
want bool
|
|
}{
|
|
{"simple.topic", false},
|
|
{"*", true},
|
|
{"test*", true},
|
|
{"*.new", true},
|
|
{"test.*.new", true},
|
|
{"test?", true},
|
|
{"test[abc]", true},
|
|
{"test[a-z]", true},
|
|
{"no-wildcards-here", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.pattern, func(t *testing.T) {
|
|
got := containsWildcard(tt.pattern)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTriggerInputUnmarshalYAML(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
yamlInput string
|
|
wantHasVars bool
|
|
wantVars map[string]string
|
|
wantType string
|
|
wantField string
|
|
wantName string
|
|
wantFunction string
|
|
}{
|
|
{
|
|
name: "legacy syntax with type event_data",
|
|
yamlInput: `
|
|
type: event_data
|
|
field: url
|
|
name: target
|
|
`,
|
|
wantHasVars: false,
|
|
wantType: "event_data",
|
|
wantField: "url",
|
|
wantName: "target",
|
|
},
|
|
{
|
|
name: "legacy syntax with function",
|
|
yamlInput: `
|
|
type: function
|
|
function: 'trim({{event.data}})'
|
|
name: result
|
|
`,
|
|
wantHasVars: false,
|
|
wantType: "function",
|
|
wantFunction: "trim({{event.data}})",
|
|
wantName: "result",
|
|
},
|
|
{
|
|
name: "new vars syntax - simple field access",
|
|
yamlInput: `
|
|
target: event_data.url
|
|
source: event.source
|
|
`,
|
|
wantHasVars: true,
|
|
wantVars: map[string]string{
|
|
"target": "event_data.url",
|
|
"source": "event.source",
|
|
},
|
|
},
|
|
{
|
|
name: "new vars syntax - with function calls",
|
|
yamlInput: `
|
|
target: event_data.url
|
|
description: trim(event_data.desc)
|
|
asset_type: event_data.type
|
|
`,
|
|
wantHasVars: true,
|
|
wantVars: map[string]string{
|
|
"target": "event_data.url",
|
|
"description": "trim(event_data.desc)",
|
|
"asset_type": "event_data.type",
|
|
},
|
|
},
|
|
{
|
|
name: "new vars syntax - nested field access",
|
|
yamlInput: `
|
|
target: event_data.metadata.url
|
|
port: event_data.metadata.port
|
|
`,
|
|
wantHasVars: true,
|
|
wantVars: map[string]string{
|
|
"target": "event_data.metadata.url",
|
|
"port": "event_data.metadata.port",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var input TriggerInput
|
|
err := yaml.Unmarshal([]byte(tt.yamlInput), &input)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.wantHasVars, input.HasVars())
|
|
|
|
if tt.wantHasVars {
|
|
assert.Equal(t, tt.wantVars, input.Vars)
|
|
// Legacy fields should be empty
|
|
assert.Empty(t, input.Type)
|
|
assert.Empty(t, input.Field)
|
|
assert.Empty(t, input.Name)
|
|
} else {
|
|
assert.Equal(t, tt.wantType, input.Type)
|
|
assert.Equal(t, tt.wantField, input.Field)
|
|
assert.Equal(t, tt.wantName, input.Name)
|
|
assert.Equal(t, tt.wantFunction, input.Function)
|
|
assert.Nil(t, input.Vars)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTriggerInputHasVars(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ti TriggerInput
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty TriggerInput",
|
|
ti: TriggerInput{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "legacy syntax",
|
|
ti: TriggerInput{
|
|
Type: "event_data",
|
|
Field: "url",
|
|
Name: "target",
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "new vars syntax",
|
|
ti: TriggerInput{
|
|
Vars: map[string]string{
|
|
"target": "event_data.url",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "empty vars map",
|
|
ti: TriggerInput{
|
|
Vars: map[string]string{},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, tt.ti.HasVars())
|
|
})
|
|
}
|
|
}
|