test(api): add unit tests for types.go helper functions
- Add TestNewLogRecord covering complete records, nil fingerprints, and zero values - Add TestDefaultConfig verifying default configuration values - Add TestJoinStringSlice testing edge cases (empty, nil, single, multiple elements) - Add TestLogRecordConversion verifying TCP options formatting Implements testing.policy.requirements.test_skeletons from architecture.yml Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
269
api/types_test.go
Normal file
269
api/types_test.go
Normal file
@ -0,0 +1,269 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewLogRecord(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clientHello TLSClientHello
|
||||
fingerprints *Fingerprints
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
name: "complete record with fingerprints",
|
||||
clientHello: TLSClientHello{
|
||||
SrcIP: "192.168.1.100",
|
||||
SrcPort: 54321,
|
||||
DstIP: "10.0.0.1",
|
||||
DstPort: 443,
|
||||
IPMeta: IPMeta{
|
||||
TTL: 64,
|
||||
TotalLength: 512,
|
||||
IPID: 12345,
|
||||
DF: true,
|
||||
},
|
||||
TCPMeta: TCPMeta{
|
||||
WindowSize: 65535,
|
||||
MSS: 1460,
|
||||
WindowScale: 7,
|
||||
Options: []string{"MSS", "WS", "SACK", "TS"},
|
||||
},
|
||||
},
|
||||
fingerprints: &Fingerprints{
|
||||
JA4: "t13d1516h2_8daaf6152771_02cb136f2775",
|
||||
JA4Hash: "8daaf6152771_02cb136f2775",
|
||||
JA3: "771,4865-4866-4867,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
|
||||
JA3Hash: "a0e6f06c7a6d15e5e3f0f0e6f06c7a6d",
|
||||
},
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "record without fingerprints",
|
||||
clientHello: TLSClientHello{
|
||||
SrcIP: "192.168.1.100",
|
||||
SrcPort: 54321,
|
||||
DstIP: "10.0.0.1",
|
||||
DstPort: 443,
|
||||
IPMeta: IPMeta{
|
||||
TTL: 64,
|
||||
TotalLength: 512,
|
||||
IPID: 12345,
|
||||
DF: true,
|
||||
},
|
||||
TCPMeta: TCPMeta{
|
||||
WindowSize: 65535,
|
||||
MSS: 1460,
|
||||
WindowScale: 7,
|
||||
Options: []string{"MSS", "WS"},
|
||||
},
|
||||
},
|
||||
fingerprints: nil,
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "record with zero values for optional fields",
|
||||
clientHello: TLSClientHello{
|
||||
SrcIP: "192.168.1.100",
|
||||
SrcPort: 54321,
|
||||
DstIP: "10.0.0.1",
|
||||
DstPort: 443,
|
||||
IPMeta: IPMeta{
|
||||
TTL: 0,
|
||||
TotalLength: 0,
|
||||
IPID: 0,
|
||||
DF: false,
|
||||
},
|
||||
TCPMeta: TCPMeta{
|
||||
WindowSize: 0,
|
||||
MSS: 0,
|
||||
WindowScale: 0,
|
||||
Options: []string{},
|
||||
},
|
||||
},
|
||||
fingerprints: nil,
|
||||
wantNil: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rec := NewLogRecord(tt.clientHello, tt.fingerprints)
|
||||
|
||||
// Verify basic fields
|
||||
if rec.SrcIP != tt.clientHello.SrcIP {
|
||||
t.Errorf("SrcIP = %v, want %v", rec.SrcIP, tt.clientHello.SrcIP)
|
||||
}
|
||||
if rec.SrcPort != tt.clientHello.SrcPort {
|
||||
t.Errorf("SrcPort = %v, want %v", rec.SrcPort, tt.clientHello.SrcPort)
|
||||
}
|
||||
if rec.DstIP != tt.clientHello.DstIP {
|
||||
t.Errorf("DstIP = %v, want %v", rec.DstIP, tt.clientHello.DstIP)
|
||||
}
|
||||
if rec.DstPort != tt.clientHello.DstPort {
|
||||
t.Errorf("DstPort = %v, want %v", rec.DstPort, tt.clientHello.DstPort)
|
||||
}
|
||||
|
||||
// Verify IPMeta fields
|
||||
if rec.IPTTL != tt.clientHello.IPMeta.TTL {
|
||||
t.Errorf("IPTTL = %v, want %v", rec.IPTTL, tt.clientHello.IPMeta.TTL)
|
||||
}
|
||||
if rec.IPTotalLen != tt.clientHello.IPMeta.TotalLength {
|
||||
t.Errorf("IPTotalLen = %v, want %v", rec.IPTotalLen, tt.clientHello.IPMeta.TotalLength)
|
||||
}
|
||||
if rec.IPID != tt.clientHello.IPMeta.IPID {
|
||||
t.Errorf("IPID = %v, want %v", rec.IPID, tt.clientHello.IPMeta.IPID)
|
||||
}
|
||||
if rec.IPDF != tt.clientHello.IPMeta.DF {
|
||||
t.Errorf("IPDF = %v, want %v", rec.IPDF, tt.clientHello.IPMeta.DF)
|
||||
}
|
||||
|
||||
// Verify TCPMeta fields
|
||||
if rec.TCPWindow != tt.clientHello.TCPMeta.WindowSize {
|
||||
t.Errorf("TCPWindow = %v, want %v", rec.TCPWindow, tt.clientHello.TCPMeta.WindowSize)
|
||||
}
|
||||
|
||||
// Verify optional fields (MSS, WindowScale)
|
||||
if tt.clientHello.TCPMeta.MSS != 0 {
|
||||
if rec.TCPMSS == nil {
|
||||
t.Error("TCPMSS should not be nil when MSS != 0")
|
||||
} else if *rec.TCPMSS != tt.clientHello.TCPMeta.MSS {
|
||||
t.Errorf("TCPMSS = %v, want %v", *rec.TCPMSS, tt.clientHello.TCPMeta.MSS)
|
||||
}
|
||||
} else {
|
||||
if rec.TCPMSS != nil {
|
||||
t.Error("TCPMSS should be nil when MSS == 0")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.clientHello.TCPMeta.WindowScale != 0 {
|
||||
if rec.TCPWScale == nil {
|
||||
t.Error("TCPWScale should not be nil when WindowScale != 0")
|
||||
} else if *rec.TCPWScale != tt.clientHello.TCPMeta.WindowScale {
|
||||
t.Errorf("TCPWScale = %v, want %v", *rec.TCPWScale, tt.clientHello.TCPMeta.WindowScale)
|
||||
}
|
||||
} else {
|
||||
if rec.TCPWScale != nil {
|
||||
t.Error("TCPWScale should be nil when WindowScale == 0")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify fingerprints
|
||||
if tt.fingerprints != nil {
|
||||
if rec.JA4 != tt.fingerprints.JA4 {
|
||||
t.Errorf("JA4 = %v, want %v", rec.JA4, tt.fingerprints.JA4)
|
||||
}
|
||||
if rec.JA4Hash != tt.fingerprints.JA4Hash {
|
||||
t.Errorf("JA4Hash = %v, want %v", rec.JA4Hash, tt.fingerprints.JA4Hash)
|
||||
}
|
||||
if rec.JA3 != tt.fingerprints.JA3 {
|
||||
t.Errorf("JA3 = %v, want %v", rec.JA3, tt.fingerprints.JA3)
|
||||
}
|
||||
if rec.JA3Hash != tt.fingerprints.JA3Hash {
|
||||
t.Errorf("JA3Hash = %v, want %v", rec.JA3Hash, tt.fingerprints.JA3Hash)
|
||||
}
|
||||
} else {
|
||||
if rec.JA4 != "" {
|
||||
t.Error("JA4 should be empty when fingerprints is nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
if cfg.Core.Interface != DefaultInterface {
|
||||
t.Errorf("Core.Interface = %v, want %v", cfg.Core.Interface, DefaultInterface)
|
||||
}
|
||||
if len(cfg.Core.ListenPorts) != 1 {
|
||||
t.Errorf("Core.ListenPorts length = %v, want 1", len(cfg.Core.ListenPorts))
|
||||
}
|
||||
if cfg.Core.ListenPorts[0] != DefaultPort {
|
||||
t.Errorf("Core.ListenPorts[0] = %v, want %v", cfg.Core.ListenPorts[0], DefaultPort)
|
||||
}
|
||||
if cfg.Core.BPFFilter != DefaultBPFFilter {
|
||||
t.Errorf("Core.BPFFilter = %v, want %v", cfg.Core.BPFFilter, DefaultBPFFilter)
|
||||
}
|
||||
if cfg.Core.FlowTimeoutSec != DefaultFlowTimeout {
|
||||
t.Errorf("Core.FlowTimeoutSec = %v, want %v", cfg.Core.FlowTimeoutSec, DefaultFlowTimeout)
|
||||
}
|
||||
if len(cfg.Outputs) != 0 {
|
||||
t.Errorf("Outputs length = %v, want 0", len(cfg.Outputs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinStringSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
slice []string
|
||||
sep string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
slice: []string{},
|
||||
sep: ",",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "nil slice",
|
||||
slice: nil,
|
||||
sep: ",",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
slice: []string{"hello"},
|
||||
sep: ",",
|
||||
want: "hello",
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
slice: []string{"MSS", "WS", "SACK", "TS"},
|
||||
sep: ",",
|
||||
want: "MSS,WS,SACK,TS",
|
||||
},
|
||||
{
|
||||
name: "multiple elements with multi-char separator",
|
||||
slice: []string{"MSS", "WS", "SACK"},
|
||||
sep: ", ",
|
||||
want: "MSS, WS, SACK",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := joinStringSlice(tt.slice, tt.sep)
|
||||
if got != tt.want {
|
||||
t.Errorf("joinStringSlice() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogRecordConversion(t *testing.T) {
|
||||
// Test that NewLogRecord correctly converts TCPMeta options to comma-separated string
|
||||
clientHello := TLSClientHello{
|
||||
SrcIP: "192.168.1.100",
|
||||
SrcPort: 54321,
|
||||
DstIP: "10.0.0.1",
|
||||
DstPort: 443,
|
||||
TCPMeta: TCPMeta{
|
||||
WindowSize: 65535,
|
||||
MSS: 1460,
|
||||
WindowScale: 7,
|
||||
Options: []string{"MSS", "WS", "SACK", "TS"},
|
||||
},
|
||||
}
|
||||
|
||||
rec := NewLogRecord(clientHello, nil)
|
||||
|
||||
// Verify options are joined with comma
|
||||
expectedOpts := "MSS,WS,SACK,TS"
|
||||
if rec.TCPOptions != expectedOpts {
|
||||
t.Errorf("TCPOptions = %v, want %v", rec.TCPOptions, expectedOpts)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user