How to Validate a Regex PIN Code in Python


The challenge

ATM machines allow 4 or 6 digit PIN codes and PIN codes cannot contain anything but exactly 4 digits or exactly 6 digits.

If the function is passed a valid PIN string, return true, else return false.

Examples

"1234"   -->  true
"12345"  -->  false
"a234"   -->  false

The solution in Python code

Option 1:

def validate_pin(pin):
    return len(pin) in (4, 6) and pin.isdigit()

Option 2:

import re

def validate_pin(pin):
    return bool(re.fullmatch("\d{4}|\d{6}", pin))

Option 3:

def validate_pin(pin):
    import re
    if len(pin) == 4 or len(pin) == 6: #not 4 or 6 digits
        if re.search('[^0-9]', pin) == None : #contains non-digit chars
            return True
            
    return False

Test cases to validate our solution

import  test
from solution import validate_pin

@test.describe("Fixed Tests")
def fixed_tests():
    @test.it("should return False for pins with length other than 4 or 6")
    def basic_test_cases():    
        test.assert_equals(validate_pin("1"),False, "Wrong output for '1'")
        test.assert_equals(validate_pin("12"),False, "Wrong output for '12'")
        test.assert_equals(validate_pin("123"),False, "Wrong output for '123'")
        test.assert_equals(validate_pin("12345"),False, "Wrong output for '12345'")
        test.assert_equals(validate_pin("1234567"),False, "Wrong output for '1234567'")
        test.assert_equals(validate_pin("-1234"),False, "Wrong output for '-1234'")
        test.assert_equals(validate_pin("-12345"),False, "Wrong output for '-12345'")
        test.assert_equals(validate_pin("1.234"),False, "Wrong output for '1.234'")
        test.assert_equals(validate_pin("00000000"),False, "Wrong output for '00000000'")
    
    @test.it("should return False for pins which contain characters other than digits")
    def _():
        test.assert_equals(validate_pin("a234"),False, "Wrong output for 'a234'")
        test.assert_equals(validate_pin(".234"),False, "Wrong output for '.234'")
    
    @test.it("should return True for valid pins")
    def _():
        test.assert_equals(validate_pin("1234"),True, "Wrong output for '1234'")
        test.assert_equals(validate_pin("0000"),True, "Wrong output for '0000'")
        test.assert_equals(validate_pin("1111"),True, "Wrong output for '1111'")
        test.assert_equals(validate_pin("123456"),True, "Wrong output for '123456'")
        test.assert_equals(validate_pin("098765"),True, "Wrong output for '098765'")
        test.assert_equals(validate_pin("000000"),True, "Wrong output for '000000'")
        test.assert_equals(validate_pin("123456"),True, "Wrong output for '123456'")
        test.assert_equals(validate_pin("090909"),True, "Wrong output for '090909'")
    
    @test.it("should handle edge cases")
    def _():
        tests = [
            '',
            '123',
            '12345',
            '1234567',
            '1234567890',
            '1234x',
            '123456x',
            '12.0',
            '1234.0',
            '123456.0',
            '123\n',
            '1234\n',
            '09876\n',
            '098765\n',
            '-111',
            '111-',
            '-44444',
            '44444-',
            '+111',
            '+88888',
            '+1111',
            '-2018',
            '+234567',
            '-234567',
            '123/',
            '456:',
            '\xbe', # "three quarters" in Python 3, just some byte in Python 2
        ]
        for s in tests:
            test.assert_equals(validate_pin(s), False, "Wrong output for '{}'".format(s))
    
@test.describe("should work with random input")
def random_tests():
        
    from random import randint, choice
    from string import ascii_letters, punctuation, digits
        
    all_chars = ascii_letters + punctuation + digits
        
    def solution(pin):
        return len(pin) in (4, 6) and pin.isdigit()
        
    def rand_valid_pin():
        length = 4 if randint(0, 1) else 6
        return "".join(choice(digits) for _ in range(length))
        
    def rand_pin():
        return "".join(choice(all_chars) for _ in range(randint(0, 10)))
        
    for _ in range(40):
        pin = rand_pin() if randint(0, 1) else rand_valid_pin()
        @test.it(f"testing for validate_pin({pin})")
        def test_case():    
            test.assert_equals(validate_pin(pin),solution(pin))