Resolved merge conflict

This commit is contained in:
Luke Else 2024-01-15 17:45:47 +00:00
commit 7b12c29319
21 changed files with 217 additions and 48 deletions

7
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,7 @@
pytest:
stage: test
script:
- python -m venv .venv
- source ./.venv/scripts/activate
- pip install -r requirements.txt
- pytest

View File

@ -18,6 +18,13 @@ python -m venv .venv
pip install -r requirements.txt pip install -r requirements.txt
``` ```
## Testing
To run the full suite of unit tests for the webapp simply run the following command in the venv
```sh
pytest
```
## Running ## Running
### Pre-Requisites ### Pre-Requisites

View File

@ -1,14 +1,25 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Mapping, Any from typing import Mapping, Any
import sqlite3 import sqlite3
import os
class DatabaseController(ABC): class DatabaseController(ABC):
__sqlitefile = "./data/wmgzon.db" __data_dir = "./data/"
__db_name = "wmgzon.db"
# Use test file if necessary
if os.environ.get("ENVIRON") == "test":
__db_name = "test_" + __db_name
__sqlitefile = __data_dir + __db_name
def __init__(self): def __init__(self):
self._conn = None self._conn = None
try: try:
self._conn = sqlite3.connect(self.__sqlitefile) # Creates a connection and specifies a flag to parse all types back down into
# Python declared types e.g. date & time
self._conn = sqlite3.connect(self.__sqlitefile, detect_types=sqlite3.PARSE_DECLTYPES)
except sqlite3.Error as e: except sqlite3.Error as e:
# Close the connection if still open # Close the connection if still open
if self._conn: if self._conn:

View File

@ -2,7 +2,7 @@ from .database import DatabaseController
from models.products.product import Product from models.products.product import Product
class ProductController(DatabaseController): class ProductController(DatabaseController):
FIELDS = ['id', 'name', 'image', 'description', 'cost', 'category', 'sellerID', 'postedDate', 'quantity'] FIELDS = ['id', 'name', 'image', 'description', 'cost', 'category', 'sellerID', 'postedDate', 'quantityAvailable']
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -20,7 +20,7 @@ class ProductController(DatabaseController):
] ]
self._conn.execute( self._conn.execute(
"INSERT INTO Products (name, cost, image, description, category, sellerID, postedDate, quantityAvailable) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO Products (name, image, description, cost, categoryID, sellerID, postedDate, quantityAvailable) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
params params
) )
self._conn.commit() self._conn.commit()
@ -46,7 +46,7 @@ class ProductController(DatabaseController):
for product in rows: for product in rows:
params = dict(zip(self.FIELDS, product)) params = dict(zip(self.FIELDS, product))
obj = self.new_instance(Product, params) obj = self.new_instance(Product, params)
products.push(obj) products.append(obj)
return products return products

View File

@ -55,14 +55,12 @@ def signup():
return redirect("/signup") return redirect("/signup")
database.create(Customer( database.create(Customer(
0,
request.form['username'], request.form['username'],
sha512(request.form['password'].encode()).hexdigest(), # Hashed as soon as it is recieved on the backend sha512(request.form['password'].encode()).hexdigest(), # Hashed as soon as it is recieved on the backend
request.form['firstname'], request.form['firstname'],
request.form['lastname'], request.form['lastname'],
request.form['email'], request.form['email'],
"123", "123"
"Customer"
)) ))
# Code 307 Preserves the original request (POST) # Code 307 Preserves the original request (POST)

View File

@ -6,6 +6,9 @@ services:
build: . build: .
environment: environment:
- APPSECRET=test - APPSECRET=test
- ENVIRON=test
# - ENVIRON=prod
tty: true
ports: ports:
- "5000:5000" - "5000:5000"
volumes: volumes:

View File

@ -3,5 +3,5 @@ COPY ./requirements.txt /app/requirements.txt
WORKDIR /app WORKDIR /app
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . /app COPY . /app
RUN chmod +x scripts/run.sh RUN chmod +x scripts/run.bash
CMD ["sh", "scripts/run.sh"] CMD ["bash", "scripts/run.bash"]

View File

@ -10,8 +10,25 @@ class Product:
self.image = "/static/assets/wmgzon.png" self.image = "/static/assets/wmgzon.png"
self.description = "" self.description = ""
self.cost = 0.0 self.cost = 0.0
self.category = "" self.category = 0
self.sellerID = 0 self.sellerID = 0
self.postedDate = datetime.now() self.postedDate = datetime.now()
self.quantityAvailable = 0 self.quantityAvailable = 0
'''
Class constructor to instatiate a customer object
No additional properties are assigned to the customer
'''
def __init__(self, name: str, image: str, description: str, cost: float, category: int,
seller_id: int, posted_date: datetime, quantity_available: int):
self.id = 0
self.name = name
self.image = image
self.description = description
self.cost = cost
self.category = category
self.sellerID = seller_id
self.postedDate = posted_date
self.quantityAvailable = quantity_available

View File

@ -6,8 +6,8 @@ class Admin(User):
No additional properties are assigned to the admin No additional properties are assigned to the admin
''' '''
def __init__(self, id: int, username: str, password: str, firstname: str, def __init__(self, username: str, password: str, firstname: str,
lastname: str, email: str, phone: str, role: str): lastname: str, email: str, phone: str):
super().__init__( super().__init__(
id, username, password, firstname, lastname, email, phone, role username, password, firstname, lastname, email, phone, "Admin"
) )

View File

@ -6,9 +6,9 @@ class Customer(User):
No additional properties are assigned to the customer No additional properties are assigned to the customer
''' '''
def __init__(self, id: int, username: str, password: str, firstname: str, def __init__(self, username: str, password: str, firstname: str,
lastname: str, email: str, phone: str, role: str): lastname: str, email: str, phone: str):
super().__init__( super().__init__(
id, username, password, firstname, lastname, email, phone, role username, password, firstname, lastname, email, phone, "Customer"
) )

View File

@ -6,9 +6,9 @@ class Seller(User):
No additional properties are assigned to the customer No additional properties are assigned to the customer
''' '''
def __init__(self, id: int, username: str, password: str, firstname: str, def __init__(self, username: str, password: str, firstname: str,
lastname: str, email: str, phone: str, role: str): lastname: str, email: str, phone: str):
super().__init__( super().__init__(
id, username, password, firstname, lastname, email, phone, role id, username, password, firstname, lastname, email, phone, "Seller"
) )
self.store = "" self.store = ""

View File

@ -3,9 +3,9 @@ from abc import ABC
class User(ABC): class User(ABC):
""" Functional Class constructor to initialise all properties in the base object """ Functional Class constructor to initialise all properties in the base object
with a value """ with a value """
def __init__(self, id: int, username: str, password: str, firstname: str, def __init__(self, username: str, password: str, firstname: str,
lastname: str, email: str, phone: str, role: str): lastname: str, email: str, phone: str, role: str):
self.id = id self.id = 0
self.username = username self.username = username
self.password = password self.password = password
self.firstName = firstname self.firstName = firstname

0
scripts/__init__.py Normal file
View File

View File

@ -17,8 +17,13 @@ def create_connection(path: str, filename: str):
sql = open("scripts/create_tables.sql", "r"); sql = open("scripts/create_tables.sql", "r");
conn.executescript(sql.read()) conn.executescript(sql.read())
print("SQLite Version: " + sqlite3.version)
print("Table creation complete") print("Table creation complete")
# Populate with test data if we are in Test Mode
if os.environ.get("ENVIRON") == "test":
sql = open("scripts/test_data.sql", "r");
conn.executescript(sql.read())
except sqlite3.Error as e: except sqlite3.Error as e:
print(e) print(e)
finally: finally:
@ -32,5 +37,22 @@ def create_directory(dir: str):
except FileExistsError: except FileExistsError:
pass pass
if __name__ == '__main__': def remove_file(dir: str):
create_connection(r"./data/", r"wmgzon.db") try:
os.remove(dir)
except FileNotFoundError:
pass
dir = r"./data/"
db_name = r"wmgzon.db"
# Check for test environ
if os.environ.get("ENVIRON") == "test":
# Remove the original test database
print("TEST ENVIRONMENT ACTIVE")
db_name = "test_" + db_name
remove_file(dir + db_name)
create_connection(dir, db_name)

View File

@ -9,8 +9,6 @@ CREATE TABLE IF NOT EXISTS Users (
role TEXT NOT NULL role TEXT NOT NULL
); );
INSERT INTO Users (first_name, last_name, username, email, phone, password, role) VALUES ("Luke", "Else", "lukejelse04", "test@test.com", "07498 289321", "test213", "Customer");
CREATE TABLE IF NOT EXISTS Categories ( CREATE TABLE IF NOT EXISTS Categories (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE name TEXT NOT NULL UNIQUE
@ -23,9 +21,6 @@ INSERT INTO Categories (name) VALUES ("Books");
INSERT INTO Categories (name) VALUES ("Phones"); INSERT INTO Categories (name) VALUES ("Phones");
INSERT INTO Categories (name) VALUES ("Music"); INSERT INTO Categories (name) VALUES ("Music");
CREATE TABLE IF NOT EXISTS Products ( CREATE TABLE IF NOT EXISTS Products (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
@ -39,21 +34,11 @@ CREATE TABLE IF NOT EXISTS Products (
categoryID INTEGER NOT NULL categoryID INTEGER NOT NULL
REFERENCES Categories (id) REFERENCES Categories (id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE NO ACTION ON UPDATE NO ACTION,
postedDate TIMESTAMP,
quantityAvailable INTEGER DEFAULT 0
); );
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 2, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 3, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 3, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 3, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 6, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 6, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 5, 5);
CREATE TABLE IF NOT EXISTS Orders ( CREATE TABLE IF NOT EXISTS Orders (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
sellerID TEXT NOT NULL sellerID TEXT NOT NULL

2
scripts/run.bash Normal file
View File

@ -0,0 +1,2 @@
#! /bin/bash
pytest --disable-warnings && python ./scripts/create_database.py && python ./app.py

View File

@ -1,4 +0,0 @@
#! /bin/sh
python ./scripts/create_database.py
python ./app.py

37
scripts/test_data.sql Normal file
View File

@ -0,0 +1,37 @@
INSERT INTO Users (first_name, last_name, username, email, phone, password, role) VALUES ("Luke", "Else", "lukejelse04", "test@test.com", "07498 289321", "test213", "Customer");
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 1);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 2);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 3);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 4);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);
INSERT INTO Products (name, image, description, cost, sellerID, categoryID) VALUES ("test", "assets/img/wmgzon.png", "this is a product", 20.99, 1, 6);

View File

@ -0,0 +1,8 @@
# Ensure test environment is set before using
import os
# Setup test environment variables
os.environ["ENVIRON"] = "test"
# Runs the database creation scripts
import scripts.create_database

View File

@ -0,0 +1,40 @@
import pytest
import sqlite3
from datetime import datetime
from controllers.database.product import ProductController
from models.products.product import Product
product = Product(
"product",
"image.png",
"description",
10.00,
1,
1,
datetime.now(),
1
)
# Tests a new product can be created
def test_create_product():
db = ProductController()
db.create(product)
# Tests the database maintains integrity when we try and add a product with the same details
def test_duplicate_product():
db = ProductController()
with pytest.raises(sqlite3.IntegrityError):
db.create(product)
# Test we the same product details get returned from the database
def test_read_product():
db = ProductController()
# Test the same product is returned
new_product = db.read("product")
assert isinstance(new_product, list)
assert isinstance(new_product[0], Product)
# Update the ID on the item as database assigns new id
product.id = new_product[0].id
assert new_product[0].__dict__ == product.__dict__

View File

@ -0,0 +1,36 @@
import pytest
import sqlite3
from controllers.database.user import UserController
from models.users.customer import Customer
customer = Customer(
"testuser",
"Password1",
"firstname",
"lastname",
"test@test",
"123456789"
)
# Tests a new user can be created
def test_create_user():
db = UserController()
db.create(customer)
# Tests the database maintains integrity when we try and add a user with the same details
def test_duplicate_user():
db = UserController()
with pytest.raises(sqlite3.IntegrityError):
db.create(customer)
# Test we the same user details get returned from the database
def test_read_user():
db = UserController()
# Test the same user is returned
user = db.read("testuser")
assert isinstance(user, Customer)
# Update the ID on the item as database assigns new id
customer.id = user.id
assert user.__dict__ == customer.__dict__