diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 4970f1aa..eec9c884 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -174,6 +174,8 @@ public static QRCodeData GenerateMicroQrCode(string plainText, ECCLevel eccLevel ValidateECCLevel(eccLevel); if (eccLevel == ECCLevel.H) throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes does not support error correction level H."); + if (eccLevel == ECCLevel.Q && requestedVersion == 0) + requestedVersion = -4; // If Q level is specified without a version, automatically select M4 since Q is only supported on M4 if (eccLevel == ECCLevel.Q && requestedVersion != -4) throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes only supports error correction level Q for version M4."); if (eccLevel != ECCLevel.Default && requestedVersion == -1) diff --git a/QRCoderTests/PayloadGeneratorTests/OneTimePasswordTests.cs b/QRCoderTests/PayloadGeneratorTests/OneTimePasswordTests.cs index d0d909db..1452c796 100644 --- a/QRCoderTests/PayloadGeneratorTests/OneTimePasswordTests.cs +++ b/QRCoderTests/PayloadGeneratorTests/OneTimePasswordTests.cs @@ -237,4 +237,252 @@ public void one_time_password_generator_totp_default_algorithm_not_included() pg.ToString().ShouldBe("otpauth://totp/Google:test%40google.com?secret=pwq65q55&issuer=Google"); } + + [Fact] + public void one_time_password_generator_should_throw_when_secret_is_null() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = null!, + Issuer = "Google", + Label = "test@google.com", + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Secret must be a filled out base32 encoded string"); + } + + [Fact] + public void one_time_password_generator_should_throw_when_secret_is_empty() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "", + Issuer = "Google", + Label = "test@google.com", + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Secret must be a filled out base32 encoded string"); + } + + [Fact] + public void one_time_password_generator_should_throw_when_secret_is_whitespace() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = " ", + Issuer = "Google", + Label = "test@google.com", + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Secret must be a filled out base32 encoded string"); + } + + [Fact] + public void one_time_password_generator_should_throw_when_issuer_contains_colon() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google:Company", + Label = "test@google.com", + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Issuer must not have a ':'"); + } + + [Fact] + public void one_time_password_generator_should_throw_when_label_contains_colon() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test:user@google.com", + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Label must not have a ':'"); + } + + [Fact] + public void one_time_password_generator_totp_should_throw_when_period_is_null() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Period = null, + }; + + Should.Throw(() => pg.ToString()) + .Message.ShouldBe("Period must be set when using OneTimePasswordAuthType.TOTP"); + } + + [Fact] + public void one_time_password_generator_totp_with_only_issuer() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + }; + + pg.ToString().ShouldBe("otpauth://totp/Google?secret=pwq65q55&issuer=Google"); + } + + [Fact] + public void one_time_password_generator_hotp_with_only_issuer() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Type = PayloadGenerator.OneTimePassword.OneTimePasswordAuthType.HOTP, + Counter = 100, + }; + + pg.ToString().ShouldBe("otpauth://hotp/Google?secret=pwq65q55&issuer=Google&counter=100"); + } + + [Fact] + public void one_time_password_generator_totp_with_no_issuer_or_label() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + }; + + pg.ToString().ShouldBe("otpauth://totp/?secret=pwq65q55"); + } + + [Fact] + public void one_time_password_generator_hotp_with_no_issuer_or_label() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Type = PayloadGenerator.OneTimePassword.OneTimePasswordAuthType.HOTP, + Counter = 50, + }; + + pg.ToString().ShouldBe("otpauth://hotp/?secret=pwq65q55&counter=50"); + } + + [Fact] + public void one_time_password_generator_hotp_with_zero_counter() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Type = PayloadGenerator.OneTimePassword.OneTimePasswordAuthType.HOTP, + Counter = 0, + }; + + pg.ToString().ShouldBe("otpauth://hotp/Google:test%40google.com?secret=pwq65q55&issuer=Google&counter=0"); + } + + [Fact] + public void one_time_password_generator_hotp_with_sha512_algorithm() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Type = PayloadGenerator.OneTimePassword.OneTimePasswordAuthType.HOTP, + Counter = 200, + AuthAlgorithm = PayloadGenerator.OneTimePassword.OneTimePasswordAuthAlgorithm.SHA512, + }; + + pg.ToString().ShouldBe("otpauth://hotp/Google:test%40google.com?secret=pwq65q55&issuer=Google&algorithm=SHA512&counter=200"); + } + + [Fact] + public void one_time_password_generator_should_strip_spaces_from_secret() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55 abcd efgh", + Issuer = "Google", + Label = "test@google.com", + }; + + pg.ToString().ShouldBe("otpauth://totp/Google:test%40google.com?secret=pwq65q55abcdefgh&issuer=Google"); + } + + [Fact] + public void one_time_password_generator_totp_with_special_characters_in_label() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test+user@google.com", + }; + + pg.ToString().ShouldBe("otpauth://totp/Google:test%2Buser%40google.com?secret=pwq65q55&issuer=Google"); + } + + [Fact] + public void one_time_password_generator_totp_with_special_characters_in_issuer() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google & Company", + Label = "test@google.com", + }; + + pg.ToString().ShouldBe("otpauth://totp/Google%20%26%20Company:test%40google.com?secret=pwq65q55&issuer=Google%20%26%20Company"); + } + + [Fact] + public void one_time_password_generator_hotp_with_large_counter() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Type = PayloadGenerator.OneTimePassword.OneTimePasswordAuthType.HOTP, + Counter = 999999999, + }; + + pg.ToString().ShouldBe("otpauth://hotp/Google:test%40google.com?secret=pwq65q55&issuer=Google&counter=999999999"); + } + + [Fact] + public void one_time_password_generator_totp_with_large_period() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Period = 3600, + }; + + pg.ToString().ShouldBe("otpauth://totp/Google:test%40google.com?secret=pwq65q55&issuer=Google&period=3600"); + } + + [Fact] + public void one_time_password_generator_totp_with_large_digits() + { + var pg = new PayloadGenerator.OneTimePassword + { + Secret = "pwq6 5q55", + Issuer = "Google", + Label = "test@google.com", + Digits = 10, + }; + + pg.ToString().ShouldBe("otpauth://totp/Google:test%40google.com?secret=pwq65q55&issuer=Google&digits=10"); + } } diff --git a/QRCoderTests/PayloadGeneratorTests/SlovenianUpnQrTests.cs b/QRCoderTests/PayloadGeneratorTests/SlovenianUpnQrTests.cs new file mode 100644 index 00000000..e4d93df8 --- /dev/null +++ b/QRCoderTests/PayloadGeneratorTests/SlovenianUpnQrTests.cs @@ -0,0 +1,445 @@ +namespace QRCoderTests.PayloadGeneratorTests; + +public class SlovenianUpnQrTests +{ + [Fact] + public void slovenian_upnqr_generator_should_generate_payload_simple() + { + var payerName = "Janez Novak"; + var payerAddress = "Slovenska cesta 1"; + var payerPlace = "1000 Ljubljana"; + var recipientName = "Podjetje d.o.o."; + var recipientAddress = "Trzaska cesta 10"; + var recipientPlace = "2000 Maribor"; + var recipientIban = "SI56011006001234567"; + var description = "Payment for services"; + var amount = 150.50; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + generator + .ToString() + .ShouldBe("UPNQR\n\n\n\n\nJanez Novak\nSlovenska cesta 1\n1000 Ljubljana\n00000015050\n\n\nOTHR\nPayment for services\n\nSI56011006001234567\nSI00\nPodjetje d.o.o.\nTrzaska cesta 10\n2000 Maribor\n167\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_generate_payload_with_deadline() + { + var payerName = "Marko Kovac"; + var payerAddress = "Celovska cesta 20"; + var payerPlace = "1000 Ljubljana"; + var recipientName = "Test Company"; + var recipientAddress = "Main Street 5"; + var recipientPlace = "3000 Celje"; + var recipientIban = "SI56020170014356205"; + var description = "Invoice payment"; + var amount = 99.99; + var deadline = new DateTime(2024, 12, 31); + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, deadline); + + var payload = generator.ToString(); + + payload.ShouldContain("31.12.2024\n"); + payload.ShouldContain("00000009999\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_generate_payload_with_custom_code() + { + var payerName = "Ana Horvat"; + var payerAddress = "Dunajska cesta 100"; + var payerPlace = "1000 Ljubljana"; + var recipientName = "Service Provider"; + var recipientAddress = "Business Park 1"; + var recipientPlace = "4000 Kranj"; + var recipientIban = "SI56031234567890123"; + var description = "Monthly subscription"; + var amount = 25.00; + var code = "GDSV"; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + recipientSiModel: "SI00", recipientSiReference: "", code: code); + + var payload = generator.ToString(); + + payload.ShouldContain("GDSV\n"); + payload.ShouldContain("00000002500\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_generate_payload_with_reference() + { + var payerName = "Peter Zupan"; + var payerAddress = "Smartinska cesta 50"; + var payerPlace = "1000 Ljubljana"; + var recipientName = "Utility Company"; + var recipientAddress = "Energy Street 15"; + var recipientPlace = "1000 Ljubljana"; + var recipientIban = "SI56191234567890123"; + var description = "Electricity bill"; + var amount = 75.25; + var recipientSiModel = "SI12"; + var recipientSiReference = "00012345678901234"; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + recipientSiModel: recipientSiModel, recipientSiReference: recipientSiReference); + + var payload = generator.ToString(); + + payload.ShouldContain("SI1200012345678901234\n"); + payload.ShouldContain("00000007525\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_limit_payer_name_length() + { + var payerName = "This is a very long name that exceeds the maximum allowed length of 33 characters"; + var payerAddress = "Address 1"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 100.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + // Name should be truncated to 33 characters + payload.ShouldContain("This is a very long name that exc\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_limit_description_length() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "This is a very long description that exceeds the maximum allowed length of 42 characters for the purpose field"; + var amount = 50.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + // Description should be truncated to 42 characters + payload.ShouldContain("This is a very long description that excee\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_format_amount_correctly() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + + // Test various amounts + var generator1 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 0.01); + generator1.ToString().ShouldContain("00000000001\n"); + + var generator2 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 1.00); + generator2.ToString().ShouldContain("00000000100\n"); + + var generator3 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 1234.56); + generator3.ToString().ShouldContain("00000123456\n"); + + var generator4 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 999999.99); + generator4.ToString().ShouldContain("00099999999\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_calculate_checksum_correctly() + { + var payerName = "Test Payer"; + var payerAddress = "Test Address"; + var payerPlace = "Test City"; + var recipientName = "Test Recipient"; + var recipientAddress = "Test Recipient Address"; + var recipientPlace = "Test Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Test Payment"; + var amount = 100.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + // Checksum should be at the end as a 3-digit number + var lines = payload.Split('\n'); + var checksumLine = lines[lines.Length - 2]; // Second to last line (last is empty) + checksumLine.Length.ShouldBe(3); + int.TryParse(checksumLine, out _).ShouldBeTrue(); + } + + [Fact] + public void slovenian_upnqr_generator_should_trim_whitespace() + { + var payerName = " Payer Name "; + var payerAddress = " Address "; + var payerPlace = " City "; + var recipientName = " Recipient "; + var recipientAddress = " Recipient Address "; + var recipientPlace = " Recipient City "; + var recipientIban = " SI56011006001234567 "; + var description = " Payment "; + var amount = 50.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + payload.ShouldContain("Payer Name\n"); + payload.ShouldContain("Address\n"); + payload.ShouldContain("City\n"); + payload.ShouldContain("SI56011006001234567\n"); + payload.ShouldContain("Payment\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_uppercase_code_and_iban() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "si56011006001234567"; + var description = "Payment"; + var amount = 100.00; + var code = "othr"; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + code: code); + + var payload = generator.ToString(); + + payload.ShouldContain("OTHR\n"); + payload.ShouldContain("SI56011006001234567\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_set_correct_qr_properties() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 100.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + generator.Version.ShouldBe(15); + generator.EccLevel.ShouldBe(QRCodeGenerator.ECCLevel.M); + generator.EciMode.ShouldBe(QRCodeGenerator.EciMode.Iso8859_2); + } + + [Fact] + public void slovenian_upnqr_generator_should_handle_zero_amount() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 0.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + payload.ShouldContain("00000000000\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_limit_iban_length() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567890123456789012345"; // Too long + var description = "Payment"; + var amount = 100.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount); + + var payload = generator.ToString(); + + // IBAN should be truncated to 34 characters + payload.ShouldContain("SI56011006001234567890123456789012\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_limit_reference_length() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 100.00; + var recipientSiModel = "SI12"; + var recipientSiReference = "123456789012345678901234567890"; // Too long + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + recipientSiModel: recipientSiModel, recipientSiReference: recipientSiReference); + + var payload = generator.ToString(); + + // Reference should be truncated to 22 characters + payload.ShouldContain("SI121234567890123456789012\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_handle_empty_deadline() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 100.00; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + deadline: null); + + var payload = generator.ToString(); + + // Should have empty line for deadline + var lines = payload.Split('\n'); + lines[13].ShouldBe(""); // Deadline line should be empty + } + + [Fact] + public void slovenian_upnqr_generator_should_uppercase_si_model() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + var amount = 100.00; + var recipientSiModel = "si12"; + + var generator = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, amount, + recipientSiModel: recipientSiModel); + + var payload = generator.ToString(); + + payload.ShouldContain("SI12\n"); + } + + [Fact] + public void slovenian_upnqr_generator_should_round_amount_correctly() + { + var payerName = "Payer"; + var payerAddress = "Address"; + var payerPlace = "City"; + var recipientName = "Recipient"; + var recipientAddress = "Recipient Address"; + var recipientPlace = "Recipient City"; + var recipientIban = "SI56011006001234567"; + var description = "Payment"; + + // Test rounding + var generator1 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 10.555); + generator1.ToString().ShouldContain("00000001056\n"); // Rounds to 10.56 + + var generator2 = new PayloadGenerator.SlovenianUpnQr( + payerName, payerAddress, payerPlace, + recipientName, recipientAddress, recipientPlace, + recipientIban, description, 10.554); + generator2.ToString().ShouldContain("00000001055\n"); // Rounds to 10.55 + } +} diff --git a/QRCoderTests/PayloadGeneratorTests/SwissQrCodeTests.cs b/QRCoderTests/PayloadGeneratorTests/SwissQrCodeTests.cs index 5ac3a2d3..d783b3ea 100644 --- a/QRCoderTests/PayloadGeneratorTests/SwissQrCodeTests.cs +++ b/QRCoderTests/PayloadGeneratorTests/SwissQrCodeTests.cs @@ -586,4 +586,197 @@ public void swissqrcode_generator_should_validate_two_lettercodes() exception.Message.ShouldBe("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); } + + [Fact] + public void swissqrcode_generator_should_generate_with_combined_address_type() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress("John Doe", "CH", "Parlamentsgebäude 1", "3003 Bern"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2430043000000789012", PayloadGenerator.SwissQrCode.Iban.IbanType.QrIban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.QRR, "990005000000000320071012303", ReferenceTextType.QrReference); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference); + + generator + .ToString() + .ShouldBe("SPC\r\n0200\r\n1\r\nCH2430043000000789012\r\nK\r\nJohn Doe\r\nParlamentsgebäude 1\r\n3003 Bern\r\n\r\n\r\nCH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nCHF\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nQRR\r\n990005000000000320071012303\r\n\r\nEPD"); + } + + [Fact] + public void swissqrcode_generator_should_throw_combined_address_name_empty() + { + var name = ""; + var country = "CH"; + var addressLine1 = "Parlamentsgebäude 1"; + var addressLine2 = "3003 Bern"; + + var exception = Should.Throw(() => PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress(name, country, addressLine1, addressLine2)); + + exception.Message.ShouldBe("Name must not be empty."); + } + + [Fact] + public void swissqrcode_generator_should_throw_combined_address_name_too_long() + { + var name = "John Dorian Peter Charles Lord of the Rings and Master of Disaster Grayham"; + var country = "CH"; + var addressLine1 = "Parlamentsgebäude 1"; + var addressLine2 = "3003 Bern"; + + var exception = Should.Throw(() => PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress(name, country, addressLine1, addressLine2)); + + exception.Message.ShouldBe("Name must be shorter than 71 chars."); + } + + [Fact] + public void swissqrcode_generator_should_throw_combined_address_line1_too_long() + { + var name = "John Doe"; + var country = "CH"; + var addressLine1 = "This is a very long address line that exceeds the maximum allowed length of 70 characters for combined address"; + var addressLine2 = "City"; + + var exception = Should.Throw(() => PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress(name, country, addressLine1, addressLine2)); + + exception.Message.ShouldBe("Address line 1 must be shorter than 71 chars."); + } + + [Fact] + public void swissqrcode_generator_should_throw_combined_address_line2_too_long() + { + var name = "John Doe"; + var country = "CH"; + var addressLine1 = "Parlamentsgebäude 1"; + var addressLine2 = "This is a very long address line that exceeds the maximum allowed length of 70 characters for combined address"; + + var exception = Should.Throw(() => PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress(name, country, addressLine1, addressLine2)); + + exception.Message.ShouldBe("Address line 2 must be shorter than 71 chars."); + } + + [Fact] + public void swissqrcode_generator_should_throw_combined_address_country_invalid() + { + var name = "John Doe"; + var country = "CHE"; + var addressLine1 = "Parlamentsgebäude 1"; + var addressLine2 = "3003 Bern"; + + var exception = Should.Throw(() => PayloadGenerator.SwissQrCode.Contact.WithCombinedAddress(name, country, addressLine1, addressLine2)); + + exception.Message.ShouldBe("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); + } + + [Fact] + public void swissqrcode_generator_should_generate_with_minimal_reference() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.NON); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference); + + generator + .ToString() + .ShouldBe("SPC\r\n0200\r\n1\r\nCH2609000000857666015\r\nS\r\nJohn Doe\r\nParlamentsgebäude\r\n1\r\n3003\r\nBern\r\nCH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nCHF\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nNON\r\n\r\n\r\nEPD"); + } + + [Fact] + public void swissqrcode_generator_should_generate_with_scor_reference() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.SCOR, "RF18539007547034", ReferenceTextType.CreditorReferenceIso11649); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference); + + generator + .ToString() + .ShouldBe("SPC\r\n0200\r\n1\r\nCH2609000000857666015\r\nS\r\nJohn Doe\r\nParlamentsgebäude\r\n1\r\n3003\r\nBern\r\nCH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nCHF\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nSCOR\r\nRF18539007547034\r\n\r\nEPD"); + } + + + [Fact] + public void swissqrcode_generator_should_handle_liechtenstein_iban() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "9490", "Vaduz", "LI", "Städtle", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("LI21088100002324013AA", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.NON); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference); + + generator.ToString().ShouldContain("LI21088100002324013AA"); + } + + [Fact] + public void swissqrcode_generator_should_throw_qrr_reference_without_qr_iban() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.QRR, "990005000000000320071012303", ReferenceTextType.QrReference); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var exception = Should.Throw(() => new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference)); + + exception.Message.ShouldBe("If non QR-IBAN is used, you have to choose either \"SCOR\" or \"NON\" as reference type!"); + } + + [Fact] + public void swissqrcode_generator_should_handle_additional_information_without_bill_information() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.NON); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + var additionalInformation = new PayloadGenerator.SwissQrCode.AdditionalInformation("Unstructured message only"); + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference, additionalInformation); + + generator.ToString().ShouldContain("Unstructured message only\r\nEPD"); + } + + [Fact] + public void swissqrcode_generator_should_handle_bill_information_without_unstructured_message() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.NON); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + var additionalInformation = new PayloadGenerator.SwissQrCode.AdditionalInformation(null, "Bill information only"); + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference, additionalInformation); + + generator.ToString().ShouldContain("\r\nEPD\r\nBill information only"); + } + + [Fact] + public void swissqrcode_generator_should_handle_empty_additional_information() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2609000000857666015", PayloadGenerator.SwissQrCode.Iban.IbanType.Iban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.NON); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + var additionalInformation = new PayloadGenerator.SwissQrCode.AdditionalInformation(); + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference, additionalInformation); + + generator.ToString().ShouldContain("\r\nEPD"); + } + + + [Fact] + public void swissqrcode_generator_should_handle_qr_iban_with_qrr_reference_default_counter() + { + var contactGeneral = PayloadGenerator.SwissQrCode.Contact.WithStructuredAddress("John Doe", "3003", "Bern", "CH", "Parlamentsgebäude", "1"); + var iban = new PayloadGenerator.SwissQrCode.Iban("CH2430043000000789012", PayloadGenerator.SwissQrCode.Iban.IbanType.QrIban); + var reference = new PayloadGenerator.SwissQrCode.Reference(ReferenceType.QRR); + var currency = PayloadGenerator.SwissQrCode.Currency.CHF; + + var generator = new PayloadGenerator.SwissQrCode(iban, currency, contactGeneral, reference); + + generator.ToString().ShouldContain("QRR\r\n\r\n"); + } } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index e1de26ab..a755f2df 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -41,6 +41,8 @@ public void validate_antilogtable() [InlineData("123456789", ECCLevel.L, "gCY4Cj1uLhI/0JjWG1F9kC4S1+I", 13)] //verified [InlineData("abcd56789012345", ECCLevel.L, "kqcKfCCdu1VTjjtsmK4iBav9FTs", 17)] //verified [InlineData("abc", ECCLevel.M, "334sxrtY5KkNZRGj1pBgb87/cFc", 15)] //reads fine, but unable to verify repeating pattern + [InlineData("ABCDEFGHIJKLMNOPQR", ECCLevel.M, "6wDTTApiAonkc6zZk451niMR06Y", 17)] // M4 with M - 18 chars for M4 + [InlineData("ABCDEFGHIJKLM", ECCLevel.Q, "RptOJSwvxUmQYoQXIPf9c1L4Ayc", 17)] // M4 with Q - 13 chars for M4 public void validate_micro_qr_code(string input, ECCLevel eccLevel, string expectedHash, int expectedSize) { var qrData = QRCodeGenerator.GenerateMicroQrCode(input, eccLevel); @@ -657,6 +659,118 @@ public void can_save_and_load_micro_qrcode_data(QRCodeData.Compression compressi reloadedQrData.ModuleMatrix.Count.ShouldBe(originalQrData.ModuleMatrix.Count); reloadedMatrix.ShouldBe(originalMatrix); } + + [Fact] + public void micro_qr_throws_when_data_too_long_for_any_version() + { + // M4 with L can hold max 35 alphanumeric characters + var input = new string('A', 36); + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.L)); + ex.Message.ShouldContain("exceeds the maximum size", Case.Insensitive); + } + + [Fact] + public void micro_qr_throws_when_data_too_long_for_selected_version() + { + // M2 with L can hold max 10 alphanumeric characters, but M3 can hold 23 + var input = new string('A', 11); + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.L, -2)); + ex.Message.ShouldContain("exceeds the maximum size", Case.Insensitive); + } + + [Fact] + public void qr_throws_when_data_too_long_for_any_version() + { + // Version 40 with L can hold max 4296 alphanumeric characters + var input = new string('A', 4297); + var ex = Should.Throw(() => + QRCodeGenerator.GenerateQrCode(input, ECCLevel.L)); + ex.Message.ShouldContain("exceeds the maximum size", Case.Insensitive); + } + + [Fact] + public void qr_throws_when_data_too_long_for_selected_version() + { + // Version 1 with L can hold max 25 alphanumeric characters, but version 2 can hold 47 + var input = new string('A', 26); + var ex = Should.Throw(() => + QRCodeGenerator.GenerateQrCode(input, ECCLevel.L, requestedVersion: 1)); + ex.Message.ShouldContain("exceeds the maximum size", Case.Insensitive); + } + + [Fact] + public void micro_qr_throws_on_invalid_version_too_low() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode("test", ECCLevel.L, -5)); + ex.ParamName.ShouldBe("requestedVersion"); + ex.Message.ShouldContain("must be -1 to -4"); + } + + [Fact] + public void micro_qr_throws_on_invalid_version_too_high() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode("test", ECCLevel.L, 1)); + ex.ParamName.ShouldBe("requestedVersion"); + ex.Message.ShouldContain("must be -1 to -4"); + } + + [Fact] + public void micro_qr_throws_on_ecc_level_h() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode("test", ECCLevel.H)); + ex.ParamName.ShouldBe("eccLevel"); + ex.Message.ShouldContain("does not support error correction level H"); + } + + [Fact] + public void micro_qr_throws_on_ecc_level_q_without_m4() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode("test", ECCLevel.Q, -3)); + ex.ParamName.ShouldBe("eccLevel"); + ex.Message.ShouldContain("only supports error correction level Q for version M4"); + } + + [Fact] + public void micro_qr_throws_on_non_default_ecc_with_m1() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode("1", ECCLevel.L, -1)); + ex.ParamName.ShouldBe("eccLevel"); + ex.Message.ShouldContain("Please specify ECCLevel.Default for version M1"); + } + + [Fact] + public void micro_qr_throws_on_null_plaintext() + { + var ex = Should.Throw(() => + QRCodeGenerator.GenerateMicroQrCode(null!, ECCLevel.L)); + ex.ParamName.ShouldBe("plainText"); + } + + [Fact] + public void throws_on_invalid_ecc_level_cast() + { + var invalidEccLevel = (ECCLevel)99; + var ex = Should.Throw(() => + QRCodeGenerator.GenerateQrCode("test", invalidEccLevel)); + ex.ParamName.ShouldBe("eccLevel"); + ex.Message.ShouldContain("Invalid error correction level"); + } + + [Fact] + public void micro_qr_should_auto_select_m4_for_ecc_level_q() + { + // Q level is only supported on M4, so the generator should automatically select M4 + var qrData = QRCodeGenerator.GenerateMicroQrCode("ABC", ECCLevel.Q); + (qrData.ModuleMatrix.Count - 8).ShouldBe(17); // M4 size is 17x17 + qrData.Version.ShouldBe(-4); + } } public static class ExtensionMethods