Files
go-sam-go/compatibility_test.go
eyedeekay ade4d51ce8 fmt
2025-10-07 12:34:08 -04:00

685 lines
19 KiB
Go

package sam3
import (
"reflect"
"strings"
"testing"
)
// TestSAM3CompatibilityAPI verifies that all documented types and functions from sigs.md
// are available and have correct signatures for drop-in replacement functionality.
// This test ensures perfect API surface compatibility with the original sam3 library.
func TestSAM3CompatibilityAPI(t *testing.T) {
t.Run("CoreTypes", func(t *testing.T) {
// Verify all core types exist and are properly aliased
coreTypes := map[string]interface{}{
"SAM": (*SAM)(nil),
"StreamSession": (*StreamSession)(nil),
"DatagramSession": (*DatagramSession)(nil),
"RawSession": (*RawSession)(nil),
"PrimarySession": (*PrimarySession)(nil),
"SAMConn": (*SAMConn)(nil),
"StreamListener": (*StreamListener)(nil),
"SAMResolver": (*SAMResolver)(nil),
"I2PConfig": (*I2PConfig)(nil),
"SAMEmit": (*SAMEmit)(nil),
"Options": (*Options)(nil),
"Option": (*Option)(nil),
"BaseSession": (*BaseSession)(nil),
}
for typeName, typeValue := range coreTypes {
if typeValue == nil {
t.Errorf("Type %s should not be nil", typeName)
continue
}
// Verify type is not nil interface
typeOf := reflect.TypeOf(typeValue)
if typeOf == nil {
t.Errorf("Type %s has nil reflection type", typeName)
continue
}
t.Logf("✓ Type %s exists and is properly defined", typeName)
}
})
t.Run("Constants", func(t *testing.T) {
// Verify all signature constants exist
expectedConstants := map[string]string{
"Sig_NONE": Sig_NONE,
"Sig_DSA_SHA1": Sig_DSA_SHA1,
"Sig_ECDSA_SHA256_P256": Sig_ECDSA_SHA256_P256,
"Sig_ECDSA_SHA384_P384": Sig_ECDSA_SHA384_P384,
"Sig_ECDSA_SHA512_P521": Sig_ECDSA_SHA512_P521,
"Sig_EdDSA_SHA512_Ed25519": Sig_EdDSA_SHA512_Ed25519,
}
for constName, constValue := range expectedConstants {
if constValue == "" {
t.Errorf("Constant %s is empty", constName)
continue
}
if !strings.Contains(constValue, "SIGNATURE_TYPE=") {
t.Errorf("Constant %s does not contain expected prefix: %s", constName, constValue)
continue
}
t.Logf("✓ Constant %s = %s", constName, constValue)
}
})
t.Run("OptionVariables", func(t *testing.T) {
// Verify all option variables exist and have expected structure
optionVars := map[string][]string{
"Options_Humongous": Options_Humongous,
"Options_Large": Options_Large,
"Options_Wide": Options_Wide,
"Options_Medium": Options_Medium,
"Options_Default": Options_Default,
"Options_Small": Options_Small,
"Options_Warning_ZeroHop": Options_Warning_ZeroHop,
}
for varName, varValue := range optionVars {
if len(varValue) == 0 {
t.Errorf("Option variable %s is empty", varName)
continue
}
// Verify it contains expected tunnel options
hasInbound := false
hasOutbound := false
for _, option := range varValue {
if strings.Contains(option, "inbound.") {
hasInbound = true
}
if strings.Contains(option, "outbound.") {
hasOutbound = true
}
}
if !hasInbound || !hasOutbound {
t.Errorf("Option variable %s missing inbound/outbound options", varName)
continue
}
t.Logf("✓ Option variable %s has %d options", varName, len(varValue))
}
})
t.Run("EnvironmentVariables", func(t *testing.T) {
// Verify environment variable handling
if SAM_HOST == "" {
t.Error("SAM_HOST should have a default value")
}
if SAM_PORT == "" {
t.Error("SAM_PORT should have a default value")
}
t.Logf("✓ SAM_HOST = %s", SAM_HOST)
t.Logf("✓ SAM_PORT = %s", SAM_PORT)
})
}
// TestSAM3CompatibilityFunctions verifies that all documented functions from sigs.md
// exist and have the correct signatures for perfect drop-in replacement compatibility.
func TestSAM3CompatibilityFunctions(t *testing.T) {
t.Run("UtilityFunctions", func(t *testing.T) {
// Test utility functions exist and have correct signatures
utilityTests := []struct {
name string
test func(*testing.T)
}{
{
name: "PrimarySessionString",
test: func(t *testing.T) {
result := PrimarySessionString()
if result == "" {
t.Error("PrimarySessionString should return non-empty string")
}
t.Logf("✓ PrimarySessionString() = %s", result)
},
},
{
name: "RandString",
test: func(t *testing.T) {
result := RandString()
if len(result) == 0 {
t.Error("RandString should return non-empty string")
}
t.Logf("✓ RandString() = %s", result)
},
},
{
name: "SAMDefaultAddr",
test: func(t *testing.T) {
result := SAMDefaultAddr("")
if result == "" {
t.Error("SAMDefaultAddr should return non-empty string")
}
t.Logf("✓ SAMDefaultAddr(\"\") = %s", result)
},
},
{
name: "ExtractDest",
test: func(t *testing.T) {
input := "test-dest RESULT=OK DESTINATION=other-dest"
result := ExtractDest(input)
if result != "test-dest" {
t.Errorf("ExtractDest expected 'test-dest', got '%s'", result)
}
t.Logf("✓ ExtractDest works correctly")
},
},
{
name: "ExtractPairString",
test: func(t *testing.T) {
input := "KEY1=value1 KEY2=value2"
result := ExtractPairString(input, "KEY1")
if result != "value1" {
t.Errorf("ExtractPairString expected 'value1', got '%s'", result)
}
t.Logf("✓ ExtractPairString works correctly")
},
},
{
name: "ExtractPairInt",
test: func(t *testing.T) {
input := "PORT=7656 COUNT=10"
result := ExtractPairInt(input, "PORT")
if result != 7656 {
t.Errorf("ExtractPairInt expected 7656, got %d", result)
}
t.Logf("✓ ExtractPairInt works correctly")
},
},
}
for _, test := range utilityTests {
t.Run(test.name, test.test)
}
})
t.Run("ConstructorFunctions", func(t *testing.T) {
// Test constructor functions exist - we can't test actual functionality
// without I2P connection, but we can verify signatures
constructorTests := []struct {
name string
test func(*testing.T)
}{
{
name: "NewSAM",
test: func(t *testing.T) {
// Test that function exists and has correct signature
fnType := reflect.TypeOf(NewSAM)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewSAM should have signature: (string) (*SAM, error)")
}
t.Logf("✓ NewSAM has correct signature")
},
},
{
name: "NewSAMResolver",
test: func(t *testing.T) {
fnType := reflect.TypeOf(NewSAMResolver)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewSAMResolver should have signature: (*SAM) (*SAMResolver, error)")
}
t.Logf("✓ NewSAMResolver has correct signature")
},
},
{
name: "NewFullSAMResolver",
test: func(t *testing.T) {
fnType := reflect.TypeOf(NewFullSAMResolver)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewFullSAMResolver should have signature: (string) (*SAMResolver, error)")
}
t.Logf("✓ NewFullSAMResolver has correct signature")
},
},
}
for _, test := range constructorTests {
t.Run(test.name, test.test)
}
})
t.Run("ConfigurationFunctions", func(t *testing.T) {
// Test configuration functions exist and work correctly
configTests := []struct {
name string
test func(*testing.T)
}{
{
name: "NewConfig",
test: func(t *testing.T) {
config, err := NewConfig()
if err != nil {
t.Errorf("NewConfig() failed: %v", err)
}
if config == nil {
t.Error("NewConfig() returned nil config")
}
t.Logf("✓ NewConfig works correctly")
},
},
{
name: "NewEmit",
test: func(t *testing.T) {
emit, err := NewEmit()
if err != nil {
t.Errorf("NewEmit() failed: %v", err)
}
if emit == nil {
t.Error("NewEmit() returned nil emit")
}
t.Logf("✓ NewEmit works correctly")
},
},
{
name: "SetType",
test: func(t *testing.T) {
emit, _ := NewEmit()
err := SetType("STREAM")(emit)
if err != nil {
t.Errorf("SetType failed: %v", err)
}
if emit.Style != "STREAM" {
t.Errorf("SetType did not set style correctly")
}
t.Logf("✓ SetType works correctly")
},
},
}
for _, test := range configTests {
t.Run(test.name, test.test)
}
})
}
// TestSAM3CompatibilityIntegration tests drop-in replacement functionality with
// real I2P connections when available. These tests verify that the wrapper
// behaves identically to the original sam3 library in actual usage scenarios.
func TestSAM3CompatibilityIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration tests in short mode")
}
t.Run("BasicSAMConnection", func(t *testing.T) {
// Test basic SAM connection using sam3 API patterns
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
// Verify SAM connection provides expected functionality
if sam == nil {
t.Fatal("SAM connection should not be nil")
}
// Test key generation through SAM
keys, err := sam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys: %v", err)
}
if keys.String() == "" {
t.Fatal("Generated keys should not be empty")
}
t.Log("✓ Basic SAM connection and key generation work correctly")
})
t.Run("SessionCreationPatterns", func(t *testing.T) {
// Test common sam3 usage patterns for session creation
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
// Test the typical sam3 usage pattern for each session type
sessionTests := []struct {
name string
test func(*testing.T)
}{
{
name: "PrimarySessionPattern",
test: func(t *testing.T) {
// Generate unique keys for each session to avoid conflicts
keys, err := sam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys for primary session: %v", err)
}
session, err := sam.NewPrimarySession("compat-primary-"+RandString(), keys, Options_Default)
if err != nil {
t.Errorf("Primary session creation failed: %v", err)
return
}
defer session.Close()
// Verify primary session can create sub-sessions
if session.SubSessionCount() != 0 {
t.Error("New primary session should have 0 sub-sessions")
}
t.Log("✓ Primary session creation pattern works")
// Explicitly close session to avoid conflicts with subsequent tests
session.Close()
},
},
{
name: "StreamSessionPattern",
test: func(t *testing.T) {
// Create a separate SAM connection for this test to avoid conflicts
streamSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fatalf("Failed to create SAM connection for stream session: %v", err)
}
defer streamSam.Close()
// Generate unique keys for each session to avoid conflicts
keys, err := streamSam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys for stream session: %v", err)
}
session, err := streamSam.NewStreamSession("compat-stream-"+RandString(), keys, Options_Small)
if err != nil {
t.Errorf("Stream session creation failed: %v", err)
return
}
defer session.Close()
// Test listener creation - typical sam3 pattern
listener, err := session.Listen()
if err != nil {
t.Errorf("Stream listener creation failed: %v", err)
return
}
defer listener.Close()
if listener.Addr() == nil {
t.Error("Stream listener should have non-nil address")
}
t.Log("✓ Stream session creation and listener pattern works")
// Explicitly close session to avoid conflicts with subsequent tests
listener.Close()
session.Close()
},
},
{
name: "DatagramSessionPattern",
test: func(t *testing.T) {
// Create a separate SAM connection for this test to avoid conflicts
datagramSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fatalf("Failed to create SAM connection for datagram session: %v", err)
}
defer datagramSam.Close()
// Generate unique keys for each session to avoid conflicts
keys, err := datagramSam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys for datagram session: %v", err)
}
session, err := datagramSam.NewDatagramSession("compat-datagram-"+RandString(), keys, Options_Small, 0)
if err != nil {
t.Errorf("Datagram session creation failed: %v", err)
return
}
defer session.Close()
// Verify datagram session provides expected interface
if session.LocalAddr() == nil {
t.Error("Datagram session should have non-nil local address")
}
t.Log("✓ Datagram session creation pattern works")
// Explicitly close session to avoid conflicts with subsequent tests
session.Close()
},
},
{
name: "RawSessionPattern",
test: func(t *testing.T) {
// Create a separate SAM connection for this test to avoid conflicts
rawSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fatalf("Failed to create SAM connection for raw session: %v", err)
}
defer rawSam.Close()
// Generate unique keys for each session to avoid conflicts
keys, err := rawSam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys for raw session: %v", err)
}
session, err := rawSam.NewRawSession("compat-raw-"+RandString(), keys, Options_Small, 0)
if err != nil {
t.Errorf("Raw session creation failed: %v", err)
return
}
defer session.Close()
// Verify raw session provides expected interface
if session.LocalAddr() == nil {
t.Error("Raw session should have non-nil local address")
}
t.Log("✓ Raw session creation pattern works")
// Explicitly close session to avoid conflicts with subsequent tests
session.Close()
},
},
}
for _, test := range sessionTests {
t.Run(test.name, test.test)
}
})
t.Run("ErrorHandlingCompatibility", func(t *testing.T) {
// Test that error handling matches expected sam3 patterns
// Test invalid SAM address
_, err := NewSAM("invalid-address:999999")
if err == nil {
t.Error("Expected error for invalid SAM address")
}
t.Logf("✓ Invalid address error: %v", err)
// Test with valid connection for other error cases
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
// Test invalid session type
emit, _ := NewEmit()
err = SetType("INVALID_TYPE")(emit)
if err == nil {
t.Error("Expected error for invalid session type")
}
t.Logf("✓ Invalid session type error: %v", err)
// Test invalid configuration values
err = SetInLength(-1)(emit)
if err == nil {
t.Error("Expected error for invalid tunnel length")
}
t.Logf("✓ Invalid configuration error: %v", err)
})
}
// TestSAM3CompatibilityBehavior verifies that the wrapper exhibits identical
// behavior to the original sam3 library in edge cases and specific scenarios.
func TestSAM3CompatibilityBehavior(t *testing.T) {
t.Run("AddressHandling", func(t *testing.T) {
// Test SAM address handling compatibility
tests := []struct {
name string
input string
expected string
}{
{"DefaultHost", "", SAM_HOST + ":" + SAM_PORT},
{"ExplicitAddress", "192.168.1.1:7656", SAM_HOST + ":" + SAM_PORT}, // SAMDefaultAddr ignores input when defaults are set
{"HostOnly", "localhost", SAM_HOST + ":" + SAM_PORT}, // SAMDefaultAddr ignores input when defaults are set
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := SAMDefaultAddr(test.input)
if result != test.expected {
t.Errorf("SAMDefaultAddr(%s) = %s, expected %s", test.input, result, test.expected)
}
t.Logf("✓ SAMDefaultAddr(%s) = %s", test.input, result)
})
}
})
t.Run("StringParsing", func(t *testing.T) {
// Test string parsing functions for SAM protocol compatibility
testCases := []struct {
name string
input string
expected map[string]interface{}
}{
{
name: "DestinationExtraction",
input: "abcd1234.b32.i2p RESULT=OK VERSION=3.3",
expected: map[string]interface{}{
"dest": "abcd1234.b32.i2p",
},
},
{
name: "MultipleParams",
input: "test.b32.i2p RESULT=OK MESSAGE=Connected",
expected: map[string]interface{}{
"dest": "test.b32.i2p",
"message": "Connected",
"result": "OK",
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
dest := ExtractDest(test.input)
if expectedDest, ok := test.expected["dest"]; ok {
if dest != expectedDest {
t.Errorf("ExtractDest expected %s, got %s", expectedDest, dest)
}
}
message := ExtractPairString(test.input, "MESSAGE")
if expectedMsg, ok := test.expected["message"]; ok {
if message != expectedMsg {
t.Errorf("ExtractPairString(MESSAGE) expected %s, got %s", expectedMsg, message)
}
}
t.Logf("✓ String parsing works correctly for %s", test.name)
})
}
})
t.Run("ConfigurationBehavior", func(t *testing.T) {
// Test configuration behavior matches sam3 expectations
emit, _ := NewEmit()
// Test option string generation
options := []string{"inbound.length=3", "outbound.length=3"}
optString := GenerateOptionString(options)
if !strings.Contains(optString, "inbound.length=3") {
t.Error("Generated option string should contain inbound.length=3")
}
if !strings.Contains(optString, "outbound.length=3") {
t.Error("Generated option string should contain outbound.length=3")
}
// Test configuration chaining
err := SetType("STREAM")(emit)
if err != nil {
t.Errorf("Configuration chaining failed: %v", err)
}
err = SetSAMHost("127.0.0.1")(emit)
if err != nil {
t.Errorf("Configuration chaining failed: %v", err)
}
if emit.Style != "STREAM" {
t.Error("Configuration chaining did not preserve previous settings")
}
if emit.I2PConfig.SamHost != "127.0.0.1" {
t.Error("Configuration chaining did not apply new settings")
}
t.Log("✓ Configuration behavior matches sam3 patterns")
})
}
// BenchmarkSAM3Compatibility benchmarks critical operations to ensure
// performance is comparable to original sam3 library expectations.
func BenchmarkSAM3Compatibility(b *testing.B) {
b.Run("UtilityFunctions", func(b *testing.B) {
b.Run("RandString", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = RandString()
}
})
b.Run("SAMDefaultAddr", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = SAMDefaultAddr("")
}
})
b.Run("ExtractDest", func(b *testing.B) {
input := "HELLO REPLY RESULT=OK DESTINATION=test.b32.i2p"
for i := 0; i < b.N; i++ {
_ = ExtractDest(input)
}
})
b.Run("GenerateOptionString", func(b *testing.B) {
options := Options_Default
for i := 0; i < b.N; i++ {
_ = GenerateOptionString(options)
}
})
})
b.Run("Configuration", func(b *testing.B) {
b.Run("NewEmit", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewEmit()
}
})
b.Run("SetType", func(b *testing.B) {
emit, _ := NewEmit()
for i := 0; i < b.N; i++ {
_ = SetType("STREAM")(emit)
}
})
b.Run("OptionVariableAccess", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Options_Default
}
})
})
}