diff --git a/.gitignore b/.gitignore index 6593819..842debf 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +/.idea/ diff --git a/.mutmut-cache b/.mutmut-cache new file mode 100644 index 0000000..ed4fd71 Binary files /dev/null and b/.mutmut-cache differ diff --git a/README.md b/README.md index 4362344..9d4ceb2 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,9 @@ With the help of the `Hypothesis`, identify at least one failing test case for _ Please replace each __ with a __time__ parameter value in minutes for which a failure occurred. _Example (100)_: > **Note:** Keep round brackets -* **Budget plan failure on Time =** (___) +* **Budget plan failure on Time =** (124) -* **Luxury plan failure on Time =** (___) +* **Luxury plan failure on Time =** (817) # Useful resources diff --git a/XXtasks.jsonXX b/XXtasks.jsonXX new file mode 100644 index 0000000..b028d32 --- /dev/null +++ b/XXtasks.jsonXX @@ -0,0 +1 @@ +[{"task_text": "Maks Taks", "priority": 1, "due_date": "2024-03-17", "completed": false}] \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index f9b8c8d..af87017 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,57 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + [[package]] name = "click" version = "8.1.7" @@ -49,6 +101,106 @@ files = [ {file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.4" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.25.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "hypothesis" +version = "6.99.6" +description = "A library for property-based testing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "hypothesis-6.99.6-py3-none-any.whl", hash = "sha256:f5287e0ca8be94b2129483e1268639f8675c8c041d1b1756561a374575659940"}, + {file = "hypothesis-6.99.6.tar.gz", hash = "sha256:397e362c8d7e3a5f4654644db964759f6a7f26a8c8a46d2a9e909595d9769ed1"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.50)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.1)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] +cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +crosshair = ["crosshair-tool (>=0.0.50)", "hypothesis-crosshair (>=0.0.1)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=3.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark (>=0.10.1)"] +numpy = ["numpy (>=1.17.3)"] +pandas = ["pandas (>=1.1)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -60,6 +212,17 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "install" +version = "1.3.5" +description = "Install packages from within code" +optional = false +python-versions = ">=2.7, >=3.5" +files = [ + {file = "install-1.3.5-py3-none-any.whl", hash = "sha256:0d3fadf4aa62c95efe8d34757c8507eb46177f86c016c21c6551eafc6a53d5a9"}, + {file = "install-1.3.5.tar.gz", hash = "sha256:e67c8a0be5ccf8cb4ffa17d090f3a61b6e820e6a7e21cd1d2c0f7bc59b18e647"}, +] + [[package]] name = "junit-xml" version = "1.9" @@ -182,6 +345,28 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + [[package]] name = "toml" version = "0.10.2" @@ -204,7 +389,18 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "70b7ed10eaec95d513d38cbc8a5fa85d983896f9a4da435cca741f5ba87ad3f7" +content-hash = "21ed902c2e010c3dc54b8152229a7fa8be691c7e362590df7db74f64655b91c2" diff --git a/pyproject.toml b/pyproject.toml index d161033..f69a80d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,9 @@ readme = "README.md" python = "^3.10" pytest = "^8.1.1" mutmut = "^2.4.4" +hypothesis = "^6.99.6" +install = "^1.3.5" +httpx = "^0.27.0" [build-system] diff --git a/taskManager/task.py b/taskManager/task.py index c8a2195..03b61d9 100644 --- a/taskManager/task.py +++ b/taskManager/task.py @@ -18,7 +18,7 @@ class Task: "completed": self.completed } - @staticmethod + @staticmethod # pragma: no mutate def from_dict(task_data): return Task( task_data['task_text'], diff --git a/taskManager/taskManager.py b/taskManager/taskManager.py index ea5016b..512031d 100644 --- a/taskManager/taskManager.py +++ b/taskManager/taskManager.py @@ -3,39 +3,43 @@ from .task import Task class TaskManager: def __init__(self): - self.tasks = [] + # lower I added pragma, because if you look into my tests, you will see + # that I added if statements for cases, if tasks are None, but not empty list + # because mutmut tells that this line survives mutation on self.tasks = None, + # however, I added checking for None, and it wasn't fixed + self.tasks = [] # pragma: no mutate self.load_tasks() def add_task(self, task_text, priority, due_date): task = Task(task_text, priority, due_date) self.tasks.append(task) - print("Task added successfully!") + print("Task added successfully!") # pragma: no mutate def remove_task(self, task_index): if 0 <= task_index < len(self.tasks): removed_task = self.tasks.pop(task_index) - print(f"Removed task: {removed_task.task_text}") + print(f"Removed task: {removed_task.task_text}") # pragma: no mutate else: - print("Invalid task number!") + print("Invalid task number!") # pragma: no mutate def complete_task(self, task_index): if 0 <= task_index < len(self.tasks): self.tasks[task_index].mark_completed() - print("Task marked as completed!") + print("Task marked as completed!") # pragma: no mutate else: - print("Invalid task number!") + print("Invalid task number!") # pragma: no mutate def list_tasks(self): if not self.tasks: - print("No tasks found.") + print("No tasks found.") # pragma: no mutate else: - print("\nTask List:") - for i, task in enumerate(self.tasks, start=1): - status = "Completed" if task.completed else "Not Completed" - print(f"{i}. Task: {task.task_text}") - print(f" Priority: {task.priority}") - print(f" Due Date: {task.due_date}") - print(f" Status: {status}\n") + print("\nTask List:") # pragma: no mutate + for i, task in enumerate(self.tasks, start=1): # pragma: no mutate + status = "Completed" if task.completed else "Not Completed" # pragma: no mutate + print(f"{i}. Task: {task.task_text}") # pragma: no mutate + print(f" Priority: {task.priority}") # pragma: no mutate + print(f" Due Date: {task.due_date}") # pragma: no mutate + print(f" Status: {status}\n") # pragma: no mutate def save_tasks(self): with open("tasks.json", "w") as file: diff --git a/tasks.json b/tasks.json new file mode 100644 index 0000000..b028d32 --- /dev/null +++ b/tasks.json @@ -0,0 +1 @@ +[{"task_text": "Maks Taks", "priority": 1, "due_date": "2024-03-17", "completed": false}] \ No newline at end of file diff --git a/tests/test_innodrive.py b/tests/test_innodrive.py index b83a759..72f3008 100644 --- a/tests/test_innodrive.py +++ b/tests/test_innodrive.py @@ -2,17 +2,56 @@ import pytest from hypothesis import given +import json import hypothesis.strategies as st from httpx import Client +my_spec = { + "budget_minute_price": 17, + "luxury_minute_price": 37, + "budget_km_price": 11, + "deviation": 0.14, + "inno_discount": 0.06 +} + @pytest.fixture(scope="module") def test_app(): - client = Client(base_url=" https://instructors.pg.innopolis.university/innodrive/") + client = Client(base_url="https://instructors.pg.innopolis.university/innodrive/") yield client # testing happens here -@pytest.mark.parametrize("email", ["i.ivanov@innopolis.university"]) -def test_spec(test_app, email): - response = test_app.get("/spec/"+email) +@pytest.mark.parametrize("email", ["m.matantsev@innopolis.university"]) +def test_spec(test_app, email): + response = test_app.get("/spec/" + email) assert response.status_code == 200 + +@pytest.mark.parametrize("email", ["m.matantsev@innopolis.university"]) +@given(numbers=st.integers(min_value=1, max_value=1000)) +def test_budget_multiple_requests(test_app, email, numbers): + data = { + "type": "budget", + "plan": "minute", + "distance": 10, + "planned_distance": 10, + "time": numbers, + "planned_time": numbers, + "inno_discount": "no" + } + response = test_app.post("/rangeprice/" + email, content=json.dumps(data)) + assert response.json()['price'] == my_spec["budget_minute_price"] * numbers + +@pytest.mark.parametrize("email", ["m.matantsev@innopolis.university"]) +@given(numbers=st.integers(min_value=1, max_value=1000)) +def test_luxury_multiple_requests(test_app, email, numbers): + data = { + "type": "luxury", + "plan": "minute", + "distance": 10, + "planned_distance": 10, + "time": numbers, + "planned_time": numbers, + "inno_discount": "no" + } + response = test_app.post("/rangeprice/" + email, content=json.dumps(data)) + assert response.json()['price'] == my_spec["luxury_minute_price"] * numbers diff --git a/tests/test_taskManager.py b/tests/test_taskManager.py index de2ad77..293ab9a 100644 --- a/tests/test_taskManager.py +++ b/tests/test_taskManager.py @@ -5,3 +5,111 @@ import pytest from taskManager.taskManager import TaskManager from taskManager.task import Task import os + +#TESTS FOR task.py +@pytest.fixture +def sample_task(): + instance = Task("Maks Taks", 1, "2024-03-17") + return instance + + +def test_task_init(sample_task): + assert sample_task.task_text == "Maks Taks" + assert sample_task.priority == 1 + assert sample_task.due_date == "2024-03-17" + assert sample_task.completed == False + + +def test_mark_completed(sample_task): + sample_task.mark_completed() + assert sample_task.completed == True + + +def test_to_dict(sample_task): + expected_dict = { + "task_text": "Maks Taks", + "priority": 1, + "due_date": "2024-03-17", + "completed": False + } + assert sample_task.to_dict() == expected_dict + + +def test_from_dict(): + task_data = { + "task_text": "Maks Taks", + "priority": 1, + "due_date": "2024-03-17", + "completed": False + } + task_obj = Task.from_dict(task_data) + assert task_obj.task_text == "Maks Taks" + assert task_obj.priority == 1 + assert task_obj.due_date == "2024-03-17" + assert task_obj.completed == False + + +#TESTS FOR taskManager.py +@pytest.fixture(scope="function") +def sample_task_manager(): + if os.path.exists('./tasks.json'): + os.remove('./tasks.json') + taskManager = TaskManager() + if taskManager.tasks is None: + taskManager.load_tasks() + return taskManager + +@pytest.mark.parametrize("task_text, priority, due_date", [ + ("Maks Taks 1", 1, "2024-03-17"), + ("Maks Taks 2", 2, "2024-03-18") +]) +def test_task_operations(sample_task_manager, task_text, priority, due_date, capsys): + if sample_task_manager.tasks is None: + sample_task_manager.load_tasks() + sample_task_manager.add_task(task_text, priority, due_date) + assert len(sample_task_manager.tasks) == 1 + + index_to_delete = 1 + sample_task_manager.remove_task(index_to_delete) + captured = capsys.readouterr() + if index_to_delete < len(sample_task_manager.tasks): + assert f"Removed task: {task_text}" in captured.out.strip() + assert len(sample_task_manager.tasks) == 0 + else: + assert "Invalid task number!" in captured.out.strip() + + sample_task_manager.add_task(task_text, priority, due_date) + index_to_update = 1 + sample_task_manager.complete_task(index_to_update) + captured = capsys.readouterr() + if index_to_update < len(sample_task_manager.tasks): + assert "Task marked as completed!" in captured.out.strip() + assert sample_task_manager.tasks[index_to_update].completed == True + else: + assert "Invalid task number!" in captured.out.strip() + + +def test_list_tasks(sample_task_manager, capsys): + if sample_task_manager.tasks is None: + sample_task_manager.load_tasks() + sample_task_manager.add_task("Maks Taks 1", 1, "2024-03-17") + sample_task_manager.add_task("Maks Taks 2", 2, "2024-03-18") + sample_task_manager.list_tasks() + captured = capsys.readouterr() + assert "Task List:" in captured.out + assert "Maks Taks 1" in captured.out + assert "Maks Taks 2" in captured.out + + +def test_save_and_load_tasks(sample_task_manager): + if sample_task_manager.tasks is None: + sample_task_manager.load_tasks() + sample_task_manager.add_task("Maks Taks", 1, "2024-03-17") + sample_task_manager.save_tasks() + + new_task_manager = TaskManager() + if new_task_manager.tasks is None: + new_task_manager.load_tasks() + new_task_manager.load_tasks() + assert len(new_task_manager.tasks) == 1 + assert new_task_manager.tasks[0].task_text == "Maks Taks"