diff --git a/validable/src/main/java/tech/devscast/validable/CardSchemeValidable.kt b/validable/src/main/java/tech/devscast/validable/CardSchemeValidable.kt new file mode 100644 index 0000000..4575c05 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/CardSchemeValidable.kt @@ -0,0 +1,130 @@ +package tech.devscast.validable + +import java.util.regex.Pattern + +/** + * Validates that a card number belongs to a specified scheme. + */ +class CardSchemeValidable(scheme: CardScheme, message: String = "") : BaseValidable( + validator = { value -> + scheme.patterns.any { pattern -> Pattern.matches(pattern, value) } + }, + errorFor = { _ -> message.ifBlank { "Unsupported card type or invalid card number." } } +) + +/** + * see https://en.wikipedia.org/wiki/Payment_card_number + * see https://www.regular-expressions.info/creditcard.html + */ +open class CardScheme(val patterns: List) { + + companion object { + fun merge(vararg schemes: CardScheme): CardScheme { + return CardScheme(schemes.flatMap { scheme -> scheme.patterns }) + } + } + + /** + * American Express card numbers start with 34 or 37 and have 15 digits. + */ + data object Amex : CardScheme( + listOf("/^3[47][0-9]{13}\$/") + ) + + /** + * China UnionPay cards start with 62 and have between 16 and 19 digits. + * Please note that these cards do not follow Luhn Algorithm as a checksum. + */ + data object ChinaUnionPay : CardScheme( + listOf("/^62[0-9]{14,17}\$/") + ) + + /** + * Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits. + * There are Diners Club cards that begin with 5 and have 16 digits. + * These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard. + */ + data object Diners : CardScheme( + listOf("/^3(?:0[0-5]|[68][0-9])[0-9]{11}\$/") + ) + + /** + * Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65. + * All have 16 digits. + */ + data object Discover : CardScheme( + listOf( + "/^6011[0-9]{12}$/", + "/^64[4-9][0-9]{13}$/", + "/^65[0-9]{14}$/", + "/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/" + ) + ) + + /** + * InstaPayment cards begin with 637 through 639 and have 16 digits. + */ + data object InstaPayment : CardScheme( + listOf("/^63[7-9][0-9]{13}\$/") + ) + + /** + * JCB cards beginning with 2131 or 1800 have 15 digits. + * JCB cards beginning with 35 have 16 digits. + */ + data object Jcb : CardScheme( + listOf("/^(?:2131|1800|35[0-9]{3})[0-9]{11}\$/") + ) + + /** + * Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits. + */ + data object Laser : CardScheme( + listOf("/^(6304|670[69]|6771)[0-9]{12,15}\$/") + ) + + /** + * Maestro international cards begin with 675900..675999 and have between 12 and 19 digits. + * Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits. + */ + data object Maestro : CardScheme( + listOf( + "/^(6759[0-9]{2})[0-9]{6,13}$/", + "/^(50[0-9]{4})[0-9]{6,13}$/", + "/^5[6-9][0-9]{10,17}$/", + "/^6[0-9]{11,18}$/" + ) + ) + + /** + * All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. + * October 2016 MasterCard numbers can also start with 222100 through 272099. + */ + data object MasterCard : CardScheme( + listOf( + "/^5[1-5][0-9]{14}$/", + "/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/" + ) + ) + + /** + * Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then between 12 and 15 digits + */ + data object Mir : CardScheme( + listOf("/^220[0-4][0-9]{12,15}\$/") + ) + + /** + * All UATP card numbers start with a 1 and have a length of 15 digits. + */ + data object Uatp : CardScheme( + listOf("/^1[0-9]{14}\$/") + ) + + /** + * All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits. + */ + data object Visa : CardScheme( + listOf("/^4([0-9]{12}|[0-9]{15}|[0-9]{18})\$/") + ) +} diff --git a/validable/src/main/java/tech/devscast/validable/EmailValidable.kt b/validable/src/main/java/tech/devscast/validable/EmailValidable.kt index 4457700..ccd46d3 100644 --- a/validable/src/main/java/tech/devscast/validable/EmailValidable.kt +++ b/validable/src/main/java/tech/devscast/validable/EmailValidable.kt @@ -2,15 +2,14 @@ package tech.devscast.validable import java.util.regex.Pattern -class EmailValidable : BaseValidable(validator = ::isEmailValid, errorFor = ::emailValidationError) - -private fun isEmailValid(email: String): Boolean { - return Pattern.matches("^[A-Za-z](.*)([@]{1})(.+)(\\.)(.+)", email) -} - /** - * Returns an error to be displayed or null if no error was found + * Validates that a value is a valid email address. */ -private fun emailValidationError(email: String): String { - return "Invalid email: $email" -} +class EmailValidable(message: String = "") : BaseValidable( + validator = { value -> + Pattern.matches("^[A-Za-z](.*)([@]{1})(.+)(\\.)(.+)", value) + }, + errorFor = { value -> + message.ifBlank { "$value is not a valid email address." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/EqualToValidable.kt b/validable/src/main/java/tech/devscast/validable/EqualToValidable.kt new file mode 100644 index 0000000..ff79b9c --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/EqualToValidable.kt @@ -0,0 +1,12 @@ +package tech.devscast.validable + +/** + * Validates that a value is equal to another value, + * To force that a value is not equal, see [NotEqualToValidable]. + */ +class EqualToValidable(comparedValue: String, message: String = "") : BaseValidable( + validator = { value -> value === comparedValue }, + errorFor = { value -> + message.ifBlank { "$value should be equal to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/GreaterThanOrEqualValidable.kt b/validable/src/main/java/tech/devscast/validable/GreaterThanOrEqualValidable.kt new file mode 100644 index 0000000..a7034a5 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/GreaterThanOrEqualValidable.kt @@ -0,0 +1,18 @@ +package tech.devscast.validable + +/** + * Validates that a value is greater than or equal to another value. + * To force that a value is greater than another value, see [GreaterThanValidable]. + */ +class GreaterThanOrEqualValidable(comparedValue: Int, message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() >= comparedValue + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be greater than or equal to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/GreaterThanValidable.kt b/validable/src/main/java/tech/devscast/validable/GreaterThanValidable.kt new file mode 100644 index 0000000..f25e055 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/GreaterThanValidable.kt @@ -0,0 +1,19 @@ +package tech.devscast.validable + +/** + * Validates that a value is greater than another value + * To force that a value is greater than or equal to another value, see [GreaterThanOrEqualValidable]. + * To force a value is less than another value, see [LessThanValidable]. + */ +class GreaterThanValidable(comparedValue: Int, message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() > comparedValue + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be equal to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/HostnameValidable.kt b/validable/src/main/java/tech/devscast/validable/HostnameValidable.kt new file mode 100644 index 0000000..f76b654 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/HostnameValidable.kt @@ -0,0 +1,18 @@ +package tech.devscast.validable + +import java.util.regex.Pattern + +private const val HOSTNAME_PATTERN = + "^(?!-)(?!.*\\d+\\.\\d+\\.\\d+\\.\\d+)(?!.*\\.example\$)(?!.*\\.invalid\$)(?!.*\\.localhost\$)(?!.*\\.test\$)[A-Za-z0-9-]{1,63}(? Pattern.matches(HOSTNAME_PATTERN, value) }, + errorFor = { value -> message.ifBlank { "$value is not a valid hostname" } } +) diff --git a/validable/src/main/java/tech/devscast/validable/IpValidable.kt b/validable/src/main/java/tech/devscast/validable/IpValidable.kt new file mode 100644 index 0000000..4cc03e2 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/IpValidable.kt @@ -0,0 +1,14 @@ +package tech.devscast.validable + +import java.util.regex.Pattern + +private const val IP_V4_PATTERN = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$" + +/** + * Validates that a value is a valid IP address + * TODO: add support for IP v6 + */ +class IpValidable(message: String = "") : BaseValidable( + validator = { value -> Pattern.matches(IP_V4_PATTERN, value) }, + errorFor = { value -> message.ifBlank { "$value is not a valid IP address." } } +) diff --git a/validable/src/main/java/tech/devscast/validable/LessThanOrEqualValidable.kt b/validable/src/main/java/tech/devscast/validable/LessThanOrEqualValidable.kt new file mode 100644 index 0000000..c25771c --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/LessThanOrEqualValidable.kt @@ -0,0 +1,18 @@ +package tech.devscast.validable + +/** + * Validates that a value is less than or equal to another value. + * To force that a value is less than another value, see [LessThanValidable]. + */ +class LessThanOrEqualValidable(comparedValue: Int, message: String = "") : BaseValidable( + validator = { value -> + try { + value.toInt() <= comparedValue + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be less than or equal to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/LessThanValidable.kt b/validable/src/main/java/tech/devscast/validable/LessThanValidable.kt new file mode 100644 index 0000000..59fe5a5 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/LessThanValidable.kt @@ -0,0 +1,19 @@ +package tech.devscast.validable + +/** + * Validates that a value is less than another value. + * To force that a value is less than or equal to another value, see [LessThanOrEqualValidable]. + * To force a value is greater than another value, see [GreaterThanValidable]. + */ +class LessThanValidable(comparedValue: Int, message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() < comparedValue + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be less than to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/NegativeOrZeroValidable.kt b/validable/src/main/java/tech/devscast/validable/NegativeOrZeroValidable.kt new file mode 100644 index 0000000..05c609a --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/NegativeOrZeroValidable.kt @@ -0,0 +1,18 @@ +package tech.devscast.validable + +/** + * Validates that a value is a negative number or equal to zero. + * If you don't want to allow zero as value, use [NegativeValidable] instead. + */ +class NegativeOrZeroValidable(message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() <= 0.0 + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be a negative number or zero." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/NegativeValidable.kt b/validable/src/main/java/tech/devscast/validable/NegativeValidable.kt new file mode 100644 index 0000000..5b307ea --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/NegativeValidable.kt @@ -0,0 +1,19 @@ +package tech.devscast.validable + +/** + * Validates that a value is a negative number. + * Zero is neither positive nor negative, + * so you must use [NegativeOrZeroValidable] if you want to allow zero as value. + */ +class NegativeValidable(message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() < 0.0 + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be a negative number." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/NotEmptyValidable.kt b/validable/src/main/java/tech/devscast/validable/NotEmptyValidable.kt index 6247d42..36c12a2 100644 --- a/validable/src/main/java/tech/devscast/validable/NotEmptyValidable.kt +++ b/validable/src/main/java/tech/devscast/validable/NotEmptyValidable.kt @@ -1,11 +1,11 @@ package tech.devscast.validable -class NotEmptyValidable : BaseValidable(validator = ::isFieldValid, errorFor = ::fieldValidationError) - -private fun isFieldValid(text: String): Boolean { - return text.isNotBlank() -} - -private fun fieldValidationError(text: String): String { - return "This field is required" -} +/** + * Validates that a value is not blank - meaning not equal to a blank string. + */ +class NotEmptyValidable(message: String = "") : BaseValidable( + validator = { value -> value.isNotBlank() }, + errorFor = { _ -> + message.ifBlank { "This field is required" } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/NotEqualToValidable.kt b/validable/src/main/java/tech/devscast/validable/NotEqualToValidable.kt new file mode 100644 index 0000000..e98b391 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/NotEqualToValidable.kt @@ -0,0 +1,12 @@ +package tech.devscast.validable + +/** + * Validates that a value is not equal to another value, + * To force that a value is equal, see [EqualToValidable]. + */ +class NotEqualToValidable(comparedValue: String, message: String = "") : BaseValidable( + validator = { value -> value !== comparedValue }, + errorFor = { value -> + message.ifBlank { "$value should not be equal to $comparedValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/PositiveOrZeroValidable.kt b/validable/src/main/java/tech/devscast/validable/PositiveOrZeroValidable.kt new file mode 100644 index 0000000..2a2b02e --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/PositiveOrZeroValidable.kt @@ -0,0 +1,18 @@ +package tech.devscast.validable + +/** + * Validates that a value is a positive number or equal to zero. + * If you don't want to allow zero as value, use [PositiveValidable] instead. + */ +class PositiveOrZeroValidable(message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() >= 0.0 + } catch (e: Exception) { + false + } + }, + errorFor = { text -> + message.ifBlank { "$text should be a positive number or zero." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/PositiveValidable.kt b/validable/src/main/java/tech/devscast/validable/PositiveValidable.kt new file mode 100644 index 0000000..f4e110c --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/PositiveValidable.kt @@ -0,0 +1,19 @@ +package tech.devscast.validable + +/** + * Validates that a value is a positive number. + * Zero is neither positive nor negative, + * so you must use [PositiveOrZeroValidable] if you want to allow zero as value. + */ +class PositiveValidable(message: String = "") : BaseValidable( + validator = { value -> + try { + value.toFloat() > 0.0 + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value should be a positive number." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/RangeValidable.kt b/validable/src/main/java/tech/devscast/validable/RangeValidable.kt new file mode 100644 index 0000000..4ef0485 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/RangeValidable.kt @@ -0,0 +1,17 @@ +package tech.devscast.validable + +/** + * Validates that a given number is between some minimum and maximum. + */ +class RangeValidable(minValue: Int, maxValue: Int, message: String = "") : BaseValidable( + validator = { value -> + try { + (minValue..maxValue).contains(value.toInt()) + } catch (e: Exception) { + false + } + }, + errorFor = { value -> + message.ifBlank { "$value value should be between $minValue and $maxValue." } + } +) diff --git a/validable/src/main/java/tech/devscast/validable/RegexValidable.kt b/validable/src/main/java/tech/devscast/validable/RegexValidable.kt new file mode 100644 index 0000000..d5bd846 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/RegexValidable.kt @@ -0,0 +1,11 @@ +package tech.devscast.validable + +import java.util.regex.Pattern + +/** + * Validates that a value matches a regular expression. + */ +class RegexValidable(pattern: String, message: String = "") : BaseValidable( + validator = { value -> Pattern.matches(pattern, value) }, + errorFor = { _ -> message.ifBlank { "This value is not valid." } } +) diff --git a/validable/src/main/java/tech/devscast/validable/UrlValidable.kt b/validable/src/main/java/tech/devscast/validable/UrlValidable.kt new file mode 100644 index 0000000..868f328 --- /dev/null +++ b/validable/src/main/java/tech/devscast/validable/UrlValidable.kt @@ -0,0 +1,19 @@ +package tech.devscast.validable + +import java.net.MalformedURLException +import java.net.URL + +/** + * Validates that a value is a valid URL string. + */ +class UrlValidable(message: String = "") : BaseValidable( + validator = { value -> + try { + URL(value) + true + } catch (e: MalformedURLException) { + false + } + }, + errorFor = { _ -> message.ifBlank { "This value is not a valid URL." } } +) diff --git a/validable/src/test/java/tech/devscast/validable/CardSchemeValidableTest.kt b/validable/src/test/java/tech/devscast/validable/CardSchemeValidableTest.kt new file mode 100644 index 0000000..0857afe --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/CardSchemeValidableTest.kt @@ -0,0 +1,105 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Test + +class CardSchemeValidableTest { + + lateinit var validable: CardSchemeValidable + + @Test + fun `valid card numbers are valid`() { + getValidCardNumbers().forEach { card -> + validable = CardSchemeValidable(card["type"] as CardScheme) + validable.value = card["number"].toString() + Assert.assertFalse(validable.errorMessage, validable.hasError()) + } + } + + @Test + fun `invalid card number have error`() { + getInvalidCardNumbers().forEach { card -> + validable = CardSchemeValidable(card["type"] as CardScheme) + validable.enableShowErrors() + validable.value = card["number"].toString() + Assert.assertTrue(validable.errorMessage, validable.hasError()) + } + } + + private fun getValidCardNumbers(): List> { + return listOf( + mapOf("type" to CardScheme.Amex, "number" to "378282246310005"), + mapOf("type" to CardScheme.Amex, "number" to "371449635398431"), + mapOf("type" to CardScheme.Amex, "number" to "378734493671000"), + mapOf("type" to CardScheme.Amex, "number" to "347298508610146"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "6228888888888888"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "62288888888888888"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "622888888888888888"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "6228888888888888888"), + mapOf("type" to CardScheme.Diners, "number" to "30569309025904"), + mapOf("type" to CardScheme.Diners, "number" to "36088894118515"), + mapOf("type" to CardScheme.Diners, "number" to "38520000023237"), + mapOf("type" to CardScheme.Discover, "number" to "6011111111111117"), + mapOf("type" to CardScheme.Discover, "number" to "6011000990139424"), + mapOf("type" to CardScheme.InstaPayment, "number" to "6372476031350068"), + mapOf("type" to CardScheme.InstaPayment, "number" to "6385537775789749"), + mapOf("type" to CardScheme.InstaPayment, "number" to "6393440808445746"), + mapOf("type" to CardScheme.Jcb, "number" to "3530111333300000"), + mapOf("type" to CardScheme.Jcb, "number" to "3566002020360505"), + mapOf("type" to CardScheme.Jcb, "number" to "213112345678901"), + mapOf("type" to CardScheme.Jcb, "number" to "180012345678901"), + mapOf("type" to CardScheme.Laser, "number" to "6304678107004080"), + mapOf("type" to CardScheme.Laser, "number" to "6706440607428128629"), + mapOf("type" to CardScheme.Laser, "number" to "6771656738314582216"), + mapOf("type" to CardScheme.Maestro, "number" to "6759744069209"), + mapOf("type" to CardScheme.Maestro, "number" to "5020507657408074712"), + mapOf("type" to CardScheme.Maestro, "number" to "5612559223580173965"), + mapOf("type" to CardScheme.Maestro, "number" to "6759744069209"), + mapOf("type" to CardScheme.Maestro, "number" to "6594371785970435599"), + mapOf("type" to CardScheme.MasterCard, "number" to "5555555555554444"), + mapOf("type" to CardScheme.MasterCard, "number" to "5105105105105100"), + mapOf("type" to CardScheme.MasterCard, "number" to "2221005555554444"), + mapOf("type" to CardScheme.MasterCard, "number" to "2230000000000000"), + mapOf("type" to CardScheme.MasterCard, "number" to "2300000000000000"), + mapOf("type" to CardScheme.MasterCard, "number" to "2699999999999999"), + mapOf("type" to CardScheme.MasterCard, "number" to "2709999999999999"), + mapOf("type" to CardScheme.MasterCard, "number" to "2720995105105100"), + mapOf("type" to CardScheme.Mir, "number" to "2200381427330082"), + mapOf("type" to CardScheme.Mir, "number" to "22003814273300821"), + mapOf("type" to CardScheme.Mir, "number" to "220038142733008212"), + mapOf("type" to CardScheme.Mir, "number" to "2200381427330082123"), + mapOf("type" to CardScheme.Uatp, "number" to "110165309696173"), + mapOf("type" to CardScheme.Visa, "number" to "4111111111111111"), + mapOf("type" to CardScheme.Visa, "number" to "4012888888881881"), + mapOf("type" to CardScheme.Visa, "number" to "4222222222222"), + mapOf("type" to CardScheme.Visa, "number" to "4917610000000000003"), + mapOf("type" to CardScheme.merge(CardScheme.Amex, CardScheme.Visa), "number" to "4111111111111111"), + mapOf("type" to CardScheme.merge(CardScheme.Amex, CardScheme.Visa), "number" to "378282246310005"), + mapOf("type" to CardScheme.merge(CardScheme.Jcb, CardScheme.MasterCard), "number" to "5105105105105100"), + mapOf("type" to CardScheme.merge(CardScheme.Visa, CardScheme.MasterCard), "number" to "5105105105105100") + ) + } + + private fun getInvalidCardNumbers(): List> { + return listOf( + mapOf("type" to CardScheme.Visa, "number" to "42424242424242424242"), + mapOf("type" to CardScheme.Amex, "number" to "357298508610146"), + mapOf("type" to CardScheme.Diners, "number" to "31569309025904"), + mapOf("type" to CardScheme.Diners, "number" to "37088894118515"), + mapOf("type" to CardScheme.InstaPayment, "number" to "6313440808445746"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "622888888888888"), + mapOf("type" to CardScheme.ChinaUnionPay, "number" to "62288888888888888888"), + mapOf("type" to CardScheme.Amex, "number" to "30569309025904"), // DINERS number + mapOf("type" to CardScheme.Amex, "number" to "invalid"), // A string + mapOf("type" to CardScheme.Amex, "number" to "0"), // a lone number + mapOf("type" to CardScheme.Amex, "number" to "000000000000"), // a lone number + mapOf("type" to CardScheme.Diners, "number" to "3056930"), // only first part of the number + mapOf("type" to CardScheme.Discover, "number" to "1117"), // only last 4 digits + mapOf("type" to CardScheme.MasterCard, "number" to "2721001234567890"), // Not assigned yet + mapOf("type" to CardScheme.MasterCard, "number" to "2220991234567890"), // Not assigned yet + mapOf("type" to CardScheme.Uatp, "number" to "11016530969617"), // invalid length + mapOf("type" to CardScheme.Mir, "number" to "220038142733008"), // invalid length + mapOf("type" to CardScheme.Mir, "number" to "22003814273300821234") // invalid length + ) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/EqualToValidableTest.kt b/validable/src/test/java/tech/devscast/validable/EqualToValidableTest.kt new file mode 100644 index 0000000..83bb238 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/EqualToValidableTest.kt @@ -0,0 +1,32 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class EqualToValidableTest { + + lateinit var validable: EqualToValidable + + @Before + fun setUp() { + validable = EqualToValidable("someString") + validable.enableShowErrors() + } + + @Test + fun `exact string is valid`() { + validable.value = "someString" + Assert.assertFalse(validable.hasError()) + Assert.assertFalse(validable.errorMessage != null) + } + + @Test + fun `different string has error`() { + validable.value = "otherString" + Assert.assertTrue(validable.hasError()) + + validable.value = "440" + Assert.assertTrue(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/GreaterThanOrEqualValidableTest.kt b/validable/src/test/java/tech/devscast/validable/GreaterThanOrEqualValidableTest.kt new file mode 100644 index 0000000..5f95e79 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/GreaterThanOrEqualValidableTest.kt @@ -0,0 +1,50 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class GreaterThanOrEqualValidableTest { + + lateinit var validable: GreaterThanOrEqualValidable + + @Before + fun setUp() { + validable = GreaterThanOrEqualValidable(100) + validable.enableShowErrors() + } + + @Test + fun `malformed string number has error`() { + validable.value = "hello" + Assert.assertTrue(validable.hasError()) + + validable.value = "#9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `bigger value is valid`() { + validable.value = "150" + Assert.assertFalse(validable.hasError()) + } + + @Test + fun `limit value is valid`() { + validable.value = "100" + Assert.assertFalse(validable.hasError()) + Assert.assertFalse(validable.errorMessage != null) + } + + @Test + fun `lower than limit value has error`() { + validable.value = "99" + Assert.assertTrue(validable.hasError()) + + validable.value = "10" + Assert.assertTrue(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/GreaterThanValidableTest.kt b/validable/src/test/java/tech/devscast/validable/GreaterThanValidableTest.kt new file mode 100644 index 0000000..f6fed9f --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/GreaterThanValidableTest.kt @@ -0,0 +1,50 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class GreaterThanValidableTest { + + lateinit var validable: GreaterThanValidable + + @Before + fun setUp() { + validable = GreaterThanValidable(100) + validable.enableShowErrors() + } + + @Test + fun `malformed string number has error`() { + validable.value = "hello" + Assert.assertTrue(validable.hasError()) + + validable.value = "#9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `bigger value is valid`() { + validable.value = "150" + Assert.assertFalse(validable.hasError()) + } + + @Test + fun `limit value has error`() { + validable.value = "100" + Assert.assertTrue(validable.hasError()) + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `lower than limit value has error`() { + validable.value = "99" + Assert.assertTrue(validable.hasError()) + + validable.value = "10" + Assert.assertTrue(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/HostnameValidableTest.kt b/validable/src/test/java/tech/devscast/validable/HostnameValidableTest.kt new file mode 100644 index 0000000..109372b --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/HostnameValidableTest.kt @@ -0,0 +1,77 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class HostnameValidableTest { + + lateinit var validable: HostnameValidable + + @Before + fun setUp() { + validable = HostnameValidable() + validable.enableShowErrors() + } + + @Test + fun `should only returns valid hostnames`() { + val hostnames = mutableListOf() + hostnames.addAll( + getHostnames().filter { hostname -> + validable.value = hostname + !validable.hasError() + } + ) + + Assert.assertEquals(getValidHostnames(), hostnames) + } + + private fun getValidHostnames(): List { + return listOf( + "exemple.com", + "bernard", + "localhost", + "bernard.tech", + "devs-cast.tech", + "42.com", + "whel234.com", + "243.cd", + "100.com", + "hs.dd.com", + "www.demo.com" + ) + } + + private fun getHostnames(): List { + return listOf( + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "0123456789 _+-.,!@#$%^&*();\\/|<>\"'", + "12345 -98.7 3.141 .6180 9,000 +42", + "555.123.4567\t+1-(800)-555-2468", + "foo@demo.net", + "bar.ba@test.co.uk", + "exemple.com", + "hell.example", + "he.localhost", + "bernard", + "localhost", + "bernard.tech", + "devs-cast.tech", + "42.com", + "whel234.com", + "243.cd", + "192.168.111.111", + "256.168.111.1", + "0.0.0.999", + "1::1", + "100.com", + "hs.dd.com", + "www.demo.com", + "http://foo.co.uk/>><", + "http://regexr.com/foo.html?q=bar", + "https://mediatemple.net", + "ldap://localhost:389" + ) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/IpValidableTest.kt b/validable/src/test/java/tech/devscast/validable/IpValidableTest.kt new file mode 100644 index 0000000..f89893f --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/IpValidableTest.kt @@ -0,0 +1,50 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class IpValidableTest { + + lateinit var validable: IpValidable + + @Before + fun setUp() { + validable = IpValidable() + validable.enableShowErrors() + } + + @Test + fun `valid ip v4 are valid`() { + getValidIPv4().forEach { + validable.value = it + Assert.assertFalse(validable.hasError()) + } + } + + @Test + fun `invalid ip v4 have error`() { + getInvalidIPv4().forEach { + validable.value = it + Assert.assertTrue(validable.hasError()) + } + } + + private fun getValidIPv4(): List { + return listOf( + "192.168.0.1", + "10.0.0.1", + "172.16.0.1", + "255.255.255.255" + ) + } + + private fun getInvalidIPv4(): List { + return listOf( + "256.0.0.1", + "192.168.0.300", + "1.2.3", + "not an IP address" + ) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/LessThanOrEqualValidableTest.kt b/validable/src/test/java/tech/devscast/validable/LessThanOrEqualValidableTest.kt new file mode 100644 index 0000000..af6a1c2 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/LessThanOrEqualValidableTest.kt @@ -0,0 +1,50 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class LessThanOrEqualValidableTest { + + lateinit var validable: LessThanOrEqualValidable + + @Before + fun setUp() { + validable = LessThanOrEqualValidable(100) + validable.enableShowErrors() + } + + @Test + fun `malformed string number has error`() { + validable.value = "hello" + Assert.assertTrue(validable.hasError()) + + validable.value = "#9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `bigger value has error`() { + validable.value = "150" + Assert.assertTrue(validable.hasError()) + } + + @Test + fun `limit value is valid`() { + validable.value = "100" + Assert.assertFalse(validable.hasError()) + Assert.assertFalse(validable.errorMessage != null) + } + + @Test + fun `lower than limit value is valid`() { + validable.value = "99" + Assert.assertFalse(validable.hasError()) + + validable.value = "10" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/LessThanValidableTest.kt b/validable/src/test/java/tech/devscast/validable/LessThanValidableTest.kt new file mode 100644 index 0000000..dedb83d --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/LessThanValidableTest.kt @@ -0,0 +1,50 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class LessThanValidableTest { + + lateinit var validable: LessThanValidable + + @Before + fun setUp() { + validable = LessThanValidable(100) + validable.enableShowErrors() + } + + @Test + fun `malformed string number has error`() { + validable.value = "hello" + Assert.assertTrue(validable.hasError()) + + validable.value = "#9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `bigger value has error`() { + validable.value = "150" + Assert.assertTrue(validable.hasError()) + } + + @Test + fun `limit value has error`() { + validable.value = "100" + Assert.assertTrue(validable.hasError()) + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `lower than limit value is valid`() { + validable.value = "99" + Assert.assertFalse(validable.hasError()) + + validable.value = "10" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/NegativeOrZeroValidableTest.kt b/validable/src/test/java/tech/devscast/validable/NegativeOrZeroValidableTest.kt new file mode 100644 index 0000000..ae37b25 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/NegativeOrZeroValidableTest.kt @@ -0,0 +1,40 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class NegativeOrZeroValidableTest { + + lateinit var validable: NegativeOrZeroValidable + + @Before + fun setUp() { + validable = NegativeOrZeroValidable() + validable.enableShowErrors() + } + + @Test + fun `positive or malformed string number has error`() { + validable.value = "" + Assert.assertTrue(validable.hasError()) + + validable.value = "9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `negative or zero number is valid`() { + validable.value = "-23" + Assert.assertFalse(validable.hasError()) + + validable.value = "0" + Assert.assertFalse(validable.hasError()) + + validable.value = "-24.3" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/NegativeValidableTest.kt b/validable/src/test/java/tech/devscast/validable/NegativeValidableTest.kt new file mode 100644 index 0000000..43a86c3 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/NegativeValidableTest.kt @@ -0,0 +1,43 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class NegativeValidableTest { + + lateinit var validable: NegativeValidable + + @Before + fun setUp() { + validable = NegativeValidable() + validable.enableShowErrors() + } + + @Test + fun `zero is neither positive nor negative`() { + validable.value = "0" + Assert.assertTrue(validable.hasError()) + } + + @Test + fun `positive or malformed string number has error`() { + validable.value = "" + Assert.assertTrue(validable.hasError()) + + validable.value = "9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `negative number is valid`() { + validable.value = "-23" + Assert.assertFalse(validable.hasError()) + + validable.value = "-24.3" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/NotEqualToValidableTest.kt b/validable/src/test/java/tech/devscast/validable/NotEqualToValidableTest.kt new file mode 100644 index 0000000..53ace68 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/NotEqualToValidableTest.kt @@ -0,0 +1,32 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class NotEqualToValidableTest { + + lateinit var validable: NotEqualToValidable + + @Before + fun setUp() { + validable = NotEqualToValidable("someString") + validable.enableShowErrors() + } + + @Test + fun `exact string has error`() { + validable.value = "someString" + Assert.assertTrue(validable.hasError()) + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `different string is valid`() { + validable.value = "otherString" + Assert.assertFalse(validable.hasError()) + + validable.value = "440" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/PositiveOrZeroValidableTest.kt b/validable/src/test/java/tech/devscast/validable/PositiveOrZeroValidableTest.kt new file mode 100644 index 0000000..91235d7 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/PositiveOrZeroValidableTest.kt @@ -0,0 +1,40 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class PositiveOrZeroValidableTest { + + lateinit var validable: PositiveOrZeroValidable + + @Before + fun setUp() { + validable = PositiveOrZeroValidable() + validable.enableShowErrors() + } + + @Test + fun `negative or malformed string number has error`() { + validable.value = "" + Assert.assertTrue(validable.hasError()) + + validable.value = "-9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `positive number or zero is valid`() { + validable.value = "23" + Assert.assertFalse(validable.hasError()) + + validable.value = "0" + Assert.assertFalse(validable.hasError()) + + validable.value = "24.3" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/PositiveValidableTest.kt b/validable/src/test/java/tech/devscast/validable/PositiveValidableTest.kt new file mode 100644 index 0000000..d838984 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/PositiveValidableTest.kt @@ -0,0 +1,43 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class PositiveValidableTest { + + lateinit var validable: PositiveValidable + + @Before + fun setUp() { + validable = PositiveValidable() + validable.enableShowErrors() + } + + @Test + fun `zero is neither positive nor negative`() { + validable.value = "0" + Assert.assertTrue(validable.hasError()) + } + + @Test + fun `negative or malformed string number has error`() { + validable.value = "" + Assert.assertTrue(validable.hasError()) + + validable.value = "-9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `positive number is valid`() { + validable.value = "23" + Assert.assertFalse(validable.hasError()) + + validable.value = "24.3" + Assert.assertFalse(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/RangeValidableTest.kt b/validable/src/test/java/tech/devscast/validable/RangeValidableTest.kt new file mode 100644 index 0000000..b371686 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/RangeValidableTest.kt @@ -0,0 +1,47 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class RangeValidableTest { + + lateinit var validable: RangeValidable + + @Before + fun setUp() { + validable = RangeValidable(minValue = 20, maxValue = 40) + validable.enableShowErrors() + } + + @Test + fun `malformed string number has error`() { + validable.value = "hello" + Assert.assertTrue(validable.hasError()) + + validable.value = "#9" + Assert.assertTrue(validable.hasError()) + + validable.value = "4-5" + Assert.assertTrue(validable.errorMessage != null) + } + + @Test + fun `inclusive range is valid`() { + validable.value = "23" + Assert.assertFalse(validable.hasError()) + + // TODO: handle floats + validable.value = "39" + Assert.assertFalse(validable.hasError()) + } + + @Test + fun `exclusive range has error`() { + validable.value = "19" + Assert.assertTrue(validable.hasError()) + + validable.value = "41" + Assert.assertTrue(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/RegexValidableTest.kt b/validable/src/test/java/tech/devscast/validable/RegexValidableTest.kt new file mode 100644 index 0000000..7f6ad83 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/RegexValidableTest.kt @@ -0,0 +1,28 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class RegexValidableTest { + + lateinit var validable: RegexValidable + + @Before + fun setUp() { + validable = RegexValidable("^[0-9]+$") + validable.enableShowErrors() + } + + @Test + fun `should match`() { + validable.value = "897" + Assert.assertFalse(validable.hasError()) + } + + @Test + fun `should not match`() { + validable.value = "kalume" + Assert.assertTrue(validable.hasError()) + } +} diff --git a/validable/src/test/java/tech/devscast/validable/UrlValidableTest.kt b/validable/src/test/java/tech/devscast/validable/UrlValidableTest.kt new file mode 100644 index 0000000..0b3eb26 --- /dev/null +++ b/validable/src/test/java/tech/devscast/validable/UrlValidableTest.kt @@ -0,0 +1,164 @@ +package tech.devscast.validable + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class UrlValidableTest { + + lateinit var validable: UrlValidable + + @Before + fun setUp() { + validable = UrlValidable() + validable.enableShowErrors() + } + + @Test + fun `invalid relative urls have errors`() { + getInvalidRelativeUrls().forEach { + validable.value = it + Assert.assertTrue(validable.errorMessage, validable.hasError()) + } + } + + @Test + fun `invalid urls has errors`() { + getInvalidUrls().forEach { + validable.value = it + Assert.assertTrue(validable.errorMessage, validable.hasError()) + } + } + + @Test + fun `valid urls are valid`() { + getValidUrls().forEach { + validable.value = it + Assert.assertFalse(validable.errorMessage, validable.hasError()) + } + } + + @Test + fun `custom valid urls are valid`() { + getValidCustomUrls().forEach { + validable.value = it + Assert.assertFalse(validable.errorMessage, validable.hasError()) + } + } + + private fun getValidCustomUrls(): List { + return listOf( + "ftp://example.com", + "file://127.0.0.1" + ) + } + + private fun getInvalidRelativeUrls(): List { + return listOf( + "/example.com", + "//example.com::aa", + "//example.com:aa", + "//127.0.0.1:aa/", + "//[::1", + "//hello.☎/", + "//:password@devscast.com", + "//:password@@devscast.com", + "//username:passworddevscast.com", + "//usern@me:password@devscast.com", + "//example.com/exploit.html?", + "//example.com/exploit.html?hel lo", + "//example.com/exploit.html?not_a%hex", + "//" + ) + } + + private fun getInvalidUrls(): List { + return listOf( + "git://[::1]/", + "faked://example.fr", + "000000000000", + "invalid" + ) + } + + private fun getValidUrls(): List { + return listOf( + "http://a.pl", + "http://www.example.com", + "http://tt.example.com", + "http://m.example.com", + "http://m.m.m.example.com", + "http://example.m.example.com", + "https://long-string_with+symbols.m.example.com", + "http://www.example.com.", + "http://www.example.museum", + "https://example.com/", + "https://example.com:80/", + "http://examp_le.com", + "http://www.sub_domain.examp_le.com", + "http://www.example.coop/", + "http://www.test-example.com/", + "http://www.devscast.com/", + "http://devscast.fake/blog/", + "http://devscast.com/?", + "http://devscast.com/search?type=&q=url+validator", + "http://devscast.com/#", + "http://devscast.com/#?", + "http://www.devscast.com/doc/current/book/validation.html#supported-constraints", + "http://very.long.domain.name.com/", + "http://localhost/", + "http://myhost123/", + "http://internal-api", + "http://internal-api.", + "http://internal-api/", + "http://internal-api/path", + "http://127.0.0.1/", + "http://127.0.0.1:80/", + "http://[::1]/", + "http://[::1]:80/", + "http://[1:2:3::4:5:6:7]/", + "http://sãopaulo.com/", + "http://xn--sopaulo-xwa.com/", + "http://sãopaulo.com.br/", + "http://xn--sopaulo-xwa.com.br/", + "http://пример.испытание/", + "http://xn--e1afmkfd.xn--80akhbyknj4f/", + "http://مثال.إختبار/", + "http://xn--mgbh0fb.xn--kgbechtv/", + "http://例子.测试/", + "http://xn--fsqu00a.xn--0zwm56d/", + "http://例子.測試/", + "http://xn--fsqu00a.xn--g6w251d/", + "http://例え.テスト/", + "http://xn--r8jz45g.xn--zckzah/", + "http://مثال.آزمایشی/", + "http://xn--mgbh0fb.xn--hgbk6aj7f53bba/", + "http://실례.테스트/", + "http://xn--9n2bp8q.xn--9t4b11yi5a/", + "http://العربية.idn.icann.org/", + "http://xn--ogb.idn.icann.org/", + "http://xn--e1afmkfd.xn--80akhbyknj4f.xn--e1afmkfd/", + "http://xn--espaa-rta.xn--ca-ol-fsay5a/", + "http://xn--d1abbgf6aiiy.xn--p1ai/", + "http://☎.com/", + "http://username:password@devscast.com", + "http://user.name:password@devscast.com", + "http://user_name:pass_word@devscast.com", + "http://username:pass.word@devscast.com", + "http://user.name:pass.word@devscast.com", + "http://user-name@devscast.com", + "http://user_name@devscast.com", + "http://u%24er:password@devscast.com", + "http://user:pa%24%24word@devscast.com", + "http://devscast.com?", + "http://devscast.com?query=1", + "http://devscast.com/?query=1", + "http://devscast.com#", + "http://devscast.com#fragment", + "http://devscast.com/#fragment", + "http://devscast.com/#one_more%20test", + "http://example.com/exploit.html?hello[0]=test", + "http://বিডিআইএ.বাংলা" + ) + } +}