// File Path: monorepo/cloud/maplefile-backend/pkg/security/ipcountryblocker/ipcountryblocker_test.go package ipcountryblocker import ( "context" "net" "testing" "github.com/stretchr/testify/assert" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config" ) // testProvider is a test-specific wrapper that allows access to internal fields // of the provider struct for verification in tests. This is a common pattern // when you need to test internal state while keeping the production interface clean. type testProvider struct { Provider // Embedded interface for normal operations internal *provider // Access to internal fields for testing } // newTestProvider creates a test provider instance with access to internal fields. // This allows us to verify the internal state in our tests while maintaining // encapsulation in production code. func newTestProvider(cfg *config.Configuration, logger *zap.Logger) testProvider { p := NewProvider(cfg, logger) return testProvider{ Provider: p, internal: p.(*provider), // Type assertion to get access to internal fields } } // TestNewProvider verifies that the provider is properly initialized with all // required components (database connection, blocked countries map, logger). func TestNewProvider(t *testing.T) { // Setup test configuration with path to test database cfg := &config.Configuration{ Security: config.SecurityConfig{ GeoLiteDBPath: "../../../static/GeoLite2-Country.mmdb", BannedCountries: []string{"US", "CN"}, }, } // Initialize logger with JSON output for structured test logs logger, _ := zap.NewDevelopment() // Create test provider and verify internal components p := newTestProvider(cfg, logger) assert.NotNil(t, p.Provider, "Provider should not be nil") assert.NotEmpty(t, p.internal.blockedCountries, "Blocked countries map should be initialized") assert.NotNil(t, p.internal.logger, "Logger should be initialized") assert.NotNil(t, p.internal.db, "Database connection should be initialized") defer p.Close() // Ensure cleanup after test } // TestProvider_IsBlockedCountry tests the country blocking functionality with // various country codes including edge cases like empty and invalid codes. func TestProvider_IsBlockedCountry(t *testing.T) { provider := setupTestProvider(t) defer provider.Close() // Table-driven test cases covering various scenarios tests := []struct { name string country string expected bool }{ // Positive test cases - blocked countries { name: "blocked country US", country: "US", expected: true, }, { name: "blocked country CN", country: "CN", expected: true, }, // Negative test cases - allowed countries { name: "non-blocked country GB", country: "GB", expected: false, }, { name: "non-blocked country JP", country: "JP", expected: false, }, // Edge cases { name: "empty country code", country: "", expected: false, }, { name: "invalid country code", country: "XX", expected: false, }, { name: "lowercase country code", // Tests case sensitivity country: "us", expected: false, }, } // Run each test case for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := provider.IsBlockedCountry(tt.country) assert.Equal(t, tt.expected, result) }) } } // TestProvider_IsBlockedIP verifies IP blocking functionality using real-world // IP addresses, including IPv4, IPv6, and various edge cases. func TestProvider_IsBlockedIP(t *testing.T) { provider := setupTestProvider(t) defer provider.Close() tests := []struct { name string ip net.IP expected bool }{ // Known IP addresses from blocked countries { name: "blocked IP (US - Google DNS)", ip: net.ParseIP("8.8.8.8"), // Google's primary DNS expected: true, }, { name: "blocked IP (US - Google DNS 2)", ip: net.ParseIP("8.8.4.4"), // Google's secondary DNS expected: true, }, { name: "blocked IP (CN - Alibaba)", ip: net.ParseIP("223.5.5.5"), // Alibaba DNS expected: true, }, // Non-blocked country IPs { name: "non-blocked IP (GB)", ip: net.ParseIP("178.62.1.1"), expected: false, }, // Edge cases and special scenarios { name: "nil IP", ip: nil, expected: false, }, { name: "invalid IP format", ip: net.ParseIP("invalid"), expected: false, }, { name: "IPv6 address", ip: net.ParseIP("2001:4860:4860::8888"), // Google's IPv6 DNS expected: true, }, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := provider.IsBlockedIP(ctx, tt.ip) assert.Equal(t, tt.expected, result) }) } } // TestProvider_GetCountryCode verifies the country code lookup functionality // for various IP addresses, including error cases. func TestProvider_GetCountryCode(t *testing.T) { provider := setupTestProvider(t) defer provider.Close() tests := []struct { name string ip net.IP expected string expectError bool }{ // Valid IP addresses with known countries { name: "US IP (Google DNS)", ip: net.ParseIP("8.8.8.8"), expected: "US", expectError: false, }, // Error cases { name: "nil IP", ip: nil, expected: "", expectError: true, }, { name: "private IP", // RFC 1918 address ip: net.ParseIP("192.168.1.1"), expected: "", expectError: true, }, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { code, err := provider.GetCountryCode(ctx, tt.ip) if tt.expectError { assert.Error(t, err, "Should return error for invalid IP") assert.Empty(t, code, "Should return empty code on error") return } assert.NoError(t, err, "Should not return error for valid IP") assert.Equal(t, tt.expected, code, "Should return correct country code") }) } } // TestProvider_Close verifies that the provider properly closes its resources // and subsequent operations fail as expected. func TestProvider_Close(t *testing.T) { provider := setupTestProvider(t) // Verify initial close succeeds err := provider.Close() assert.NoError(t, err, "Initial close should succeed") // Verify operations fail after close code, err := provider.GetCountryCode(context.Background(), net.ParseIP("8.8.8.8")) assert.Error(t, err, "Operations should fail after close") assert.Empty(t, code, "No data should be returned after close") } // setupTestProvider is a helper function that creates a properly configured // provider instance for testing, using the test database path. func setupTestProvider(t *testing.T) Provider { cfg := &config.Configuration{ Security: config.SecurityConfig{ GeoLiteDBPath: "../../../static/GeoLite2-Country.mmdb", BannedCountries: []string{"US", "CN"}, }, } logger, _ := zap.NewDevelopment() return NewProvider(cfg, logger) }