diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..59d5c5e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: SQLAlchemy Model Tests + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + run_test_suite: + name: Run Test Suite + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + # Match the Python version specified in pyproject.toml + python-version: "3.10" + cache: "pip" + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run Test Suite + # GitHub's Ubuntu runners come with Docker pre-installed so the testcontainers + # package should work out of the box. + run: | + pytest tests/ diff --git a/README.md b/README.md index fd5ebc8..4197ad9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,75 @@ # database-schema -CARPI MySQL database schemas. + +This repository is a Python package that contains SQLAlchemy data models for use in the CARPI Course Planner project. + +## Installing as a Dependency + +In your project's dependency list (often named `requirements.txt` or similar), add the following line: + +``` +carpi-data-model @ git+https://github.com/Project-CARPI/database-schema.git +``` + +If installing directly on the command line using pip, use the following command: + +```bash +pip3 install git+https://github.com/Project-CARPI/database-schema.git +``` + +## Local Development & Testing + +Along with the core SQLAlchemy models, this repository includes a PyTest-based test suite. + +### Prerequisites + +- **Python >= 3.10**: If you don't have it installed, [download it here.](https://www.python.org/) +- **Docker**: Required to automatically spin up temporary MySQL containers during testing. [Download Docker Desktop here.](https://www.docker.com/products/docker-desktop/) + +### Setup Instructions + +**1. Set Up a Virtual Environment** +To avoid cluttering your global Python environment, create a virtual environment in the project root: + +```bash +# Create a virtual environment directory named .venv +python -m venv .venv +``` + +**2. Activate the Environment** +Depending on your operating system, activate the virtual environment: + +- **Windows:** + ```powershell + .venv\Scripts\activate + ``` +- **macOS / Linux:** + ```bash + source .venv/bin/activate + ``` + +You will see a `(.venv)` prefix in your terminal prompt when the environment is successfully active: + +```bash +(.venv) raymond@Macbook-Pro database-schema % +``` + +**3. Install Required Dependencies** +With the virtual environment active, install the required packages for testing: + +```bash +pip install -r requirements.txt +``` + +### Running the Tests + +Once your environment is set up and **Docker is running** in the background, you can execute the test suite using pytest: + +```bash +pytest tests/ +``` + +To exit the virtual environment when you are finished, run the deactivate command: + +```bash +deactivate +``` diff --git a/pyproject.toml b/pyproject.toml index cbc5619..4b9fd90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "carpi-data-model" version = "1.0" dependencies = ["SQLAlchemy>=2.0.0"] -requires-python = ">=3.7" +requires-python = ">=3.10" authors = [{ name = "Raymond Chen" }, { name = "Jack Zgombic" }] maintainers = [{ name = "Raymond Chen" }] description = "The MySQL database schema used in Project CARPI." diff --git a/requirements.txt b/requirements.txt index 27f8f50..853de12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,25 @@ -SQLAlchemy==2.0.44 +# Self-dependency that allows PyTest to import the package being tested. +# +# As an "editable" dependency, Python will automatically track changes to the source code +# without needing to reinstall the package after every change, which is ideal for +# development environments. +-e . + +# Other third-party dependencies +certifi==2026.2.25 +charset-normalizer==3.4.7 +docker==7.1.0 +idna==3.11 +iniconfig==2.3.0 +mysql-connector-python==9.6.0 +packaging==26.0 +pluggy==1.6.0 +Pygments==2.20.0 +pytest==9.0.3 +python-dotenv==1.2.2 +requests==2.33.1 +SQLAlchemy==2.0.49 +testcontainers==4.14.2 typing_extensions==4.15.0 +urllib3==2.6.3 +wrapt==2.1.2 diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..d3a4b68 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,80 @@ +import pytest +from sqlalchemy import create_engine, inspect +from sqlalchemy.orm import Session +from testcontainers.mysql import MySqlContainer + +from carpi_data_model.models import ( + Attribute, + Base, + Course, + Course_Attribute, + Course_Faculty, + Course_Offering, + Course_Relationship, + Course_Restriction, + Faculty, + Restriction, + Subject, +) + + +@pytest.fixture(scope="session") +def engine(): + """ + Spin up a temporary MySQL Docker container for the whole test session. This + requires Docker to be running on your machine. + """ + with MySqlContainer("mysql:8.0", dialect="mysqlconnector") as mysql: + db_engine = create_engine(mysql.get_connection_url(), echo=False) + try: + yield db_engine + finally: + db_engine.dispose() + + +@pytest.fixture(autouse=True) +def setup_database(engine): + """ + Creates all tables before each test and drops them after to ensure a clean + state for every test. + """ + Base.metadata.create_all(bind=engine) + yield + Base.metadata.drop_all(bind=engine) + + +def test_create_tables(engine): + """ + Test that the SQLAlchemy schema can be translated into DDL and executed + in a Native MySQL database without throwing any mapping or constraint + errors. Ensures the tables actually exist in the database catalog. + """ + inspector = inspect(engine) + existing_tables = inspector.get_table_names() + + assert Subject.__tablename__ in existing_tables + assert Attribute.__tablename__ in existing_tables + assert Restriction.__tablename__ in existing_tables + assert Faculty.__tablename__ in existing_tables + assert Course.__tablename__ in existing_tables + assert Course_Attribute.__tablename__ in existing_tables + assert Course_Relationship.__tablename__ in existing_tables + assert Course_Restriction.__tablename__ in existing_tables + assert Course_Offering.__tablename__ in existing_tables + assert Course_Faculty.__tablename__ in existing_tables + + +def test_insert_and_query(engine): + """ + Test basic database operations (insert and query) on a table to ensure the + ORM definitions functionally work. + """ + with Session(engine) as session: + subj = Subject(subj_code="CSCI", title="Computer Science") + session.add(subj) + session.commit() + + fetched = session.query(Subject).filter_by(subj_code="CSCI").first() + assert fetched is not None + assert fetched.subj_code == "CSCI" + assert fetched.title == "Computer Science"