From 6332b9e233594e76df5aee6ee5a15ce5f1cd2e31 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Wed, 4 Aug 2021 11:16:01 +0200 Subject: [PATCH] refactor: fake repo and tesrt coverage --- .coveragerc | 8 ++++++++ .gitignore | 2 +- app/__main__.py | 2 +- app/data/type.py | 4 ++-- app/repositories/fake.py | 11 +++++------ app/repositories/type.py | 6 +++--- app/usecases/pass_gen.py | 5 +++-- setup.cfg | 5 +++++ tests/main_test.py | 10 ++++++++-- tests/pass_gen_test.py | 34 ++++++++++++++++------------------ 10 files changed, 52 insertions(+), 35 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..9ce363b --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +omit = + # omit tests + */tests/* + # omit non testable files + app/__main__.py +[html] +directory = .htmlcov diff --git a/.gitignore b/.gitignore index 9980f7e..d632823 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ MANIFEST *.spec pip-log.txt pip-delete-this-directory.txt -htmlcov/ +.htmlcov/ .tox/ .nox/ .coverage diff --git a/app/__main__.py b/app/__main__.py index 771dd7c..b031721 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -1,4 +1,4 @@ from .main import app -app() # pragma nocover +app() diff --git a/app/data/type.py b/app/data/type.py index f9f2570..18f7051 100644 --- a/app/data/type.py +++ b/app/data/type.py @@ -4,7 +4,7 @@ from typing import Any, Protocol class DBConnector(Protocol): def commit(self) -> None: - ... + ... # pragma nocover def execute(self, query: str, *args: Any) -> Cursor: - ... + ... # pragma nocover diff --git a/app/repositories/fake.py b/app/repositories/fake.py index 600c6d6..4bf6941 100644 --- a/app/repositories/fake.py +++ b/app/repositories/fake.py @@ -1,22 +1,21 @@ from __future__ import annotations -from pydantic import SecretStr - from app.models.password import Password class FakeRepository: + _values = {} + def save(self, service: str, password: str) -> None: - ... + self._values[service] = password def list_all(self) -> list[Password]: return [ - Password(id=0, service="first", password=SecretStr("2yW4AcqG")), - Password(id=1, service="second", password=SecretStr("iK2ZWeqh")), + Password(id=i, service=v[0], password=v[1]) for i, v in enumerate(self._values.items()) ] def exists(self, service: str) -> bool: - return False + return service in self._values.keys() @classmethod def get_instance(cls) -> FakeRepository: diff --git a/app/repositories/type.py b/app/repositories/type.py index 428b92c..6aacf22 100644 --- a/app/repositories/type.py +++ b/app/repositories/type.py @@ -5,10 +5,10 @@ from app.models.password import Password class Repository(Protocol): def save(self, service: str, password: str) -> None: - ... + ... # pragma nocover def list_all(self) -> list[Password]: - ... + ... # pragma nocover def exists(self, service: str) -> bool: - ... + ... # pragma nocover diff --git a/app/usecases/pass_gen.py b/app/usecases/pass_gen.py index 6d23385..45acbea 100644 --- a/app/usecases/pass_gen.py +++ b/app/usecases/pass_gen.py @@ -2,13 +2,14 @@ import random import string from typing import Protocol -from pydantic import BaseModel +from pydantic.dataclasses import dataclass from app.models.password import Password from app.repositories.type import Repository -class PassGenOptions(BaseModel): +@dataclass(frozen=True) +class PassGenOptions: service: str seed: int length: int = 8 diff --git a/setup.cfg b/setup.cfg index c206218..9130fd3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,3 +11,8 @@ warn_unused_ignores = True warn_unreachable = True warn_redundant_casts = True disallow_untyped_defs=True + +plugins = pydantic.mypy +[pydantic-mypy] +init_forbid_extra = True +init_typed = True \ No newline at end of file diff --git a/tests/main_test.py b/tests/main_test.py index 0fe74a9..278ef0b 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -1,10 +1,9 @@ from typing import Any from faker import Factory - -from app.main import app from typer.testing import CliRunner, Result +from app.main import app runner = CliRunner() faker = Factory.create() @@ -49,6 +48,13 @@ def test_cli_can_save_to_db() -> None: assert "2yW4AcqG" in result.stdout +def test_cli_cannot_save_password_for_service_twice() -> None: + service_name = faker.pystr() + runner.invoke(app, ["save", service_name, "--no-random"]) + result = runner.invoke(app, ["save", service_name, "--no-random"]) + assert "already been set" in result.stdout + + def _run_cli(*args: Any) -> Result: result = runner.invoke(app, ["save", faker.pystr(), "--no-random", *args]) assert result.exit_code == 0 diff --git a/tests/pass_gen_test.py b/tests/pass_gen_test.py index 413ce0e..4b15236 100644 --- a/tests/pass_gen_test.py +++ b/tests/pass_gen_test.py @@ -1,12 +1,11 @@ import pytest -from pydantic import SecretStr +from faker import Factory -from app.models.password import Password from app.repositories.fake import FakeRepository from app.usecases.pass_gen import PassGenOptions, generate_password, list_all_saved_passwords fake_repo = FakeRepository.get_instance() -service = "service_name" +faker = Factory.create() @pytest.mark.parametrize( @@ -17,7 +16,7 @@ service = "service_name" ], ) def test_can_generate_random_password(seed: int, expected: str) -> None: - options = PassGenOptions(seed=seed, service=service) + options = PassGenOptions(seed=seed, service=faker.pystr()) assert generate_password(fake_repo, options) == expected @@ -29,7 +28,7 @@ def test_can_generate_random_password(seed: int, expected: str) -> None: ], ) def test_control_password_length(seed: int, length: int, expected: str) -> None: - options = PassGenOptions(seed=seed, length=length, service=service) + options = PassGenOptions(seed=seed, length=length, service=faker.pystr()) assert generate_password(fake_repo, options) == expected @@ -41,7 +40,7 @@ def test_control_password_length(seed: int, length: int, expected: str) -> None: ], ) def test_password_can_contain_symbols(seed: int, symbols: bool, expected: str) -> None: - options = PassGenOptions(seed=seed, symbols=symbols, service=service) + options = PassGenOptions(seed=seed, symbols=symbols, service=faker.pystr()) assert generate_password(fake_repo, options) == expected @@ -53,18 +52,17 @@ def test_password_can_contain_symbols(seed: int, symbols: bool, expected: str) - ], ) def test_password_can_contain_numbers(seed: int, numbers: bool, expected: str) -> None: - options = PassGenOptions(seed=seed, numbers=numbers, service=service) + options = PassGenOptions(seed=seed, numbers=numbers, service=faker.pystr()) assert generate_password(fake_repo, options) == expected -@pytest.mark.parametrize( - "expected", - [ - [ - Password(id=0, service="first", password=SecretStr("2yW4AcqG")), - Password(id=1, service="second", password=SecretStr("iK2ZWeqh")), - ], - ], -) -def test_can_read_all_saved_passwords(expected: list[str]) -> None: - assert list_all_saved_passwords(fake_repo) == expected +@pytest.mark.parametrize("expected", [8]) +def test_can_read_all_saved_passwords(expected: int) -> None: + assert len(list_all_saved_passwords(fake_repo)) == expected + + +def test_cannot_save_password_for_service_twice() -> None: + with pytest.raises(ValueError, match="already been set"): + options = PassGenOptions(seed=0, service=faker.pystr()) + generate_password(fake_repo, options) + generate_password(fake_repo, options)