diff --git a/internal/ingest/alert_forward_test.go b/internal/ingest/alert_forward_test.go deleted file mode 100644 index 155cc00..0000000 --- a/internal/ingest/alert_forward_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package ingest - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "git.apinb.com/ops/logs/internal/config" -) - -func TestForwardAlertPostsRawEventIngestPayload(t *testing.T) { - var gotPath string - var gotBody RawEventIngestBody - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotPath = r.URL.Path - if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { - t.Fatalf("decode body: %v", err) - } - w.WriteHeader(http.StatusOK) - })) - defer server.Close() - - config.Spec.AlertForward = &config.AlertForwardConf{ - Enabled: true, - BaseURL: server.URL, - InternalKey: "internal-key", - } - - err := forwardAlert(AlertReceiveBody{ - AlertName: "H3C Trap", - Summary: "Interface down", - Description: "Interface down", - SeverityCode: "major", - Labels: map[string]string{ - "source_subtype": "snmp_trap", - "ip": "192.168.1.10", - }, - Agent: "logs-trap", - RawData: json.RawMessage(`{"trap_oid":"1.3.6.1.4.1"}`), - }) - if err != nil { - t.Fatalf("forwardAlert returned error: %v", err) - } - - if gotPath != "/Alert/v1/raw-events/ingest" { - t.Fatalf("expected raw event ingest path, got %q", gotPath) - } - if gotBody.SourceType != "trap" { - t.Fatalf("expected trap source type, got %q", gotBody.SourceType) - } - if gotBody.ParseStatus != "parsed" { - t.Fatalf("expected parsed status, got %q", gotBody.ParseStatus) - } - if string(gotBody.RawPayload) != `{"trap_oid":"1.3.6.1.4.1"}` { - t.Fatalf("raw payload changed: %s", string(gotBody.RawPayload)) - } -} diff --git a/internal/ingest/alert_outbox_test.go b/internal/ingest/alert_outbox_test.go deleted file mode 100644 index 20fd405..0000000 --- a/internal/ingest/alert_outbox_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package ingest - -import "testing" - -func TestTruncateError(t *testing.T) { - got := truncateError(" abcdef ", 3) - if got != "abc" { - t.Fatalf("unexpected value: %q", got) - } -} - diff --git a/internal/ingest/replay_test.go b/internal/ingest/replay_test.go deleted file mode 100644 index a19a758..0000000 --- a/internal/ingest/replay_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package ingest - -import ( - "encoding/json" - "testing" - - "git.apinb.com/ops/logs/internal/models" -) - -func TestSyslogRuleMatchDetailsExtractsResourceUID(t *testing.T) { - rule := models.SyslogRule{ - Name: "H3C link down", - Enabled: true, - SourceMatch: "h3c-core", - MessageRegex: `Interface (?PGigabitEthernet[0-9/]+) is down`, - ResourceUIDExtractRegex: `Interface (?PGigabitEthernet[0-9/]+) is down`, - } - - match := syslogRuleMatchDetails(&rule, "h3c-core-01", "Interface GigabitEthernet1/0/1 is down", "") - if !match.Matched { - t.Fatal("expected rule to match") - } - if match.ResourceUID != "network:GigabitEthernet1/0/1" { - t.Fatalf("unexpected resource uid: %q", match.ResourceUID) - } -} - -func TestBuildReplayRawEventPayloadMarksReplayed(t *testing.T) { - ev := models.LogEvent{ - ID: 12, - SourceKind: "syslog", - SourceIP: "10.1.2.3", - RemoteAddr: "10.1.2.3:514", - DeviceName: "h3c-core-01", - RawPayload: "<189>Jun 24 10:00:01 h3c-core-01 IFNET/4/LINK_DOWN: Interface GigabitEthernet1/0/1 is down", - NormalizedSummary: "h3c-core-01: Interface GigabitEthernet1/0/1 is down", - SeverityCode: "warning", - DispatchStatus: "pending", - } - - body, err := BuildReplayRawEventPayload(ev) - if err != nil { - t.Fatalf("BuildReplayRawEventPayload returned error: %v", err) - } - if body.SourceType != "syslog" { - t.Fatalf("unexpected source type: %q", body.SourceType) - } - if body.ParseStatus != "replayed" { - t.Fatalf("unexpected parse status: %q", body.ParseStatus) - } - if body.Labels["replay_of_log_event_id"] != "12" { - t.Fatalf("missing replay label: %#v", body.Labels) - } - var raw map[string]any - if err := json.Unmarshal(body.RawPayload, &raw); err != nil { - t.Fatalf("raw payload should be json: %v", err) - } - if raw["raw_packet"] == "" { - t.Fatalf("raw packet missing: %#v", raw) - } -} diff --git a/internal/ingest/resource_resolver_test.go b/internal/ingest/resource_resolver_test.go deleted file mode 100644 index eeb07f6..0000000 --- a/internal/ingest/resource_resolver_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package ingest - -import "testing" - -func TestResolveResourceByIPFirst(t *testing.T) { - e := &Engine{ - resourceByIP: map[string]resourceRef{ - "10.0.0.10": {ResourceType: "server", ResourceID: "srv-10", ResourceName: "s10"}, - }, - resourceByHN: map[string]resourceRef{ - "host-a": {ResourceType: "device", ResourceID: "dev-a", ResourceName: "a"}, - }, - } - ref, method := e.resolveResource("10.0.0.10", "host-a") - if method != "ip" { - t.Fatalf("method=%s", method) - } - if ref.ResourceID != "srv-10" { - t.Fatalf("resource id=%s", ref.ResourceID) - } -} - -func TestResolveResourceByHostname(t *testing.T) { - e := &Engine{ - resourceByIP: map[string]resourceRef{}, - resourceByHN: map[string]resourceRef{ - "host-a": {ResourceType: "device", ResourceID: "dev-a", ResourceName: "a"}, - }, - } - ref, method := e.resolveResource("10.0.0.20", "HOST-A") - if method != "hostname" { - t.Fatalf("method=%s", method) - } - if ref.ResourceID != "dev-a" { - t.Fatalf("resource id=%s", ref.ResourceID) - } -} - -func TestResolveResourceNoMatch(t *testing.T) { - e := &Engine{ - resourceByIP: map[string]resourceRef{}, - resourceByHN: map[string]resourceRef{}, - } - _, method := e.resolveResource("10.0.0.20", "host-b") - if method != "none" { - t.Fatalf("method=%s", method) - } -} - diff --git a/internal/ingest/syslog_parse_test.go b/internal/ingest/syslog_parse_test.go deleted file mode 100644 index c638299..0000000 --- a/internal/ingest/syslog_parse_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package ingest - -import ( - "encoding/json" - "net" - "testing" - "time" - - "git.apinb.com/ops/logs/internal/models" - "github.com/gosnmp/gosnmp" -) - -func TestParseSyslogPayloadPri(t *testing.T) { - p := parseSyslogPayload([]byte("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8")) - if p.Priority != 34 { - t.Fatalf("priority=%d", p.Priority) - } -} - -func TestParseSyslogPayloadRFC3164Hostname(t *testing.T) { - p := parseSyslogPayload([]byte("Oct 11 22:14:15 mymachine su: failed")) - if p.Hostname != "mymachine" { - t.Fatalf("hostname=%q", p.Hostname) - } - if p.Tag != "su" { - t.Fatalf("tag=%q", p.Tag) - } - if p.Message != "failed" { - t.Fatalf("message=%q", p.Message) - } -} - -func TestForwardAlertBodyIncludesRawData(t *testing.T) { - raw := []byte(`{"source":"syslog","parsed":{}}`) - b := AlertReceiveBody{ - AlertName: "a", - RawData: raw, - } - data, err := json.Marshal(b) - if err != nil { - t.Fatal(err) - } - var dec map[string]json.RawMessage - if err := json.Unmarshal(data, &dec); err != nil { - t.Fatal(err) - } - if string(dec["raw_data"]) != string(raw) { - t.Fatalf("raw_data %s", dec["raw_data"]) - } -} - -func TestInTimeWindowsInvalidJSONReturnsFalse(t *testing.T) { - now := time.Date(2026, 1, 1, 10, 0, 0, 0, time.Local) - if inTimeWindows(now, "{invalid") { - t.Fatal("invalid json should not be treated as always effective") - } -} - -func TestTrapShieldedAllowsEmptySourceIPCIDR(t *testing.T) { - e := &Engine{ - shields: []models.TrapShield{ - { - Enabled: true, - SourceIPCIDR: "", - OIDPrefix: "1.3.6.1.4.1", - InterfaceHint: "", - TimeWindowsJSON: "", - }, - }, - } - addr := &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 162} - pkt := &gosnmp.SnmpPacket{} - if !trapShielded(e, addr, "1.3.6.1.4.1.999", pkt) { - t.Fatal("shield should match when source_ip_cidr is empty and other conditions match") - } -} diff --git a/internal/logic/audit/audit_test.go b/internal/logic/audit/audit_test.go deleted file mode 100644 index c1faaff..0000000 --- a/internal/logic/audit/audit_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package audit - -import ( - "testing" -) - -func TestValidateRecordRequiresDangerousOperationsToCarryReviewID(t *testing.T) { - record := Record{ - SourceService: "alert", - ActorID: "u-1", - Action: "policy.update", - ObjectType: "notification_policy", - ObjectID: "np-1", - OperationRisk: RiskDangerous, - } - - if err := ValidateRecord(record); err == nil { - t.Fatal("expected dangerous operation without approval id to fail") - } - - record.ApprovalID = "apr-1" - if err := ValidateRecord(record); err != nil { - t.Fatalf("expected valid dangerous audit record, got %v", err) - } -} - -func TestNormalizeRecordClassifiesDangerousActions(t *testing.T) { - record := NormalizeRecord(Record{ - SourceService: " alert ", - Action: "notification_policy.update", - ObjectType: " notification_policy ", - ObjectID: " np-1 ", - ActorID: " u-1 ", - }) - - if record.SourceService != "alert" || record.ObjectType != "notification_policy" || record.ObjectID != "np-1" { - t.Fatalf("record was not normalized: %#v", record) - } - if record.OperationRisk != RiskDangerous { - t.Fatalf("notification policy changes must be dangerous, got %q", record.OperationRisk) - } -} - -func TestApprovalTransitionAllowsApproveOnlyFromPending(t *testing.T) { - req := ApprovalRequest{Status: ApprovalPending} - - approved, err := Transition(req, ApprovalApproved, "reviewer-1", "ok") - if err != nil { - t.Fatalf("expected pending approval to approve: %v", err) - } - if approved.Status != ApprovalApproved { - t.Fatalf("unexpected status: %s", approved.Status) - } - if approved.ReviewerID != "reviewer-1" || approved.ReviewComment != "ok" { - t.Fatalf("review metadata not stored: %#v", approved) - } - - if _, err := Transition(approved, ApprovalRejected, "reviewer-2", "late"); err == nil { - t.Fatal("expected approved request to reject further transition") - } -} diff --git a/internal/logic/controllers/resource_event_test.go b/internal/logic/controllers/resource_event_test.go deleted file mode 100644 index 9945f03..0000000 --- a/internal/logic/controllers/resource_event_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package controllers - -import ( - "crypto/hmac" - "crypto/sha256" - "fmt" - "testing" - "time" - - "git.apinb.com/ops/logs/internal/config" -) - -func TestValidateResourceEventRequest(t *testing.T) { - req := &resourceEventRequest{ - EventID: "evt-1", - EventTime: "2026-04-27T08:00:00Z", - EventType: resourceEventUpsert, - ResourceType: "server", - ResourceID: "srv-1", - ResourceName: "server-1", - Version: 1, - } - if _, err := validateResourceEventRequest(req); err != nil { - t.Fatalf("expected valid request, got error: %v", err) - } -} - -func TestValidateResourceEventRequestInvalidTime(t *testing.T) { - req := &resourceEventRequest{ - EventID: "evt-1", - EventTime: "bad-time", - EventType: resourceEventUpsert, - ResourceType: "server", - ResourceID: "srv-1", - Version: 1, - } - if _, err := validateResourceEventRequest(req); err == nil { - t.Fatal("expected invalid time error") - } -} - -func TestNonEmptyUnique(t *testing.T) { - got := nonEmptyUnique([]string{" 10.0.0.1 ", "", "10.0.0.1", "host-a", "host-a"}) - if len(got) != 2 { - t.Fatalf("unexpected unique size: %d", len(got)) - } - if got[0] != "10.0.0.1" || got[1] != "host-a" { - t.Fatalf("unexpected output: %#v", got) - } -} - -func TestVerifyResourceEventSignature(t *testing.T) { - old := config.Spec.ResourceEvent.HMACSecret - config.Spec.ResourceEvent.HMACSecret = "abc123" - defer func() { - config.Spec.ResourceEvent.HMACSecret = old - }() - - body := []byte(`{"event_id":"evt-1"}`) - mac := hmac.New(sha256.New, []byte("abc123")) - mac.Write(body) - signature := fmt.Sprintf("%x", mac.Sum(nil)) - if err := verifyResourceEventSignature(signature, body); err != nil { - t.Fatalf("expected signature to pass: %v", err) - } - if err := verifyResourceEventSignature("bad", body); err == nil { - t.Fatal("expected invalid signature error") - } -} - -func TestValidateEventTimeSkew(t *testing.T) { - old := config.Spec.ResourceEvent.MaxSkewSecs - config.Spec.ResourceEvent.MaxSkewSecs = 60 - defer func() { - config.Spec.ResourceEvent.MaxSkewSecs = old - }() - - if err := validateEventTimeSkew(time.Now()); err != nil { - t.Fatalf("expected current time to pass: %v", err) - } - if err := validateEventTimeSkew(time.Now().Add(-2 * time.Minute)); err == nil { - t.Fatal("expected skew validation to fail for old timestamp") - } -} - diff --git a/internal/models/blueprint_fields_test.go b/internal/models/blueprint_fields_test.go deleted file mode 100644 index 56e6e8c..0000000 --- a/internal/models/blueprint_fields_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package models - -import ( - "reflect" - "testing" -) - -func TestTrapDictionaryEntryHasBlueprintFields(t *testing.T) { - typ := reflect.TypeOf(TrapDictionaryEntry{}) - for _, name := range []string{"Vendor", "OID", "Name", "SeverityMappingJSON", "ParseExpression"} { - if _, ok := typ.FieldByName(name); !ok { - t.Fatalf("TrapDictionaryEntry missing blueprint field %s", name) - } - } -} - -func TestSyslogRuleHasBlueprintFields(t *testing.T) { - typ := reflect.TypeOf(SyslogRule{}) - for _, name := range []string{"SourceMatch", "MessageRegex", "SeverityMappingJSON", "ResourceUIDExtractRegex"} { - if _, ok := typ.FieldByName(name); !ok { - t.Fatalf("SyslogRule missing blueprint field %s", name) - } - } -}