diff --git a/Pipfile b/Pipfile index f772a51a..eb946fb9 100644 --- a/Pipfile +++ b/Pipfile @@ -19,6 +19,7 @@ ipython = "*" ipdb = "*" pylint = "*" black = "*" +pytest-watch = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 2cf47dcc..2fadae1e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "53a2048f5dc853a0ac2d565164c23dcdb2df802e055c1530864bfc8390af3c3e" + "sha256": "3bea02ccdb0e3877f2595d7fb405408114ec8947e0484d5b4aaf14a4c8ff78b2" }, "pipfile-spec": 6, "requires": { @@ -119,6 +119,13 @@ "markers": "sys_platform == 'darwin'", "version": "==0.1.0" }, + "argh": { + "hashes": [ + "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", + "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65" + ], + "version": "==0.26.2" + }, "astroid": { "hashes": [ "sha256:a8d8c7fe34e34e868426b9bafce852c355a3951eef60bc831b2ed541558f8d37", @@ -170,6 +177,13 @@ ], "version": "==6.7" }, + "colorama": { + "hashes": [ + "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", + "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + ], + "version": "==0.3.9" + }, "decorator": { "hashes": [ "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", @@ -177,6 +191,12 @@ ], "version": "==4.3.0" }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, "gitdb2": { "hashes": [ "sha256:b60e29d4533e5e25bb50b7678bbc187c8f6bcff1344b4f293b2ba55c85795f09", @@ -280,10 +300,16 @@ }, "parso": { "hashes": [ - "sha256:8105449d86d858e53ce3e0044ede9dd3a395b1c9716c696af8aa3787158ab806", - "sha256:d250235e52e8f9fc5a80cc2a5f804c9fefd886b2e67a2b1099cf085f403f8e33" + "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", + "sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24" ], - "version": "==0.3.0" + "version": "==0.3.1" + }, + "pathtools": { + "hashes": [ + "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" + ], + "version": "==0.1.2" }, "pbr": { "hashes": [ @@ -370,6 +396,13 @@ "index": "pypi", "version": "==0.5.0" }, + "pytest-watch": { + "hashes": [ + "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9" + ], + "index": "pypi", + "version": "==4.2.0" + }, "pyyaml": { "hashes": [ "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", @@ -460,6 +493,12 @@ "markers": "python_version < '3.7' and implementation_name == 'cpython'", "version": "==1.1.0" }, + "watchdog": { + "hashes": [ + "sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162" + ], + "version": "==0.8.3" + }, "wcwidth": { "hashes": [ "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", diff --git a/README.md b/README.md index 64996466..d759de47 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ To run only the unit tests: pipenv run python -m pytest +To re-run tests each time a file is changed: + + pipenv run ptw + ## Notes tornado templates are like mustache templates -- add the diff --git a/tests/conftest.py b/tests/conftest.py index d0da68f2..a8c45680 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,3 +19,23 @@ def app(): deps.update(TEST_DEPS) return make_app(config, deps) + +class DummyForm(dict): + pass + + +class DummyField(object): + def __init__(self, data=None, errors=(), raw_data=None): + self.data = data + self.errors = list(errors) + self.raw_data = raw_data + + +@pytest.fixture +def dummy_form(): + return DummyForm() + + +@pytest.fixture +def dummy_field(): + return DummyField() diff --git a/tests/forms/test_validators.py b/tests/forms/test_validators.py new file mode 100644 index 00000000..b648cc66 --- /dev/null +++ b/tests/forms/test_validators.py @@ -0,0 +1,61 @@ +from wtforms.validators import ValidationError +import pytest + +from atst.forms.validators import Alphabet, IsNumber, PhoneNumber + + +class TestIsNumber: + + @pytest.mark.parametrize("valid", ["0", "12", "-12"]) + def test_IsNumber_accepts_integers(self, valid, dummy_form, dummy_field): + validator = IsNumber() + dummy_field.data = valid + validator(dummy_form, dummy_field) + + @pytest.mark.parametrize("invalid", ["12.1", "two", ""]) + def test_IsNumber_rejects_anything_else(self, invalid, dummy_form, dummy_field): + validator = IsNumber() + dummy_field.data = invalid + with pytest.raises(ValidationError): + validator(dummy_form, dummy_field) + + +class TestPhoneNumber: + + @pytest.mark.parametrize("valid", [ + "12345", + "1234567890", + "(123) 456-7890", + ]) + def test_PhoneNumber_accepts_valid_numbers(self, valid, dummy_form, dummy_field): + validator = PhoneNumber() + dummy_field.data = valid + validator(dummy_form, dummy_field) + + @pytest.mark.parametrize("invalid", [ + "1234", + "123456", + "1234567abc", + "(123) 456-789012", + ]) + def test_PhoneNumber_rejects_invalid_numbers(self, invalid, dummy_form, dummy_field): + validator = PhoneNumber() + dummy_field.data = invalid + with pytest.raises(ValidationError): + validator(dummy_form, dummy_field) + + +class TestAlphabet: + + @pytest.mark.parametrize("valid", ["a", "abcde"]) + def test_Alphabet_accepts_letters(self, valid, dummy_form, dummy_field): + validator = Alphabet() + dummy_field.data = valid + validator(dummy_form, dummy_field) + + @pytest.mark.parametrize("invalid", ["", "hi mark", "cloud9"]) + def test_Alphabet_rejects_non_letters(self, invalid, dummy_form, dummy_field): + validator = Alphabet() + dummy_field.data = invalid + with pytest.raises(ValidationError): + validator(dummy_form, dummy_field)