Rearrange and rename application routes.

- move application routes to their own Flask blueprint
- squash application routes to be resource-specific
- reorganize application routes
This commit is contained in:
dandds 2019-04-18 14:54:45 -04:00
parent ed25078c39
commit 849c5d4b58
24 changed files with 583 additions and 646 deletions

View File

@ -14,6 +14,7 @@ from atst.filters import register_filters
from atst.routes import bp from atst.routes import bp
from atst.routes.portfolios import portfolios_bp as portfolio_routes from atst.routes.portfolios import portfolios_bp as portfolio_routes
from atst.routes.task_orders import task_orders_bp from atst.routes.task_orders import task_orders_bp
from atst.routes.applications import applications_bp
from atst.routes.dev import bp as dev_routes from atst.routes.dev import bp as dev_routes
from atst.routes.users import bp as user_routes from atst.routes.users import bp as user_routes
from atst.routes.errors import make_error_pages from atst.routes.errors import make_error_pages
@ -71,6 +72,7 @@ def make_app(config):
app.register_blueprint(bp) app.register_blueprint(bp)
app.register_blueprint(portfolio_routes) app.register_blueprint(portfolio_routes)
app.register_blueprint(task_orders_bp) app.register_blueprint(task_orders_bp)
app.register_blueprint(applications_bp)
app.register_blueprint(user_routes) app.register_blueprint(user_routes)
if ENV != "prod": if ENV != "prod":

View File

@ -1,9 +1,7 @@
from flask import current_app as app from flask import current_app as app
from sqlalchemy.orm.exc import NoResultFound
from atst.database import db from atst.database import db
from atst.domain.exceptions import NotFoundError from atst.models import EnvironmentRole
from atst.models import EnvironmentRole, Environment, Application
class EnvironmentRoles(object): class EnvironmentRoles(object):
@ -15,23 +13,6 @@ class EnvironmentRoles(object):
app.csp.cloud.create_role(env_role) app.csp.cloud.create_role(env_role)
return 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 @classmethod
def get(cls, user_id, environment_id): def get(cls, user_id, environment_id):
existing_env_role = ( existing_env_role = (

View File

@ -76,14 +76,16 @@ def home():
) )
else: else:
return redirect( return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id) url_for(
"applications.portfolio_applications", portfolio_id=portfolio_id
)
) )
else: else:
portfolios = Portfolios.for_user(g.current_user) portfolios = Portfolios.for_user(g.current_user)
first_portfolio = sorted(portfolios, key=lambda portfolio: portfolio.name)[0] first_portfolio = sorted(portfolios, key=lambda portfolio: portfolio.name)[0]
return redirect( return redirect(
url_for( url_for(
"portfolios.portfolio_applications", portfolio_id=first_portfolio.id "applications.portfolio_applications", portfolio_id=first_portfolio.id
) )
) )

View File

@ -0,0 +1,32 @@
from flask import Blueprint, current_app as app, g, redirect, url_for
applications_bp = Blueprint("applications", __name__)
from . import index
from . import new
from . import settings
from . import team
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.exceptions import UnauthorizedError
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.utils.context_processors import portfolio as portfolio_context_processor
applications_bp.context_processor(portfolio_context_processor)
def wrap_environment_role_lookup(user, environment_id=None, **kwargs):
env_role = EnvironmentRoles.get(user.id, environment_id)
if not env_role:
raise UnauthorizedError(user, "access environment {}".format(environment_id))
return True
@applications_bp.route("/environments/<environment_id>/access")
@user_can(None, override=wrap_environment_role_lookup, message="access environment")
def access_environment(environment_id):
env_role = EnvironmentRoles.get(g.current_user.id, environment_id)
token = app.csp.cloud.get_access_token(env_role)
return redirect(url_for("atst.csp_environment_access", token=token))

View File

@ -0,0 +1,11 @@
from flask import render_template
from . import applications_bp
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@applications_bp.route("/portfolios/<portfolio_id>/applications")
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
def portfolio_applications(portfolio_id):
return render_template("portfolios/applications/index.html")

View File

@ -0,0 +1,36 @@
from flask import redirect, render_template, request as http_request, url_for
from . import applications_bp
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.forms.application import NewApplicationForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@applications_bp.route("/portfolios/<portfolio_id>/applications/new")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def new(portfolio_id):
form = NewApplicationForm()
return render_template("portfolios/applications/new.html", form=form)
@applications_bp.route("/portfolios/<portfolio_id>/applications", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION, message="create new application")
def create(portfolio_id):
portfolio = Portfolios.get_for_update(portfolio_id)
form = NewApplicationForm(http_request.form)
if form.validate():
application_data = form.data
Applications.create(
portfolio,
application_data["name"],
application_data["description"],
application_data["environment_names"],
)
return redirect(
url_for("applications.portfolio_applications", portfolio_id=portfolio_id)
)
else:
return render_template("portfolios/applications/new.html", form=form)

View File

@ -0,0 +1,76 @@
from flask import redirect, render_template, request as http_request, url_for
from . import applications_bp
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.applications import Applications
from atst.forms.application import ApplicationForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
def get_environments_obj_for_app(application):
environments_obj = {}
for env in application.environments:
environments_obj[env.name] = []
for user in env.users:
env_role = EnvironmentRoles.get(user.id, env.id)
environments_obj[env.name].append(
{"name": user.full_name, "role": env_role.displayname}
)
return environments_obj
@applications_bp.route("/applications/<application_id>/settings")
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
def settings(application_id):
application = Applications.get(application_id)
form = ApplicationForm(name=application.name, description=application.description)
return render_template(
"portfolios/applications/edit.html",
application=application,
form=form,
environments_obj=get_environments_obj_for_app(application=application),
)
@applications_bp.route("/applications/<application_id>/edit", methods=["POST"])
@user_can(Permissions.EDIT_APPLICATION, message="update application")
def update(application_id):
application = Applications.get(application_id)
form = ApplicationForm(http_request.form)
if form.validate():
application_data = form.data
Applications.update(application, application_data)
return redirect(
url_for(
"applications.portfolio_applications",
portfolio_id=application.portfolio_id,
)
)
else:
return render_template(
"portfolios/applications/edit.html",
application=application,
form=form,
environments_obj=get_environments_obj_for_app(application=application),
)
@applications_bp.route("/applications/<application_id>/delete", methods=["POST"])
@user_can(Permissions.DELETE_APPLICATION, message="delete application")
def delete(application_id):
application = Applications.get(application_id)
Applications.delete(application)
flash("application_deleted", application_name=application.name)
return redirect(
url_for(
"applications.portfolio_applications", portfolio_id=application.portfolio_id
)
)

View File

@ -0,0 +1,49 @@
from flask import render_template
from . import applications_bp
from atst.domain.environments import Environments
from atst.domain.applications import Applications
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate
def permission_str(member, edit_perm_set):
if member.has_permission_set(edit_perm_set):
return translate("portfolios.members.permissions.edit_access")
else:
return translate("portfolios.members.permissions.view_only")
@applications_bp.route("/applications/<application_id>/team")
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
def team(application_id):
application = Applications.get(resource_id=application_id)
environment_users = {}
for member in application.members:
user_id = member.user.id
environment_users[user_id] = {
"permissions": {
"delete_access": permission_str(
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS
),
"environment_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS
),
"team_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_TEAM
),
},
"environments": Environments.for_user(
user=member.user, application=application
),
}
return render_template(
"portfolios/applications/team.html",
application=application,
environment_users=environment_users,
)

View File

@ -4,7 +4,6 @@ from operator import attrgetter
portfolios_bp = Blueprint("portfolios", __name__) portfolios_bp = Blueprint("portfolios", __name__)
from . import index from . import index
from . import applications
from . import members from . import members
from . import invitations from . import invitations
from . import task_orders from . import task_orders

View File

@ -1,185 +0,0 @@
from flask import (
current_app as app,
g,
redirect,
render_template,
request as http_request,
url_for,
)
from . import portfolios_bp
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.environments import Environments
from atst.domain.exceptions import UnauthorizedError
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.forms.application import NewApplicationForm, ApplicationForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate
@portfolios_bp.route("/portfolios/<portfolio_id>/applications")
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
def portfolio_applications(portfolio_id):
return render_template("portfolios/applications/index.html")
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def new_application(portfolio_id):
form = NewApplicationForm()
return render_template("portfolios/applications/new.html", form=form)
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION, message="create new application")
def create_application(portfolio_id):
portfolio = Portfolios.get_for_update(portfolio_id)
form = NewApplicationForm(http_request.form)
if form.validate():
application_data = form.data
Applications.create(
portfolio,
application_data["name"],
application_data["description"],
application_data["environment_names"],
)
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id)
)
else:
return render_template("portfolios/applications/new.html", form=form)
def get_environments_obj_for_app(application):
environments_obj = {}
for env in application.environments:
environments_obj[env.name] = []
for user in env.users:
env_role = EnvironmentRoles.get(user.id, env.id)
environments_obj[env.name].append(
{"name": user.full_name, "role": env_role.displayname}
)
return environments_obj
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/edit")
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
def edit_application(portfolio_id, 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",
application=application,
form=form,
environments_obj=get_environments_obj_for_app(application=application),
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/edit", methods=["POST"]
)
@user_can(Permissions.EDIT_APPLICATION, message="update application")
def update_application(portfolio_id, 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)
)
else:
return render_template(
"portfolios/applications/edit.html",
application=application,
form=form,
environments_obj=get_environments_obj_for_app(application=application),
)
def wrap_environment_role_lookup(
user, portfolio_id=None, environment_id=None, **kwargs
):
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))
return True
@portfolios_bp.route("/portfolios/<portfolio_id>/environments/<environment_id>/access")
@user_can(None, override=wrap_environment_role_lookup, message="access environment")
def access_environment(portfolio_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))
@portfolios_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/delete", methods=["POST"]
)
@user_can(Permissions.DELETE_APPLICATION, message="delete application")
def delete_application(portfolio_id, application_id):
application = Applications.get(application_id, portfolio_id=portfolio_id)
Applications.delete(application)
flash("application_deleted", application_name=application.name)
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id)
)
def permission_str(member, edit_perm_set):
if member.has_permission_set(edit_perm_set):
return translate("portfolios.members.permissions.edit_access")
else:
return translate("portfolios.members.permissions.view_only")
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/team")
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
def application_team(portfolio_id, application_id):
application = Applications.get(
resource_id=application_id, portfolio_id=portfolio_id
)
environment_users = {}
for member in application.members:
user_id = member.user.id
environment_users[user_id] = {
"permissions": {
"delete_access": permission_str(
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS
),
"environment_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS
),
"team_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_TEAM
),
},
"environments": Environments.for_user(
user=member.user, application=application
),
}
return render_template(
"portfolios/applications/team.html",
application=application,
environment_users=environment_users,
)

View File

@ -171,7 +171,7 @@ def edit_portfolio(portfolio_id):
if form.validate(): if form.validate():
Portfolios.update(portfolio, form.data) Portfolios.update(portfolio, form.data)
return redirect( return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
) )
else: else:
# rerender portfolio admin page # rerender portfolio admin page
@ -182,7 +182,7 @@ def edit_portfolio(portfolio_id):
@user_can(Permissions.VIEW_PORTFOLIO, message="view portfolio") @user_can(Permissions.VIEW_PORTFOLIO, message="view portfolio")
def show_portfolio(portfolio_id): def show_portfolio(portfolio_id):
return redirect( return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id) url_for("applications.portfolio_applications", portfolio_id=portfolio_id)
) )

View File

@ -11,7 +11,7 @@
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div> <div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
<form method="POST" action="{{ url_for('portfolios.edit_application', portfolio_id=portfolio.id, application_id=application.id) }}"> <form method="POST" action="{{ url_for('applications.update', application_id=application.id) }}">
<div class="panel"> <div class="panel">
<div class="panel__content"> <div class="panel__content">
@ -112,7 +112,7 @@
<input id="deleted-text" v-model="deleteText"> <input id="deleted-text" v-model="deleteText">
</div> </div>
<div class="action-group"> <div class="action-group">
<form method="POST" action="{{ url_for('portfolios.delete_application', portfolio_id=portfolio.id, application_id=application.id) }}"> <form method="POST" action="{{ url_for('applications.delete', application_id=application.id) }}">
{{ form.csrf_token }} {{ form.csrf_token }}
<button class="usa-button button-danger" v-bind:disabled="!valid"> <button class="usa-button button-danger" v-bind:disabled="!valid">
{{ "portfolios.applications.delete.button" | translate }} {{ "portfolios.applications.delete.button" | translate }}

View File

@ -13,7 +13,7 @@
<div class='portfolio-applications__header--title col col--grow'>Applications</div> <div class='portfolio-applications__header--title col col--grow'>Applications</div>
<div class='portfolio-applications__header--actions col'> <div class='portfolio-applications__header--actions col'>
{% if can_create_applications %} {% if can_create_applications %}
<a class='icon-link' href='{{ url_for('portfolios.new_application', portfolio_id=portfolio.id) }}'> <a class='icon-link' href='{{ url_for('applications.new', portfolio_id=portfolio.id) }}'>
{{ 'portfolios.applications.add_application_text' | translate }} {{ 'portfolios.applications.add_application_text' | translate }}
{{ Icon("plus", classes="sidenav__link-icon icon--circle") }} {{ Icon("plus", classes="sidenav__link-icon icon--circle") }}
</a> </a>
@ -26,7 +26,7 @@
{{ EmptyState( {{ EmptyState(
'This portfolio doesnt have any applications yet.', 'This portfolio doesnt have any applications yet.',
action_label='Add a new application' if can_create_applications else None, action_label='Add a new application' if can_create_applications else None,
action_href=url_for('portfolios.new_application', portfolio_id=portfolio.id) if can_create_applications else None, action_href=url_for('applications.new', portfolio_id=portfolio.id) if can_create_applications else None,
icon='cloud', icon='cloud',
sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.' sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.'
) }} ) }}
@ -43,14 +43,14 @@
<span class='accordion__description'>{{ application.description }}</span> <span class='accordion__description'>{{ application.description }}</span>
<div class='accordion__actions'> <div class='accordion__actions'>
{% if user_can(permissions.VIEW_APPLICATION) %} {% if user_can(permissions.VIEW_APPLICATION) %}
<a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'> <a class='icon-link' href='{{ url_for("applications.settings", application_id=application.id) }}'>
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span> <span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
</a> </a>
<div class='separator'></div> <div class='separator'></div>
{% endif %} {% endif %}
{% if user_can(permissions.VIEW_PORTFOLIO_USERS) %} {% if user_can(permissions.VIEW_PORTFOLIO_USERS) %}
<a <a
href="{{ url_for('portfolios.application_team', portfolio_id=portfolio.id, application_id=application.id) }}" href="{{ url_for('applications.team', application_id=application.id) }}"
class='icon-link'> class='icon-link'>
<span>{{ "portfolios.applications.team_text" | translate }}</span> <span>{{ "portfolios.applications.team_text" | translate }}</span>
<span class='counter'>{{ application.num_users }}</span> <span class='counter'>{{ application.num_users }}</span>
@ -76,7 +76,7 @@
<span>{{ environment.name }}</span> <span>{{ environment.name }}</span>
</div> </div>
<a href='{{ url_for("portfolios.access_environment", portfolio_id=portfolio.id, environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__csp_link icon-link'> <a href='{{ url_for("applications.access_environment", environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__csp_link icon-link'>
<span>{{ "portfolios.applications.csp_console_text" | translate }}</span> <span>{{ "portfolios.applications.csp_console_text" | translate }}</span>
</a> </a>

View File

@ -16,7 +16,7 @@
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div> <div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
<new-application inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'> <new-application inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
<form method="POST" action="{{ url_for('portfolios.create_application', portfolio_id=portfolio.id) }}" v-on:submit="handleSubmit"> <form method="POST" action="{{ url_for('applications.create', portfolio_id=portfolio.id) }}" v-on:submit="handleSubmit">
<div class="panel"> <div class="panel">
<div class="panel__content"> <div class="panel__content">

View File

@ -1,7 +1,7 @@
{% from "components/icon.html" import Icon %} {% from "components/icon.html" import Icon %}
<div class="row portfolio-breadcrumbs"> <div class="row portfolio-breadcrumbs">
<a class="icon-link portfolio-breadcrumbs__home {{ 'icon-link--disabled' if not secondary_breadcrumb }}" href="{{ url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) }}"> <a class="icon-link portfolio-breadcrumbs__home {{ 'icon-link--disabled' if not secondary_breadcrumb }}" href="{{ url_for("applications.portfolio_applications", portfolio_id=portfolio.id) }}">
{{ Icon("home") }} {{ Icon("home") }}
<span> <span>
{{ portfolio.name }} Portfolio {{ portfolio.name }} Portfolio

View File

@ -143,7 +143,7 @@
{{ EmptyState( {{ EmptyState(
'Nothing to report', 'Nothing to report',
action_label='Add a new application' if can_create_applications else None, action_label='Add a new application' if can_create_applications else None,
action_href=url_for('portfolios.new_application', portfolio_id=portfolio.id) if can_create_applications else None, action_href=url_for('applications.new', portfolio_id=portfolio.id) if can_create_applications else None,
icon='chart', icon='chart',
sub_message=message sub_message=message
) }} ) }}

View File

@ -1,27 +0,0 @@
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
)

View File

@ -0,0 +1,70 @@
from flask import url_for, get_flashed_messages
from tests.factories import (
UserFactory,
PortfolioFactory,
PortfolioRoleFactory,
EnvironmentRoleFactory,
EnvironmentFactory,
ApplicationFactory,
)
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.utils import captured_templates
def test_user_with_permission_has_budget_report_link(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.get(
url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id)
in response.data.decode()
)
def test_user_without_permission_has_no_budget_report_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(
user, portfolio, status=PortfolioRoleStatus.ACTIVE
)
user_session(user)
response = client.get(
url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id)
not in response.data.decode()
)
def test_user_with_permission_has_add_application_link(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.get(
url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("applications.create", portfolio_id=portfolio.id)
in response.data.decode()
)
def test_user_without_permission_has_no_add_application_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio)
user_session(user)
response = client.get(
url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("applications.create", portfolio_id=portfolio.id)
not in response.data.decode()
)

View File

@ -0,0 +1,47 @@
from flask import url_for, get_flashed_messages
from tests.factories import (
UserFactory,
PortfolioFactory,
PortfolioRoleFactory,
EnvironmentRoleFactory,
EnvironmentFactory,
ApplicationFactory,
)
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.utils import captured_templates
def create_environment(user):
portfolio = PortfolioFactory.create()
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user)
application = ApplicationFactory.create(portfolio=portfolio)
return EnvironmentFactory.create(application=application, name="new environment!")
def test_environment_access_with_env_role(client, user_session):
user = UserFactory.create()
environment = create_environment(user)
env_role = EnvironmentRoleFactory.create(
user=user, environment=environment, role="developer"
)
user_session(user)
response = client.get(
url_for("applications.access_environment", environment_id=environment.id)
)
assert response.status_code == 302
assert "csp-environment-access" in response.location
def test_environment_access_with_no_role(client, user_session):
user = UserFactory.create()
environment = create_environment(user)
user_session(user)
response = client.get(
url_for("applications.access_environment", environment_id=environment.id)
)
assert response.status_code == 404

View File

@ -0,0 +1,21 @@
from flask import url_for
from tests.factories import PortfolioFactory
def test_creating_application(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.post(
url_for("applications.create", portfolio_id=portfolio.id),
data={
"name": "Test Application",
"description": "This is only a test",
"environment_names-0": "dev",
"environment_names-1": "staging",
"environment_names-2": "prod",
},
)
assert response.status_code == 302
assert len(portfolio.applications) == 1
assert len(portfolio.applications[0].environments) == 3

View File

@ -0,0 +1,185 @@
from flask import url_for, get_flashed_messages
from tests.factories import (
UserFactory,
PortfolioFactory,
PortfolioRoleFactory,
EnvironmentRoleFactory,
EnvironmentFactory,
ApplicationFactory,
)
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.utils import captured_templates
def test_application_settings(client, user_session):
portfolio = PortfolioFactory.create()
application = Applications.create(
portfolio,
"Snazzy Application",
"A new application for me and my friends",
{"env1", "env2"},
)
user_session(portfolio.owner)
response = client.get(
url_for("applications.settings", application_id=application.id)
)
assert response.status_code == 200
def test_edit_application_environments_obj(app, client, user_session):
portfolio = PortfolioFactory.create()
application = Applications.create(
portfolio,
"Snazzy Application",
"A new application for me and my friends",
{"env1", "env2"},
)
user1 = UserFactory.create()
user2 = UserFactory.create()
env1 = application.environments[0]
env2 = application.environments[1]
env_role1 = EnvironmentRoleFactory.create(environment=env1, user=user1)
env_role2 = EnvironmentRoleFactory.create(environment=env1, user=user2)
env_role3 = EnvironmentRoleFactory.create(environment=env2, user=user1)
user_session(portfolio.owner)
with captured_templates(app) as templates:
response = app.test_client().get(
url_for("applications.settings", application_id=application.id)
)
assert response.status_code == 200
_, context = templates[0]
assert context["environments_obj"] == {
env1.name: [
{"name": user1.full_name, "role": env_role1.role},
{"name": user2.full_name, "role": env_role2.role},
],
env2.name: [{"name": user1.full_name, "role": env_role3.role}],
}
def test_user_with_permission_can_update_application(client, user_session):
owner = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
applications=[
{
"name": "Awesome Application",
"description": "It's really awesome!",
"environments": [{"name": "dev"}, {"name": "prod"}],
}
],
)
application = portfolio.applications[0]
user_session(owner)
response = client.post(
url_for("applications.update", application_id=application.id),
data={
"name": "Really Cool Application",
"description": "A very cool application.",
},
follow_redirects=True,
)
assert response.status_code == 200
assert application.name == "Really Cool Application"
assert application.description == "A very cool application."
def test_user_without_permission_cannot_update_application(client, user_session):
dev = UserFactory.create()
owner = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
members=[{"user": dev, "role_name": "developer"}],
applications=[
{
"name": "Great Application",
"description": "Cool stuff happening here!",
"environments": [{"name": "dev"}, {"name": "prod"}],
}
],
)
application = portfolio.applications[0]
user_session(dev)
response = client.post(
url_for("applications.update", application_id=application.id),
data={"name": "New Name", "description": "A new description."},
follow_redirects=True,
)
assert response.status_code == 404
assert application.name == "Great Application"
assert application.description == "Cool stuff happening here!"
def test_user_can_only_access_apps_in_their_portfolio(client, user_session):
portfolio = PortfolioFactory.create()
other_portfolio = PortfolioFactory.create(
applications=[
{
"name": "Awesome Application",
"description": "More cool stuff happening here!",
"environments": [{"name": "dev"}],
}
]
)
other_application = other_portfolio.applications[0]
user_session(portfolio.owner)
# user can't view application edit form
response = client.get(
url_for("applications.settings", application_id=other_application.id)
)
assert response.status_code == 404
# user can't post update application form
time_updated = other_application.time_updated
response = client.post(
url_for("applications.update", application_id=other_application.id),
data={"name": "New Name", "description": "A new description."},
)
assert response.status_code == 404
assert time_updated == other_application.time_updated
def test_delete_application(client, user_session):
user = UserFactory.create()
port = PortfolioFactory.create(
owner=user,
applications=[
{
"name": "mos eisley",
"environments": [
{"name": "bar"},
{"name": "booth"},
{"name": "band stage"},
],
}
],
)
application = port.applications[0]
user_session(user)
response = client.post(
url_for("applications.delete", application_id=application.id)
)
# appropriate response and redirect
assert response.status_code == 302
assert response.location == url_for(
"applications.portfolio_applications", portfolio_id=port.id, _external=True
)
# appropriate flash message
message = get_flashed_messages()[0]
assert "deleted" in message["message"]
assert application.name in message["message"]
# app and envs are soft deleted
assert len(port.applications) == 0
assert len(application.environments) == 0

View File

@ -0,0 +1,14 @@
from flask import url_for
from tests.factories import PortfolioFactory, ApplicationFactory
def test_application_team(client, user_session):
portfolio = PortfolioFactory.create()
application = ApplicationFactory.create(portfolio=portfolio)
user_session(portfolio.owner)
response = client.get(url_for("applications.team", application_id=application.id))
assert response.status_code == 200

View File

@ -1,358 +0,0 @@
from flask import url_for, get_flashed_messages
from tests.factories import (
UserFactory,
PortfolioFactory,
PortfolioRoleFactory,
EnvironmentRoleFactory,
EnvironmentFactory,
ApplicationFactory,
)
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.utils import captured_templates
def test_user_with_permission_has_budget_report_link(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.get(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id)
in response.data.decode()
)
def test_user_without_permission_has_no_budget_report_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(
user, portfolio, status=PortfolioRoleStatus.ACTIVE
)
user_session(user)
response = client.get(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id)
not in response.data.decode()
)
def test_user_with_permission_has_add_application_link(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.get(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.create_application", portfolio_id=portfolio.id)
in response.data.decode()
)
def test_user_without_permission_has_no_add_application_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio)
user_session(user)
response = client.get(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
assert (
url_for("portfolios.create_application", portfolio_id=portfolio.id)
not in response.data.decode()
)
def test_creating_application(client, user_session):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.post(
url_for("portfolios.create_application", portfolio_id=portfolio.id),
data={
"name": "Test Application",
"description": "This is only a test",
"environment_names-0": "dev",
"environment_names-1": "staging",
"environment_names-2": "prod",
},
)
assert response.status_code == 302
assert len(portfolio.applications) == 1
assert len(portfolio.applications[0].environments) == 3
def test_view_edit_application(client, user_session):
portfolio = PortfolioFactory.create()
application = Applications.create(
portfolio,
"Snazzy Application",
"A new application for me and my friends",
{"env1", "env2"},
)
user_session(portfolio.owner)
response = client.get(
url_for(
"portfolios.update_application",
portfolio_id=portfolio.id,
application_id=application.id,
)
)
assert response.status_code == 200
def test_edit_application_environments_obj(app, client, user_session):
portfolio = PortfolioFactory.create()
application = Applications.create(
portfolio,
"Snazzy Application",
"A new application for me and my friends",
{"env1", "env2"},
)
user1 = UserFactory.create()
user2 = UserFactory.create()
env1 = application.environments[0]
env2 = application.environments[1]
env_role1 = EnvironmentRoleFactory.create(environment=env1, user=user1)
env_role2 = EnvironmentRoleFactory.create(environment=env1, user=user2)
env_role3 = EnvironmentRoleFactory.create(environment=env2, user=user1)
user_session(portfolio.owner)
with captured_templates(app) as templates:
response = app.test_client().get(
url_for(
"portfolios.edit_application",
portfolio_id=portfolio.id,
application_id=application.id,
)
)
assert response.status_code == 200
_, context = templates[0]
assert context["environments_obj"] == {
env1.name: [
{"name": user1.full_name, "role": env_role1.role},
{"name": user2.full_name, "role": env_role2.role},
],
env2.name: [{"name": user1.full_name, "role": env_role3.role}],
}
def test_user_with_permission_can_update_application(client, user_session):
owner = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
applications=[
{
"name": "Awesome Application",
"description": "It's really awesome!",
"environments": [{"name": "dev"}, {"name": "prod"}],
}
],
)
application = portfolio.applications[0]
user_session(owner)
response = client.post(
url_for(
"portfolios.update_application",
portfolio_id=portfolio.id,
application_id=application.id,
),
data={
"name": "Really Cool Application",
"description": "A very cool application.",
},
follow_redirects=True,
)
assert response.status_code == 200
assert application.name == "Really Cool Application"
assert application.description == "A very cool application."
def test_user_without_permission_cannot_update_application(client, user_session):
dev = UserFactory.create()
owner = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
members=[{"user": dev, "role_name": "developer"}],
applications=[
{
"name": "Great Application",
"description": "Cool stuff happening here!",
"environments": [{"name": "dev"}, {"name": "prod"}],
}
],
)
application = portfolio.applications[0]
user_session(dev)
response = client.post(
url_for(
"portfolios.update_application",
portfolio_id=portfolio.id,
application_id=application.id,
),
data={"name": "New Name", "description": "A new description."},
follow_redirects=True,
)
assert response.status_code == 404
assert application.name == "Great Application"
assert application.description == "Cool stuff happening here!"
def test_user_can_only_access_apps_in_their_portfolio(client, user_session):
portfolio = PortfolioFactory.create()
other_portfolio = PortfolioFactory.create(
applications=[
{
"name": "Awesome Application",
"description": "More cool stuff happening here!",
"environments": [{"name": "dev"}],
}
]
)
other_application = other_portfolio.applications[0]
user_session(portfolio.owner)
# user can't view application edit form
response = client.get(
url_for(
"portfolios.edit_application",
portfolio_id=portfolio.id,
application_id=other_application.id,
)
)
assert response.status_code == 404
# user can't post update application form
time_updated = other_application.time_updated
response = client.post(
url_for(
"portfolios.update_application",
portfolio_id=portfolio.id,
application_id=other_application.id,
),
data={"name": "New Name", "description": "A new description."},
)
assert response.status_code == 404
assert time_updated == other_application.time_updated
def create_environment(user):
portfolio = PortfolioFactory.create()
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user)
application = ApplicationFactory.create(portfolio=portfolio)
return EnvironmentFactory.create(application=application, name="new environment!")
def test_environment_access_with_env_role(client, user_session):
user = UserFactory.create()
environment = create_environment(user)
env_role = EnvironmentRoleFactory.create(
user=user, environment=environment, role="developer"
)
user_session(user)
response = client.get(
url_for(
"portfolios.access_environment",
portfolio_id=environment.portfolio.id,
environment_id=environment.id,
)
)
assert response.status_code == 302
assert "csp-environment-access" in response.location
def test_environment_access_with_no_role(client, user_session):
user = UserFactory.create()
environment = create_environment(user)
user_session(user)
response = client.get(
url_for(
"portfolios.access_environment",
portfolio_id=environment.portfolio.id,
environment_id=environment.id,
)
)
assert response.status_code == 404
def test_delete_application(client, user_session):
user = UserFactory.create()
port = PortfolioFactory.create(
owner=user,
applications=[
{
"name": "mos eisley",
"environments": [
{"name": "bar"},
{"name": "booth"},
{"name": "band stage"},
],
}
],
)
application = port.applications[0]
user_session(user)
response = client.post(
url_for(
"portfolios.delete_application",
portfolio_id=port.id,
application_id=application.id,
)
)
# appropriate response and redirect
assert response.status_code == 302
assert response.location == url_for(
"portfolios.portfolio_applications", portfolio_id=port.id, _external=True
)
# appropriate flash message
message = get_flashed_messages()[0]
assert "deleted" in message["message"]
assert application.name in message["message"]
# 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
def test_application_team(client, user_session):
portfolio = PortfolioFactory.create()
application = ApplicationFactory.create(portfolio=portfolio)
user_session(portfolio.owner)
response = client.get(
url_for(
"portfolios.application_team",
portfolio_id=portfolio.id,
application_id=application.id,
)
)
assert response.status_code == 200

View File

@ -126,8 +126,8 @@ def test_atst_activity_history_access(get_url_assert_status):
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
# portfolios.access_environment # applications.access_environment
def test_portfolios_access_environment_access(get_url_assert_status): def test_applications_access_environment_access(get_url_assert_status):
dev = UserFactory.create() dev = UserFactory.create()
rando = UserFactory.create() rando = UserFactory.create()
ccpo = UserFactory.create_ccpo() ccpo = UserFactory.create_ccpo()
@ -149,24 +149,20 @@ def test_portfolios_access_environment_access(get_url_assert_status):
) )
env = portfolio.applications[0].environments[0] env = portfolio.applications[0].environments[0]
url = url_for( url = url_for("applications.access_environment", environment_id=env.id)
"portfolios.access_environment",
portfolio_id=portfolio.id,
environment_id=env.id,
)
get_url_assert_status(dev, url, 302) get_url_assert_status(dev, url, 302)
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
get_url_assert_status(ccpo, url, 404) get_url_assert_status(ccpo, url, 404)
# portfolios.create_application # applications.create
def test_portfolios_create_application_access(post_url_assert_status): def test_applications_create_access(post_url_assert_status):
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
owner = user_with() owner = user_with()
rando = user_with() rando = user_with()
portfolio = PortfolioFactory.create(owner=owner) portfolio = PortfolioFactory.create(owner=owner)
url = url_for("portfolios.create_application", portfolio_id=portfolio.id) url = url_for("applications.create", portfolio_id=portfolio.id)
post_url_assert_status(ccpo, url, 200) post_url_assert_status(ccpo, url, 200)
post_url_assert_status(owner, url, 200) post_url_assert_status(owner, url, 200)
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
@ -185,8 +181,8 @@ def test_portfolios_create_member_access(post_url_assert_status):
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
# portfolios.delete_application # applications.delete
def test_portfolios_delete_application_access(post_url_assert_status, monkeypatch): def test_applications_delete_access(post_url_assert_status, monkeypatch):
ccpo = UserFactory.create_ccpo() ccpo = UserFactory.create_ccpo()
owner = user_with() owner = user_with()
app_admin = user_with() app_admin = user_with()
@ -212,19 +208,15 @@ def test_portfolios_delete_application_access(post_url_assert_status, monkeypatc
monkeypatch.setattr("atst.domain.applications.Applications.delete", lambda *a: True) monkeypatch.setattr("atst.domain.applications.Applications.delete", lambda *a: True)
url = url_for( url = url_for("applications.delete", application_id=application.id)
"portfolios.delete_application",
portfolio_id=portfolio.id,
application_id=application.id,
)
post_url_assert_status(app_admin, url, 404) post_url_assert_status(app_admin, url, 404)
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
post_url_assert_status(owner, url, 302) post_url_assert_status(owner, url, 302)
post_url_assert_status(ccpo, url, 302) post_url_assert_status(ccpo, url, 302)
# portfolios.edit_application # applications.settings
def test_portfolios_edit_application_access(get_url_assert_status): def test_application_settings_access(get_url_assert_status):
ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT) ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT)
owner = user_with() owner = user_with()
rando = user_with() rando = user_with()
@ -234,9 +226,7 @@ def test_portfolios_edit_application_access(get_url_assert_status):
) )
app = portfolio.applications[0] app = portfolio.applications[0]
url = url_for( url = url_for("applications.settings", application_id=app.id)
"portfolios.edit_application", portfolio_id=portfolio.id, application_id=app.id
)
get_url_assert_status(ccpo, url, 200) get_url_assert_status(ccpo, url, 200)
get_url_assert_status(owner, url, 200) get_url_assert_status(owner, url, 200)
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
@ -295,14 +285,14 @@ def test_portfolios_ko_review_access(get_url_assert_status):
get_url_assert_status(cor, url, 200) get_url_assert_status(cor, url, 200)
# portfolios.new_application # applications.new
def test_portfolios_new_application_access(get_url_assert_status): def test_applications_new_access(get_url_assert_status):
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
owner = user_with() owner = user_with()
rando = user_with() rando = user_with()
portfolio = PortfolioFactory.create(owner=owner) portfolio = PortfolioFactory.create(owner=owner)
url = url_for("portfolios.new_application", portfolio_id=portfolio.id) url = url_for("applications.new", portfolio_id=portfolio.id)
get_url_assert_status(ccpo, url, 200) get_url_assert_status(ccpo, url, 200)
get_url_assert_status(owner, url, 200) get_url_assert_status(owner, url, 200)
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
@ -321,14 +311,14 @@ def test_portfolios_portfolio_admin_access(get_url_assert_status):
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
# portfolios.portfolio_applications # applications.portfolio_applications
def test_portfolios_portfolio_applications_access(get_url_assert_status): def test_applications_portfolio_applications_access(get_url_assert_status):
ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT) ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT)
owner = user_with() owner = user_with()
rando = user_with() rando = user_with()
portfolio = PortfolioFactory.create(owner=owner) portfolio = PortfolioFactory.create(owner=owner)
url = url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) url = url_for("applications.portfolio_applications", portfolio_id=portfolio.id)
get_url_assert_status(ccpo, url, 200) get_url_assert_status(ccpo, url, 200)
get_url_assert_status(owner, url, 200) get_url_assert_status(owner, url, 200)
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
@ -519,8 +509,8 @@ def test_portfolios_task_order_invitations_access(get_url_assert_status):
get_url_assert_status(rando, url, 404) get_url_assert_status(rando, url, 404)
# portfolios.update_application # applications.update
def test_portfolios_update_application_access(post_url_assert_status): def test_applications_update_access(post_url_assert_status):
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
dev = UserFactory.create() dev = UserFactory.create()
rando = UserFactory.create() rando = UserFactory.create()
@ -531,11 +521,7 @@ def test_portfolios_update_application_access(post_url_assert_status):
) )
app = portfolio.applications[0] app = portfolio.applications[0]
url = url_for( url = url_for("applications.update", application_id=app.id)
"portfolios.update_application",
portfolio_id=portfolio.id,
application_id=app.id,
)
post_url_assert_status(dev, url, 200) post_url_assert_status(dev, url, 200)
post_url_assert_status(ccpo, url, 200) post_url_assert_status(ccpo, url, 200)
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
@ -718,18 +704,14 @@ def test_task_orders_update_access(post_url_assert_status):
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
def test_portfolio_application_team_access(get_url_assert_status): def test_applications_application_team_access(get_url_assert_status):
ccpo = UserFactory.create_ccpo() ccpo = UserFactory.create_ccpo()
rando = UserFactory.create() rando = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
application = ApplicationFactory.create(portfolio=portfolio) application = ApplicationFactory.create(portfolio=portfolio)
url = url_for( url = url_for("applications.team", application_id=application.id)
"portfolios.application_team",
portfolio_id=portfolio.id,
application_id=application.id,
)
get_url_assert_status(ccpo, url, 200) get_url_assert_status(ccpo, url, 200)
get_url_assert_status(portfolio.owner, url, 200) get_url_assert_status(portfolio.owner, url, 200)