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:
Jacquin Antoine
2026-02-26 23:59:29 +01:00
parent 9280cb545c
commit 39033c5424

269
api/types_test.go Normal file
View 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)
}
}