From 1ac2ff12def833d9f194bd97fb2bc86c75a1411c Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 07:37:17 +0000 Subject: [PATCH] Initial commit --- .gitignore | 160 ++++++++++++++++++++++++++ README.md | 121 ++++++++++++++++++++ openapi.json | 241 ++++++++++++++++++++++++++++++++++++++++ tests/__init__.py | 0 tests/test_innodrive.py | 16 +++ 5 files changed, 538 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 openapi.json create mode 100644 tests/__init__.py create mode 100644 tests/test_innodrive.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# 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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3fa00e --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# InnoDrive - Problem + +The InnoDrive is a hypothetical autonomous driving car-sharing service that provides transportation means in the Kazan region created by the SQR graduates from Innopolis. The service proposes two types of cars __budget__ and __luxury__. The service offers flexible two tariff plans. With the “Minute” plan, the service charges its client by time per minute of use. The Service also proposes a “Fixed price” plan where the price is fixed at the reservation time when the route is chosen. If the driver deviates from the planned route for more than 10%, the tariff plan automatically switches to the “Minute” plan. The deviation is calculated as the difference in the distance or duration from the initially planned route. The “Fixed price” plan is available for budget cars only. The Innopolis city sponsors the mobility and offers a 10% discount to Innopolis residents. + +You are a member of the quality assurance team at InnoCar. You learned about input domain testing methods and would like to apply them to the billing application that charges the client when he or she completes the ride. + +> **Note:** the exact __tariffs__, __deviation %__ and __discount %__ will differ for your individual case see below. + +## InnoDrive Service + +The InnoDrive app offers 2 services that can be accessed via the following URL https://innodrive.containers.cloud.ru: + +* `spec` - provides you with the specification for your individual problem + + * you_email is your IU e-mail address in all lowercase letters e.g. + +* `price` - calculate the price for a given ride. The possible input parameters are: + * type={budget|luxury} + * plan={fixed_price|minute} + * distance=110 + * planned_distance=100 + * time=110 + * planned_time=100 + * inno_discount={yes|no} +* the output is the price calculated according to the specification + * In case of error, __406 Not Acceptable:__ is raised with the __Error code: Price == -1__ + +> **Note:** You can see the API and try the services at https://innodrive.containers.cloud.ru/docs + +> **Note:** Alternatively, the InnoDrive services are available at https://instructors.pg.innopolis.university/innodrive/ +> +> See the API documentation at https://instructors.pg.innopolis.university/innodrive/docs + +## Practical Assignment + +Develop a comprehensive test suite to evaluate the system's functionality thoroughly. Afterwards, categorize each identified error in the list below, specifying whether it is a domain error (indicating failure to validate input within the acceptable range) or a computational error (indicating inaccuracies in outcome computation). + +> **Note:** Build you tests inside of `./tests/test_innodrive.py` [see the test file](./tests/test_innodrive.py) and __strictly__ follow the provided example. + +> **Note:** Ensure your test suite is designed to uncover all potential errors, not just those specific to your email scenario. + +**Email:** + +> **Note:** Make sure to replace the email with your Innopolis email + +### 1. Domain Errors (4pt) + +Please replace each `no` with a `yes` for each parameter if it passes domain tests: + +* **Ride Type:** no + +* **Ride Plan:** no + +* **Distance:** no + +* **Inno Discount:** no + +* **Type Plan:** no + +* **Time:** no + +> **Note:** Type Plan here refers to checking the compatibility of ride type and ride plan + +### 2. Computational Errors (4pt) + +Please replace each `no` with a `yes` for each parameter if it passes computational tests: + +* **Deviation:** no + +* **Discount Application:** no + +* **Discount Calc:** no + +* **Budget Min:** no + +* **Budget Km:** no + +* **Luxury Min:** no + +### 3. Tests coverage (2pt) + +The effectiveness of your test suite will be assessed based on its ability to detect all the errors outlined previously. + +# Additional info +* Service Open API [json](./openapi.json) + +* Swagger documentation and client ([API doc](https://innodrive.containers.cloud.ru/docs)) + +## Swagger screenshots +### Services +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/a5efcc6b-7a7a-4fc6-b5d8-abbe66cad666) + +### Spec service +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/ac9eae17-fb85-42a1-a3e8-713bd1f48338) + + +### Price service +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/c7bee4ab-6897-4b1f-8c44-ef848b5377f5) + +### Example. Spec for i.ivanov@innopolis.university +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/324c17fe-0f38-437c-8346-85ecefa969c8) + + +### Example. Price for i.ivanov@innopolis.university +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/c945bff3-1f55-437e-bc56-e79b6336e707) + +### Example. Error indication. (-1 for the price) +![image](https://github.com/QualityInUse/project-innodrive/assets/2123188/c1a56de0-9229-4686-8057-bac8402a5f67) + + + + + + + + + + + + + diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..bbb670b --- /dev/null +++ b/openapi.json @@ -0,0 +1,241 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/spec/{email}": { + "get": { + "summary": "Spec", + "operationId": "spec_spec__email__get", + "parameters": [ + { + "name": "email", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Email" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Spec" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/price/{email}": { + "post": { + "summary": "Price", + "operationId": "price_price__email__post", + "parameters": [ + { + "name": "email", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Email" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Input" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Price" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "Input": { + "properties": { + "type": { + "type": "string", + "title": "Type" + }, + "plan": { + "type": "string", + "title": "Plan" + }, + "distance": { + "type": "integer", + "title": "Distance" + }, + "planned_distance": { + "type": "integer", + "title": "Planned Distance" + }, + "time": { + "type": "integer", + "title": "Time" + }, + "planned_time": { + "type": "integer", + "title": "Planned Time" + }, + "inno_discount": { + "type": "string", + "title": "Inno Discount" + } + }, + "type": "object", + "required": [ + "type", + "plan", + "distance", + "planned_distance", + "time", + "planned_time", + "inno_discount" + ], + "title": "Input" + }, + "Price": { + "properties": { + "price": { + "type": "number", + "title": "Price" + } + }, + "type": "object", + "required": [ + "price" + ], + "title": "Price" + }, + "Spec": { + "properties": { + "budget_minute_price": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "Budget Minute Price" + }, + "luxury_minute_price": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "Luxury Minute Price" + }, + "budget_km_price": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "Budget Km Price" + }, + "deviation": { + "type": "number", + "exclusiveMinimum": 0, + "title": "Deviation" + }, + "inno_discount": { + "type": "number", + "exclusiveMinimum": 0, + "title": "Inno Discount" + } + }, + "type": "object", + "required": [ + "budget_minute_price", + "luxury_minute_price", + "budget_km_price", + "deviation", + "inno_discount" + ], + "title": "Spec" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } + } \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_innodrive.py b/tests/test_innodrive.py new file mode 100644 index 0000000..431d79d --- /dev/null +++ b/tests/test_innodrive.py @@ -0,0 +1,16 @@ +# This is the template for the InnoDrive e2e and domain and computation test cases. + +import pytest + +from httpx import Client + +@pytest.fixture(scope="module") +def test_app(): + client = Client(base_url="https://innodrive.containers.cloud.ru") + 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) + + assert response.status_code == 200