Files
osmedeus/internal/core/trigger_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

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())
})
}
}