Event ingestion now uses the source syslog PRI and an explicit-offset timezone timeline instead of assuming host-local time. CPU PPIN is exported as the source-backed CPU serial, and an active NVMe fault SEL event promotes the matching drive's storage status. See ADL-056, ADL-057. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
3.5 KiB
Go
86 lines
3.5 KiB
Go
package inspur
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
)
|
|
|
|
func TestDedupSELEvents(t *testing.T) {
|
|
ts := time.Date(2026, 7, 27, 4, 52, 55, 0, time.UTC)
|
|
events := []models.Event{
|
|
{Source: "BMC", Timestamp: ts, EventType: "Assert", Severity: models.SeverityCritical, Description: "Sys_Health Transition to Critical from less severe"},
|
|
{Source: "SEL", Timestamp: ts, EventType: "Transition to Critical from less severe", Severity: models.SeverityCritical, Description: "Transition to Critical from less severe", RawData: "Asserted"},
|
|
{Source: "SEL", Timestamp: ts.Add(time.Second), EventType: "Asserted", Description: "PSU0 fail"},
|
|
{Source: "SEL", Timestamp: ts.Add(time.Second), EventType: "Asserted", Description: "PSU0 fail"},
|
|
{Source: "BMC", Timestamp: ts.Add(2 * time.Second), EventType: "Assert", Description: "recurring alarm"},
|
|
{Source: "BMC", Timestamp: ts.Add(2 * time.Second), EventType: "Assert", Description: "recurring alarm"},
|
|
}
|
|
|
|
out := dedupSELEvents(events)
|
|
if len(out) != 4 {
|
|
t.Fatalf("expected IDL/SEL copy and exact SEL copy removed, recurring BMC alarms retained; got %d: %+v", len(out), out)
|
|
}
|
|
if out[0].Source != "BMC" {
|
|
t.Fatalf("expected richer IDL/BMC event to be retained, got %+v", out[0])
|
|
}
|
|
}
|
|
|
|
func TestParseSELListWithLocation_UsesProvidedTimezone(t *testing.T) {
|
|
content := []byte("sel elist:\n1,02/28/2026,04:18:18,Sensor X,Event,Asserted\n")
|
|
shanghai, err := time.LoadLocation("Asia/Shanghai")
|
|
if err != nil {
|
|
t.Fatalf("load location: %v", err)
|
|
}
|
|
|
|
events := ParseSELListWithLocation(content, shanghai)
|
|
if len(events) != 1 {
|
|
t.Fatalf("expected 1 event, got %d", len(events))
|
|
}
|
|
|
|
// 04:18:18 +08:00 == 20:18:18Z (previous day)
|
|
want := time.Date(2026, 2, 27, 20, 18, 18, 0, time.UTC)
|
|
if !events[0].Timestamp.UTC().Equal(want) {
|
|
t.Fatalf("unexpected timestamp: got %s want %s", events[0].Timestamp.UTC(), want)
|
|
}
|
|
}
|
|
|
|
func TestParseTimezoneConfigLocation(t *testing.T) {
|
|
content := []byte("[TimeZoneConfig]\ntimezone=Asia/Shanghai\n")
|
|
got := parseTimezoneConfigLocation(content)
|
|
if got != "Asia/Shanghai" {
|
|
t.Fatalf("unexpected timezone: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSELTimezoneResolverUsesExplicitOffsetTimeline(t *testing.T) {
|
|
files := []parser.ExtractedFile{
|
|
{Path: "onekeylog/configuration/conf/timezone.conf", Content: []byte("[TimeZoneConfig]\ntimezone=Asia/Shanghai\n")},
|
|
{Path: "onekeylog/log/syslog/idl.log", Content: []byte(
|
|
"|2026-01-30T14:25:14+08:00|BMC|Assert|Info|1|old timezone|\n" +
|
|
"|2026-07-24T02:20:38+03:00|BMC|Assert|Critical|2|new timezone|\n")},
|
|
}
|
|
fallback := inferInspurArchiveLocation(files)
|
|
resolver := inferInspurTimezoneResolver(files, fallback)
|
|
events := parseSELListWithResolver([]byte("sel elist:\n"+
|
|
"1,01/30/2026,14:25:14,Chassis Sys_Health,old timezone,Asserted\n"+
|
|
"2,07/24/2026,02:20:38,Chassis Sys_Health,new timezone,Asserted\n"), resolver)
|
|
if len(events) != 2 {
|
|
t.Fatalf("expected 2 events, got %d", len(events))
|
|
}
|
|
wantOld := time.Date(2026, 1, 30, 6, 25, 14, 0, time.UTC)
|
|
wantNew := time.Date(2026, 7, 23, 23, 20, 38, 0, time.UTC)
|
|
if !events[0].Timestamp.UTC().Equal(wantOld) || !events[1].Timestamp.UTC().Equal(wantNew) {
|
|
t.Fatalf("unexpected resolved timestamps: got %s and %s", events[0].Timestamp.UTC(), events[1].Timestamp.UTC())
|
|
}
|
|
}
|
|
|
|
func TestDetermineSELSeverityDeassertedFaultIsInfo(t *testing.T) {
|
|
got := determineSELSeverity("Power Supply PSU0", "Failure detected", "Deasserted")
|
|
if got != models.SeverityInfo {
|
|
t.Fatalf("expected cleared fault to be info, got %q", got)
|
|
}
|
|
}
|