diff --git a/api/db_migrations/versions/06b310a6251e_create_organisations_table.py b/api/db_migrations/versions/06b310a6251e_create_organisations_table.py index d25ebae4..3e09cfc3 100644 --- a/api/db_migrations/versions/06b310a6251e_create_organisations_table.py +++ b/api/db_migrations/versions/06b310a6251e_create_organisations_table.py @@ -22,7 +22,7 @@ def upgrade(): "organisations", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), sa.Column("name", sa.Unicode(255), nullable=False, unique=True), - sa.Column("created_at", sa.DateTime, default=sa.func.now()), + sa.Column("created_at", sa.DateTime, default=sa.func.utc_timestamp()), sa.Column("updated_at", sa.DateTime, onupdate=sa.func.utc_timestamp()), ) diff --git a/api/db_migrations/versions/b9ab107cd56a_create_users_table.py b/api/db_migrations/versions/b9ab107cd56a_create_users_table.py index 2b4e300d..ecf81661 100644 --- a/api/db_migrations/versions/b9ab107cd56a_create_users_table.py +++ b/api/db_migrations/versions/b9ab107cd56a_create_users_table.py @@ -28,7 +28,7 @@ def upgrade(): sa.Column( "access_token", postgresql.UUID(as_uuid=True), nullable=False, unique=True ), - sa.Column("created_at", sa.DateTime, default=sa.func.now()), + 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"], diff --git a/api/db_migrations/versions/bf6d44121dce_create_templates_table.py b/api/db_migrations/versions/bf6d44121dce_create_templates_table.py index 9023f535..412f9486 100644 --- a/api/db_migrations/versions/bf6d44121dce_create_templates_table.py +++ b/api/db_migrations/versions/bf6d44121dce_create_templates_table.py @@ -23,7 +23,7 @@ def upgrade(): sa.Column("organisation_id", postgresql.UUID(as_uuid=True), nullable=False), sa.Column("token", postgresql.UUID(as_uuid=True), nullable=False, unique=True), sa.Column("name", sa.String(), nullable=False), - sa.Column("created_at", sa.DateTime, default=sa.func.now()), + 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"], diff --git a/api/db_migrations/versions/da6527f6cd76_create_scan_types_table.py b/api/db_migrations/versions/da6527f6cd76_create_scan_types_table.py index 29ed7813..64b41379 100644 --- a/api/db_migrations/versions/da6527f6cd76_create_scan_types_table.py +++ b/api/db_migrations/versions/da6527f6cd76_create_scan_types_table.py @@ -22,7 +22,7 @@ def upgrade(): "scan_types", sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), sa.Column("name", sa.Unicode(255), nullable=False, unique=True), - sa.Column("created_at", sa.DateTime, default=sa.func.now()), + sa.Column("created_at", sa.DateTime, default=sa.func.utc_timestamp()), sa.Column("updated_at", sa.DateTime, onupdate=sa.func.utc_timestamp()), ) diff --git a/api/db_migrations/versions/db98eafe1333_create_template_scan_triggers_table.py b/api/db_migrations/versions/db98eafe1333_create_template_scan_triggers_table.py new file mode 100644 index 00000000..bc3afb5b --- /dev/null +++ b/api/db_migrations/versions/db98eafe1333_create_template_scan_triggers_table.py @@ -0,0 +1,35 @@ +"""create template scan triggers table + +Revision ID: db98eafe1333 +Revises: e6ec4f01db2f +Create Date: 2021-08-24 17:21:59.537651 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "db98eafe1333" +down_revision = "e6ec4f01db2f" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "template_scan_triggers", + sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), + sa.Column("template_scan_id", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("callback", postgresql.JSONB(), 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( + ["template_scan_id"], + ["template_scans.id"], + ), + ) + + +def downgrade(): + op.drop_table("template_scan_triggers") diff --git a/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py b/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py index 7a08a934..4b6973b8 100644 --- a/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py +++ b/api/db_migrations/versions/e6ec4f01db2f_create_template_scans_table.py @@ -23,7 +23,7 @@ def upgrade(): 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("created_at", sa.DateTime, default=sa.func.utc_timestamp()), sa.Column("updated_at", sa.DateTime, onupdate=sa.func.utc_timestamp()), sa.ForeignKeyConstraint( ["template_id"], diff --git a/api/models/TemplateScan.py b/api/models/TemplateScan.py index 23c2aa2b..20c1a360 100644 --- a/api/models/TemplateScan.py +++ b/api/models/TemplateScan.py @@ -38,7 +38,9 @@ class TemplateScan(Base): ) scan_type = relationship("ScanType", back_populates="template_scans") + template_scan_triggers = relationship("TemplateScanTrigger") + @validates("data") - def validate_name(self, _key, value): + def validate_data(self, _key, value): assert value != "" return value diff --git a/api/models/TemplateScanTrigger.py b/api/models/TemplateScanTrigger.py new file mode 100644 index 00000000..f42e7b5e --- /dev/null +++ b/api/models/TemplateScanTrigger.py @@ -0,0 +1,41 @@ +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.TemplateScan import TemplateScan + + +class TemplateScanTrigger(Base): + __tablename__ = "template_scan_triggers" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + callback = 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_scan_id = Column( + UUID(as_uuid=True), ForeignKey(TemplateScan.id), index=True, nullable=False + ) + template_scan = relationship( + "TemplateScan", back_populates="template_scan_triggers" + ) + + @validates("callback") + def validate_callback(self, _key, value): + assert value != "" + return value diff --git a/api/tests/conftest.py b/api/tests/conftest.py index d6576f59..000d9972 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -7,6 +7,7 @@ from models.Organisation import Organisation from models.ScanType import ScanType from models.Template import Template +from models.TemplateScan import TemplateScan from sqlalchemy import create_engine @@ -62,3 +63,14 @@ def template_fixture(session, organisation_fixture): template = Template(name="name", organisation=organisation_fixture) session.add(template) return template + + +@pytest.fixture(scope="session") +def template_scan_fixture(scan_type_fixture, template_fixture, session): + template_scan = TemplateScan( + data={"jsonb": "data"}, + scan_type=scan_type_fixture, + template=template_fixture, + ) + session.add(template_scan) + return template_scan diff --git a/api/tests/models/test_TemplateScanTrigger.py b/api/tests/models/test_TemplateScanTrigger.py new file mode 100644 index 00000000..1c66744d --- /dev/null +++ b/api/tests/models/test_TemplateScanTrigger.py @@ -0,0 +1,65 @@ +import pytest + +from sqlalchemy.exc import IntegrityError + +from models.TemplateScanTrigger import TemplateScanTrigger + + +def test_template_scan_trigger_belongs_to_an_template_scan( + template_scan_fixture, session +): + template_scan_trigger = TemplateScanTrigger( + callback={"jsonb": "data"}, + template_scan=template_scan_fixture, + ) + session.add(template_scan_trigger) + session.commit() + assert ( + template_scan_fixture.template_scan_triggers[0].id == template_scan_trigger.id + ) + session.delete(template_scan_trigger) + session.commit() + + +def test_template_scan_trigger_model(template_scan_fixture): + template_scan_trigger = TemplateScanTrigger( + callback={"jsonb": "data"}, + template_scan=template_scan_fixture, + ) + assert template_scan_trigger.callback == {"jsonb": "data"} + assert template_scan_trigger.template_scan is not None + + +def test_template_scan_trigger_model_saved( + assert_new_model_saved, template_scan_fixture, session +): + template_scan_trigger = TemplateScanTrigger( + callback={"jsonb": "data"}, + template_scan=template_scan_fixture, + ) + session.add(template_scan_trigger) + session.commit() + assert template_scan_trigger.callback == {"jsonb": "data"} + assert_new_model_saved(template_scan_trigger) + session.delete(template_scan_trigger) + session.commit() + + +def test_template_scan_trigger_empty_data_fails(template_scan_fixture, session): + template_scan_trigger = TemplateScanTrigger( + template_scan=template_scan_fixture, + ) + session.add(template_scan_trigger) + with pytest.raises(IntegrityError): + session.commit() + session.rollback() + + +def test_template_scan_trigger_empty_template_scan_fails(session): + template_scan_trigger = TemplateScanTrigger( + callback={"jsonb": "data"}, + ) + session.add(template_scan_trigger) + with pytest.raises(IntegrityError): + session.commit() + session.rollback()