diff --git a/api/db_migrations/versions/eb58f2b364a3_create_scans_table.py b/api/db_migrations/versions/eb58f2b364a3_create_scans_table.py new file mode 100644 index 00000000..91122ce8 --- /dev/null +++ b/api/db_migrations/versions/eb58f2b364a3_create_scans_table.py @@ -0,0 +1,44 @@ +"""create scans table + +Revision ID: eb58f2b364a3 +Revises: db98eafe1333 +Create Date: 2021-08-24 18:35:42.224567 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "eb58f2b364a3" +down_revision = "db98eafe1333" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "scans", + sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), + sa.Column("organisation_id", postgresql.UUID(as_uuid=True), nullable=False), + 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("created_at", sa.DateTime, default=sa.func.utc_timestamp()), + sa.Column("updated_at", sa.DateTime, onupdate=sa.func.utc_timestamp()), + sa.ForeignKeyConstraint( + ["organisation_id"], + ["organisations.id"], + ), + sa.ForeignKeyConstraint( + ["template_id"], + ["templates.id"], + ), + sa.ForeignKeyConstraint( + ["scan_type_id"], + ["scan_types.id"], + ), + ) + + +def downgrade(): + op.drop_table("scans") diff --git a/api/models/Organisation.py b/api/models/Organisation.py index bfd175e1..cbb1d94e 100644 --- a/api/models/Organisation.py +++ b/api/models/Organisation.py @@ -29,6 +29,7 @@ class Organisation(Base): onupdate=datetime.datetime.utcnow, ) + scans = relationship("Scan") templates = relationship("Template") users = relationship("User") diff --git a/api/models/Scan.py b/api/models/Scan.py new file mode 100644 index 00000000..0e7b9250 --- /dev/null +++ b/api/models/Scan.py @@ -0,0 +1,43 @@ +import datetime +import uuid + +from sqlalchemy import DateTime, Column, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from models import Base +from models.Organisation import Organisation +from models.ScanType import ScanType +from models.Template import Template + + +class Scan(Base): + __tablename__ = "scans" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + 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, + ) + organisation_id = Column( + UUID(as_uuid=True), ForeignKey(Organisation.id), index=True, nullable=False + ) + organisation = relationship("Organisation", back_populates="scans") + template_id = Column( + UUID(as_uuid=True), ForeignKey(Template.id), index=True, nullable=False + ) + template = relationship("Template", back_populates="scans") + scan_type_id = Column( + UUID(as_uuid=True), ForeignKey(ScanType.id), index=True, nullable=False + ) + scan_type = relationship("ScanType", back_populates="scans") diff --git a/api/models/ScanType.py b/api/models/ScanType.py index cf052b23..15f24f67 100644 --- a/api/models/ScanType.py +++ b/api/models/ScanType.py @@ -29,6 +29,7 @@ class ScanType(Base): onupdate=datetime.datetime.utcnow, ) + scans = relationship("Scan") template_scans = relationship("TemplateScan") @validates("name") diff --git a/api/models/Template.py b/api/models/Template.py index 448c7591..657daf03 100644 --- a/api/models/Template.py +++ b/api/models/Template.py @@ -34,6 +34,7 @@ class Template(Base): ) organisation = relationship("Organisation", back_populates="templates") + scans = relationship("Scan") template_scans = relationship("TemplateScan") @validates("name") diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 000d9972..bf11b9bf 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -26,7 +26,7 @@ def f(model): @pytest.fixture(scope="session") def organisation_fixture(session): - organisation = Organisation(name="name") + organisation = Organisation(name="fixture_name") session.add(organisation) return organisation @@ -53,14 +53,14 @@ def setup_db(): @pytest.fixture(scope="session") def scan_type_fixture(session): - scan_type = ScanType(name="name") + scan_type = ScanType(name="fixture_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) + template = Template(name="fixture_name", organisation=organisation_fixture) session.add(template) return template diff --git a/api/tests/models/test_Scan.py b/api/tests/models/test_Scan.py new file mode 100644 index 00000000..b4fa9f9a --- /dev/null +++ b/api/tests/models/test_Scan.py @@ -0,0 +1,84 @@ +import pytest + +from sqlalchemy.exc import IntegrityError + +from models.Scan import Scan + + +def test_scan_belongs_to_a_template_a_scan_type_and_an_organisation( + scan_type_fixture, template_fixture, organisation_fixture, session +): + scan = Scan( + organisation=organisation_fixture, + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(scan) + session.commit() + assert organisation_fixture.scans[-1].id == scan.id + assert scan_type_fixture.scans[-1].id == scan.id + assert template_fixture.scans[-1].id == scan.id + session.delete(scan) + session.commit() + + +def test_scan_model(scan_type_fixture, template_fixture, organisation_fixture): + scan = Scan( + organisation=organisation_fixture, + scan_type=scan_type_fixture, + template=template_fixture, + ) + assert scan.scan_type is not None + assert scan.template is not None + + +def test_scan_model_saved( + assert_new_model_saved, + scan_type_fixture, + template_fixture, + organisation_fixture, + session, +): + scan = Scan( + organisation=organisation_fixture, + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(scan) + session.commit() + assert_new_model_saved(scan) + session.delete(scan) + session.commit() + + +def test_scan_empty_template_fails(scan_type_fixture, organisation_fixture, session): + scan = Scan( + organisation=organisation_fixture, + scan_type=scan_type_fixture, + ) + session.add(scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() + + +def test_scan_empty_scan_type_fails(template_fixture, organisation_fixture, session): + scan = Scan( + organisation=organisation_fixture, + template=template_fixture, + ) + session.add(scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() + + +def test_scan_empty_organisation_fails(template_fixture, scan_type_fixture, session): + scan = Scan( + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(scan) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() diff --git a/api/tests/models/test_Template.py b/api/tests/models/test_Template.py index 811fd9b3..17c39c8d 100644 --- a/api/tests/models/test_Template.py +++ b/api/tests/models/test_Template.py @@ -12,7 +12,7 @@ def test_template_belongs_to_an_organisation(organisation_fixture, session): ) session.add(template) session.commit() - assert organisation_fixture.templates[0].id == template.id + assert organisation_fixture.templates[-1].id == template.id session.delete(template) session.commit() diff --git a/api/tests/models/test_TemplateScan.py b/api/tests/models/test_TemplateScan.py index a194b405..ecae2859 100644 --- a/api/tests/models/test_TemplateScan.py +++ b/api/tests/models/test_TemplateScan.py @@ -5,7 +5,7 @@ from models.TemplateScan import TemplateScan -def test_template_scan_belongs_to_an_template( +def test_template_scan_belongs_to_a_template_and_a_scan_type( scan_type_fixture, template_fixture, session ): template_scan = TemplateScan( @@ -15,7 +15,8 @@ def test_template_scan_belongs_to_an_template( ) session.add(template_scan) session.commit() - assert template_fixture.template_scans[0].id == template_scan.id + assert template_fixture.template_scans[-1].id == template_scan.id + assert scan_type_fixture.template_scans[-1].id == template_scan.id session.delete(template_scan) session.commit() @@ -27,6 +28,7 @@ def test_template_scan_model(scan_type_fixture, template_fixture): template=template_fixture, ) assert template_scan.data == {"jsonb": "data"} + assert template_scan.scan_type is not None assert template_scan.template is not None diff --git a/api/tests/models/test_TemplateScanTrigger.py b/api/tests/models/test_TemplateScanTrigger.py index 1c66744d..1e9691ed 100644 --- a/api/tests/models/test_TemplateScanTrigger.py +++ b/api/tests/models/test_TemplateScanTrigger.py @@ -5,7 +5,7 @@ from models.TemplateScanTrigger import TemplateScanTrigger -def test_template_scan_trigger_belongs_to_an_template_scan( +def test_template_scan_trigger_belongs_to_a_template_scan( template_scan_fixture, session ): template_scan_trigger = TemplateScanTrigger( diff --git a/api/tests/models/test_User.py b/api/tests/models/test_User.py index d0d4e20a..4689929d 100644 --- a/api/tests/models/test_User.py +++ b/api/tests/models/test_User.py @@ -15,7 +15,7 @@ def test_user_belongs_to_an_organisation(organisation_fixture, session): ) session.add(user) session.commit() - assert organisation_fixture.users[0].id == user.id + assert organisation_fixture.users[-1].id == user.id session.delete(user) session.commit()