diff --git a/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py b/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py new file mode 100644 index 00000000..7a08a934 --- /dev/null +++ b/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py @@ -0,0 +1,40 @@ +"""create template scans table + +Revision ID: e6ec4f01db2f +Revises: bf6d44121dce +Create Date: 2021-08-24 13:59:34.800116 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "e6ec4f01db2f" +down_revision = "bf6d44121dce" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "template_scans", + sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), + sa.Column("template_id", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("scan_type_id", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("data", postgresql.JSONB(), nullable=False), + sa.Column("created_at", sa.DateTime, default=sa.func.now()), + sa.Column("updated_at", sa.DateTime, onupdate=sa.func.utc_timestamp()), + sa.ForeignKeyConstraint( + ["template_id"], + ["templates.id"], + ), + sa.ForeignKeyConstraint( + ["scan_type_id"], + ["scan_types.id"], + ), + ) + + +def downgrade(): + op.drop_table("template_scans") diff --git a/api/models/ScanType.py b/api/models/ScanType.py index 420c27ce..cf052b23 100644 --- a/api/models/ScanType.py +++ b/api/models/ScanType.py @@ -3,7 +3,7 @@ from sqlalchemy import DateTime, Column, String from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import validates +from sqlalchemy.orm import relationship, validates from models import Base @@ -29,6 +29,8 @@ class ScanType(Base): onupdate=datetime.datetime.utcnow, ) + template_scans = relationship("TemplateScan") + @validates("name") def validate_name(self, _key, value): assert value != "" diff --git a/api/models/Template.py b/api/models/Template.py index 91bedb9b..448c7591 100644 --- a/api/models/Template.py +++ b/api/models/Template.py @@ -34,6 +34,8 @@ class Template(Base): ) organisation = relationship("Organisation", back_populates="templates") + template_scans = relationship("TemplateScan") + @validates("name") def validate_name(self, _key, value): assert value != "" diff --git a/api/models/TemplateScan.py b/api/models/TemplateScan.py new file mode 100644 index 00000000..23c2aa2b --- /dev/null +++ b/api/models/TemplateScan.py @@ -0,0 +1,44 @@ +import datetime +import uuid + +from sqlalchemy import DateTime, Column, ForeignKey +from sqlalchemy.dialects.postgresql import JSONB, UUID +from sqlalchemy.orm import relationship, validates + +from models import Base +from models.ScanType import ScanType +from models.Template import Template + + +class TemplateScan(Base): + __tablename__ = "template_scans" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + data = Column(JSONB, nullable=False) + created_at = Column( + DateTime, + index=False, + unique=False, + nullable=False, + default=datetime.datetime.utcnow, + ) + updated_at = Column( + DateTime, + index=False, + unique=False, + nullable=True, + onupdate=datetime.datetime.utcnow, + ) + template_id = Column( + UUID(as_uuid=True), ForeignKey(Template.id), index=True, nullable=False + ) + template = relationship("Template", back_populates="template_scans") + scan_type_id = Column( + UUID(as_uuid=True), ForeignKey(ScanType.id), index=True, nullable=False + ) + scan_type = relationship("ScanType", back_populates="template_scans") + + @validates("data") + def validate_name(self, _key, value): + assert value != "" + return value diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 31428966..d6576f59 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -5,6 +5,9 @@ from alembic import command from models.Organisation import Organisation +from models.ScanType import ScanType +from models.Template import Template + from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -45,3 +48,17 @@ def setup_db(): command.upgrade(alembic_cfg, "head") yield + + +@pytest.fixture(scope="session") +def scan_type_fixture(session): + scan_type = ScanType(name="name") + session.add(scan_type) + return scan_type + + +@pytest.fixture(scope="session") +def template_fixture(session, organisation_fixture): + template = Template(name="name", organisation=organisation_fixture) + session.add(template) + return template diff --git a/api/tests/models/test_TemplateScan.py b/api/tests/models/test_TemplateScan.py new file mode 100644 index 00000000..a194b405 --- /dev/null +++ b/api/tests/models/test_TemplateScan.py @@ -0,0 +1,79 @@ +import pytest + +from sqlalchemy.exc import IntegrityError + +from models.TemplateScan import TemplateScan + + +def test_template_scan_belongs_to_an_template( + scan_type_fixture, template_fixture, session +): + template_scan = TemplateScan( + data={"jsonb": "data"}, + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(template_scan) + session.commit() + assert template_fixture.template_scans[0].id == template_scan.id + session.delete(template_scan) + session.commit() + + +def test_template_scan_model(scan_type_fixture, template_fixture): + template_scan = TemplateScan( + data={"jsonb": "data"}, + scan_type=scan_type_fixture, + template=template_fixture, + ) + assert template_scan.data == {"jsonb": "data"} + assert template_scan.template is not None + + +def test_template_scan_model_saved( + assert_new_model_saved, scan_type_fixture, template_fixture, session +): + template_scan = TemplateScan( + data={"jsonb": "data"}, + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(template_scan) + session.commit() + assert template_scan.data == {"jsonb": "data"} + assert_new_model_saved(template_scan) + session.delete(template_scan) + session.commit() + + +def test_template_scan_empty_data_fails(scan_type_fixture, template_fixture, session): + template_scan = TemplateScan( + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(template_scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() + + +def test_template_scan_empty_template_fails(scan_type_fixture, session): + template_scan = TemplateScan( + data={"jsonb": "data"}, + scan_type=scan_type_fixture, + ) + session.add(template_scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() + + +def test_template_scan_empty_scan_type_fails(template_fixture, session): + template_scan = TemplateScan( + data={"jsonb": "data"}, + template=template_fixture, + ) + session.add(template_scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback()