ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c

View file

@ -0,0 +1,184 @@
package main
import (
"archive/zip"
"fmt"
"os"
"path/filepath"
"testing"
"time"
)
func writeZipFiles(t *testing.T, files map[string]string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "fixture.zip")
output, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
archive := zip.NewWriter(output)
for name, contents := range files {
entry, err := archive.Create(name)
if err != nil {
t.Fatal(err)
}
if _, err := fmt.Fprint(entry, contents); err != nil {
t.Fatal(err)
}
}
if err := archive.Close(); err != nil {
t.Fatal(err)
}
if err := output.Close(); err != nil {
t.Fatal(err)
}
return path
}
func mustAddress(t *testing.T, value string) uint32 {
t.Helper()
address, ok := rangeFromPrefix(value + "/32")
if !ok {
t.Fatalf("invalid test address %q", value)
}
return address.start
}
func rangeContains(ranges []addressRange, address uint32) bool {
for _, candidate := range ranges {
if candidate.start <= address && candidate.end >= address {
return true
}
}
return false
}
func rangesEqual(left, right []addressRange) bool {
left = mergeRanges(left)
right = mergeRanges(right)
if len(left) != len(right) {
return false
}
for index := range left {
if left[index] != right[index] {
return false
}
}
return true
}
func TestGeographySetsSeparateVPNExceptionCountries(t *testing.T) {
path := writeZipFiles(t, map[string]string{
"ip-to-country.csv": "network,country_code,continent_code\n192.0.2.0/28,JP,AS\n192.0.2.16/28,KR,AS\n198.51.100.0/24,US,NA\n203.0.113.0/24,BR,SA\n",
})
sets, err := ipLocateGeography(path)
if err != nil {
t.Fatal(err)
}
if !rangeContains(sets.admission, mustAddress(t, "198.51.100.1")) || rangeContains(sets.admission, mustAddress(t, "203.0.113.1")) {
t.Fatalf("unexpected admission ranges: %#v", sets.admission)
}
if !rangeContains(sets.vpnException, mustAddress(t, "192.0.2.1")) || !rangeContains(sets.vpnException, mustAddress(t, "192.0.2.17")) || rangeContains(sets.vpnException, mustAddress(t, "198.51.100.1")) {
t.Fatalf("unexpected VPN exception ranges: %#v", sets.vpnException)
}
}
func TestMaxMindGeographySetsSeparateVPNExceptionCountries(t *testing.T) {
path := writeZipFiles(t, map[string]string{
"GeoLite2-Country-Locations-en.csv": "geoname_id,locale_code,continent_code,country_iso_code\n1,en,AS,JP\n2,en,AS,KR\n3,en,NA,US\n4,en,SA,BR\n",
"GeoLite2-Country-Blocks-IPv4.csv": "network,geoname_id,registered_country_geoname_id\n192.0.2.0/28,1,1\n192.0.2.16/28,2,2\n198.51.100.0/24,3,3\n203.0.113.0/24,4,4\n",
})
sets, err := maxMindGeography(path)
if err != nil {
t.Fatal(err)
}
if !rangeContains(sets.admission, mustAddress(t, "198.51.100.1")) || rangeContains(sets.admission, mustAddress(t, "203.0.113.1")) {
t.Fatalf("unexpected admission ranges: %#v", sets.admission)
}
if !rangeContains(sets.vpnException, mustAddress(t, "192.0.2.1")) || !rangeContains(sets.vpnException, mustAddress(t, "192.0.2.17")) || rangeContains(sets.vpnException, mustAddress(t, "198.51.100.1")) {
t.Fatalf("unexpected VPN exception ranges: %#v", sets.vpnException)
}
}
func TestIPLocateASNRangesSelectConfiguredOperators(t *testing.T) {
path := writeZipFiles(t, map[string]string{
"ip-to-asn.csv": "network,asn,country_code,name,org,domain\n192.0.2.0/28,147049,AU,selected,selected,example.test\n192.0.2.16/28,64500,JP,other,other,example.test\n198.51.100.0/24,AS147049,AU,selected,selected,example.test\n",
})
ranges, err := ipLocateASNRanges(path, map[uint32]struct{}{147049: {}})
if err != nil {
t.Fatal(err)
}
if !rangeContains(ranges, mustAddress(t, "192.0.2.1")) || !rangeContains(ranges, mustAddress(t, "198.51.100.1")) || rangeContains(ranges, mustAddress(t, "192.0.2.17")) {
t.Fatalf("unexpected ASN ranges: %#v", ranges)
}
}
func TestVPNExceptionCompositionPreservesConsensusAndDenies(t *testing.T) {
consensus := []addressRange{{start: 10, end: 20}}
vpnASN := []addressRange{{start: 15, end: 30}}
vpnGeography := []addressRange{{start: 18, end: 28}}
denied := []addressRange{{start: 24, end: 25}}
candidate := subtractRanges(intersectRanges(vpnASN, vpnGeography), consensus)
exceptions := subtractRanges(candidate, denied)
want := []addressRange{{start: 21, end: 23}, {start: 26, end: 28}}
if !rangesEqual(exceptions, want) {
t.Fatalf("exceptions=%#v want=%#v", exceptions, want)
}
}
func TestParseASN(t *testing.T) {
for _, value := range []string{"147049", "AS147049", "as147049"} {
asn, err := parseASN(value)
if err != nil || asn != 147049 {
t.Fatalf("parseASN(%q)=%d,%v", value, asn, err)
}
}
for _, value := range []string{"", "AS", "0", "-1", "4294967296"} {
if _, err := parseASN(value); err == nil {
t.Fatalf("parseASN(%q) succeeded", value)
}
}
}
func TestLoadIP2ProxyClassificationsOmitsStaleCache(t *testing.T) {
now := time.Unix(2000000000, 0)
path := filepath.Join(t.TempDir(), "ip2proxy.zip")
if err := os.WriteFile(path, []byte("invalid archive"), 0o600); err != nil {
t.Fatal(err)
}
modified := now.Add(-36 * 24 * time.Hour)
if err := os.Chtimes(path, modified, modified); err != nil {
t.Fatal(err)
}
result, state, age, err := loadIP2ProxyClassifications(path, now, 35*24*time.Hour)
if err != nil {
t.Fatal(err)
}
if state != "stale" || age != 36*24*time.Hour || len(result.all) != 0 {
t.Fatalf("state=%s age=%s ranges=%d", state, age, len(result.all))
}
}
func TestLoadIP2ProxyClassificationsRequiresValidFreshCache(t *testing.T) {
now := time.Unix(2000000000, 0)
path := filepath.Join(t.TempDir(), "ip2proxy.zip")
if err := os.WriteFile(path, []byte("invalid archive"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chtimes(path, now, now); err != nil {
t.Fatal(err)
}
if _, _, _, err := loadIP2ProxyClassifications(path, now, 35*24*time.Hour); err == nil {
t.Fatal("expected fresh invalid cache to fail")
}
}
func TestLoadIP2ProxyClassificationsAllowsAbsentCache(t *testing.T) {
result, state, age, err := loadIP2ProxyClassifications(filepath.Join(t.TempDir(), "missing.zip"), time.Now(), 35*24*time.Hour)
if err != nil {
t.Fatal(err)
}
if state != "absent" || age != 0 || len(result.all) != 0 {
t.Fatalf("state=%s age=%s ranges=%d", state, age, len(result.all))
}
}