Files
iban-ex/test/iban_ex/validator_test.exs
2025-11-29 22:47:56 -05:00

434 lines
14 KiB
Elixir

defmodule IbanEx.ValidatorTest do
@moduledoc """
Comprehensive test coverage for IbanEx.Validator module.
Based on Test Coverage Improvement Plan - Phase 1: Critical Coverage.
"""
use ExUnit.Case, async: true
alias IbanEx.{Validator, TestData}
describe "valid?/1 - comprehensive validation" do
test "returns true for all 105 registry valid IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
assert TestData.valid?(iban), "Expected valid IBAN: #{iban}"
end)
end
test "returns true for edge case: shortest IBAN (Norway, 15 chars)" do
edge_cases = TestData.edge_cases()
assert TestData.valid?(edge_cases.shortest)
assert String.length(edge_cases.shortest) == 15
end
test "returns true for edge case: longest IBAN (Russia, 33 chars)" do
edge_cases = TestData.edge_cases()
assert TestData.valid?(edge_cases.longest)
assert String.length(edge_cases.longest) == 33
end
test "returns true for complex IBANs with branch code and national check" do
edge_cases = TestData.edge_cases()
Enum.each(edge_cases.complex, fn iban ->
assert TestData.valid?(iban), "Expected valid complex IBAN: #{iban}"
end)
end
test "returns false for invalid checksum" do
# Valid IBAN with flipped checksum
# Changed 89 to 00
refute TestData.valid?("DE00370400440532013000")
# Changed 14 to 00
refute TestData.valid?("FR0020041010050500013M02606")
# Changed 29 to 00
refute TestData.valid?("GB00NWBK60161331926819")
end
test "returns false for invalid length (too short)" do
# Missing 1 char
refute TestData.valid?("DE8937040044053201300")
# Missing 1 char from shortest
refute TestData.valid?("NO938601111794")
end
test "returns false for invalid length (too long)" do
# Extra char
refute TestData.valid?("DE89370400440532013000X")
# Extra char on shortest
refute TestData.valid?("NO9386011117947X")
end
test "returns false for unsupported country code" do
refute TestData.valid?("XX89370400440532013000")
refute TestData.valid?("ZZ1234567890123456")
end
test "returns false for invalid characters in BBAN" do
# Cyrillic character
refute TestData.valid?("DE89370400440532013Ї00")
# CInvalcdchara in shorerst
refute TestData.valid?("NO938601111794Ї")
end
test "returns false for lowercase country code" do
refute TestData.valid?("de89370400440532013000")
end
test "returns false for empty string" do
refute TestData.valid?("")
end
test "returns false for nil" do
refute TestData.valid?(nil)
end
test "accepts both electronic and print formats" do
assert TestData.valid?("DE89370400440532013000")
assert TestData.valid?("DE89 3704 0044 0532 0130 00")
end
end
describe "violations/1 - detailed violation reporting" do
test "returns empty list for valid IBAN" do
assert Validator.violations("DE89370400440532013000") == []
assert Validator.violations("NO9386011117947") == []
end
test "returns all violations for completely invalid IBAN" do
violations = Validator.violations("XX00INVALID")
assert :unsupported_country_code in violations
assert :invalid_checksum in violations or :length_to_short in violations
end
test "returns checksum violation for invalid check digits" do
violations = Validator.violations("DE00370400440532013000")
assert :invalid_checksum in violations
refute :invalid_length in violations
end
test "returns length violation for too short IBAN" do
violations = Validator.violations("DE8937040044053201300")
assert :length_to_short in violations
end
test "returns length violation for too long IBAN" do
violations = Validator.violations("DE89370400440532013000XXX")
assert :length_to_long in violations
end
test "returns format violations for invalid BBAN structure" do
# IBAN with letters in numeric-only bank code
violations = Validator.violations("DEXX370400440532013000")
assert length(violations) > 0
end
test "violations are deterministically ordered" do
violations1 = Validator.violations("XX00INVALID")
violations2 = Validator.violations("XX00INVALID")
assert violations1 == violations2
end
test "returns multiple BBAN violations when applicable" do
# Test IBAN with multiple BBAN format issues
violations = Validator.violations("AT6119043002A4573201")
assert length(violations) > 0
end
end
describe "check_iban_length/1" do
test "returns :ok for all registry IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
assert Validator.check_iban_length(iban) == :ok, "Length check failed for: #{iban}"
end)
end
test "returns :ok for shortest IBAN (15 chars)" do
assert Validator.check_iban_length("NO9386011117947") == :ok
end
test "returns :ok for longest IBAN (33 chars)" do
longest = TestData.edge_cases().longest
assert Validator.check_iban_length(longest) == :ok
end
test "returns {:error, :length_to_short} for too short IBAN" do
assert Validator.check_iban_length("FI2112345CC600007") == {:error, :length_to_short}
end
test "returns {:error, :length_to_long} for too long IBAN" do
assert Validator.check_iban_length("FI2112345CC6000007a") == {:error, :length_to_long}
end
test "returns {:error, :unsupported_country_code} for invalid country" do
assert Validator.check_iban_length("FG2112345CC6000007") ==
{:error, :unsupported_country_code}
assert Validator.check_iban_length("UK2112345CC6000007") ==
{:error, :unsupported_country_code}
end
test "validates length for all 18 different IBAN lengths (15-33)" do
# Test coverage for all unique lengths in registry
length_ranges = [
{15, "NO9386011117947"},
{16, "BE68539007547034"},
{18, "DK5000400440116243"},
{22, "DE89370400440532013000"},
{24, "CZ6508000000192000145399"},
{27, "FR1420041010050500013M02606"},
{29, "BR1800360305000010009795493C1"},
{33, TestData.edge_cases().longest}
]
Enum.each(length_ranges, fn {expected_length, iban} ->
assert String.length(iban) == expected_length
assert Validator.check_iban_length(iban) == :ok
end)
end
end
describe "iban_violates_bank_code_format?/1" do
test "returns false for all registry valid IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
refute Validator.iban_violates_bank_code_format?(iban),
"Bank code format violation for: #{iban}"
end)
end
test "returns true for invalid bank code format in numeric-only country" do
# Germany expects numeric bank code
assert Validator.iban_violates_bank_code_format?("DE89ABCD00440532013000")
end
test "validates bank code for countries with alphanumeric format" do
# Bahrain allows alphanumeric bank code (4!a)
refute Validator.iban_violates_bank_code_format?("BH67BMAG00001299123456")
end
test "handles edge cases with very short bank codes" do
# Sweden has 3-digit bank code
refute Validator.iban_violates_bank_code_format?("SE4550000000058398257466")
end
test "handles edge cases with very long bank codes" do
# Russia has 9-digit bank code
longest = TestData.edge_cases().longest
refute Validator.iban_violates_bank_code_format?(longest)
end
end
describe "iban_violates_branch_code_format?/1" do
test "returns false for all registry valid IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
refute Validator.iban_violates_branch_code_format?(iban),
"Branch code format violation for: #{iban}"
end)
end
test "validates branch code for countries with branch codes" do
# France has 5-digit branch code
refute Validator.iban_violates_branch_code_format?("FR1420041010050500013M02606")
# GB has 6-digit sort code (branch code)
refute Validator.iban_violates_branch_code_format?("GB29NWBK60161331926819")
end
test "handles countries without branch codes" do
# Germany has no branch code
refute Validator.iban_violates_branch_code_format?("DE89370400440532013000")
end
test "returns true for invalid branch code format" do
# France with letters in numeric branch code
assert Validator.iban_violates_branch_code_format?("FR142004ABCD050500013M02606")
end
end
describe "iban_violates_account_number_format?/1" do
test "returns false for all registry valid IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
refute Validator.iban_violates_account_number_format?(iban),
"Account number format violation for: #{iban}"
end)
end
test "returns true for account number too short" do
assert Validator.iban_violates_account_number_format?("AL4721211009000000023568741")
assert Validator.iban_violates_account_number_format?("AD120001203020035900100")
end
test "returns true for invalid characters in numeric account" do
assert Validator.iban_violates_account_number_format?("AT6119043002A4573201")
assert Validator.iban_violates_account_number_format?("BH67BMAG000012991A3456")
end
test "returns true for account number too short AND invalid characters" do
assert Validator.iban_violates_account_number_format?("BR18003603050000100097CC1")
end
test "handles alphanumeric account numbers correctly" do
# Qatar has alphanumeric account number
refute Validator.iban_violates_account_number_format?("QA58DOHB00001234567890ABCDEFG")
end
test "handles shortest account numbers" do
# Norway has 6-digit account number
refute Validator.iban_violates_account_number_format?("NO9386011117947")
end
test "handles longest account numbers" do
# Russia has 15-character account number
longest = TestData.edge_cases().longest
refute Validator.iban_violates_account_number_format?(longest)
end
end
describe "iban_violates_national_check_format?/1" do
test "returns false for all registry valid IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
refute Validator.iban_violates_national_check_format?(iban),
"National check format violation for: #{iban}"
end)
end
test "validates national check for countries that have it" do
# France has 2-digit national check
refute Validator.iban_violates_national_check_format?("FR1420041010050500013M02606")
# Spain has 2-digit national check
refute Validator.iban_violates_national_check_format?("ES9121000418450200051332")
# Italy has 1-character check
refute Validator.iban_violates_national_check_format?("IT60X0542811101000000123456")
end
test "handles countries without national check" do
# Germany has no national check
refute Validator.iban_violates_national_check_format?("DE89370400440532013000")
end
test "returns true for invalid national check format" do
# Assuming implementation checks format validity
# This would need actual invalid examples based on implementation
end
end
describe "checksum validation" do
test "validates checksum for all registry IBANs" do
TestData.valid_ibans()
|> Enum.each(fn iban ->
violations = Validator.violations(iban)
refute :invalid_checksum in violations, "Invalid checksum for registry IBAN: #{iban}"
end)
end
test "detects invalid checksum with check digit 00" do
refute TestData.valid?("DE00370400440532013000")
end
test "detects invalid checksum with check digit 01" do
refute TestData.valid?("DE01370400440532013000")
end
test "detects invalid checksum with check digit 99" do
refute TestData.valid?("DE99370400440532013000")
end
test "validates checksum for shortest IBAN" do
shortest = TestData.edge_cases().shortest
violations = Validator.violations(shortest)
refute :invalid_checksum in violations
end
test "validates checksum for longest IBAN" do
longest = TestData.edge_cases().longest
violations = Validator.violations(longest)
refute :invalid_checksum in violations
end
test "validates checksum for alphanumeric BBANs" do
# Bahrain has alphanumeric BBAN
assert TestData.valid?("BH67BMAG00001299123456")
# Qatar has alphanumeric BBAN
assert TestData.valid?("QA58DOHB00001234567890ABCDEFG")
end
end
describe "SEPA country validation" do
test "validates all 53 SEPA country IBANs" do
sepa_ibans = TestData.valid_ibans(sepa_only: true)
assert length(sepa_ibans) == 53
Enum.each(sepa_ibans, fn iban ->
assert TestData.valid?(iban), "SEPA IBAN validation failed: #{iban}"
end)
end
test "validates major SEPA countries" do
major_sepa = ["DE", "FR", "IT", "ES", "NL", "BE", "AT", "CH", "SE"]
Enum.each(major_sepa, fn country_code ->
[iban] = TestData.valid_ibans(country: country_code)
assert TestData.valid?(iban), "Major SEPA country #{country_code} validation failed"
end)
end
end
describe "character type validation" do
test "validates numeric-only BBAN structure" do
# Get IBANs with numeric-only BBANs
numeric_ibans = TestData.ibans_with(numeric_only: true)
assert length(numeric_ibans) > 0
Enum.each(numeric_ibans, fn iban ->
assert TestData.valid?(iban)
end)
end
test "validates alphanumeric BBAN structure" do
alphanumeric_ibans = TestData.ibans_with(numeric_only: false)
assert length(alphanumeric_ibans) > 0
Enum.each(alphanumeric_ibans, fn iban ->
assert TestData.valid?(iban)
end)
end
end
describe "format handling" do
test "validates electronic format" do
assert TestData.valid?("DE89370400440532013000")
end
test "validates print format with spaces" do
assert TestData.valid?("DE89 3704 0044 0532 0130 00")
end
test "validates both formats produce same result" do
electronic = "DE89370400440532013000"
print = "DE89 3704 0044 0532 0130 00"
assert TestData.valid?(electronic) == TestData.valid?(print)
assert Validator.violations(electronic) == Validator.violations(print)
end
end
end