Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-validator

Typed fluent validation rules for Pudu

0.1.1Apache-2.01

InstallClose

RulesTest.pudu

Pudu107 lines7.2 KB

GitHub ↗
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