diff --git a/alembic/versions/08f2a640e9c2_add_uniqueness_contraint_to_environment_.py b/alembic/versions/08f2a640e9c2_add_uniqueness_contraint_to_environment_.py new file mode 100644 index 00000000..56985f73 --- /dev/null +++ b/alembic/versions/08f2a640e9c2_add_uniqueness_contraint_to_environment_.py @@ -0,0 +1,28 @@ +"""add uniqueness contraint to environment within an application + +Revision ID: 08f2a640e9c2 +Revises: c487d91f1a26 +Create Date: 2019-12-16 10:43:12.331095 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '08f2a640e9c2' # pragma: allowlist secret +down_revision = 'c487d91f1a26' # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint('environments_name_application_id_key', 'environments', ['name', 'application_id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('environments_name_application_id_key', 'environments', type_='unique') + # ### end Alembic commands ### diff --git a/atst/domain/environments.py b/atst/domain/environments.py index a1056623..fd5f3eb0 100644 --- a/atst/domain/environments.py +++ b/atst/domain/environments.py @@ -12,6 +12,7 @@ from atst.models import ( CLIN, ) from atst.domain.environment_roles import EnvironmentRoles +from atst.utils import update_or_raise_already_exists_error from .exceptions import NotFoundError, DisabledError @@ -21,7 +22,7 @@ class Environments(object): def create(cls, user, application, name): environment = Environment(application=application, name=name, creator=user) db.session.add(environment) - db.session.commit() + update_or_raise_already_exists_error(message="environment") return environment @classmethod @@ -39,7 +40,8 @@ class Environments(object): if name is not None: environment.name = name db.session.add(environment) - db.session.commit() + update_or_raise_already_exists_error(message="environment") + return environment @classmethod def get(cls, environment_id): diff --git a/atst/models/environment.py b/atst/models/environment.py index 5fc642e5..115f3ed7 100644 --- a/atst/models/environment.py +++ b/atst/models/environment.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, ForeignKey, String, TIMESTAMP +from sqlalchemy import Column, ForeignKey, String, TIMESTAMP, UniqueConstraint from sqlalchemy.orm import relationship from sqlalchemy.dialects.postgresql import JSONB from enum import Enum @@ -38,6 +38,12 @@ class Environment( primaryjoin="and_(EnvironmentRole.environment_id == Environment.id, EnvironmentRole.deleted == False)", ) + __table_args__ = ( + UniqueConstraint( + "name", "application_id", name="environments_name_application_id_key" + ), + ) + class ProvisioningStatus(Enum): PENDING = "pending" COMPLETED = "completed" diff --git a/tests/domain/test_environments.py b/tests/domain/test_environments.py index 3aa5f547..298f2675 100644 --- a/tests/domain/test_environments.py +++ b/tests/domain/test_environments.py @@ -4,7 +4,7 @@ from uuid import uuid4 from atst.domain.environments import Environments from atst.domain.environment_roles import EnvironmentRoles -from atst.domain.exceptions import NotFoundError, DisabledError +from atst.domain.exceptions import AlreadyExistsError, DisabledError, NotFoundError from atst.models.environment_role import CSPRole, EnvironmentRole from tests.factories import ( @@ -100,6 +100,27 @@ def test_update_environment(): assert environment.name == "name 2" +def test_create_does_not_duplicate_names_within_application(): + application = ApplicationFactory.create() + name = "Your Environment" + user = application.portfolio.owner + + assert Environments.create(user, application, name) + with pytest.raises(AlreadyExistsError): + Environments.create(user, application, name) + + +def test_update_does_not_duplicate_names_within_application(): + application = ApplicationFactory.create() + name = "Your Environment" + environment = EnvironmentFactory.create(application=application, name=name) + dupe_env = EnvironmentFactory.create(application=application) + user = application.portfolio.owner + + with pytest.raises(AlreadyExistsError): + Environments.update(dupe_env, name) + + class EnvQueryTest: @property def NOW(self):