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