orama/pkg/serverless/triggers/triggers_test.go

220 lines
6.5 KiB
Go

package triggers
import (
"context"
"encoding/json"
"sync/atomic"
"testing"
"time"
"github.com/DeBrosOfficial/network/pkg/serverless"
"go.uber.org/zap"
)
// ---------------------------------------------------------------------------
// Mock Invoker
// ---------------------------------------------------------------------------
type mockInvokeCall struct {
Namespace string
FunctionName string
TriggerType serverless.TriggerType
Input []byte
}
// mockInvokerForTest wraps a real nil invoker but tracks calls.
// Since we can't construct a real Invoker without engine/registry/hostfuncs,
// we test the dispatcher at a higher level by checking its behavior.
// ---------------------------------------------------------------------------
// Dispatcher Tests
// ---------------------------------------------------------------------------
func TestDispatcher_DepthLimit(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger) // store won't be called
d := NewPubSubDispatcher(store, nil, nil, logger)
// Dispatch at max depth should be a no-op (no panic, no store call)
d.Dispatch(context.Background(), "ns", "topic", []byte("data"), maxTriggerDepth)
d.Dispatch(context.Background(), "ns", "topic", []byte("data"), maxTriggerDepth+1)
}
func TestCacheKey(t *testing.T) {
key := cacheKey("my-namespace", "my-topic")
if key != "triggers:my-namespace:my-topic" {
t.Errorf("unexpected cache key: %s", key)
}
}
func TestPubSubEvent_Marshal(t *testing.T) {
event := PubSubEvent{
Topic: "chat",
Data: json.RawMessage(`{"msg":"hello"}`),
Namespace: "my-app",
TriggerDepth: 1,
Timestamp: 1708300000,
}
data, err := json.Marshal(event)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
var decoded PubSubEvent
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
if decoded.Topic != "chat" {
t.Errorf("expected topic 'chat', got '%s'", decoded.Topic)
}
if decoded.Namespace != "my-app" {
t.Errorf("expected namespace 'my-app', got '%s'", decoded.Namespace)
}
if decoded.TriggerDepth != 1 {
t.Errorf("expected depth 1, got %d", decoded.TriggerDepth)
}
}
// ---------------------------------------------------------------------------
// Store Tests (validation only — DB operations require rqlite.Client)
// ---------------------------------------------------------------------------
func TestStore_AddValidation(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger)
_, err := store.Add(context.Background(), "", "topic")
if err == nil {
t.Error("expected error for empty function ID")
}
_, err = store.Add(context.Background(), "fn-123", "")
if err == nil {
t.Error("expected error for empty topic")
}
}
func TestStore_RemoveValidation(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger)
err := store.Remove(context.Background(), "")
if err == nil {
t.Error("expected error for empty trigger ID")
}
}
func TestStore_RemoveByFunctionValidation(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger)
err := store.RemoveByFunction(context.Background(), "")
if err == nil {
t.Error("expected error for empty function ID")
}
}
func TestStore_ListByFunctionValidation(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger)
_, err := store.ListByFunction(context.Background(), "")
if err == nil {
t.Error("expected error for empty function ID")
}
}
func TestStore_GetByTopicAndNamespace_Empty(t *testing.T) {
logger, _ := zap.NewDevelopment()
store := NewPubSubTriggerStore(nil, logger)
// Empty topic/namespace should return nil, nil (not an error)
matches, err := store.GetByTopicAndNamespace(context.Background(), "", "ns")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if matches != nil {
t.Errorf("expected nil matches for empty topic, got %v", matches)
}
matches, err = store.GetByTopicAndNamespace(context.Background(), "topic", "")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if matches != nil {
t.Errorf("expected nil matches for empty namespace, got %v", matches)
}
}
// ---------------------------------------------------------------------------
// Dispatcher Integration-like Tests
// ---------------------------------------------------------------------------
func TestDispatcher_NoMatchesNoPanic(t *testing.T) {
// Dispatcher with nil olricClient and nil invoker should handle
// the case where there are no matches gracefully.
logger, _ := zap.NewDevelopment()
// Create a mock store that returns empty matches
store := &mockTriggerStore{matches: nil}
d := &PubSubDispatcher{
store: &PubSubTriggerStore{db: nil, logger: logger},
invoker: nil,
logger: logger,
}
// Replace store field directly for testing
d.store = store.asPubSubTriggerStore()
// This should not panic even with nil invoker since no matches
// We can't easily test this without a real store, so we test the depth limit instead
d.Dispatch(context.Background(), "ns", "topic", []byte("data"), maxTriggerDepth)
}
// mockTriggerStore is used only for structural validation in tests.
type mockTriggerStore struct {
matches []TriggerMatch
}
func (m *mockTriggerStore) asPubSubTriggerStore() *PubSubTriggerStore {
// Can't return a mock as *PubSubTriggerStore since it's a concrete type.
// This is a limitation — integration tests with a real rqlite would be needed.
return nil
}
// ---------------------------------------------------------------------------
// Callback Wiring Test
// ---------------------------------------------------------------------------
func TestOnPublishCallback(t *testing.T) {
var called atomic.Int32
var receivedNS, receivedTopic string
var receivedData []byte
callback := func(ctx context.Context, namespace, topic string, data []byte) {
called.Add(1)
receivedNS = namespace
receivedTopic = topic
receivedData = data
}
// Simulate what gateway.go does
callback(context.Background(), "my-ns", "events", []byte("hello"))
time.Sleep(10 * time.Millisecond) // Let goroutine complete
if called.Load() != 1 {
t.Errorf("expected callback called once, got %d", called.Load())
}
if receivedNS != "my-ns" {
t.Errorf("expected namespace 'my-ns', got '%s'", receivedNS)
}
if receivedTopic != "events" {
t.Errorf("expected topic 'events', got '%s'", receivedTopic)
}
if string(receivedData) != "hello" {
t.Errorf("expected data 'hello', got '%s'", string(receivedData))
}
}