Adds tests across previously-uncovered or under-covered modules: - Mix tasks: backfill_checks, copy_mibs, import_mibs (new test files) - Status pages: StatusIncident changeset (new file) - Webhook signature verification on AgentReleaseWebhookController - CloudflareBanWorker: HTTP-stubbed success / 5xx / transport-error paths - FirmwareVersionFetcherWorker: stubbed perform/0 success + non-200 + tx error - Geoip.Import: production --production code path (HTTP-stubbed) - AgentLive.Show: superuser restart_agent + update_agent + non-superuser paths - NetworkMapLive: node_clicked, deep-link node, topology_updated PubSub - PreseemInsightsLive: toggle_select, deselect_all, filter, dismiss-twice - AlertQuery: 1-arity defaults variants - ProfileWatcher: yaml + reload-trigger event passthrough - MobileAuthController: verify_qr_token success + get/revoke session - HealthController: redis-configured branch - RedisHealthCheck: un-tag :integration tests now that Redis is local - FourOhFourTracker: un-skip module (Redis available) - PingExecutor: un-tag local-only tests + KeyError surface + clamp branch - CheckExecutorWorker: dispatch tcp/dns/ssl/ping branches - UserResetPasswordController: drop dead edit/update + their template Also removes dead code: edit/update actions on UserResetPasswordController and the unused edit.html.heex template — both routed via LiveView.
551 lines
20 KiB
Elixir
551 lines
20 KiB
Elixir
defmodule Mix.Tasks.Geoip.ImportTest do
|
|
use Towerops.DataCase, async: true
|
|
|
|
import ExUnit.CaptureIO
|
|
|
|
import Mix.Tasks.Geoip.Import,
|
|
only: [
|
|
load_locations: 1,
|
|
load_blocks: 2,
|
|
run: 1,
|
|
parse_location_line: 1,
|
|
parse_block_line: 1,
|
|
build_block_entry: 3,
|
|
parse_geoname_id: 1,
|
|
parse_cidr: 1
|
|
]
|
|
|
|
alias Mix.Tasks.Geoip.Import
|
|
alias Towerops.GeoIP.Block
|
|
alias Towerops.GeoIP.Location
|
|
alias Towerops.Repo
|
|
|
|
setup do
|
|
# Create temporary directory for test CSV files
|
|
temp_dir = Path.join(System.tmp_dir!(), "geoip_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(temp_dir)
|
|
|
|
on_exit(fn ->
|
|
File.rm_rf!(temp_dir)
|
|
end)
|
|
|
|
{:ok, temp_dir: temp_dir}
|
|
end
|
|
|
|
describe "load_locations/1" do
|
|
test "imports locations from CSV file", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "locations.csv")
|
|
|
|
File.write!(csv_file, """
|
|
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union
|
|
5391959,en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0
|
|
6167865,en,NA,North America,CA,Canada,ON,Ontario,,,Toronto,0,America/Toronto,0
|
|
2643743,en,EU,Europe,GB,United Kingdom,ENG,England,,,London,0,Europe/London,1
|
|
""")
|
|
|
|
{count, geoname_ids} = load_locations(csv_file)
|
|
|
|
assert count == 3
|
|
assert MapSet.size(geoname_ids) == 3
|
|
assert MapSet.member?(geoname_ids, 5_391_959)
|
|
assert MapSet.member?(geoname_ids, 6_167_865)
|
|
assert MapSet.member?(geoname_ids, 2_643_743)
|
|
|
|
# Verify database records
|
|
locations = Repo.all(Location)
|
|
assert length(locations) == 3
|
|
|
|
san_francisco = Enum.find(locations, &(&1.geoname_id == 5_391_959))
|
|
assert san_francisco.country_code == "US"
|
|
assert san_francisco.country_name == "United States"
|
|
assert san_francisco.city_name == "San Francisco"
|
|
assert san_francisco.subdivision_1_name == "California"
|
|
end
|
|
|
|
test "skips locations without country code", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "locations.csv")
|
|
|
|
File.write!(csv_file, """
|
|
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union
|
|
5391959,en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0
|
|
9999999,en,NA,North America,,,,,,,,0,,0
|
|
""")
|
|
|
|
{count, geoname_ids} = load_locations(csv_file)
|
|
|
|
assert count == 1
|
|
assert MapSet.size(geoname_ids) == 1
|
|
assert MapSet.member?(geoname_ids, 5_391_959)
|
|
end
|
|
|
|
test "skips locations without geoname_id", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "locations.csv")
|
|
|
|
File.write!(csv_file, """
|
|
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union
|
|
5391959,en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0
|
|
,en,NA,North America,CA,Canada,ON,Ontario,,,Toronto,0,America/Toronto,0
|
|
""")
|
|
|
|
{count, geoname_ids} = load_locations(csv_file)
|
|
|
|
assert count == 1
|
|
assert MapSet.size(geoname_ids) == 1
|
|
end
|
|
|
|
test "handles empty optional fields", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "locations.csv")
|
|
|
|
File.write!(csv_file, """
|
|
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union
|
|
5391959,en,NA,North America,US,,,,,,,,America/Los_Angeles,0
|
|
""")
|
|
|
|
{count, _geoname_ids} = load_locations(csv_file)
|
|
assert count == 1
|
|
|
|
location = Repo.get_by!(Location, geoname_id: 5_391_959)
|
|
assert location.country_name == nil
|
|
assert location.city_name == nil
|
|
assert location.subdivision_1_name == nil
|
|
assert location.subdivision_2_name == nil
|
|
end
|
|
|
|
test "processes large batches correctly", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "locations.csv")
|
|
|
|
# Generate 10,000 locations (more than batch size of 5,000)
|
|
header =
|
|
"geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union\n"
|
|
|
|
rows =
|
|
Enum.map(1..10_000, fn i ->
|
|
"#{i},en,NA,North America,US,United States,CA,California,,,City#{i},807,America/Los_Angeles,0"
|
|
end)
|
|
|
|
File.write!(csv_file, header <> Enum.join(rows, "\n") <> "\n")
|
|
|
|
{count, geoname_ids} = load_locations(csv_file)
|
|
|
|
assert count == 10_000
|
|
assert MapSet.size(geoname_ids) == 10_000
|
|
assert Repo.aggregate(Location, :count) == 10_000
|
|
end
|
|
end
|
|
|
|
describe "load_blocks/2" do
|
|
test "imports IP blocks from CSV file", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
|
|
# First, create some locations
|
|
location1 = insert(:geoip_location, geoname_id: 5_391_959, country_code: "US")
|
|
location2 = insert(:geoip_location, geoname_id: 6_167_865, country_code: "CA")
|
|
|
|
valid_geoname_ids = MapSet.new([location1.geoname_id, location2.geoname_id])
|
|
|
|
File.write!(csv_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
1.0.0.0/24,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
1.0.1.0/24,6167865,6167865,,,0,M5H,43.6532,-79.3832,1000
|
|
1.0.2.0/24,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
""")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
|
|
assert count == 3
|
|
|
|
# Verify database records
|
|
blocks = Repo.all(Block)
|
|
assert length(blocks) == 3
|
|
|
|
block1 = Enum.find(blocks, &(&1.network == "1.0.0.0/24"))
|
|
assert block1.geoname_id == 5_391_959
|
|
assert block1.start_ip_int == 16_777_216
|
|
assert block1.end_ip_int == 16_777_471
|
|
end
|
|
|
|
test "filters out blocks with invalid geoname_id", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
|
|
location = insert(:geoip_location, geoname_id: 5_391_959, country_code: "US")
|
|
valid_geoname_ids = MapSet.new([location.geoname_id])
|
|
|
|
File.write!(csv_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
1.0.0.0/24,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
1.0.1.0/24,9999999,9999999,,,0,M5H,43.6532,-79.3832,1000
|
|
""")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
|
|
assert count == 1
|
|
assert Repo.aggregate(Block, :count) == 1
|
|
end
|
|
|
|
test "uses registered_country_geoname_id as fallback", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
|
|
location = insert(:geoip_location, geoname_id: 5_391_959, country_code: "US")
|
|
valid_geoname_ids = MapSet.new([location.geoname_id])
|
|
|
|
File.write!(csv_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
1.0.0.0/24,,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
""")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
|
|
assert count == 1
|
|
|
|
block = Repo.one!(Block)
|
|
assert block.geoname_id == 5_391_959
|
|
assert block.registered_country_geoname_id == 5_391_959
|
|
end
|
|
|
|
test "skips blocks with no valid geoname_id", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
valid_geoname_ids = MapSet.new()
|
|
|
|
File.write!(csv_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
1.0.0.0/24,,,,,0,94102,37.7749,-122.4194,1000
|
|
""")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
|
|
assert count == 0
|
|
assert Repo.aggregate(Block, :count) == 0
|
|
end
|
|
|
|
test "calculates IP ranges correctly", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
|
|
location = insert(:geoip_location, geoname_id: 5_391_959, country_code: "US")
|
|
valid_geoname_ids = MapSet.new([location.geoname_id])
|
|
|
|
File.write!(csv_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
192.168.1.0/24,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
10.0.0.0/8,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
172.16.0.0/12,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
""")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
assert count == 3
|
|
|
|
blocks = Repo.all(Block)
|
|
|
|
# 192.168.1.0/24 = 192.168.1.0 to 192.168.1.255
|
|
block1 = Enum.find(blocks, &(&1.network == "192.168.1.0/24"))
|
|
assert block1.start_ip_int == 3_232_235_776
|
|
assert block1.end_ip_int == 3_232_236_031
|
|
|
|
# 10.0.0.0/8 = 10.0.0.0 to 10.255.255.255
|
|
block2 = Enum.find(blocks, &(&1.network == "10.0.0.0/8"))
|
|
assert block2.start_ip_int == 167_772_160
|
|
assert block2.end_ip_int == 184_549_375
|
|
|
|
# 172.16.0.0/12 = 172.16.0.0 to 172.31.255.255
|
|
block3 = Enum.find(blocks, &(&1.network == "172.16.0.0/12"))
|
|
assert block3.start_ip_int == 2_886_729_728
|
|
assert block3.end_ip_int == 2_887_778_303
|
|
end
|
|
|
|
test "processes large batches correctly", %{temp_dir: temp_dir} do
|
|
csv_file = Path.join(temp_dir, "blocks.csv")
|
|
|
|
location = insert(:geoip_location, geoname_id: 5_391_959, country_code: "US")
|
|
valid_geoname_ids = MapSet.new([location.geoname_id])
|
|
|
|
# Generate 10,000 blocks (more than batch size of 5,000)
|
|
header =
|
|
"network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius\n"
|
|
|
|
rows =
|
|
Enum.map(1..10_000, fn i ->
|
|
# Use sequential /32 blocks
|
|
octet1 = div(i, 256 * 256)
|
|
octet2 = rem(div(i, 256), 256)
|
|
octet3 = rem(i, 256)
|
|
"#{octet1}.#{octet2}.#{octet3}.0/32,5391959,5391959,,,0,94102,37.7749,-122.4194,1000"
|
|
end)
|
|
|
|
File.write!(csv_file, header <> Enum.join(rows, "\n") <> "\n")
|
|
|
|
count = load_blocks(csv_file, valid_geoname_ids)
|
|
|
|
assert count == 10_000
|
|
assert Repo.aggregate(Block, :count) == 10_000
|
|
end
|
|
end
|
|
|
|
describe "run/1 with invalid arguments" do
|
|
test "shows usage help when no directory is provided" do
|
|
output = capture_io(:stderr, fn -> run([]) end)
|
|
assert output =~ "Usage: mix geoip.import <directory>"
|
|
end
|
|
|
|
test "shows usage help when too many arguments are provided" do
|
|
output = capture_io(:stderr, fn -> run(["dir1", "dir2"]) end)
|
|
assert output =~ "Usage: mix geoip.import <directory>"
|
|
end
|
|
|
|
test "requires TOWEROPS_KEY env var for production mode" do
|
|
System.delete_env("TOWEROPS_KEY")
|
|
|
|
assert catch_exit(
|
|
capture_io(:stderr, fn ->
|
|
run(["~/geoip", "--production"])
|
|
end)
|
|
) == {:shutdown, 1}
|
|
end
|
|
end
|
|
|
|
describe "parse_location_line/1" do
|
|
test "parses a complete location line" do
|
|
line =
|
|
"5391959,en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0"
|
|
|
|
result = parse_location_line(line)
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.country_code == "US"
|
|
assert result.country_name == "United States"
|
|
assert result.city_name == "San Francisco"
|
|
assert result.subdivision_1_name == "California"
|
|
assert result.subdivision_2_name == nil
|
|
assert result.latitude == nil
|
|
assert result.longitude == nil
|
|
end
|
|
|
|
test "returns nil for row with empty geoname_id" do
|
|
line = ",en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0"
|
|
assert parse_location_line(line) == nil
|
|
end
|
|
|
|
test "returns nil for row with empty country_code" do
|
|
line = "5391959,en,NA,North America,,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0"
|
|
assert parse_location_line(line) == nil
|
|
end
|
|
|
|
test "handles empty optional fields as nil" do
|
|
line = "5391959,en,NA,North America,US,,,,,,,America/Los_Angeles,0"
|
|
result = parse_location_line(line)
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.country_name == nil
|
|
assert result.city_name == nil
|
|
assert result.subdivision_1_name == nil
|
|
assert result.subdivision_2_name == nil
|
|
end
|
|
|
|
test "returns nil for line with too few columns" do
|
|
line = "5391959,en,NA"
|
|
assert parse_location_line(line) == nil
|
|
end
|
|
end
|
|
|
|
describe "parse_block_line/1" do
|
|
test "parses a complete block line" do
|
|
line = "1.0.0.0/24,5391959,5391959,,,,,94102,37.7749,-122.4194,1000"
|
|
|
|
result = parse_block_line(line)
|
|
assert result.network == "1.0.0.0/24"
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.registered_country_geoname_id == 5_391_959
|
|
end
|
|
|
|
test "falls back to registered_country_geoname_id when geoname_id is empty" do
|
|
line = "1.0.0.0/24,,5391959,,,,,94102,37.7749,-122.4194,1000"
|
|
|
|
result = parse_block_line(line)
|
|
assert result.network == "1.0.0.0/24"
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.registered_country_geoname_id == 5_391_959
|
|
end
|
|
|
|
test "returns nil when both geoname_ids are empty" do
|
|
line = "1.0.0.0/24,,,,,,,94102,37.7749,-122.4194,1000"
|
|
assert parse_block_line(line) == nil
|
|
end
|
|
|
|
test "returns nil for line with too few columns" do
|
|
line = "1.0.0.0/24"
|
|
assert parse_block_line(line) == nil
|
|
end
|
|
|
|
test "returns nil for invalid CIDR" do
|
|
line = "not-a-cidr,5391959,5391959,,,,,94102,37.7749,-122.4194,1000"
|
|
assert parse_block_line(line) == nil
|
|
end
|
|
|
|
test "parses block with empty registered_country" do
|
|
line = "1.0.0.0/24,5391959,,,,,,94102,37.7749,-122.4194,1000"
|
|
|
|
result = parse_block_line(line)
|
|
assert result.network == "1.0.0.0/24"
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.registered_country_geoname_id == nil
|
|
end
|
|
end
|
|
|
|
describe "build_block_entry/3" do
|
|
test "builds entry for valid CIDR" do
|
|
result = build_block_entry("1.0.0.0/24", 5_391_959, 5_391_959)
|
|
|
|
assert result.network == "1.0.0.0/24"
|
|
assert result.geoname_id == 5_391_959
|
|
assert result.registered_country_geoname_id == 5_391_959
|
|
assert result.start_ip_int == 16_777_216
|
|
assert result.end_ip_int == 16_777_471
|
|
end
|
|
|
|
test "returns nil for nil geoname_id" do
|
|
assert build_block_entry("1.0.0.0/24", nil, nil) == nil
|
|
end
|
|
|
|
test "returns nil for invalid CIDR" do
|
|
assert build_block_entry("invalid", 5_391_959, nil) == nil
|
|
end
|
|
|
|
test "builds entry with nil registered_country" do
|
|
result = build_block_entry("10.0.0.0/8", 1, nil)
|
|
|
|
assert result.network == "10.0.0.0/8"
|
|
assert result.geoname_id == 1
|
|
assert result.registered_country_geoname_id == nil
|
|
assert result.start_ip_int == 167_772_160
|
|
assert result.end_ip_int == 184_549_375
|
|
end
|
|
end
|
|
|
|
describe "parse_geoname_id/1" do
|
|
test "parses a valid integer" do
|
|
assert parse_geoname_id("5391959") == 5_391_959
|
|
end
|
|
|
|
test "returns nil for empty string" do
|
|
assert parse_geoname_id("") == nil
|
|
end
|
|
|
|
test "returns nil for non-numeric string" do
|
|
assert parse_geoname_id("abc") == nil
|
|
end
|
|
|
|
test "parses integer with trailing chars from CSV parsing" do
|
|
assert parse_geoname_id("123") == 123
|
|
end
|
|
end
|
|
|
|
describe "production mode (--production)" do
|
|
setup %{temp_dir: temp_dir} do
|
|
previous_key = System.get_env("TOWEROPS_KEY")
|
|
System.put_env("TOWEROPS_KEY", "fake-superuser-token")
|
|
|
|
on_exit(fn ->
|
|
if previous_key do
|
|
System.put_env("TOWEROPS_KEY", previous_key)
|
|
else
|
|
System.delete_env("TOWEROPS_KEY")
|
|
end
|
|
end)
|
|
|
|
locations_file = Path.join(temp_dir, "GeoLite2-City-Locations-en.csv")
|
|
blocks_file = Path.join(temp_dir, "GeoLite2-City-Blocks-IPv4.csv")
|
|
|
|
File.write!(locations_file, """
|
|
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union
|
|
5391959,en,NA,North America,US,United States,CA,California,,,San Francisco,807,America/Los_Angeles,0
|
|
""")
|
|
|
|
File.write!(blocks_file, """
|
|
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius
|
|
1.0.0.0/24,5391959,5391959,,,0,94102,37.7749,-122.4194,1000
|
|
""")
|
|
|
|
{:ok, temp_dir: temp_dir}
|
|
end
|
|
|
|
test "sends data to the configured API URL", %{temp_dir: temp_dir} do
|
|
api_url = "http://geoip-test.example.invalid"
|
|
System.put_env("TOWEROPS_URL", api_url)
|
|
on_exit(fn -> System.delete_env("TOWEROPS_URL") end)
|
|
|
|
Req.Test.stub(Import, fn conn ->
|
|
assert conn.request_path == "/admin/api/geoip/import"
|
|
Req.Test.json(conn, %{"ok" => true})
|
|
end)
|
|
|
|
capture_io(fn -> run([temp_dir, "--production"]) end)
|
|
end
|
|
|
|
test "raises when API responds with a non-200 status", %{temp_dir: temp_dir} do
|
|
Req.Test.stub(Import, fn conn ->
|
|
conn
|
|
|> Plug.Conn.put_resp_header("content-type", "application/json")
|
|
|> Plug.Conn.send_resp(401, ~s({"error":"unauthorized"}))
|
|
end)
|
|
|
|
assert_raise RuntimeError, ~r/Failed to send batch/, fn ->
|
|
capture_io(:stderr, fn -> run([temp_dir, "--production"]) end)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "parse_cidr/1" do
|
|
test "parses valid /24 CIDR" do
|
|
assert parse_cidr("1.0.0.0/24") == {:ok, 16_777_216, 16_777_471}
|
|
end
|
|
|
|
test "parses valid /32 CIDR" do
|
|
assert parse_cidr("1.0.0.1/32") == {:ok, 16_777_217, 16_777_217}
|
|
end
|
|
|
|
test "parses valid /8 CIDR" do
|
|
assert parse_cidr("10.0.0.0/8") == {:ok, 167_772_160, 184_549_375}
|
|
end
|
|
|
|
test "parses valid /16 CIDR" do
|
|
assert parse_cidr("192.168.0.0/16") == {:ok, 3_232_235_520, 3_232_301_055}
|
|
end
|
|
|
|
test "returns :error for missing prefix" do
|
|
assert parse_cidr("1.0.0.0") == :error
|
|
end
|
|
|
|
test "returns :error for invalid IP" do
|
|
assert parse_cidr("999.999.999.999/24") == :error
|
|
end
|
|
|
|
test "returns :error for invalid prefix" do
|
|
assert parse_cidr("1.0.0.0/abc") == :error
|
|
end
|
|
|
|
test "returns :error for empty string" do
|
|
assert parse_cidr("") == :error
|
|
end
|
|
|
|
test "returns :error for prefix > 32" do
|
|
assert_raise ArithmeticError, fn -> parse_cidr("1.0.0.0/33") end
|
|
end
|
|
end
|
|
|
|
# Helper to insert location for tests
|
|
defp insert(:geoip_location, attrs) do
|
|
defaults = %{
|
|
country_code: "US",
|
|
country_name: "United States",
|
|
city_name: "Test City",
|
|
subdivision_1_name: nil,
|
|
subdivision_2_name: nil,
|
|
latitude: nil,
|
|
longitude: nil,
|
|
inserted_at: Towerops.Time.now(),
|
|
updated_at: Towerops.Time.now()
|
|
}
|
|
|
|
attrs = Map.merge(defaults, Map.new(attrs))
|
|
|
|
%Location{}
|
|
|> Ecto.Changeset.change(attrs)
|
|
|> Repo.insert!()
|
|
end
|
|
end
|