Skip to content

Commit

Permalink
Add testing framework (#118)
Browse files Browse the repository at this point in the history
## Describe Your Changes
Add testing framework, GitHub Action, and baseline test.

## Relevant Information
https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/

## Checklist Before Requesting a Review
- [x] The code runs successfully.

```commandline
(prijatelitree-py3.11) michaelp@MacBook-Air-18 PrijateliTree % make test
docker-compose up -d
[+] Building 0.0s (0/0)                                                                                                                                                                    docker:desktop-linux
[+] Running 2/0
 ✔ Container prijatelitree-postgres-1  Running                                                                                                                                                             0.0s 
 ✔ Container prijatelitree-web-1       Running                                                                                                                                                             0.0s 
ENV=testing docker-compose run --rm web pytest -vsx ./prijateli_tree/tests/;
[+] Building 0.0s (0/0)                                                                                                                                                                    docker:desktop-linux
[+] Creating 1/0
 ✔ Container prijatelitree-postgres-1  Running                                                                                                                                                             0.0s 
[+] Building 0.0s (0/0)                                                                                                                                                                    docker:desktop-linux
DEBUG:	Routers loaded and static files mounted.
============================================================================================= test session starts ==============================================================================================
platform linux -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- /usr/local/bin/python
cachedir: .pytest_cache
rootdir: /usr/src/app
plugins: anyio-3.7.1
collected 1 item                                                                                                                                                                                               

prijateli_tree/tests/test_home.py::test_home_page DEBUG:	Using selector: EpollSelector
INFO:	HTTP Request: GET http://testserver/ "HTTP/1.1 200 OK"
PASSED

=============================================================================================== warnings summary ===============================================================================================
../../local/lib/python3.11/site-packages/passlib/utils/__init__.py:854
  /usr/local/lib/python3.11/site-packages/passlib/utils/__init__.py:854: DeprecationWarning: 'crypt' is deprecated and slated for removal in Python 3.13
    from crypt import crypt as _crypt

prijateli_tree/app/database.py:34
  /usr/src/app/prijateli_tree/app/database.py:34: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
    Base = declarative_base()

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================================================================================== 1 passed, 2 warnings in 0.03s =========================================================================================
(prijatelitree-py3.11) michaelp@MacBook-Air-18 PrijateliTree % 
...
2023-12-24 16:13:55 INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
2023-12-24 16:13:55 INFO:     Started reloader process [1] using WatchFiles
2023-12-24 16:13:56 DEBUG:      Routers loaded and static files mounted.
2023-12-24 16:13:56 INFO:     Started server process [8]
2023-12-24 16:13:56 INFO:     Waiting for application startup.
2023-12-24 16:13:56 INFO:     Application startup complete.
2023-12-24 16:14:08 INFO:     192.168.65.1:65143 - "GET / HTTP/1.1" 200 OK
2023-12-24 16:14:08 INFO:     192.168.65.1:65143 - "GET /css/bootstrap.min.css HTTP/1.1" 200 OK
2023-12-24 16:14:08 INFO:     192.168.65.1:65144 - "GET /js/bootstrap.bundle.min.js HTTP/1.1" 200 OK
2023-12-24 16:14:08 INFO:     192.168.65.1:65148 - "GET /js/dataTables.bootstrap5.min.js HTTP/1.1" 304 Not Modified
2023-12-24 16:14:08 INFO:     192.168.65.1:65146 - "GET /js/jquery.dataTables.min.js HTTP/1.1" 200 OK
```
  • Loading branch information
michplunkett authored Dec 24, 2023
1 parent f6536f4 commit 4c3cea3
Show file tree
Hide file tree
Showing 18 changed files with 353 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Format-and-Fail
name: Continuous-Integration

on:
pull_request:
branches:
Expand All @@ -7,7 +8,21 @@ on:
branches:
- main

workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
env:
ENV: "testing"
strategy:
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v3
- name: Run tests
run: make test

pre-commit:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ repos:
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: name-tests-test
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-json
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
default: build start create_db

.PHONY: build
build: create-requirements
build: create_requirements
docker-compose build

.PHONY: create-requirements
create-requirements:
.PHONY: create_requirements
create_requirements:
poetry export --without-hashes --format=requirements.txt > requirements.txt

.PHONY: start
Expand All @@ -30,8 +30,8 @@ lint:
pre-commit run --all-files

.PHONY: test
test:
pytest -vs ./prijateli_tree/tests/
test: start
docker-compose run --rm web-test pytest -vsx ./prijateli_tree/tests/;

.PHONY: stop
stop:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ The games are a means to provide insights into social learning, the economics of

## Dev commands
- `make lint`: Runs the `pre-commit` processes and lints the repository.
- `make create_requirements`: Creates a `requirements.txt` file based off of the imported packages in the `pyproject.toml` file.
- `make build`: Builds the Docker images for the application and the database pass through.
- `make start`: Starts the Docker containers based on the images created in the `make build` step.
- `make create_db`: Runs the alembic scripts. This MUST be run before you run the `make start` command, or it will error.
- `make update_db`: Updates the DB to the latest Alembic version.
- `make stop`: Stops the running Docker containers.
- `make clean`: Removes all Docker containers.
- `make clean_all`: Removes the database link between the Docker containers and the self-hosted version of PostgreSQL.
- `make test`: Runs all the tests in the `prijateli_tree/tests` folder.
- `make test`: Runs all the tests in the `prijateli_tree/tests` folder using a Dockerized version of SQLite.

## General Debugging Notes
- If you are failing the `Format-and-Fail` GitHub Action, you must run `make lint` and make any changes it requests.
Expand Down
14 changes: 13 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ services:
restart: always
build:
context: .
dockerfile: ./Dockerfile
dockerfile: dockerfiles/Dockerfile
command: uvicorn prijateli_tree.app.main:app --reload --host 0.0.0.0 --port 8000
volumes:
- .:/usr/src/app
Expand All @@ -39,3 +39,15 @@ services:
LOGIN_SECRET_KEY: '3a5f3125e1af1763756297e040cd05f8f24fcab15f78805e'
links:
- 'postgres:postgres'

web-test:
restart: always
build:
context: .
dockerfile: dockerfiles/Dockerfile-test
volumes:
- .:/usr/src/app
environment:
ENV: "testing"
DATABASE_URL: "sqlite:///:memory:"
LOGIN_SECRET_KEY: 'f07014cc557aeb5ff75fdc9e0938331291dbf8a7dc93cd8fbae6d619b9f80f95'
6 changes: 3 additions & 3 deletions Dockerfile → dockerfiles/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update \
&& apt-get -y install netcat gcc postgresql libpq-dev python-dev \
&& apt-get -y install netcat gcc postgresql libpq-dev python3-dev \
&& apt-get clean

RUN pip install --upgrade pip
COPY ./requirements.txt .
COPY ../requirements.txt .
RUN pip install -r requirements.txt

COPY . .
COPY .. .
15 changes: 15 additions & 0 deletions dockerfiles/Dockerfile-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.11-slim-buster

WORKDIR /usr/src/app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DATABASE_URL=""

RUN apt-get update && apt-get -y install gcc libpq-dev python3-dev libsqlite3-0 && apt-get clean

RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt

COPY . .
Binary file added misc/DB-Screenshot-12:2023.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
221 changes: 220 additions & 1 deletion poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion prijateli_tree/app/routers/administration.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ def add_students(
if user is None:
return RedirectResponse("login", status_code=HTTPStatus.FOUND)

students_added = 0
try:
student_df = pd.read_csv(file.file)
expected_fields = [
Expand All @@ -423,6 +424,7 @@ def add_students(
role="student",
)
db.add(student_in)
students_added += 1
db.commit()

except Exception as e:
Expand All @@ -435,7 +437,7 @@ def add_students(
file.file.close()

redirect_url = URL("/admin/dashboard").include_query_params(
success=f"{index + 1} students added to the database."
success=f"{students_added} students added to the database."
)

return RedirectResponse(
Expand Down
12 changes: 5 additions & 7 deletions prijateli_tree/app/templates/score_header.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
<div class="container-sm general-container">
<div class="row">
<div class="col text-start">
{% if not completed_game %}
<p style="margin-bottom: 5px;">{{ text.game_first_round.round }}: {{ round_progress }}</p>
{% endif %}
{% if not completed_game %}<p class="mb-4">{{ text.game_first_round.round }}: {{ round_progress }}</p>{% endif %}
{% if practice_game %}
<p style="margin-bottom: 5px;">{{ text.game_video.game }}: {{ practice_game_progress }}</p>
<p class="mb-4">{{ text.game_video.game }}: {{ practice_game_progress }}</p>
{% else %}
<p style="margin-bottom: 5px;">Real Game: {{ real_game_progress }}</p>
<p class="mb-4">Real Game: {{ real_game_progress }}</p>
{% endif %}
</div>
<div class="col text-end">
<p style="margin-bottom: 5px;">{{ player_name }}</p>
<p style="margin-bottom: 5px;">{{ text.game_video.score }}: {{ player_score }}</p>
<p class="mb-4">{{ player_name }}</p>
<p class="mb-4">{{ text.game_video.score }}: {{ player_score }}</p>
</div>
</div>
</div>
8 changes: 3 additions & 5 deletions prijateli_tree/app/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
KEY_ENV_TESTING = "testing"
KEY_LOGIN_SECRET = "LOGIN_SECRET_KEY"

# user id for system
SYSTEM_ID = 0

# File Constants
FILE_MODE_READ = "r"
STANDARD_ENCODING = "utf-8"
Expand All @@ -20,10 +17,11 @@
NETWORK_TYPE_INTEGRATED = "integrated"
NETWORK_TYPE_SEGREGATED = "segregated"
NETWORK_TYPE_SELF_SELECTED = "self-selected"
NUMBER_OF_ROUNDS = 3
ROLE_ADMIN = "admin"
ROLE_STUDENT = "student"
ROLE_SUPER_ADMIN = "super-admin"
NUMBER_OF_ROUNDS = 3
SYSTEM_ID = 0

# Language Constants
LANGUAGE_ALBANIAN = "al"
Expand All @@ -35,7 +33,7 @@
WINNING_SCORE = 100
DENAR_FACTOR = 0.25

# Survey Links
# Survey Constants
PRE_SURVEY_LINK = (
"https://uchicago.co1.qualtrics.com/jfe/form/SV_eaqREcRYZgwsuHk"
)
Expand Down
7 changes: 5 additions & 2 deletions prijateli_tree/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
from sqlalchemy import engine_from_config, pool

import prijateli_tree.app.database as models
from prijateli_tree.app.utils.constants import KEY_DATABASE_URI
from prijateli_tree.app.config import config
from prijateli_tree.app.utils.constants import KEY_ENV


SQL_ALCHEMY_URL = "sqlalchemy.url"
app_config = config[os.getenv(KEY_ENV)]


config = context.config
config.set_main_option(
SQL_ALCHEMY_URL,
os.getenv(KEY_DATABASE_URI).replace("postgres://", "postgresql://", 1),
app_config.SQLALCHEMY_DATABASE_URI,
)

fileConfig(config.config_file_name)
Expand Down
40 changes: 40 additions & 0 deletions prijateli_tree/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import os

import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel.pool import StaticPool

from prijateli_tree.app.main import app
from prijateli_tree.app.utils.constants import KEY_DATABASE_URI


@pytest.fixture(name="session")
def session_fixture():
print(os.getenv(KEY_DATABASE_URI))
engine = create_engine(
os.getenv(KEY_DATABASE_URI),
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
add_mockdata(session)
yield session


def add_mockdata(session: Session):
# Add data you want to the test DB using the `session` object.
pass


@pytest.fixture(name="client")
def client_fixture(session: Session):
def get_session_override():
return session

app.dependency_overrides["get_session"] = get_session_override

client = TestClient(app)
yield client
app.dependency_overrides.clear()
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
from prijateli_tree.app.utils.constants import BALL_BLUE, BALL_RED


API = "http://localhost:8000/"
POSSIBLE_ANSWERS = [BALL_RED, BALL_BLUE]
11 changes: 11 additions & 0 deletions prijateli_tree/tests/test_home.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from http import HTTPStatus

from fastapi.testclient import TestClient


def test_home_page(client: TestClient):
"""Test home page functionality."""
response = client.get("/")

assert "Welcome to the PrijateliTree application!" in response.text
assert response.status_code == HTTPStatus.OK
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jinja2 = "^3.1.2"
python-multipart = "^0.0.6"
pytest = "^7.4.3"
requests = "^2.31.0"
sqlmodel = "^0.0.14"
httpx = "^0.26.0"
pandas = "^2.1.4"


[build-system]
Expand Down
10 changes: 9 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ fastapi-sqlalchemy==0.2.1 ; python_version >= "3.11" and python_version < "4.0"
fastapi==0.103.2 ; python_version >= "3.11" and python_version < "4.0"
greenlet==3.0.2 ; python_version >= "3.11" and python_version < "4.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32")
h11==0.14.0 ; python_version >= "3.11" and python_version < "4.0"
httpcore==1.0.2 ; python_version >= "3.11" and python_version < "4.0"
httptools==0.6.1 ; python_version >= "3.11" and python_version < "4.0"
httpx==0.26.0 ; python_version >= "3.11" and python_version < "4.0"
idna==3.6 ; python_version >= "3.11" and python_version < "4.0"
iniconfig==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
jinja2==3.1.2 ; python_version >= "3.11" and python_version < "4.0"
mako==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
markupsafe==2.1.3 ; python_version >= "3.11" and python_version < "4.0"
numpy==1.26.2 ; python_version >= "3.12" and python_version < "4.0" or python_version == "3.11"
outcome==1.3.0.post0 ; python_version >= "3.11" and python_version < "4.0"
packaging==23.2 ; python_version >= "3.11" and python_version < "4.0"
pandas==2.1.4 ; python_version >= "3.11" and python_version < "4.0"
passlib[bcrypt]==1.7.4 ; python_version >= "3.11" and python_version < "4.0"
pandas==2.1.4
pluggy==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
psycopg2==2.9.9 ; python_version >= "3.11" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.11" and os_name == "nt" and implementation_name != "pypy" and python_version < "4.0"
Expand All @@ -35,16 +38,21 @@ pydantic==2.5.2 ; python_version >= "3.11" and python_version < "4.0"
pyi18n-v2==1.2.1 ; python_version >= "3.11" and python_version < "4.0"
pyjwt==2.8.0 ; python_version >= "3.11" and python_version < "4.0"
pytest==7.4.3 ; python_version >= "3.11" and python_version < "4.0"
python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "4.0"
python-dotenv==1.0.0 ; python_version >= "3.11" and python_version < "4.0"
python-multipart==0.0.6 ; python_version >= "3.11" and python_version < "4.0"
pytz==2023.3.post1 ; python_version >= "3.11" and python_version < "4.0"
pyyaml==6.0.1 ; python_version >= "3.11" and python_version < "4.0"
requests==2.31.0 ; python_version >= "3.11" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.11" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
sortedcontainers==2.4.0 ; python_version >= "3.11" and python_version < "4.0"
sqlalchemy==2.0.23 ; python_version >= "3.11" and python_version < "4.0"
sqlmodel==0.0.14 ; python_version >= "3.11" and python_version < "4.0"
starlette==0.27.0 ; python_version >= "3.11" and python_version < "4.0"
trio==0.21.0 ; python_version >= "3.11" and python_version < "4.0"
typing-extensions==4.9.0 ; python_version >= "3.11" and python_version < "4.0"
tzdata==2023.3 ; python_version >= "3.11" and python_version < "4.0"
urllib3==2.1.0 ; python_version >= "3.11" and python_version < "4.0"
uvicorn[standard]==0.23.2 ; python_version >= "3.11" and python_version < "4.0"
uvloop==0.19.0 ; (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" and python_version >= "3.11" and python_version < "4.0"
Expand Down

0 comments on commit 4c3cea3

Please sign in to comment.