package mock import ( "context" "testing" "time" "tera-clone/services/tera-api/internal/domain" "tera-clone/services/tera-api/internal/logstorage" ) func TestReceivedKeywordFromSerialJournalNotDependentOnLogViewer(t *testing.T) { journal := &sequencedMemoryJournal{} delivery := &recordingAlarmDeliveryController{} store, err := NewStoreWithOptions(fixedClock, StoreOptions{LogJournal: journal, AlarmDeliveryController: delivery}) if err != nil { t.Fatal(err) } rule := store.runningAlarms["port_ch20_keyword"] rule.Enabled = true rule.Trigger = "Invalid interface" rule.Delivery = []string{"web"} store.runningAlarms["port_ch20_keyword"] = rule now := fixedClock() appendRX := func(channel, direction, text string, matched bool) { t.Helper() now = now.Add(time.Millisecond) event := domain.PortLogEvent{Timestamp: now, Channel: channel, EventType: "traffic", Direction: direction, Actor: "tera-port-service", Message: "serial payload", Payload: []byte(text), PayloadBytes: len(text), PayloadEncoding: "base64", DisplayFormat: "ascii", AlarmTriggered: matched} if err := journal.Append(logstorage.ClassPort, now, event); err != nil { t.Fatal(err) } } evaluate := func() { t.Helper() if err := store.EvaluateOperationalAlarms(context.Background()); err != nil { t.Fatal(err) } } appendRX("CH20", "RX", "Invalid interface", false) evaluate() if len(delivery.requests) != 0 { t.Fatal("replayed old history on startup") } appendRX("CH20", "TX", "Invalid interface", false) appendRX("CH19", "RX", "Invalid interface", false) evaluate() if len(delivery.requests) != 0 { t.Fatal("matched wrong channel or TX") } appendRX("CH20", "RX", "\r\nInvalid inter", false) evaluate() if len(delivery.requests) != 0 { t.Fatal("matched incomplete keyword") } appendRX("CH20", "RX", "face\r\nEnter interface name", false) // A UI read advances its own journal cursor before the alarm evaluator. if _, err := store.PortLogEvents(context.Background(), domain.PortLogFilter{Channel: "CH20"}); err != nil { t.Fatal(err) } evaluate() evaluate() if len(delivery.requests) != 1 || delivery.requests[0].RuleID != "port_ch20_keyword" { t.Fatalf("requests=%+v", delivery.requests) } appendRX("CH20", "RX", "more prompt output", false) appendRX("CH20", "RX", "Invalid interface", true) evaluate() if len(delivery.requests) != 1 { t.Fatal("duplicate match or already processed API event") } rule.Enabled = false store.runningAlarms["port_ch20_keyword"] = rule appendRX("CH20", "RX", "Invalid interface", false) evaluate() if len(delivery.requests) != 1 { t.Fatal("disabled rule fired") } rule.Enabled = true rule.Trigger = "NEW KEYWORD" store.runningAlarms["port_ch20_keyword"] = rule appendRX("CH20", "RX", "Invalid interface", false) evaluate() appendRX("CH20", "RX", "new keyword", false) evaluate() if len(delivery.requests) != 2 { t.Fatal("updated case insensitive keyword not observed") } }