Compare commits

..

23 Commits
CICD ... master

Author SHA1 Message Date
u5500327
95f543ca1d Merge branch 'e2eTesting' into 'master'
E2e testing

See merge request u5500327/wmgzon!2
2024-02-26 08:11:55 +00:00
db83438bae Added lazy loading to product images 2024-02-26 08:04:11 +00:00
7dbdcdcd4f Made test teardown reset environment variables 2024-02-25 14:49:32 +00:00
11ce2f16a4 Merge branch 'e2eTesting' of https://mygit.wmg.warwick.ac.uk/u5500327/wmgzon into e2eTesting 2024-02-24 14:09:34 +00:00
36bbd58e61 #11 Changed test data to include differing prices 2024-02-24 14:09:25 +00:00
c5b60f22da #11 Added defaults to every fetch of 'ENVIRON' to ensure a value is available 2024-02-24 08:47:57 +00:00
0434d85ddb #11 Created product filter test :) 2024-02-23 22:07:57 +00:00
7a9bc61d04 #11 REFACTOR: Moved all tests into test classes 2024-02-23 21:15:06 +00:00
68f738a241 #11 Fixed pep8 test failures 2024-02-23 18:33:19 +00:00
bcde471f33 #11 Starting on the creation of an end to end test environment 2024-02-23 18:25:05 +00:00
d056146a44 #11 Added extra functional tests for admin endpoints 2024-02-19 08:14:35 +00:00
263bea92f4 CHORE: Exposed correct port in docker file 2024-02-18 21:05:19 +00:00
2e9641bf27 CHORE: Changed pipeline docker build container 2024-02-18 20:50:50 +00:00
43e58a3717 CHORE: Update CICD to build containers for amd not arm 2024-02-18 20:31:58 +00:00
c3593d2109 CHORE: Update CICD to build containers for amd not arm 2024-02-18 20:28:43 +00:00
c04f3fa5f8 CHORE: Update run.bash to have the correct shebang 2024-02-18 20:13:25 +00:00
59c94e6c51 CHORE: Updated dockerfile to include entrypoint 2024-02-18 20:06:12 +00:00
02f69f2e68 CHORE: Removed unecessary environ variables from pipeline 2024-02-18 19:28:38 +00:00
cd112fa644 CHORE: gitignore stopped actual env file being committed 2024-02-18 19:27:03 +00:00
dba4cf01a3 CHORE: Created .env file to store environment vars 2024-02-18 19:22:35 +00:00
01cf5d641b Merge branch 'master' of https://mygit.wmg.warwick.ac.uk/u5500327/wmgzon 2024-02-16 17:11:00 +00:00
1a673f866f CHORE: Updated readme to container new deployment instructions 2024-02-16 17:10:51 +00:00
u5500327
8d99fc5595 Merge branch 'CICD' into 'master'
Cicd

See merge request u5500327/wmgzon!1
2024-02-16 15:06:22 +00:00
30 changed files with 390 additions and 235 deletions

13
.env Normal file
View File

@ -0,0 +1,13 @@
# Environment Variables used to configure WMGZON
# ENVIRON: Used to incidicate the type of environment the app is running in.
# DEFAULT = prod
ENVIRON=prod
# ENVIRON=test
# App secret used to encrpy client-side cookies
APPSECRET=test
# Filestore for product photos
FILESTORE=static/assets/img/products/

1
.gitignore vendored
View File

@ -17,7 +17,6 @@ cicd/runner-data
instance/* instance/*
!instance/.gitignore !instance/.gitignore
.webassets-cache .webassets-cache
.env
### Flask.Python Stack ### ### Flask.Python Stack ###
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@ -1,7 +1,4 @@
variables: variables:
APPSECRET: "test"
ENVIRON: "test"
FILESTORE: "static/assets/img/products/"
DOCKER_HOST: tcp://docker:2375/ DOCKER_HOST: tcp://docker:2375/
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "" DOCKER_TLS_CERTDIR: ""
@ -19,13 +16,13 @@ deploy:
stage: deploy stage: deploy
tags: tags:
- docker - docker
image: docker:20.10.16 image: docker:latest
services: services:
- name: docker:20.10.16-dind - name: docker:dind
alias: docker alias: docker
script: script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker buildx build --platform linux/amd64 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest - docker push $CI_REGISTRY_IMAGE:latest

View File

@ -40,4 +40,25 @@ In order to run the web app, simply use the command
docker-compose up -d docker-compose up -d
``` ```
to run the container in a detatched mode. to run the container in a detatched mode.
### Container
Alternatively, to deploy the app from the lastest published container:
```yml
version: '3.8'
services:
wmgzon:
container_name: "wmgzon"
image: lukeelse/wmgzon:latest
environment:
- FILESTORE=static/assets/img/products/
tty: true
ports:
- "8080:8080"
volumes:
- ./files:/app/$FILESTORE
restart: unless-stopped
```

2
app.py
View File

@ -1,5 +1,6 @@
from flask import Flask from flask import Flask
from os import environ from os import environ
from dotenv import load_dotenv
from controllers.web.endpoints import blueprint from controllers.web.endpoints import blueprint
''' '''
@ -11,6 +12,7 @@ from controllers.web.endpoints import blueprint
app: Flask = Flask(__name__) app: Flask = Flask(__name__)
# Set app secret key to sign session cookies # Set app secret key to sign session cookies
load_dotenv()
secret_key = environ.get("APPSECRET") secret_key = environ.get("APPSECRET")
if secret_key is None: if secret_key is None:
# NO Secret Key set! # NO Secret Key set!

View File

@ -0,0 +1,13 @@
version: '3.8'
services:
wmgzon:
container_name: "wmgzon"
image: lukeelse/wmgzon:latest
tty: true
ports:
- "8080:8080"
volumes:
- ./files:/app/static/assets/img/products/
- ./data:/app/data/
restart: unless-stopped

View File

@ -1,15 +1,15 @@
version: '3.8' version: '3.8'
services: services:
gitlab-runner-python: gitlab-runner-python:
image: gitlab/gitlab-runner:latest image: gitlab/gitlab-runner:latest
volumes: volumes:
- ./runner-data-python:/etc/gitlab-runner - ./runner-data-python:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped restart: unless-stopped
gitlab-runner-docker: gitlab-runner-docker:
image: gitlab/gitlab-runner:latest image: gitlab/gitlab-runner:latest
volumes: volumes:
- ./runner-data-docker:/etc/gitlab-runner - ./runner-data-docker:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped restart: unless-stopped

View File

@ -12,7 +12,7 @@ class DatabaseController(ABC):
__db_name = "wmgzon.db" __db_name = "wmgzon.db"
# Use test file if necessary # Use test file if necessary
if os.environ.get("ENVIRON") == "test": if os.environ.get("ENVIRON", "test") == "test":
__db_name = "test_" + __db_name __db_name = "test_" + __db_name
__sqlitefile = __data_dir + __db_name __sqlitefile = __data_dir + __db_name

View File

@ -2,7 +2,6 @@
in the web app in the web app
""" """
from flask import Blueprint from flask import Blueprint
from flask import render_template, request, flash, session, redirect, url_for from flask import render_template, request, flash, session, redirect, url_for
from controllers.database.stats import StatsController from controllers.database.stats import StatsController
from controllers.database.product import ProductController from controllers.database.product import ProductController
@ -48,8 +47,8 @@ def view_product_stats(id: int):
# Recent Views # Recent Views
product_view_frequency_data = dict(map( product_view_frequency_data = dict(map(
lambda k, v: (k, random.randint(70, 100)), # lambda k, v: (k, random.randint(70, 100)),
# lambda k, v: (k, len(v)), lambda k, v: (k, len(v)),
data.keys(), data.keys(),
data.values() data.values()
)) ))

View File

@ -5,14 +5,12 @@ services:
container_name: "wmgzon" container_name: "wmgzon"
build: . build: .
environment: environment:
- APPSECRET=test
- ENVIRON=test - ENVIRON=test
#- ENVIRON=prod
- FILESTORE=static/assets/img/products/
tty: true tty: true
ports: ports:
- "8080:8080" - "8080:8080"
# Dev container -> Copies complete root directory with latest
# code into the container
volumes: volumes:
- .:/app - .:/app
restart: unless-stopped restart: unless-stopped

View File

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

1
pep8.bat Normal file
View File

@ -0,0 +1 @@
autopep8 --exclude '*/.*/*' --in-place --recursive .

Binary file not shown.

View File

@ -26,7 +26,7 @@ def create_connection(path: str, filename: str):
print("Table creation complete") print("Table creation complete")
# Populate with test data if we are in Test Mode # Populate with test data if we are in Test Mode
if os.environ.get("ENVIRON") == "test": if os.environ.get("ENVIRON", "test") == "test":
sql = open("scripts/test_data.sql", "r") sql = open("scripts/test_data.sql", "r")
conn.executescript(sql.read()) conn.executescript(sql.read())
@ -37,15 +37,21 @@ def create_connection(path: str, filename: str):
conn.close() conn.close()
def run():
""" Create the database for the application"""
dir = r"./data/"
db_name = r"wmgzon.db"
# Check for test environ
if os.environ.get("ENVIRON", "test") == "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)
# Ensure a directory is created given a path to it # Ensure a directory is created given a path to it
dir = r"./data/" if __name__ == "__main__":
db_name = r"wmgzon.db" run()
# 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

@ -1,24 +1,26 @@
#! /bin/bash #!/bin/bash
pytest --disable-warnings pytest --disable-warnings
python ./scripts/create_database.py python ./scripts/create_database.py
EXPECTED_VALUE="test" EXPECTED_VALUE="test"
if [ -z "$ENVIRON" ]; then if [ -z "$ENVIRON" ]; then
echo "ENVIRON is not set." echo "ENVIRON is not set."
else echo "Using DEFAULT Environ Value: prod"
echo "ENVIRON is set to: $ENVIRON"
# Trim leading and trailing whitespaces export ENVIRON=prod
ACTUAL_VALUE=$(echo "$ENVIRON" | tr -d '[:space:]')
if [ "$ACTUAL_VALUE" = "$EXPECTED_VALUE" ]; then
echo "Launching DEV Server"
python ./app.py
else
echo "Launching PROD Server"
waitress-serve --host 0.0.0.0 app:app
fi
fi fi
echo "ENVIRON is set to: $ENVIRON"
# Trim leading and trailing whitespaces
ACTUAL_VALUE=$(echo "$ENVIRON" | tr -d '[:space:]')
if [ "$ACTUAL_VALUE" = "$EXPECTED_VALUE" ]; then
echo "Launching DEV Server"
python ./app.py
else
echo "Launching PROD Server"
waitress-serve --host 0.0.0.0 app:app
fi

View File

@ -8,34 +8,34 @@ INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quan
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 2, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 2, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 2, 3, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 2, 3, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 3, 9, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 3, 9, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 4, 3, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 129.99, 1, 4, 3, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 4, 2, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 129.99, 1, 4, 2, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 4, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 129.99, 1, 4, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 6, 7, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 6, 7, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 6, 1232, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, quantityAvailable, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 6, 1232, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 17.37, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 2, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 17.37, 1, 2, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 2, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 17.37, 1, 2, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 3, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 17.37, 1, 3, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 17.37, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 6.99, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 6, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 6, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 6, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 6, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 1, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 6.99, 1, 1, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 2, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 6.99, 1, 2, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 2, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 69.99, 1, 2, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 3, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 3, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("12' Brake Disks", "brake-disks.bmp", "this is a product", 20.99, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 6.99, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 20.99, 1, 4, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Single Turbo", "turbo.bmp", "this is a product", 69.99, 1, 4, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 20.99, 1, 6, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("Exhaust Manifold", "manifold.bmp", "This is a super cool product that can be installed into your car to take the gasses from the inside all the way to the outside. Mad I know.", 6.99, 1, 6, datetime());
INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 6, datetime()); INSERT INTO Products (name, image, description, cost, sellerID, categoryID, postedDate) VALUES ("17' Alloy Wheels", "alloy.bmp", "These super stylish alloys offer a fresh trendy look for your car. Whether a brand new Mercedes or a niffty little banger, it will uplift the vehicle 10-fold", 20.99, 1, 6, datetime());
INSERT INTO Views (userID, productID, viewDate) VALUES (1, 1, datetime()); INSERT INTO Views (userID, productID, viewDate) VALUES (1, 1, datetime());

View File

@ -7,7 +7,7 @@
<a href="{{ url_for('main.products.product', id=product.id) }}" class="product product-link"> <a href="{{ url_for('main.products.product', id=product.id) }}" class="product product-link">
<div class="product-title">{{product.name}}</div> <div class="product-title">{{product.name}}</div>
<div class="product-content-container"> <div class="product-content-container">
<img class="product-image-preview" src="{{ url_for('static', filename='assets/img/products/' + product.image) }}" alt="Brake Disks" /> <img class="product-image-preview" src="{{ url_for('static', filename='assets/img/products/' + product.image) }}" alt="Brake Disks" loading="lazy"/>
<div class="product-details"> <div class="product-details">
<div class="product-price">£{{product.cost}}</div> <div class="product-price">£{{product.cost}}</div>

View File

@ -1,8 +0,0 @@
import os
# Setup test environment variables
# Capture environment variables at start of testing so we can
# Monitor current setting during tests
old_env = os.environ.get("ENVIRON")
os.environ["ENVIRON"] = "test"

39
tests/base_test.py Normal file
View File

@ -0,0 +1,39 @@
import pytest
import os
from scripts.create_database import run
from app import app
from dotenv import load_dotenv
from flask.testing import FlaskClient
class TestBase:
""" Base test class that ensures environment variables
and test setup is complete before any other unit tests are run
"""
def setup_class(self):
""" Setup class level resources or configurations
Setup test environment variables
"""
load_dotenv()
# Capture environment variables at start of testing so we can
# Monitor current setting during tests
self.old_env = os.environ.get("ENVIRON")
os.environ["ENVIRON"] = "test"
run()
def teardown_class(self):
""" Teardown class level resources or configurations """
os.environ["ENVIRON"] = self.old_env
@pytest.fixture(scope="class")
def test_client(self) -> FlaskClient:
""" Enables tests to create requests to the web app
"""
os.environ['CONFIG_TYPE'] = 'config.TestingConfig'
with app.test_client() as testing_client:
with app.app_context():
yield testing_client

View File

View File

@ -0,0 +1,67 @@
from bs4 import BeautifulSoup
from flask.testing import FlaskClient
from tests.base_test import TestBase
class TestFunctionalRequirements(TestBase):
def test_product_filters(self, test_client: FlaskClient):
base_url = '/products/Car Part?filter={{FILTER}}'
# Make get request for all products in car parts
response = test_client.get(
base_url.replace("{{FILTER}}", "Price: High -> Low")
)
assert response.status_code == 200
# Extract first and last product
first, last = self.get_first_and_last_product(response.data)
# Get Prices of each
first_cost = float(self.get_tag_value(
str(first), "product-price").replace('£', '')
)
last_cost = float(self.get_tag_value(
str(last), "product-price").replace('£', '')
)
# Check filter is working
assert first_cost >= last_cost
# =============================================
# Test the reverse of the previous filter
# Get html data
response = test_client.get(
base_url.replace("{{FILTER}}", "Price: Low -> High")
)
assert response.status_code == 200
# Extract first and last product
first, last = self.get_first_and_last_product(response.data)
# Get Prices of each
first_cost = float(self.get_tag_value(
str(first), "product-price").replace('£', '')
)
last_cost = float(self.get_tag_value(
str(last), "product-price").replace('£', '')
)
# Check filter is working
assert first_cost <= last_cost
def get_tag_value(self, html: str, class_name: str) -> str:
""" Returns the value of a given tag in a html element """
soup = BeautifulSoup(html, features='html.parser')
return str(soup.find("div", {'class': class_name}).contents[0])
def get_first_and_last_product(self, html: str) -> (str, str):
""" Returns the first and last product on the page """
soup = BeautifulSoup(html, features='html.parser')
products = soup.findAll("a", {"class": "product"})
assert len(products) > 0
first = products[0]
last = products[-1]
return (first, last)

View File

@ -1,41 +0,0 @@
from app import app
from flask.testing import FlaskClient
import pytest
import os
@pytest.fixture(scope="module")
def test_client() -> FlaskClient:
""" Test that required environment variables are set
ahead of runtime
"""
os.environ['CONFIG_TYPE'] = 'config.TestingConfig'
with app.test_client() as testing_client:
with app.app_context():
yield testing_client
def test_homepage(test_client: FlaskClient):
""" Tests that the main homepage loads correctly
once the '/' endpoint is hit
"""
response = test_client.get('/')
assert response.status_code == 302
response = test_client.get('/products')
assert response.status_code == 308
def test_products(test_client: FlaskClient):
""" Tests that a product page is displayed when
hitting one of the product endpoints
"""
response = test_client.get('/products/2')
assert response.status_code == 200
response = test_client.get('/products/50')
assert response.status_code == 302
response = test_client.get('/products/Books')
assert response.status_code == 200

View File

@ -0,0 +1,41 @@
from flask.testing import FlaskClient
from tests.base_test import TestBase
class TestPages(TestBase):
""" Test class that encapsulates tests for
the main pages on the site
"""
def test_homepage(self, test_client: FlaskClient):
""" Tests that the main homepage loads correctly
once the '/' endpoint is hit
"""
response = test_client.get('/')
assert response.status_code == 302
response = test_client.get('/products')
assert response.status_code == 308
def test_products(self, test_client: FlaskClient):
""" Tests that a product page is displayed when
hitting one of the product endpoints
"""
response = test_client.get('/products/2')
assert response.status_code == 200
response = test_client.get('/products/50')
assert response.status_code == 302
response = test_client.get('/products/Books')
assert response.status_code == 200
def test_admin(self, test_client: FlaskClient):
""" Tests that the admin pages can be reached and redirect
upon reaching
"""
response = test_client.get('/admin/users/')
assert response.status_code == 302
response = test_client.get('/admin/products/')
assert response.status_code == 302

View File

@ -1,3 +0,0 @@
# Ensure test environment is set before using
# Runs the database creation scripts
import scripts.create_database

View File

@ -0,0 +1,8 @@
from tests.base_test import TestBase
class TestDatabase(TestBase):
""" Class that controls the
testing of the WMGZON database
"""
pass

View File

@ -1,5 +1,4 @@
import pytest from tests.unit.database.database_test_class import TestDatabase
import sqlite3
from datetime import datetime from datetime import datetime
from controllers.database.product import ProductController from controllers.database.product import ProductController
from models.products.product import Product from models.products.product import Product
@ -15,53 +14,52 @@ product = Product(
1 1
) )
# Tests a new product can be created
class TestProduct(TestDatabase):
""" Class that controls the
testing of the WMGZON database
"""
def test_create_product(): def test_create_product(self):
db = ProductController() """Tests a new product can be created"""
db.create(product) db = ProductController()
db.create(product)
# Tests the database maintains integrity when we try def test_duplicate_product(self):
# and add a product with the same details """ Tests the database maintains integrity when we try
and add a product with the same detail
"""
self.test_create_product()
def test_search_category(self):
""" Tests that products can be refined by category """
db = ProductController()
def test_duplicate_product(): # Check each category for correct amount of test products
test_create_product() assert len(db.read_all("Car Parts")) == 9 + \
2 # Added in previous tests
assert len(db.read_all("Books")) == 9
assert db.read_all("Phones") is None
# Tests that products can be refined by category def test_search_term(self):
""" Tests that products can be refined by search term"""
db = ProductController()
# Check each search term for correct amount of test products
assert len(db.read_all(search_term="Alloy")) == 9
assert len(db.read_all("Car Parts", "tur")) == 2
assert len(db.read_all(search_term="fold")) == 8
assert db.read_all(search_term="Twin") is None
def test_search_category(): def test_read_product(self):
db = ProductController() """ Test we the same product details get returned from the database """
db = ProductController()
# Check each category for correct amount of test products # Test the same product is returned
assert len(db.read_all("Car Parts")) == 9 + 2 # Added in previous tests new_product = db.read("product")
assert len(db.read_all("Books")) == 9 assert isinstance(new_product, list)
assert db.read_all("Phones") is None assert isinstance(new_product[0], Product)
# Update the ID on the item as database assigns new id
# Tests that products can be refined by search term product.id = new_product[0].id
def test_search_term(): assert new_product[0].__dict__ == product.__dict__
db = ProductController()
# Check each search term for correct amount of test products
assert len(db.read_all(search_term="Alloy")) == 9
assert len(db.read_all("Car Parts", "tur")) == 2
assert len(db.read_all(search_term="fold")) == 8
assert db.read_all(search_term="Twin") is None
# 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

@ -1,5 +1,6 @@
import pytest import pytest
import sqlite3 import sqlite3
from tests.unit.database.database_test_class import TestDatabase
from controllers.database.user import UserController from controllers.database.user import UserController
from models.users.customer import Customer from models.users.customer import Customer
from models.users.seller import Seller from models.users.seller import Seller
@ -22,53 +23,53 @@ seller = Seller(
"987654321" "987654321"
) )
# Tests a new user can be created
class TestUsers(TestDatabase):
""" Class to encapsulate all of the user
datbase tests
"""
def test_create_user(): def test_create_user(self):
db = UserController() """ Tests a new user can be created """
db.create(customer) db = UserController()
# 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) db.create(customer)
# Test we the same user details get returned from the database def test_duplicate_user(self):
""" Tests the database maintains integrity when we try
and add a user with the same details
"""
db = UserController()
with pytest.raises(sqlite3.IntegrityError):
db.create(customer)
def test_read_user(self):
""" Test we the same user details get returned from the database """
db = UserController()
def test_read_user(): # Test the same user is returned
db = UserController() user = db.read(customer.username)
assert isinstance(user, Customer)
# Test the same user is returned # Update the ID on the item as database assigns new id
user = db.read(customer.username) customer.id = user.id
assert isinstance(user, Customer) assert user.__dict__ == customer.__dict__
# Update the ID on the item as database assigns new id def test_create_seller(self):
customer.id = user.id """ Tests a new seller can be created """
assert user.__dict__ == customer.__dict__ db = UserController()
db.create(seller)
def test_read_seller(self):
""" Test that the same seller details get
returned from the database
"""
db = UserController()
# Tests a new seller can be created # Test the same user is returned
def test_create_seller(): user = db.read(seller.username)
db = UserController() assert isinstance(user, Seller)
db.create(seller)
# Test that the same seller details get returned from the database # Update the ID on the item as database assigns new id
seller.id = user.id
user.store = ""
def test_read_seller(): assert user.__dict__ == seller.__dict__
db = UserController()
# Test the same user is returned
user = db.read(seller.username)
assert isinstance(user, Seller)
# Update the ID on the item as database assigns new id
seller.id = user.id
user.store = ""
assert user.__dict__ == seller.__dict__

View File

@ -1,31 +1,31 @@
from tests.base_test import TestBase
from os import environ from os import environ
from warnings import warn from warnings import warn
from tests import old_env
# Tests environment variables used within the projects domain are
# set in the correct environment
VARS = ['ENVIRON', 'APPSECRET', 'FILESTORE']
ENV_STATES = ['test', 'prod']
def test_env_vars(): class TestEnv(TestBase):
""" Test that required environment variables are set """ Tests environment variables used within the projects domain are
ahead of runtime set in the correct environment
""" """
for var in VARS: VARS = ['ENVIRON', 'APPSECRET', 'FILESTORE']
env = environ.get(var)
# Check to see what variable we are comparing ENV_STATES = ['test', 'prod']
if env is None:
warn(f"Variable {var} is not set!")
def test_env_vars(self):
""" Test that required environment variables are set
ahead of runtime
"""
for var in self.VARS:
env = environ.get(var)
def test_environment_var_state(): # Check to see what variable we are comparing
""" Tests that the 'ENVIRON' Environment variable if env is None:
is in a correct state warn(f"Variable {var} is not set!")
"""
var = old_env def test_environment_var_state(self):
assert var is not None """ Tests that the 'ENVIRON' Environment variable
assert (var in ENV_STATES) is in a correct state
"""
var = self.old_env
assert var is not None
assert (var in self.ENV_STATES)

View File

@ -1,11 +1,13 @@
import pycodestyle import pycodestyle
from tests.base_test import TestBase
# Tests files to ensure they conform to pep8 standards # Tests files to ensure they conform to pep8 standards
def test_pep8_conformance(): class TestPep8(TestBase):
"""Test that we conform to PEP8.""" def test_pep8_conformance(self):
pep8style = pycodestyle.StyleGuide() """Test that we conform to PEP8."""
dirs = ["./controllers", "./models", "./scripts", "./tests", "./utils"] pep8style = pycodestyle.StyleGuide()
result = pep8style.check_files(dirs) dirs = ["./controllers", "./models", "./scripts", "./tests", "./utils"]
assert result.total_errors == 0 result = pep8style.check_files(dirs)
assert result.total_errors == 0

View File

@ -1 +0,0 @@
./.venv/Scripts/activate