From ef153f5226faf084aac8351b6345d6b8990b4a88 Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 16 Aug 2018 09:51:35 -0400 Subject: [PATCH] basic workspace model and repository implementation --- .../4be312655ceb_add_workspaces_table.py | 37 +++++++++++++++ atst/domain/workspaces.py | 46 ++++++++++++++----- atst/models/__init__.py | 1 + atst/models/workspace.py | 19 ++++++++ tests/domain/test_workspaces.py | 36 +++++++++++++++ tests/factories.py | 11 +++++ 6 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 alembic/versions/4be312655ceb_add_workspaces_table.py create mode 100644 atst/models/workspace.py create mode 100644 tests/domain/test_workspaces.py diff --git a/alembic/versions/4be312655ceb_add_workspaces_table.py b/alembic/versions/4be312655ceb_add_workspaces_table.py new file mode 100644 index 00000000..6d0ba7d9 --- /dev/null +++ b/alembic/versions/4be312655ceb_add_workspaces_table.py @@ -0,0 +1,37 @@ +"""add workspaces table + +Revision ID: 4be312655ceb +Revises: 05d6272bdb43 +Create Date: 2018-08-16 09:25:19.888549 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4be312655ceb' +down_revision = '05d6272bdb43' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('workspaces', + sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False), + sa.Column('request_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('task_order_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['request_id'], ['requests.id'], ), + sa.ForeignKeyConstraint(['task_order_id'], ['task_order.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('workspaces') + # ### end Alembic commands ### diff --git a/atst/domain/workspaces.py b/atst/domain/workspaces.py index 64d7ce50..f062431e 100644 --- a/atst/domain/workspaces.py +++ b/atst/domain/workspaces.py @@ -1,23 +1,44 @@ +from sqlalchemy.orm.exc import NoResultFound + +from atst.database import db +from atst.domain.exceptions import NotFoundError +from atst.models.workspace import Workspace + + class Workspaces(object): - MOCK_WORKSPACES = [ - { - "name": "Unclassified IaaS and PaaS for Defense Digital Service (DDS)", - "id": "5966187a-eff9-44c3-aa15-4de7a65ac7ff", - "task_order": {"number": 123456}, - "user_count": 23, - } - ] + # will a request have a TO association? + # do we automatically create an entry for the request.creator in the + # workspace_roles table? + + @classmethod + def create(cls, request, task_order, name=None): + name = name or request.id + return Workspace(request=request, task_order=task_order, name=name) @classmethod def get(cls, workspace_id): - return cls.MOCK_WORKSPACES[0] + try: + workspace = db.session.query(Workspace).filter_by(id=workspace_id).one() + except NoResultFound: + raise NotFoundError("workspace") + + return workspace @classmethod - def get_many(cls): - return cls.MOCK_WORKSPACES + def get_by_request(cls, request): + try: + workspace = db.session.query(Workspace).filter_by(request=request).one() + except NoResultFound: + raise NotFoundError("workspace") + + return workspace + class Projects(object): + def __init__(self): + pass + @classmethod def create(cls, creator_id, body): pass @@ -67,6 +88,9 @@ class Projects(object): class Members(object): + def __init__(self): + pass + @classmethod def create(cls, creator_id, body): pass diff --git a/atst/models/__init__.py b/atst/models/__init__.py index 1d6daae1..129064eb 100644 --- a/atst/models/__init__.py +++ b/atst/models/__init__.py @@ -10,3 +10,4 @@ from .user import User from .workspace_role import WorkspaceRole from .pe_number import PENumber from .task_order import TaskOrder +from .workspace import Workspace diff --git a/atst/models/workspace.py b/atst/models/workspace.py new file mode 100644 index 00000000..4fd11951 --- /dev/null +++ b/atst/models/workspace.py @@ -0,0 +1,19 @@ +from sqlalchemy import Column, ForeignKey, String +from sqlalchemy.orm import relationship + +from atst.models import Base +from atst.models.types import Id + + +class Workspace(Base): + __tablename__ = "workspaces" + + id = Id() + + request_id = Column(ForeignKey("requests.id"), nullable=False) + request = relationship("Request") + + task_order_id = Column(ForeignKey("task_order.id"), nullable=False) + task_order = relationship("TaskOrder") + + name = Column(String, unique=True) diff --git a/tests/domain/test_workspaces.py b/tests/domain/test_workspaces.py new file mode 100644 index 00000000..48940d07 --- /dev/null +++ b/tests/domain/test_workspaces.py @@ -0,0 +1,36 @@ +import pytest +from uuid import uuid4 + +from atst.domain.exceptions import NotFoundError +from atst.domain.workspaces import Workspaces + +from tests.factories import WorkspaceFactory, RequestFactory, TaskOrderFactory + + +def test_can_create_workspace(): + request = RequestFactory.create() + to = TaskOrderFactory.create() + workspace = Workspaces.create(request, to) + assert workspace.request == request + assert workspace.task_order == to + assert workspace.name == request.id + + workspace = Workspaces.create(request, to, name="frugal-whale") + assert workspace.name == "frugal-whale" + + +def test_can_get_workspace(): + workspace = WorkspaceFactory.create() + found = Workspaces.get(workspace.id) + assert workspace == found + + +def test_nonexistent_workspace_raises(): + with pytest.raises(NotFoundError): + Workspaces.get(uuid4()) + + +def test_can_get_workspace_by_request(): + workspace = WorkspaceFactory.create() + found = Workspaces.get_by_request(workspace.request) + assert workspace == found diff --git a/tests/factories.py b/tests/factories.py index a6370060..1691ec61 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -10,6 +10,7 @@ from atst.models.pe_number import PENumber from atst.models.task_order import TaskOrder from atst.models.user import User from atst.models.role import Role +from atst.models.workspace import Workspace from atst.models.request_status_event import RequestStatusEvent from atst.domain.roles import Roles @@ -102,3 +103,13 @@ class PENumberFactory(factory.alchemy.SQLAlchemyModelFactory): class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = TaskOrder + + +class WorkspaceFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = Workspace + + request = factory.SubFactory(RequestFactory) + task_order = factory.SubFactory(TaskOrderFactory) + # name it the same as the request ID by default + name = factory.LazyAttribute(lambda w: w.request.id)