diff --git a/atst/domain/__init__.py b/atst/domain/__init__.py index e69de29b..9b574efc 100644 --- a/atst/domain/__init__.py +++ b/atst/domain/__init__.py @@ -0,0 +1,25 @@ +from sqlalchemy.orm.exc import NoResultFound + +from atst.database import db +from atst.domain.exceptions import NotFoundError + + +class BaseDomainClass(object): + model = None + resource_name = None + + @classmethod + def get(cls, resource_id, **kwargs): + base_query = db.session.query(cls.model).filter(cls.model.id == resource_id) + if getattr(cls.model, "deleted", False): + base_query = base_query.filter(cls.model.deleted == False) + + for col, val in kwargs.items(): + base_query = base_query.filter(getattr(cls.model, col) == val) + + try: + resource = base_query.one() + + return resource + except NoResultFound: + raise NotFoundError(cls.resource_name) diff --git a/atst/domain/applications.py b/atst/domain/applications.py index ebb37606..ba81461d 100644 --- a/atst/domain/applications.py +++ b/atst/domain/applications.py @@ -1,6 +1,7 @@ from sqlalchemy.orm.exc import NoResultFound from atst.database import db +from . import BaseDomainClass from atst.domain.environments import Environments from atst.domain.exceptions import NotFoundError from atst.models.application import Application @@ -8,7 +9,10 @@ from atst.models.environment import Environment from atst.models.environment_role import EnvironmentRole -class Applications(object): +class Applications(BaseDomainClass): + model = Application + resource_name = "application" + @classmethod def create(cls, portfolio, name, description, environment_names): application = Application( @@ -21,19 +25,6 @@ class Applications(object): db.session.commit() return application - @classmethod - def get(cls, application_id): - try: - application = ( - db.session.query(Application) - .filter_by(id=application_id, deleted=False) - .one() - ) - except NoResultFound: - raise NotFoundError("application") - - return application - @classmethod def for_user(self, user, portfolio): return ( diff --git a/atst/domain/environment_roles.py b/atst/domain/environment_roles.py index d5200ac4..ab5a082c 100644 --- a/atst/domain/environment_roles.py +++ b/atst/domain/environment_roles.py @@ -1,7 +1,9 @@ from flask import current_app as app +from sqlalchemy.orm.exc import NoResultFound -from atst.models.environment_role import EnvironmentRole from atst.database import db +from atst.domain.exceptions import NotFoundError +from atst.models import EnvironmentRole, Environment, Application class EnvironmentRoles(object): @@ -13,6 +15,23 @@ class EnvironmentRoles(object): app.csp.cloud.create_role(env_role) return env_role + @classmethod + def get_for_portfolio(cls, user_id, environment_id, portfolio_id): + try: + return ( + db.session.query(EnvironmentRole) + .join(Environment, EnvironmentRole.environment_id == Environment.id) + .join(Application, Environment.application_id == Application.id) + .filter( + EnvironmentRole.user_id == user_id, + EnvironmentRole.environment_id == environment_id, + Application.portfolio_id == portfolio_id, + ) + .one() + ) + except NoResultFound: + raise NotFoundError("environment_role") + @classmethod def get(cls, user_id, environment_id): existing_env_role = ( diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 98b86a77..1297ab5b 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -1,19 +1,21 @@ -from sqlalchemy.orm.exc import NoResultFound from flask import current_app as app from atst.database import db from atst.models.task_order import TaskOrder from atst.models.dd_254 import DD254 +from . import BaseDomainClass from atst.domain.portfolios import Portfolios from atst.domain.permission_sets import PermissionSets -from .exceptions import NotFoundError class TaskOrderError(Exception): pass -class TaskOrders(object): +class TaskOrders(BaseDomainClass): + model = TaskOrder + resource_name = "task_order" + SECTIONS = { "app_info": [ "portfolio_name", @@ -51,15 +53,6 @@ class TaskOrders(object): UNCLASSIFIED_FUNDING = ["performance_length", "csp_estimate", "clin_01", "clin_03"] - @classmethod - def get(cls, task_order_id): - try: - task_order = db.session.query(TaskOrder).filter_by(id=task_order_id).one() - - return task_order - except NoResultFound: - raise NotFoundError("task_order") - @classmethod def create(cls, creator, portfolio): task_order = TaskOrder(portfolio=portfolio, creator=creator) diff --git a/atst/routes/portfolios/applications.py b/atst/routes/portfolios/applications.py index 85216b9e..a00a4f66 100644 --- a/atst/routes/portfolios/applications.py +++ b/atst/routes/portfolios/applications.py @@ -21,18 +21,14 @@ from atst.utils.flash import formatted_flash as flash @portfolios_bp.route("/portfolios//applications") @user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications") def portfolio_applications(portfolio_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) - return render_template("portfolios/applications/index.html", portfolio=portfolio) + return render_template("portfolios/applications/index.html") @portfolios_bp.route("/portfolios//applications/new") @user_can(Permissions.CREATE_APPLICATION, message="view create new application form") def new_application(portfolio_id): - portfolio = Portfolios.get_for_update(portfolio_id) form = NewApplicationForm() - return render_template( - "portfolios/applications/new.html", portfolio=portfolio, form=form - ) + return render_template("portfolios/applications/new.html", form=form) @portfolios_bp.route("/portfolios//applications/new", methods=["POST"]) @@ -50,12 +46,10 @@ def create_application(portfolio_id): application_data["environment_names"], ) return redirect( - url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) + url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id) ) else: - return render_template( - "portfolios/applications/new.html", portfolio=portfolio, form=form - ) + return render_template("portfolios/applications/new.html", form=form) def get_environments_obj_for_app(application): @@ -70,13 +64,11 @@ def get_environments_obj_for_app(application): @portfolios_bp.route("/portfolios//applications//edit") @user_can(Permissions.EDIT_APPLICATION, message="view application edit form") def edit_application(portfolio_id, application_id): - portfolio = Portfolios.get_for_update(portfolio_id) - application = Applications.get(application_id) + application = Applications.get(application_id, portfolio_id=portfolio_id) form = ApplicationForm(name=application.name, description=application.description) return render_template( "portfolios/applications/edit.html", - portfolio=portfolio, application=application, form=form, environments_obj=get_environments_obj_for_app(application=application), @@ -88,20 +80,18 @@ def edit_application(portfolio_id, application_id): ) @user_can(Permissions.EDIT_APPLICATION, message="update application") def update_application(portfolio_id, application_id): - portfolio = Portfolios.get_for_update(portfolio_id) - application = Applications.get(application_id) + application = Applications.get(application_id, portfolio_id=portfolio_id) form = ApplicationForm(http_request.form) if form.validate(): application_data = form.data Applications.update(application, application_data) return redirect( - url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) + url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id) ) else: return render_template( "portfolios/applications/edit.html", - portfolio=portfolio, application=application, form=form, environments_obj=get_environments_obj_for_app(application=application), @@ -111,7 +101,9 @@ def update_application(portfolio_id, application_id): def wrap_environment_role_lookup( user, portfolio_id=None, environment_id=None, **kwargs ): - env_role = EnvironmentRoles.get(user.id, environment_id) + env_role = EnvironmentRoles.get_for_portfolio( + user.id, environment_id, portfolio_id=portfolio_id + ) if not env_role: raise UnauthorizedError(user, "access environment {}".format(environment_id)) @@ -121,7 +113,9 @@ def wrap_environment_role_lookup( @portfolios_bp.route("/portfolios//environments//access") @user_can(None, override=wrap_environment_role_lookup, message="access environment") def access_environment(portfolio_id, environment_id): - env_role = EnvironmentRoles.get(g.current_user.id, environment_id) + env_role = EnvironmentRoles.get_for_portfolio( + g.current_user.id, environment_id, portfolio_id=portfolio_id + ) token = app.csp.cloud.get_access_token(env_role) return redirect(url_for("atst.csp_environment_access", token=token)) @@ -132,7 +126,7 @@ def access_environment(portfolio_id, environment_id): ) @user_can(Permissions.DELETE_APPLICATION, message="delete application") def delete_application(portfolio_id, application_id): - application = Applications.get(application_id) + application = Applications.get(application_id, portfolio_id=portfolio_id) Applications.delete(application) flash("application_deleted", application_name=application.name) diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index 444fc60b..b4c15a70 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -119,7 +119,7 @@ def edit_portfolio_members(portfolio_id): return redirect( url_for( "portfolios.portfolio_admin", - portfolio_id=portfolio.id, + portfolio_id=portfolio_id, fragment="portfolio-members", _anchor="portfolio-members", ) diff --git a/atst/routes/portfolios/invitations.py b/atst/routes/portfolios/invitations.py index 4774bd24..dd221bb3 100644 --- a/atst/routes/portfolios/invitations.py +++ b/atst/routes/portfolios/invitations.py @@ -1,7 +1,6 @@ from flask import g, redirect, url_for, render_template from . import portfolios_bp -from atst.domain.portfolios import Portfolios from atst.domain.invitations import Invitations from atst.queue import queue from atst.utils.flash import formatted_flash as flash @@ -47,10 +46,9 @@ def accept_invitation(token): ) @user_can(Permissions.EDIT_PORTFOLIO_USERS, message="revoke invitation") def revoke_invitation(portfolio_id, token): - portfolio = Portfolios.get_for_update(portfolio_id) Invitations.revoke(token) - return redirect(url_for("portfolios.portfolio_members", portfolio_id=portfolio.id)) + return redirect(url_for("portfolios.portfolio_members", portfolio_id=portfolio_id)) @portfolios_bp.route( diff --git a/atst/routes/portfolios/members.py b/atst/routes/portfolios/members.py index 1db59cfb..76187dc7 100644 --- a/atst/routes/portfolios/members.py +++ b/atst/routes/portfolios/members.py @@ -41,7 +41,6 @@ def portfolio_members(portfolio_id): return render_template( "portfolios/members/index.html", - portfolio=portfolio, status_choices=MEMBER_STATUS_CHOICES, members=members_list, ) @@ -51,13 +50,12 @@ def portfolio_members(portfolio_id): @user_can(Permissions.VIEW_APPLICATION_MEMBER, message="view application members") def application_members(portfolio_id, application_id): portfolio = Portfolios.get_for_update(portfolio_id) - application = Applications.get(application_id) + application = Applications.get(application_id, portfolio_id=portfolio_id) # TODO: this should show only members that have env roles in this application members_list = [serialize_portfolio_role(k) for k in portfolio.members] return render_template( "portfolios/applications/members.html", - portfolio=portfolio, application=application, members=members_list, ) @@ -68,11 +66,8 @@ def application_members(portfolio_id, application_id): Permissions.CREATE_PORTFOLIO_USERS, message="view create new portfolio member form" ) def new_member(portfolio_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) form = member_forms.NewForm() - return render_template( - "portfolios/members/new.html", portfolio=portfolio, form=form - ) + return render_template("portfolios/members/new.html", form=form) @portfolios_bp.route("/portfolios//members/new", methods=["POST"]) @@ -94,7 +89,7 @@ def create_member(portfolio_id): return redirect( url_for( "portfolios.portfolio_admin", - portfolio_id=portfolio.id, + portfolio_id=portfolio_id, fragment="portfolio-members", _anchor="portfolio-members", ) @@ -104,9 +99,7 @@ def create_member(portfolio_id): "error.html", message="There was an error processing your request." ) else: - return render_template( - "portfolios/members/new.html", portfolio=portfolio, form=form - ) + return render_template("portfolios/members/new.html", form=form) @portfolios_bp.route("/portfolios//members//member_edit") @@ -124,7 +117,6 @@ def view_member(portfolio_id, member_id): return render_template( "portfolios/members/edit.html", - portfolio=portfolio, member=member, applications=applications, form=form, @@ -143,7 +135,6 @@ def view_member(portfolio_id, member_id): ) @user_can(Permissions.EDIT_PORTFOLIO_USERS, message="update portfolio member") def update_member(portfolio_id, member_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) member = PortfolioRoles.get(portfolio_id, member_id) ids_and_roles = [] @@ -162,15 +153,10 @@ def update_member(portfolio_id, member_id): flash("environment_access_changed") return redirect( - url_for("portfolios.portfolio_members", portfolio_id=portfolio.id) + url_for("portfolios.portfolio_members", portfolio_id=portfolio_id) ) else: - return render_template( - "portfolios/members/edit.html", - form=form, - portfolio=portfolio, - member=member, - ) + return render_template("portfolios/members/edit.html", form=form, member=member) @portfolios_bp.route( diff --git a/atst/routes/portfolios/task_orders.py b/atst/routes/portfolios/task_orders.py index a3c77631..35b3eb32 100644 --- a/atst/routes/portfolios/task_orders.py +++ b/atst/routes/portfolios/task_orders.py @@ -57,7 +57,6 @@ def portfolio_funding(portfolio_id): return render_template( "portfolios/task_orders/index.html", - portfolio=portfolio, pending_task_orders=( task_orders_by_status.get(TaskOrderStatus.STARTED, []) + task_orders_by_status.get(TaskOrderStatus.PENDING, []) @@ -71,8 +70,7 @@ def portfolio_funding(portfolio_id): @portfolios_bp.route("/portfolios//task_order/") @user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="view task order details") def view_task_order(portfolio_id, task_order_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) to_form_complete = TaskOrders.all_sections_complete(task_order) dd_254_complete = DD254s.is_complete(task_order.dd_254) return render_template( @@ -82,15 +80,14 @@ def view_task_order(portfolio_id, task_order_id): is_ko=Authorization.is_ko(g.current_user, task_order), is_so=Authorization.is_so(g.current_user, task_order), is_to_signed=TaskOrders.is_signed_by_ko(task_order), - portfolio=portfolio, task_order=task_order, to_form_complete=to_form_complete, user=g.current_user, ) -def wrap_check_is_ko_or_cor(user, task_order_id=None, **_kwargs): - task_order = TaskOrders.get(task_order_id) +def wrap_check_is_ko_or_cor(user, task_order_id=None, portfolio_id=None, **_kwargs): + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) Authorization.check_is_ko_or_cor(user, task_order) return True @@ -103,13 +100,11 @@ def wrap_check_is_ko_or_cor(user, task_order_id=None, **_kwargs): message="view contracting officer review form", ) def ko_review(portfolio_id, task_order_id): - task_order = TaskOrders.get(task_order_id) - portfolio = Portfolios.get(g.current_user, portfolio_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) if TaskOrders.all_sections_complete(task_order): return render_template( "/portfolios/task_orders/review.html", - portfolio=portfolio, task_order=task_order, form=KOReviewForm(obj=task_order), ) @@ -132,7 +127,7 @@ def resend_invite(portfolio_id, task_order_id): invite_type_info = OFFICER_INVITATIONS[invite_type] - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) portfolio = Portfolios.get(g.current_user, portfolio_id) officer = getattr(task_order, invite_type_info["role"]) @@ -172,8 +167,8 @@ def resend_invite(portfolio_id, task_order_id): return redirect( url_for( "portfolios.task_order_invitations", - portfolio_id=portfolio.id, - task_order_id=task_order.id, + portfolio_id=portfolio_id, + task_order_id=task_order_id, ) ) @@ -185,7 +180,7 @@ def resend_invite(portfolio_id, task_order_id): None, override=wrap_check_is_ko_or_cor, message="submit contracting officer review" ) def submit_ko_review(portfolio_id, task_order_id, form=None): - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) form_data = {**http_request.form, **http_request.files} form = KOReviewForm(form_data) @@ -207,10 +202,7 @@ def submit_ko_review(portfolio_id, task_order_id, form=None): ) else: return render_template( - "/portfolios/task_orders/review.html", - portfolio=Portfolios.get(g.current_user, portfolio_id), - task_order=task_order, - form=form, + "/portfolios/task_orders/review.html", task_order=task_order, form=form ) @@ -221,14 +213,12 @@ def submit_ko_review(portfolio_id, task_order_id, form=None): Permissions.EDIT_TASK_ORDER_DETAILS, message="view task order invitations page" ) def task_order_invitations(portfolio_id, task_order_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) form = EditTaskOrderOfficersForm(obj=task_order) if TaskOrders.all_sections_complete(task_order): return render_template( "portfolios/task_orders/invitations.html", - portfolio=portfolio, task_order=task_order, form=form, user=g.current_user, @@ -243,8 +233,7 @@ def task_order_invitations(portfolio_id, task_order_id): ) @user_can(Permissions.EDIT_TASK_ORDER_DETAILS, message="edit task order invitations") def edit_task_order_invitations(portfolio_id, task_order_id): - portfolio = Portfolios.get(g.current_user, portfolio_id) - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) form = EditTaskOrderOfficersForm(formdata=http_request.form, obj=task_order) if form.validate(): @@ -256,7 +245,7 @@ def edit_task_order_invitations(portfolio_id, task_order_id): return redirect( url_for( "portfolios.task_order_invitations", - portfolio_id=portfolio.id, + portfolio_id=portfolio_id, task_order_id=task_order.id, ) ) @@ -264,7 +253,6 @@ def edit_task_order_invitations(portfolio_id, task_order_id): return ( render_template( "portfolios/task_orders/invitations.html", - portfolio=portfolio, task_order=task_order, form=form, ), @@ -289,8 +277,8 @@ def so_review_form(task_order): return DD254Form(data=form_data) -def wrap_check_is_so(user, task_order_id=None, **_kwargs): - task_order = TaskOrders.get(task_order_id) +def wrap_check_is_so(user, task_order_id=None, portfolio_id=None, **_kwargs): + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) Authorization.check_is_so(user, task_order) return True @@ -299,14 +287,11 @@ def wrap_check_is_so(user, task_order_id=None, **_kwargs): @portfolios_bp.route("/portfolios//task_order//dd254") @user_can(None, override=wrap_check_is_so, message="view security officer review form") def so_review(portfolio_id, task_order_id): - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) form = so_review_form(task_order) return render_template( - "portfolios/task_orders/so_review.html", - form=form, - portfolio=task_order.portfolio, - task_order=task_order, + "portfolios/task_orders/so_review.html", form=form, task_order=task_order ) @@ -317,7 +302,7 @@ def so_review(portfolio_id, task_order_id): None, override=wrap_check_is_so, message="submit security officer review form" ) def submit_so_review(portfolio_id, task_order_id): - task_order = TaskOrders.get(task_order_id) + task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id) form = DD254Form(http_request.form) if form.validate(): @@ -332,8 +317,5 @@ def submit_so_review(portfolio_id, task_order_id): ) else: return render_template( - "portfolios/task_orders/so_review.html", - form=form, - portfolio=task_order.portfolio, - task_order=task_order, + "portfolios/task_orders/so_review.html", form=form, task_order=task_order ) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 7ef462a1..685b3563 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -61,7 +61,12 @@ class ShowTaskOrderWorkflow: @property def task_order(self): if not self._task_order and self.task_order_id: - self._task_order = TaskOrders.get(self.task_order_id) + if self.portfolio_id: + self._task_order = TaskOrders.get( + self.task_order_id, portfolio_id=self.portfolio_id + ) + else: + self._task_order = TaskOrders.get(self.task_order_id) return self._task_order @@ -260,6 +265,7 @@ def is_new_task_order(*_args, **kwargs): ) +# TODO: /task_orders/new// should not exist @task_orders_bp.route("/task_orders/new/") @task_orders_bp.route("/task_orders/new//") @task_orders_bp.route("/portfolios//task_orders/new/") @@ -309,6 +315,7 @@ def new(screen, task_order_id=None, portfolio_id=None): return render_template(workflow.template, **template_args) +# TODO: /task_orders/new// should not exist @task_orders_bp.route("/task_orders/new/", methods=["POST"]) @task_orders_bp.route("/task_orders/new//", methods=["POST"]) @task_orders_bp.route( diff --git a/atst/routes/task_orders/signing.py b/atst/routes/task_orders/signing.py index 74f4f561..5f40bea8 100644 --- a/atst/routes/task_orders/signing.py +++ b/atst/routes/task_orders/signing.py @@ -27,6 +27,7 @@ def wrap_check_is_ko(user, task_order_id=None, **_kwargs): return True +# TODO: this route needs to be moved under the portfolio blueprint @task_orders_bp.route("/task_orders//digital_signature", methods=["GET"]) @user_can( None, override=wrap_check_is_ko, message="view contracting officer signature page" @@ -42,6 +43,7 @@ def signature_requested(task_order_id): ) +# TODO: this route needs to be moved under the portfolio blueprint @task_orders_bp.route( "/task_orders//digital_signature", methods=["POST"] ) diff --git a/tests/domain/test_applications.py b/tests/domain/test_applications.py index e955ed4e..b10c15f3 100644 --- a/tests/domain/test_applications.py +++ b/tests/domain/test_applications.py @@ -1,4 +1,5 @@ import pytest +from uuid import uuid4 from atst.domain.applications import Applications from atst.domain.exceptions import NotFoundError @@ -70,6 +71,16 @@ def test_get_excludes_deleted(): Applications.get(app.id) +def test_get_application(): + app = ApplicationFactory.create() + assert Applications.get(app.id) == app + assert Applications.get(app.id, portfolio_id=app.portfolio_id) == app + with pytest.raises(NotFoundError): + # make the uuid a string like you'd get from a route + rando_id = str(uuid4()) + Applications.get(app.id, portfolio_id=rando_id) + + def test_delete_application(session): app = ApplicationFactory.create() app_role = ApplicationRoleFactory.create(user=UserFactory.create(), application=app) diff --git a/tests/domain/test_environment_roles.py b/tests/domain/test_environment_roles.py new file mode 100644 index 00000000..f2f1a5a2 --- /dev/null +++ b/tests/domain/test_environment_roles.py @@ -0,0 +1,27 @@ +import pytest + +from atst.domain.environment_roles import EnvironmentRoles +from atst.domain.exceptions import NotFoundError + +from tests.factories import * + + +def test_get_for_portfolio(): + user = UserFactory.create() + portfolio = PortfolioFactory.create() + application = ApplicationFactory.create(portfolio=portfolio) + environment = EnvironmentFactory.create(application=application) + env_role = EnvironmentRoleFactory.create( + environment=environment, user=user, role="basic access" + ) + + assert ( + EnvironmentRoles.get_for_portfolio( + user.id, environment.id, portfolio_id=portfolio.id + ) + == env_role + ) + with pytest.raises(NotFoundError): + EnvironmentRoles.get_for_portfolio( + user.id, environment.id, portfolio_id=application.id + ) diff --git a/tests/routes/portfolios/test_applications.py b/tests/routes/portfolios/test_applications.py index 8df582e3..e9168685 100644 --- a/tests/routes/portfolios/test_applications.py +++ b/tests/routes/portfolios/test_applications.py @@ -329,3 +329,19 @@ def test_delete_application(client, user_session): # app and envs are soft deleted assert len(port.applications) == 0 assert len(application.environments) == 0 + + +def test_edit_application_scope(client, user_session): + owner = UserFactory.create() + port1 = PortfolioFactory.create(owner=owner, applications=[{"name": "first app"}]) + port2 = PortfolioFactory.create(owner=owner, applications=[{"name": "second app"}]) + + user_session(owner) + response = client.get( + url_for( + "portfolios.edit_application", + portfolio_id=port2.id, + application_id=port1.applications[0].id, + ) + ) + assert response.status_code == 404