commit bc35187221dd3367445d6764125f033aaae77d5b Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue Feb 20 09:57:32 2024 +0000 Initial commit diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..f65f010 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,3 @@ +name: lab-testing + +# add your pipline description below diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e674ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# 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/ +.DS_Store +*.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe5b44c --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# Bank Management System Project [LAB-TESTING] + +## Overview + +This project simulates a basic bank management system, designed to demonstrate the functionalities such as managing customers, accounts, and processing transactions. It's developed with a focus on testing, to utilize unit tests, mocks, and end-to-end (E2E) tests to ensure reliability and functionality. + +## Background on Testing + +Testing is an essential aspect of software development, ensuring that each part of the codebase works correctly and as expected. For this project, we emphasize three main keypoints: + +- **Unit Tests**: Aim to test individual components or functions in isolation, ensuring that each piece of the system performs its intended task. +- **Mocking**: Involves simulating the behavior of real objects in controlled ways. Mocking is crucial for unit testing components that interact with external systems, like databases, to ensure tests are isolated and repeatable without side effects. +- **End-to-End (E2E) Tests**: Simulate real-user scenarios from start to finish, testing the system as a whole. E2E tests verify that all components of the application work together correctly to perform the intended tasks. + +## Dependency Management with Poetry + +For this project, we use [Poetry](https://python-poetry.org/) for dependency management and packaging. Poetry helps to simplify the management of project dependencies and environments. + +To get started with Poetry: + +1. Install Poetry by following the instructions on the [official documentation](https://python-poetry.org/docs/#installation). +2. Navigate to the project directory and run `poetry install` to install all dependencies defined in `pyproject.toml`. + +```bash +sudo -H pip3 install poetry +poetry install +poetry add pytest pytest-mock +``` + + +# LAB ASSIGNMENT + +In this lab, you are required to create several tests. Please note that there are 5 main modules of the project under `app/` folder. +You have to learn them and write tests in the corresponding files inside the `tests/`. + +For `Unit tests` - please add your tests in the files inside `tests/unit/test_*.py`. You'll need to add tests exclusively to these files. + +> :warning: **Note:** if you add tests, all tests should pass for final submission. + +> :warning: **Note:** the `app/` folder should not be altered and will be replaced in the autograding step. + +> :warning: **Note:** use `poetry` for the Python environment and dependencies management. + + +### 1. Unit Tests (3pt) + +- Create unit tests for the following modules to ensure each component functions correctly in isolation: + - `database.py` (At least 5 tests) + - `customer.py` (At least 2 tests) + - `account.py` (At least 2 tests) + - `transaction.py` (At least 2 tests) + - `bank.py` +- Utilize `unittest.mock` and `pytest-mock` to simulate external dependencies (code components from other modules) effectively. + +- Add your tests in the corresponding files under `/tests/unit/` directory. + + +- Apply mocks in testing for all modules to accurately simulate external dependencies without relying on the actual database. + +> :warning: **Note:** that mocking must be proper. For example, when you mock the database, the actual database file should not be touched by the system. + +### 2. End-to-End (E2E) Tests (1pt) + +- **`test_bank.py`**: Acts as an E2E test, it should call combination of all scripts. It should simulate real-life usage scenarios of the bank system. + +- Add your test in the corresponding file `/tests/e2e/test_bank.py` + +### 3. CI/CD with GitHub Actions (1pt) + +- **Pipeline**: Implement a CI/CD pipeline using GitHub Actions, configured in the `main.yaml` file. This pipeline should automate the execution of tests. + +> :warning: **Note:** The unit tests with mocks should be called on PUSH and the end-to-end on PULL.The unit tests with mocks should be called on when a commit is pushed and the end-to-end when a pull request is created. + + +## Running Tests + +Tests are to be created inside the corresponding test files ( check the boiler plates in the files). +To execute all tests, use the following Poetry command: + +```bash +poetry run pytest +``` + +To run a specific test: +```bash +poetry run pytest tests/unit/test_*.py +``` + +## Project Structure +``` +Bank/ +│ +├── app/ +│ ├── __init__.py +│ ├── bank.py +│ ├── database.py +│ ├── customer.py +│ ├── account.py +│ └── transaction.py +│ +└── tests/ + ├── __init__.py + ├── unit/ + │ ├── __init__.py + │ ├── test_account.py + │ ├── test_customer.py + │ ├── test_transaction.py + │ └── test_database.py + └── e2e/ + ├── __init__.py + └── test_bank.py +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/account.py b/app/account.py new file mode 100644 index 0000000..dd7d402 --- /dev/null +++ b/app/account.py @@ -0,0 +1,13 @@ +class Account: + + def __init__(self, database, customer_id=None, account_type="checking", balance=0.0): + self.database = database + self.balance = balance + if customer_id: + self.account_id = self._create_account(customer_id, account_type, balance) + + def _create_account(self, customer_id, account_type, balance): + return self.database.add_account(customer_id, account_type, balance) + + def delete_account(self, account_id): + self.database.delete_account(account_id) diff --git a/app/bank.py b/app/bank.py new file mode 100644 index 0000000..5919df9 --- /dev/null +++ b/app/bank.py @@ -0,0 +1,69 @@ +from .database import Database +from .transaction import Transaction +from .customer import Customer +from .account import Account + +class Bank: + def __init__(self, db_path='bank.db'): + self.database = Database(db_path) + self.transaction_system = Transaction(self.database) + + def add_customer(self, name, address): + """Add a new customer to the bank.""" + return self.database.add_customer(name, address) + + def update_customer_details(self, customer_id, name, address): + """Update details for an existing customer.""" + self.database.update_customer(customer_id, name, address) + + def delete_customer(self, customer_id): + """Remove a customer and their accounts from the bank.""" + + accounts = self.database.get_customer_accounts(customer_id) + for account in accounts: + self.close_account(account[0]) + self.database.delete_customer(customer_id) + + def open_account(self, customer_id, account_type, balance): + """Open a new account for an existing customer.""" + + account = Account(self.database, customer_id=customer_id, account_type=account_type, balance=balance) + return account.account_id + + def close_account(self, account_id): + """Close an existing account.""" + + self.database.delete_account(account_id) + + def deposit_to_account(self, account_id, amount): + """Deposit money into an account.""" + self.transaction_system.deposit(account_id, amount) + + def withdraw_from_account(self, account_id, amount): + """Withdraw money from an account.""" + self.transaction_system.withdraw(account_id, amount) + + def transfer_between_accounts(self, from_account_id, to_account_id, amount): + """Transfer money between two accounts.""" + self.transaction_system.transfer(from_account_id, to_account_id, amount) + + def get_customer_accounts(self, customer_id): + """Retrieve all accounts associated with a customer.""" + + return self.database.get_customer_accounts(customer_id) + + def get_account_transactions(self, account_id): + """Get a list of transactions for a specific account.""" + + return self.database.get_transactions(account_id) + + def get_all_customers(self): + """Retrieve all customers from the bank.""" + return self.database.get_all_customers() + + def get_account(self, account_id): + """Retrieve details for a specific account.""" + return self.database.get_account(account_id) + + def close_connection(self): + self.database.close() diff --git a/app/customer.py b/app/customer.py new file mode 100644 index 0000000..3090965 --- /dev/null +++ b/app/customer.py @@ -0,0 +1,21 @@ +class Customer: + def __init__(self, database, customer_id=None, name=None, address=None): + self.database = database + self.customer_id = customer_id + if customer_id is None and name and address: + self.customer_id = self.database.add_customer(name, address) + elif customer_id: + self._load_customer() + + def _load_customer(self): + details = self.database.get_customer(self.customer_id) + if details: + self.name, self.address = details[1], details[2] + else: + raise ValueError("Customer does not exist.") + + def update_details(self, name, address): + self.database.update_customer(self.customer_id, name, address) + + def delete_customer(self): + self.database.delete_customer(self.customer_id) diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..22ffd91 --- /dev/null +++ b/app/database.py @@ -0,0 +1,127 @@ +import sqlite3 + +class Database: + def __init__(self, db_path='bank.db'): + self.db_path = db_path + self.conn = sqlite3.connect(self.db_path) + self._init_db() + + def _init_db(self): + cursor = self.conn.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS customers ( + customer_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + address TEXT NOT NULL + );""") + cursor.execute(""" + CREATE TABLE IF NOT EXISTS accounts ( + account_id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id INTEGER, + account_type TEXT NOT NULL, + balance REAL NOT NULL, + FOREIGN KEY(customer_id) REFERENCES customers(customer_id) + );""") + cursor.execute(""" + CREATE TABLE IF NOT EXISTS transactions ( + transaction_id INTEGER PRIMARY KEY AUTOINCREMENT, + from_account_id INTEGER, + to_account_id INTEGER, + amount REAL NOT NULL, + transaction_type TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(from_account_id) REFERENCES accounts(account_id), + FOREIGN KEY(to_account_id) REFERENCES accounts(account_id) + );""") + self.conn.commit() + + def add_customer(self, name, address): + cursor = self.conn.cursor() + cursor.execute(""" + INSERT INTO customers (name, address) VALUES (?, ?) + """, (name, address)) + self.conn.commit() + return cursor.lastrowid + + def update_customer(self, customer_id, name, address): + cursor = self.conn.cursor() + cursor.execute(""" + UPDATE customers SET name = ?, address = ? WHERE customer_id = ? + """, (name, address, customer_id)) + self.conn.commit() + + def get_customer(self, customer_id): + """Retrieve a customer's details by their customer ID.""" + cursor = self.conn.cursor() + cursor.execute("SELECT * FROM customers WHERE customer_id = ?", (customer_id,)) + return cursor.fetchone() + + def delete_customer(self, customer_id): + cursor = self.conn.cursor() + cursor.execute(""" + DELETE FROM customers WHERE customer_id = ? + """, (customer_id,)) + self.conn.commit() + + def add_account(self, customer_id, account_type, balance): + cursor = self.conn.cursor() + cursor.execute(""" + INSERT INTO accounts (customer_id, account_type, balance) VALUES (?, ?, ?) + """, (customer_id, account_type, balance)) + self.conn.commit() + return cursor.lastrowid + + + def get_account(self, account_id): + cursor = self.conn.cursor() + cursor.execute(""" + SELECT account_id, customer_id, account_type, balance FROM accounts WHERE account_id = ? + """, (account_id,)) + return cursor.fetchone() + + + + def get_customer_accounts(self, customer_id): + """Retrieve all accounts associated with a customer by their customer ID.""" + cursor = self.conn.cursor() + cursor.execute("SELECT * FROM accounts WHERE customer_id = ?", (customer_id,)) + + return cursor.fetchall() + + def update_account_balance(self, account_id, balance): + cursor = self.conn.cursor() + cursor.execute(""" + UPDATE accounts SET balance = ? WHERE account_id = ? + """, (balance, account_id)) + self.conn.commit() + + def delete_account(self, account_id): + cursor = self.conn.cursor() + cursor.execute(""" + DELETE FROM accounts WHERE account_id = ? + """, (account_id,)) + self.conn.commit() + + def add_transaction(self, from_account_id, to_account_id, amount, transaction_type): + self.conn.execute("INSERT INTO transactions (from_account_id, to_account_id, amount, transaction_type) VALUES (?, ?, ?, ?)", + (from_account_id, to_account_id, amount, transaction_type)) + self.conn.commit() + + def get_transactions(self, account_id): + cursor = self.conn.cursor() + cursor.execute(""" + SELECT transaction_id, from_account_id, to_account_id, amount, transaction_type, timestamp + FROM transactions + WHERE from_account_id = ? OR to_account_id = ? + """, (account_id, account_id,)) + return cursor.fetchall() + + def get_all_customers(self): + """Retrieve all customers from the database.""" + cursor = self.conn.cursor() + cursor.execute("SELECT * FROM customers ORDER BY customer_id ASC") + return cursor.fetchall() + + def close(self): + if self.conn: + self.conn.close() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..6f708a7 --- /dev/null +++ b/app/main.py @@ -0,0 +1,102 @@ +from .bank import Bank + +def print_menu(): + print("\n--- Bank System Main Menu ---") + print("1. Add Customer") + print("2. Update Customer Details") + print("3. Delete Customer") + print("4. Open Account") + print("5. Close Account") + print("6. Deposit") + print("7. Withdraw") + print("8. Transfer") + print("9. View Customer Accounts") + print("10. View Account Transactions") + print("11. View All Customers") + print("12. Exit") + +def main(): + bank = Bank() + while True: + print_menu() + choice = input("Enter your choice: ") + + try: + if choice == "1": + name = input("Customer name: ") + address = input("Customer address: ") + customer_id = bank.add_customer(name, address) + print(f"Customer added with ID: {customer_id}") + + elif choice == "2": + customer_id = int(input("Customer ID: ")) + name = input("New name: ") + address = input("New address: ") + bank.update_customer_details(customer_id, name, address) + print("Customer details updated.") + + elif choice == "3": + customer_id = int(input("Customer ID to delete: ")) + bank.delete_customer(customer_id) + print("Customer deleted.") + + elif choice == "4": + customer_id = int(input("Customer ID for new account: ")) + account_type = input("Account type (checking/savings): ") + balance = float(input("Initial balance: ")) + account_id = bank.open_account(customer_id, account_type, balance) + print(f"Account {account_id} opened.") + + elif choice == "5": + account_id = int(input("Account ID to close: ")) + bank.close_account(account_id) + print("Account closed.") + + elif choice == "6": + account_id = int(input("Account ID for deposit: ")) + amount = float(input("Amount to deposit: ")) + bank.deposit_to_account(account_id, amount) + print("Deposit successful.") + + elif choice == "7": + account_id = int(input("Account ID for withdrawal: ")) + amount = float(input("Amount to withdraw: ")) + bank.withdraw_from_account(account_id, amount) + print("Withdrawal successful.") + + elif choice == "8": + from_account_id = int(input("From Account ID: ")) + to_account_id = int(input("To Account ID: ")) + amount = float(input("Amount to transfer: ")) + bank.transfer_between_accounts(from_account_id, to_account_id, amount) + print("Transfer successful.") + + elif choice == "9": + customer_id = int(input("Customer ID to view accounts: ")) + accounts = bank.get_customer_accounts(customer_id) + for account in accounts: + print(account) + + elif choice == "10": + account_id = int(input("Account ID to view transactions: ")) + transactions = bank.get_account_transactions(account_id) + for transaction in transactions: + print(transaction) + + elif choice == "11": + customers = bank.get_all_customers() + for customer in customers: + print(f"ID: {customer[0]}, Name: {customer[1]}, Address: {customer[2]}") + + elif choice == "12": + print("Exiting the bank system.") + break + + else: + print("Invalid choice, please try again.") + + except Exception as e: + print(f"An error occurred: {e}") + +if __name__ == "__main__": + main() diff --git a/app/transaction.py b/app/transaction.py new file mode 100644 index 0000000..ec528b9 --- /dev/null +++ b/app/transaction.py @@ -0,0 +1,34 @@ +class Transaction: + def __init__(self, database): + self.database = database + + def deposit(self, account_id, amount): + if amount <= 0: + raise ValueError("Amount must be positive.") + account = self.database.get_account(account_id) + if not account: + raise ValueError("Account does not exist.") + new_balance = account[3] + amount + self.database.update_account_balance(account_id, new_balance) + self.database.add_transaction(None, account_id, amount, "deposit") + + def withdraw(self, account_id, amount): + if amount <= 0: + raise ValueError("Amount must be positive.") + account = self.database.get_account(account_id) + if account[3] < amount: + raise ValueError("Insufficient funds.") + new_balance = account[3] - amount + self.database.update_account_balance(account_id, new_balance) + self.database.add_transaction(account_id, None, amount, "withdrawal") + + def transfer(self, from_account_id, to_account_id, amount): + if amount <= 0: + raise ValueError("Amount must be positive.") + from_account = self.database.get_account(from_account_id) + to_account = self.database.get_account(to_account_id) + if from_account[3] < amount: + raise ValueError("Insufficient funds in the source account.") + self.database.update_account_balance(from_account_id, from_account[3] - amount) + self.database.update_account_balance(to_account_id, to_account[3] + amount) + self.database.add_transaction(from_account_id, to_account_id, amount, "transfer") diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..650576f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,189 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.4.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "mock" +version = "5.1.0" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, + {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.0.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca"}, + {file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.3.0,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.12.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, + {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "ba455ebc9b953fd646775b0eede8374fdf31580c422eebd588af5d272c410aa1" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3bfe140 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "bank" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +pytest = "^8.0.1" +pytest-mock = "^3.11.0" +mock = "^5.1.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/test_bank.py b/tests/e2e/test_bank.py new file mode 100644 index 0000000..a843738 --- /dev/null +++ b/tests/e2e/test_bank.py @@ -0,0 +1,6 @@ +# - In this file, you have to write an E2E test on Bank project. +# - See, app/bank.py +# - For understanding purposes, you can interact with main.py +# - Create a real life usage scenario for this project and follow the order for testing components +# - Make sure that the test tests almost all of the functionalities of the project. + diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_account.py b/tests/unit/test_account.py new file mode 100644 index 0000000..ae070d6 --- /dev/null +++ b/tests/unit/test_account.py @@ -0,0 +1,4 @@ +# - In this file, you have to add your tests on Account module. +# - See, app/account.py +# - Test account creation and deletion +# - Use mocks diff --git a/tests/unit/test_customer.py b/tests/unit/test_customer.py new file mode 100644 index 0000000..81efcf4 --- /dev/null +++ b/tests/unit/test_customer.py @@ -0,0 +1,5 @@ +# - In this file, you have to add your tests on Customer module. +# - See, app/customer.py +# - Test customer creation, loading, updating and deletion +# - Use mocks + diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py new file mode 100644 index 0000000..0503956 --- /dev/null +++ b/tests/unit/test_database.py @@ -0,0 +1,5 @@ +# - In this file, you have to add your tests on Database module. +# - See, app/database.py +# - Test most of the methods +# - Use mocks in proper parts + diff --git a/tests/unit/test_transaction.py b/tests/unit/test_transaction.py new file mode 100644 index 0000000..5fd1eb5 --- /dev/null +++ b/tests/unit/test_transaction.py @@ -0,0 +1,5 @@ +# - In this file, you have to add your tests on Transaction module. +# - See, app/transaction.py +# - Test transaction with different types - deposit, withdraw and transfer +# - Use mocks accordingly +