Skip to content

Commit

Permalink
Merge pull request #15 from UoA-eResearch/add-factories
Browse files Browse the repository at this point in the history
add factories for models
  • Loading branch information
JLoveUOA authored Dec 9, 2024
2 parents eb0f329 + 94679e5 commit e654816
Show file tree
Hide file tree
Showing 7 changed files with 474 additions and 183 deletions.
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[MASTER]
ignore-paths="^tests/.*$"
ignore-paths="^tests/.*$"
init-hook='import sys; sys.path.append("src")'
423 changes: 243 additions & 180 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fastapi = {extras = ["standard"], version = "^0.115.0"}
sqlmodel = "^0.0.22"
sqlalchemy = "^2.0.36"
bagit = "^1.8.1"
factory-boy = "^3.3.1"


[tool.poetry.group.dev.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/models/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from models.services import ResearchDriveService


class DriveOffboardSubmission(SQLModel):
class DriveOffboardSubmission(SQLModel, table=True):
"""Model that represents a user's submission in the drive
offboarding process retrieved."""

Expand Down
182 changes: 182 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
# pylint: disable=missing-class-docstring,too-few-public-methods,missing-module-docstring,missing-module-docstring
# factory meta classes don't need docstrings
import random
from datetime import datetime
from typing import Any

import factory
import pytest
from factory.alchemy import SQLAlchemyModelFactory
from sqlalchemy import create_engine
from sqlmodel import Session, SQLModel
from sqlmodel.pool import StaticPool

from models.common import DataClassification
from models.member import Member
from models.person import Person
from models.project import Code, Project
from models.role import Role, prepopulate_roles
from models.services import ResearchDriveService
from models.submission import DriveOffboardSubmission

ROLES = prepopulate_roles()


def random_role() -> Role:
"Choose a random role from the prepoulated roles"
return random.choice(ROLES)


@pytest.fixture(name="session")
def session_fixture() -> Session:
"scoped session for each unit test"
engine = create_engine(
"sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
session.rollback()
session.close()


@pytest.fixture
Expand All @@ -21,3 +55,151 @@ def submission() -> dict[str, Any]:
"is_project_updated": True,
"drive_id": 1,
}


@pytest.fixture
def person_factory(session: Session) -> SQLAlchemyModelFactory:
"fixture for person factories"

class PersonFactory(SQLAlchemyModelFactory):
"""Factory for generating test person objects"""

class Meta:
model = Person
sqlalchemy_session = session

id: int = factory.sequence(lambda n: n)
email = random.choice([factory.Faker("email"), None])
full_name = factory.Faker("name")
username = factory.Faker(
"bothify", text="????###", letters="abcdefghijklmnopqrstuvwxyz"
)

return PersonFactory


@pytest.fixture
def drive_offboard_submission_factory(session: Session) -> SQLAlchemyModelFactory:
"fixture for DriveOffboardSubmission factories"

class DriveOffboardSubmissionFactory(SQLAlchemyModelFactory):
"""factory for generating test submission objects"""

class Meta:
model = DriveOffboardSubmission
sqlalchemy_session = session

retention_period_years = random.choice(
[6, 10, 20, 26, random.randrange(7, 100)]
)
retention_period_justification = random.choice(
[factory.Faker("sentence"), None]
)
data_classification = random.choice(list(DataClassification))
is_completed = True
updated_time = factory.Faker("date_time")
is_project_updated = False

return DriveOffboardSubmissionFactory


@pytest.fixture
def research_drive_service_factory(
session: Session, drive_offboard_submission_factory: SQLAlchemyModelFactory
) -> SQLAlchemyModelFactory:
"fixture for research drive service factories"

class ResearchDriveServiceFactory(SQLAlchemyModelFactory):
"""factory for generating test person objects"""

class Meta:
model = ResearchDriveService
sqlalchemy_session = session

id: int = factory.sequence(lambda n: n)

date = factory.Faker("date_time")
first_day = factory.Faker("date_time")
free_gb = random.random() * random.randrange(1, 5000)
used_gb = random.random() * random.randrange(1, 5000)
allocated_gb = free_gb + used_gb
percentage_used = used_gb / (allocated_gb)
last_day = random.choice([factory.Faker("date_time"), None])
name = factory.Faker(
"bothify",
text="res???#########-????????",
letters="abcdefghijklmnopqrstuvwxyz",
)
submission = factory.SubFactory(drive_offboard_submission_factory)

return ResearchDriveServiceFactory


@pytest.fixture
def member_factory(
session: Session, person_factory: SQLAlchemyModelFactory
) -> SQLAlchemyModelFactory:
"fixture wrapping a member factory"

class MemberFactory(SQLAlchemyModelFactory):
"""factory for generating test member objects"""

class Meta:
model = Member
sqlalchemy_session = session

role = random_role()
project = None
person = factory.SubFactory(person_factory)

return MemberFactory


@pytest.fixture
def code_factory(session: Session) -> SQLAlchemyModelFactory:
"fixture for Code Factories"

class CodeFactory(SQLAlchemyModelFactory):
"""factory for generating test code objects"""

class Meta:
model = Code
sqlalchemy_session = session

code = factory.Faker("bothify", text="????????#####")

return CodeFactory


@pytest.fixture
def project_factory(
session: Session,
member_factory: SQLAlchemyModelFactory,
code_factory: SQLAlchemyModelFactory,
research_drive_service_factory: SQLAlchemyModelFactory,
) -> SQLAlchemyModelFactory:
"fixture for project Factories"

class ProjectFactory(SQLAlchemyModelFactory):
"""factory for generating test project objects"""

class Meta:
model = Project
sqlalchemy_session = session

title = factory.Faker("sentence")
description = factory.Faker("paragraph")
division = factory.Faker("company")
start_date = factory.Faker("date_time")
end_date = factory.Faker("date_time")
members = factory.List(
[factory.SubFactory(member_factory) for _ in range(random.randrange(0, 10))]
)
codes = factory.List(
[factory.SubFactory(code_factory) for _ in range(random.randrange(0, 5))]
)
research_drives = factory.List(
[factory.SubFactory(research_drive_service_factory)]
)

return ProjectFactory
44 changes: 44 additions & 0 deletions tests/test_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"functions and classes for testing the factories for populating classes"
from factory.alchemy import SQLAlchemyModelFactory
from sqlmodel import Session, select

from models.project import Project


def test_factories_all( # pylint: disable=too-many-arguments,too-many-positional-arguments
person_factory: SQLAlchemyModelFactory,
member_factory: SQLAlchemyModelFactory,
code_factory: SQLAlchemyModelFactory,
research_drive_service_factory: SQLAlchemyModelFactory,
drive_offboard_submission_factory: SQLAlchemyModelFactory,
project_factory: SQLAlchemyModelFactory,
) -> None:
"Test the basic functionality of all factories"
person_factory.build()
member_factory.build()
code_factory.build()
research_drive_service_factory.build()
drive_offboard_submission_factory.build()
project_factory.build()


def test_project_retrieval(
project_factory: SQLAlchemyModelFactory, session: Session
) -> None:
"test factories create within DB correctly"
project = project_factory.create(title="test12345")

project_query = select(Project).where(Project.title == "test12345")
project_found = session.exec(project_query).first()
assert project_found == project


def test_project_session_scope(
project_factory: SQLAlchemyModelFactory, session: Session
) -> None:
"check session scope is only within each unit test for factories"
project_factory.create(title="test54321")

project_query = select(Project).where(Project.title == "test12345")
project_found = session.exec(project_query).first()
assert project_found is None
2 changes: 1 addition & 1 deletion tests/test_submission_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
from models.submission import DriveOffboardSubmission


def test_submission_can_create(submission: dict[str, Any]):
def test_submission_can_create(submission: dict[str, Any]) -> None:
"""Tests whether a submission instance can be made"""
DriveOffboardSubmission.model_validate(submission)

0 comments on commit e654816

Please sign in to comment.