
RulesTest.pudu
Pudu107 lines7.2 KB
1/** @Test.Validation.Rules.Suite — checks built-in rule boundaries */2module PuduLangValidator.RulesTest34import Std.Test as Test5import PuduLangValidator.Rule as Rule6import PuduLangValidator.Rules.Decimal as DecimalRule7import PuduLangValidator.Rules.Presence as PresenceRule8import PuduLangValidator.Rules.Text as TextRule9import PuduLangValidator.Validator as Validator1011type Fields = { text: Str, amount: Decimal, optional: Option[Str] }1213fn textRule() -> Validator.Validator[Fields] {14 let rule = Rule.build(TextRule.creditCard(Rule.ruleFor("text", |item: Fields| item.text)))15 Validator.add(Validator.create(), rule)16}1718fn amountRule(ignore: Bool) -> Validator.Validator[Fields] {19 let rule = Rule.build(DecimalRule.precisionScale(Rule.ruleFor("amount", |item: Fields| item.amount), 5, 2, ignore))20 Validator.add(Validator.create(), rule)21}2223fn optionalRule() -> Validator.Validator[Fields] {24 let rule = Rule.build(PresenceRule.notEmpty(Rule.ruleFor("optional", |item: Fields| item.optional)))25 Validator.add(Validator.create(), rule)26}2728fn maxLengthRule() -> Validator.Validator[Fields] {29 let rule = Rule.build(TextRule.maximumLength(Rule.ruleFor("text", |item: Fields| item.text), 2))30 Validator.add(Validator.create(), rule)31}3233fn minLengthRule() -> Validator.Validator[Fields] {34 let rule = Rule.build(TextRule.minimumLength(Rule.ruleFor("text", |item: Fields| item.text), 1))35 Validator.add(Validator.create(), rule)36}3738fn lengthRule() -> Validator.Validator[Fields] {39 let rule = Rule.build(TextRule.length(Rule.ruleFor("text", |item: Fields| item.text), 2, 4))40 Validator.add(Validator.create(), rule)41}4243fn emailRule() -> Validator.Validator[Fields] {44 let rule = Rule.build(TextRule.emailAddress(Rule.ruleFor("text", |item: Fields| item.text)))45 Validator.add(Validator.create(), rule)46}4748fn enumRule(caseSensitive: Bool) -> Validator.Validator[Fields] {49 let rule = Rule.build(TextRule.enumName(Rule.ruleFor("text", |item: Fields| item.text), ["Ada", "Bo"], caseSensitive))50 Validator.add(Validator.create(), rule)51}5253fn cardRule() -> Validator.Validator[Fields] {54 let rule = Rule.build(TextRule.creditCard(Rule.ruleFor("text", |item: Fields| item.text)))55 Validator.add(Validator.create(), rule)56}5758fn main() -> Int {59 let card = textRule()60 let strict = amountRule(false)61 let trimmed = amountRule(true)62 let optional = optionalRule()63 let capped = maxLengthRule()64 let floored = minLengthRule()65 let bounded = lengthRule()66 let email = emailRule()67 let sensitive = enumRule(true)68 let folded = enumRule(false)69 let cardNumber = cardRule()70 let valid = Fields{text: "4111 1111 1111 1111", amount: 123.4500d, optional: Some("Ada")}71 let bad = Fields{text: "4111 1111 1111 1112", amount: 123.4500d, optional: None}72 let checks = Test.suite("Rules", &[73 Test.equals("card checksum accepts valid test number", &Validator.validate(&card, valid).isValid, &true),74 Test.equals("card checksum rejects altered last digit", &Validator.validate(&card, bad).isValid, &false),75 Test.equals("precision counts trailing zeroes", &Validator.validate(&strict, valid).isValid, &false),76 Test.equals("precision can ignore trailing zeroes", &Validator.validate(&trimmed, valid).isValid, &true),77 Test.equals("optional text rejects absence", &Validator.validate(&optional, bad).isValid, &false),78 Test.equals("optional text accepts content", &Validator.validate(&optional, valid).isValid, &true),79 Test.equals("maximum length accepts an empty string", &Validator.validate(&capped, Fields{text: "", amount: 1.00d, optional: None}).isValid, &true),80 Test.equals("maximum length accepts the boundary", &Validator.validate(&capped, Fields{text: "ab", amount: 1.00d, optional: None}).isValid, &true),81 Test.equals("maximum length rejects overlong text", &Validator.validate(&capped, Fields{text: "abc", amount: 1.00d, optional: None}).isValid, &false),82 Test.equals("minimum length rejects an empty string", &Validator.validate(&floored, Fields{text: "", amount: 1.00d, optional: None}).isValid, &false),83 Test.equals("minimum length accepts the boundary", &Validator.validate(&floored, Fields{text: "a", amount: 1.00d, optional: None}).isValid, &true),84 Test.equals("length accepts both boundaries", &Validator.validate(&bounded, Fields{text: "ab", amount: 1.00d, optional: None}).isValid, &true),85 Test.equals("length accepts the upper boundary", &Validator.validate(&bounded, Fields{text: "abcd", amount: 1.00d, optional: None}).isValid, &true),86 Test.equals("length rejects below the minimum", &Validator.validate(&bounded, Fields{text: "a", amount: 1.00d, optional: None}).isValid, &false),87 Test.equals("length rejects above the maximum", &Validator.validate(&bounded, Fields{text: "abcde", amount: 1.00d, optional: None}).isValid, &false),88 Test.equals("email accepts one internal @", &Validator.validate(&email, Fields{text: "a@b", amount: 1.00d, optional: None}).isValid, &true),89 Test.equals("email rejects a missing @", &Validator.validate(&email, Fields{text: "ab", amount: 1.00d, optional: None}).isValid, &false),90 Test.equals("email rejects a leading @", &Validator.validate(&email, Fields{text: "@b", amount: 1.00d, optional: None}).isValid, &false),91 Test.equals("email rejects a trailing @", &Validator.validate(&email, Fields{text: "a@", amount: 1.00d, optional: None}).isValid, &false),92 Test.equals("email rejects a second @", &Validator.validate(&email, Fields{text: "a@b@c", amount: 1.00d, optional: None}).isValid, &false),93 Test.equals("enum accepts an exact name", &Validator.validate(&sensitive, Fields{text: "Ada", amount: 1.00d, optional: None}).isValid, &true),94 Test.equals("enum rejects a case mismatch when sensitive", &Validator.validate(&sensitive, Fields{text: "ada", amount: 1.00d, optional: None}).isValid, &false),95 Test.equals("enum accepts a case mismatch when folded", &Validator.validate(&folded, Fields{text: "ada", amount: 1.00d, optional: None}).isValid, &true),96 Test.equals("enum rejects an unknown name when folded", &Validator.validate(&folded, Fields{text: "Zed", amount: 1.00d, optional: None}).isValid, &false),97 Test.equals("card rejects dots as separators", &Validator.validate(&cardNumber, Fields{text: "4111.1111.1111.1111", amount: 1.00d, optional: None}).isValid, &false),98 Test.equals("card rejects a dot among zeroes", &Validator.validate(&cardNumber, Fields{text: ".000 0000 0000 0000", amount: 1.00d, optional: None}).isValid, &false),99 Test.equals("card rejects letters", &Validator.validate(&cardNumber, Fields{text: "4111 1111 1111 111a", amount: 1.00d, optional: None}).isValid, &false),100 Test.equals("card accepts zeroes", &Validator.validate(&cardNumber, Fields{text: "0000 0000 0000 0000", amount: 1.00d, optional: None}).isValid, &true),101 Test.equals("card accepts twelve digits", &Validator.validate(&cardNumber, Fields{text: "5260 1815 9081", amount: 1.00d, optional: None}).isValid, &true),102 Test.equals("card accepts nineteen digits", &Validator.validate(&cardNumber, Fields{text: "3016 6131 8609 1390 996", amount: 1.00d, optional: None}).isValid, &true),103 Test.equals("card rejects a Luhn-passing short number", &Validator.validate(&cardNumber, Fields{text: "0", amount: 1.00d, optional: None}).isValid, &false)104 ])105 Test.report(&Test.run(&checks))106}107