fix(ja4ebpf): split bpf2go generate into Ja4Tc + Ja4Ssl, fix RPM systemd-rpm-macros
- Use two separate //go:generate directives (Ja4Tc for tc_capture.c, Ja4Ssl
for uprobe_ssl.c) to avoid duplicate LICENSE symbol and multi-file clang issue
- Update loader.go to hold tcObjs/sslObjs separately with correct field names:
UprobeSslSetFd, UprobeSslReadEntry, UretprobeSslReadExit,
KprobeAccept4Entry, KretprobeAccept4Exit
- Add systemd-rpm-macros to all three RPM build stages (el8/el9/el10)
so that %{_unitdir} macro resolves correctly
- RPMs now build successfully for el8, el9, el10
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
221
old/services/sentinel/cmd/ja4sentinel/main_test.go
Normal file
221
old/services/sentinel/cmd/ja4sentinel/main_test.go
Normal file
@ -0,0 +1,221 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ports []uint16
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
ports: []uint16{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "single port",
|
||||
ports: []uint16{443},
|
||||
want: "443",
|
||||
},
|
||||
{
|
||||
name: "multiple ports",
|
||||
ports: []uint16{443, 8443, 9443},
|
||||
want: "443,8443,9443",
|
||||
},
|
||||
{
|
||||
name: "two ports",
|
||||
ports: []uint16{80, 443},
|
||||
want: "80,443",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatPorts(tt.ports)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatPorts() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMain_VersionFlag_VerifiesOutput tests that the version flag produces correct output
|
||||
// Note: This test verifies the version variables are set correctly
|
||||
func TestMain_VersionFlag_VerifiesOutput(t *testing.T) {
|
||||
// Verify version variables are set
|
||||
if Version == "" {
|
||||
t.Error("Version should not be empty")
|
||||
}
|
||||
if BuildTime == "" {
|
||||
t.Error("BuildTime should not be empty")
|
||||
}
|
||||
if GitCommit == "" {
|
||||
t.Error("GitCommit should not be empty")
|
||||
}
|
||||
|
||||
// Verify version format
|
||||
expectedPrefix := "ja4sentinel version"
|
||||
got := getVersionString()
|
||||
if !strings.HasPrefix(got, expectedPrefix) {
|
||||
t.Errorf("getVersionString() = %v, should start with %v", got, expectedPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
// getVersionString returns the version string (helper for testing)
|
||||
func getVersionString() string {
|
||||
return "ja4sentinel version " + Version + " (built " + BuildTime + ", commit " + GitCommit + ")"
|
||||
}
|
||||
|
||||
func TestFlagParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantConfig string
|
||||
wantVersion bool
|
||||
}{
|
||||
{
|
||||
name: "config flag",
|
||||
args: []string{"ja4sentinel", "-config", "/path/to/config.yml"},
|
||||
wantConfig: "/path/to/config.yml",
|
||||
wantVersion: false,
|
||||
},
|
||||
{
|
||||
name: "version flag",
|
||||
args: []string{"ja4sentinel", "-version"},
|
||||
wantConfig: "",
|
||||
wantVersion: true,
|
||||
},
|
||||
{
|
||||
name: "no flags",
|
||||
args: []string{"ja4sentinel"},
|
||||
wantConfig: "",
|
||||
wantVersion: false,
|
||||
},
|
||||
{
|
||||
name: "config with long form",
|
||||
args: []string{"ja4sentinel", "--config", "/etc/ja4sentinel/config.yml"},
|
||||
wantConfig: "/etc/ja4sentinel/config.yml",
|
||||
wantVersion: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
configPath := fs.String("config", "", "Path to configuration file (YAML)")
|
||||
version := fs.Bool("version", false, "Show version information")
|
||||
|
||||
err := fs.Parse(tt.args[1:])
|
||||
if err != nil {
|
||||
t.Fatalf("Flag parsing failed: %v", err)
|
||||
}
|
||||
|
||||
if *configPath != tt.wantConfig {
|
||||
t.Errorf("config = %v, want %v", *configPath, tt.wantConfig)
|
||||
}
|
||||
if *version != tt.wantVersion {
|
||||
t.Errorf("version = %v, want %v", *version, tt.wantVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMain_WithInvalidConfig tests that main exits gracefully with invalid config
|
||||
func TestMain_WithInvalidConfig(t *testing.T) {
|
||||
// This test verifies that the application handles config errors gracefully
|
||||
// We can't easily test the full main() function, but we can test the
|
||||
// config loading and error handling paths
|
||||
t.Log("Note: Full main() testing requires integration tests with mocked dependencies")
|
||||
}
|
||||
|
||||
// TestSignalHandling_VerifiesConstants tests that signal constants are defined
|
||||
func TestSignalHandling_VerifiesConstants(t *testing.T) {
|
||||
// Verify that we import the required packages for signal handling
|
||||
// This test ensures the imports are present
|
||||
t.Log("syscall and os/signal packages are imported for signal handling")
|
||||
}
|
||||
|
||||
// TestGracefulShutdown_SimulatesSignal tests graceful shutdown behavior
|
||||
func TestGracefulShutdown_SimulatesSignal(t *testing.T) {
|
||||
// This test documents the expected shutdown behavior
|
||||
// Full testing requires integration tests with actual signal sending
|
||||
|
||||
expectedBehavior := `
|
||||
Graceful shutdown sequence:
|
||||
1. Receive SIGINT or SIGTERM
|
||||
2. Stop packet capture
|
||||
3. Close output writers
|
||||
4. Flush pending logs
|
||||
5. Exit cleanly
|
||||
`
|
||||
t.Log(expectedBehavior)
|
||||
}
|
||||
|
||||
// TestLogRotation_SIGHUP tests SIGHUP handling for log rotation
|
||||
func TestLogRotation_SIGHUP(t *testing.T) {
|
||||
// This test documents the expected log rotation behavior
|
||||
// Full testing requires integration tests with actual SIGHUP signal
|
||||
|
||||
expectedBehavior := `
|
||||
Log rotation sequence (SIGHUP):
|
||||
1. Receive SIGHUP
|
||||
2. Reopen all reopenable writers (FileWriter, MultiWriter)
|
||||
3. Continue operation with new file handles
|
||||
4. No data loss during rotation
|
||||
`
|
||||
t.Log(expectedBehavior)
|
||||
}
|
||||
|
||||
// TestMain_ConfigValidation tests config validation before starting
|
||||
func TestMain_ConfigValidation(t *testing.T) {
|
||||
// Test that invalid configs are rejected before starting the pipeline
|
||||
tests := []struct {
|
||||
name string
|
||||
configErr string
|
||||
}{
|
||||
{
|
||||
name: "empty_interface",
|
||||
configErr: "interface cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "no_listen_ports",
|
||||
configErr: "at least one listen port required",
|
||||
},
|
||||
{
|
||||
name: "invalid_output_type",
|
||||
configErr: "unknown output type",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Verify that these error conditions are documented
|
||||
t.Logf("Expected error for %s: %s", tt.name, tt.configErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPipelineConstruction verifies the pipeline is built correctly
|
||||
func TestPipelineConstruction(t *testing.T) {
|
||||
// This test documents the expected pipeline construction
|
||||
// Full testing requires integration tests
|
||||
|
||||
expectedPipeline := `
|
||||
Pipeline construction:
|
||||
1. Load configuration
|
||||
2. Create logger
|
||||
3. Create capture engine
|
||||
4. Create TLS parser
|
||||
5. Create fingerprint engine
|
||||
6. Create output writer(s)
|
||||
7. Connect pipeline: capture -> parser -> fingerprint -> output
|
||||
8. Start signal handling
|
||||
9. Run capture loop
|
||||
`
|
||||
t.Log(expectedPipeline)
|
||||
}
|
||||
Reference in New Issue
Block a user