From 83479f60af254841a8da4cc389bdd046af7d7019 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Tue, 17 Sep 2019 11:34:57 -0400 Subject: [PATCH 01/13] Flash messages for saving/ submitting applications --- atst/utils/flash.py | 7 +++++++ translations.yaml | 1 + 2 files changed, 8 insertions(+) diff --git a/atst/utils/flash.py b/atst/utils/flash.py index da1bece8..8828adbe 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -7,6 +7,13 @@ MESSAGES = { "message_template": "Portfolio '{{portfolio_name}}' has been deleted", "category": "success", }, + "application_created": { + "title_template": translate("flash.success"), + "message_template": """ + {{ "flash.application.created" | translate({"application_name": application_name}) }} + """, + "category": "success", + }, "application_deleted": { "title_template": translate("flash.success"), "message_template": """ diff --git a/translations.yaml b/translations.yaml index f3001cef..0e87df0b 100644 --- a/translations.yaml +++ b/translations.yaml @@ -101,6 +101,7 @@ email: portfolio_invite: "{inviter_name} has invited you to a JEDI cloud portfolio" flash: application: + created: 'You have successfully created the {application_name} application.' deleted: 'You have successfully deleted the {application_name} application. To view the retained activity log, visit the portfolio administration page.' delete_member_success: 'You have successfully deleted {member_name} from the portfolio.' deleted_member: Portfolio member deleted From cd37e181785fc5602980d6c619c1de52ac8f6642 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:14:27 -0400 Subject: [PATCH 02/13] Rename application-related forms --- atst/forms/application.py | 4 ++-- atst/routes/applications/settings.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/atst/forms/application.py b/atst/forms/application.py index 4fae3f1c..f9c5ca0e 100644 --- a/atst/forms/application.py +++ b/atst/forms/application.py @@ -11,7 +11,7 @@ class EditEnvironmentForm(BaseForm): ) -class ApplicationForm(BaseForm): +class NameAndDescriptionForm(BaseForm): name = StringField( label=translate("forms.application.name_label"), validators=[Required()] ) @@ -20,7 +20,7 @@ class ApplicationForm(BaseForm): ) -class NewApplicationForm(ApplicationForm): +class EnvironmentsForm(BaseForm): environment_names = FieldList( StringField(label=translate("forms.application.environment_names_label")), validators=[ diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 2d5e8422..144800fd 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -8,8 +8,8 @@ from atst.domain.application_roles import ApplicationRoles from atst.domain.audit_log import AuditLog from atst.domain.common import Paginator from atst.domain.environment_roles import EnvironmentRoles -from atst.forms.application import ApplicationForm, EditEnvironmentForm from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm +from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS from atst.domain.authz.decorator import user_can_access_decorator as user_can from atst.models.permissions import Permissions @@ -125,7 +125,7 @@ def render_settings_page(application, **kwargs): members = get_members_data(application) if "application_form" not in kwargs: - kwargs["application_form"] = ApplicationForm( + kwargs["application_form"] = NameAndDescriptionForm( name=application.name, description=application.description ) @@ -229,7 +229,7 @@ def new_environment(application_id): @user_can(Permissions.EDIT_APPLICATION, message="update application") def update(application_id): application = Applications.get(application_id) - form = ApplicationForm(http_request.form) + form = NameAndDescriptionForm(http_request.form) if form.validate(): application_data = form.data Applications.update(application, application_data) From 8914419dacaa5b888bc013906dda0dfc3dbb5124 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:16:11 -0400 Subject: [PATCH 03/13] Break new application route into multiple routes --- atst/routes/applications/new.py | 133 +++++++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/atst/routes/applications/new.py b/atst/routes/applications/new.py index 2d04d7a3..05893ed0 100644 --- a/atst/routes/applications/new.py +++ b/atst/routes/applications/new.py @@ -3,35 +3,128 @@ from flask import redirect, render_template, request as http_request, url_for, g from . import applications_bp from atst.domain.applications import Applications from atst.domain.portfolios import Portfolios -from atst.forms.application import NewApplicationForm +from atst.forms.application import NameAndDescriptionForm, EnvironmentsForm 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_new_application_form(form_data, form_class, application_id=None): + if application_id: + application = Applications.get(application_id) + return form_class(form_data, obj=application) + else: + return form_class(form_data) + + +def render_new_application_form( + template, form_class, portfolio_id=None, application_id=None, form=None +): + render_args = {"application_id": application_id} + if application_id: + application = Applications.get(application_id) + render_args["form"] = form or form_class(obj=application) + else: + render_args["form"] = form or form_class() + + return render_template(template, **render_args) @applications_bp.route("/portfolios//applications/new") +@applications_bp.route( + "/portfolios//applications//step_1" +) +def view_new_application_step_1(portfolio_id, application_id=None): + return render_new_application_form( + "portfolios/applications/new/step_1.html", + NameAndDescriptionForm, + portfolio_id=portfolio_id, + application_id=application_id, + ) + + +@applications_bp.route( + "/portfolios//applications/new", + endpoint="create_new_application_step_1", + methods=["POST"], +) +@applications_bp.route( + "/portfolios//applications//step_1", + endpoint="update_new_application_step_1", + methods=["POST"], +) @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//applications", methods=["POST"]) -@user_can(Permissions.CREATE_APPLICATION, message="create new application") -def create(portfolio_id): +def create_or_update_new_application_step_1(portfolio_id, application_id=None): portfolio = Portfolios.get_for_update(portfolio_id) - form = NewApplicationForm(http_request.form) + form = get_new_application_form( + {**http_request.form}, NameAndDescriptionForm, application_id + ) if form.validate(): - application_data = form.data - Applications.create( - g.current_user, - portfolio, - application_data["name"], - application_data["description"], - application_data["environment_names"], - ) + application = None + if application_id: + application = Applications.get(application_id) + application = Applications.update(application, form.data) + else: + application = Applications.create(g.current_user, portfolio, **form.data) return redirect( - url_for("applications.portfolio_applications", portfolio_id=portfolio_id) + url_for( + "applications.update_new_application_step_2", + portfolio_id=portfolio_id, + application_id=application.id, + ) ) else: - return render_template("portfolios/applications/new.html", form=form) + return ( + render_new_application_form( + "portfolios/applications/new/step_1.html", + NameAndDescriptionForm, + portfolio_id, + application_id, + form, + ), + 400, + ) + + +@applications_bp.route( + "/portfolios//applications//step_2" +) +def view_new_application_step_2(portfolio_id, application_id): + return render_new_application_form( + "portfolios/applications/new/step_2.html", + EnvironmentsForm, + portfolio_id=portfolio_id, + application_id=application_id, + ) + + +@applications_bp.route( + "/portfolios//applications//step_2", methods=["POST"] +) +@user_can(Permissions.CREATE_APPLICATION, message="view create new application form") +def update_new_application_step_2(portfolio_id, application_id): + form = get_new_application_form( + {**http_request.form}, EnvironmentsForm, application_id + ) + if form.validate(): + application = Applications.get(application_id) + application = Applications.update(application, form.data) + flash("application_created", application_name=application.name) + return redirect( + url_for( + "applications.portfolio_applications", + portfolio_id=application.portfolio_id, + ) + ) + else: + return ( + render_new_application_form( + "portfolios/applications/new/step_2.html", + EnvironmentsForm, + portfolio_id, + application_id, + form, + ), + 400, + ) From 1a11182eef03219a37627d240ea1469c333ea3b3 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:30:08 -0400 Subject: [PATCH 04/13] Break new-application JS component into form steps --- .../environments.js} | 44 +++++-------------- .../new_application/name_and_description.js | 19 ++++++++ js/index.js | 6 ++- 3 files changed, 34 insertions(+), 35 deletions(-) rename js/components/forms/{new_application.js => new_application/environments.js} (70%) create mode 100644 js/components/forms/new_application/name_and_description.js diff --git a/js/components/forms/new_application.js b/js/components/forms/new_application/environments.js similarity index 70% rename from js/components/forms/new_application.js rename to js/components/forms/new_application/environments.js index b695bd6b..655fd817 100644 --- a/js/components/forms/new_application.js +++ b/js/components/forms/new_application/environments.js @@ -1,11 +1,11 @@ -import FormMixin from '../../mixins/form' -import textinput from '../text_input' +import FormMixin from '../../../mixins/form' +import textinput from '../../text_input' import * as R from 'ramda' const createEnvironment = name => ({ name }) export default { - name: 'new-application', + name: 'application-environments', mixins: [FormMixin], @@ -18,7 +18,6 @@ export default { type: Object, default: () => ({}), }, - modalName: String, }, data: function() { @@ -47,16 +46,18 @@ export default { errors: [], environments, name, - readyToSubmit: false, + changed: true, } }, mounted: function() { this.$root.$on('onEnvironmentAdded', this.addEnvironment) + this.validate() }, methods: { - addEnvironment: function(event) { + addEnvironment: function(_event) { + this.changed = false this.environments.push(createEnvironment('')) }, @@ -64,6 +65,7 @@ export default { if (this.environments.length > 1) { this.environments.splice(index, 1) } + this.validate() }, validate: function() { @@ -75,6 +77,7 @@ export default { R.filter(Boolean), R.take(1) )(this.validations) + this.invalid = this.errors.length > 0 }, hasEnvironments: function() { @@ -99,34 +102,9 @@ export default { return names.every((n, index) => names.indexOf(n) === index) }, - handleSubmit: function(event) { - if (!this.readyToSubmit) { - event.preventDefault() - this.validateAndOpenModal() - } - }, - - handleCancelSubmit: function() { - this.readyToSubmit = false - this.closeModal(this.modalName) - }, - - validateAndOpenModal: function() { - let isValid = this.$children.reduce((previous, newVal) => { - // display textInput error if it is not valid - if (!newVal.showValid) { - newVal.showError = true - } - - return newVal.showValid && previous - }, true) + onInput: function(e) { + this.changed = true this.validate() - isValid = this.errors.length == 0 && isValid - - if (isValid) { - this.readyToSubmit = true - this.openModal(this.modalName) - } }, }, } diff --git a/js/components/forms/new_application/name_and_description.js b/js/components/forms/new_application/name_and_description.js new file mode 100644 index 00000000..f39234f9 --- /dev/null +++ b/js/components/forms/new_application/name_and_description.js @@ -0,0 +1,19 @@ +import FormMixin from '../../../mixins/form' +import textinput from '../../text_input' +import * as R from 'ramda' + +export default { + name: 'application-name-and-description', + + mixins: [FormMixin], + + components: { + textinput, + }, + + data: function() { + return { + errors: [], + } + }, +} diff --git a/js/index.js b/js/index.js index e1197b46..ddabd527 100644 --- a/js/index.js +++ b/js/index.js @@ -17,7 +17,8 @@ import EditOfficerForm from './components/forms/edit_officer_form' import poc from './components/forms/poc' import oversight from './components/forms/oversight' import toggler from './components/toggler' -import NewApplication from './components/forms/new_application' +import ApplicationNameAndDescription from './components/forms/new_application/name_and_description' +import ApplicationEnvironments from './components/forms/new_application/environments' import { EditEnvironmentRole } from './components/forms/edit_environment_role' import EditApplicationRoles from './components/forms/edit_application_roles' import MultiStepModalForm from './components/forms/multi_step_modal_form' @@ -60,7 +61,8 @@ const app = new Vue({ checkboxinput, poc, oversight, - NewApplication, + ApplicationNameAndDescription, + ApplicationEnvironments, selector, BudgetChart, SpendTable, From f0593cde7061c478575d68507aa193d466c06444 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:30:30 -0400 Subject: [PATCH 05/13] Break new application template into form steps --- .../portfolios/applications/new/step_1.html | 51 +++++++++++++++++ .../{new.html => new/step_2.html} | 57 +++---------------- 2 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 templates/portfolios/applications/new/step_1.html rename templates/portfolios/applications/{new.html => new/step_2.html} (51%) diff --git a/templates/portfolios/applications/new/step_1.html b/templates/portfolios/applications/new/step_1.html new file mode 100644 index 00000000..a128ba38 --- /dev/null +++ b/templates/portfolios/applications/new/step_1.html @@ -0,0 +1,51 @@ +{% extends "portfolios/applications/base.html" %} + +{% from "components/alert.html" import Alert %} +{% from "components/text_input.html" import TextInput %} +{% from 'components/save_button.html' import SaveButton %} + +{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %} + + {% if application_id %} + {% set action = url_for('applications.update_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %} + {% else %} + {% set action = url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %} + {% endif %} + + +{% block application_content %} + + {% include "fragments/flash.html" %} + +
{{ 'portfolios.applications.settings_heading' | translate }}
+ + +
+
+
+ {{ form.csrf_token }} +

+ {{ "fragments.edit_application_form.explain" | translate }} +

+
+
+ {{ TextInput(form.name, optional=False) }} +
+
+
+
+ {{ TextInput(form.description, paragraph=True, optional=False) }} +
+
+
+
+ + + {% block next_button %} + {{ SaveButton(text=('portfolios.applications.next_button_text' | translate)) }} + {% endblock %} + +
+
+ +{% endblock %} diff --git a/templates/portfolios/applications/new.html b/templates/portfolios/applications/new/step_2.html similarity index 51% rename from templates/portfolios/applications/new.html rename to templates/portfolios/applications/new/step_2.html index 0ddc8b88..d63d6b29 100644 --- a/templates/portfolios/applications/new.html +++ b/templates/portfolios/applications/new/step_2.html @@ -3,7 +3,6 @@ {% from "components/alert.html" import Alert %} {% from "components/icon.html" import Icon %} {% from "components/modal.html" import Modal %} -{% from "components/text_input.html" import TextInput %} {% from 'components/save_button.html' import SaveButton %} {% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %} @@ -14,50 +13,11 @@ {% include "fragments/flash.html" %}
{{ 'portfolios.applications.settings_heading' | translate }}
- - -
+ +
- - {% call Modal(name=modalName, dismissable=False) %} -

Create application !{ name }

- -

- When you click {{ 'portfolios.applications.create_button_text' | translate }}, the environments - - !{environment.name} - - will be created as individual cloud resource groups under !{ name } application. -

- -
- - -
- {% endcall %} - {{ form.csrf_token }} -

- {{ "fragments.edit_application_form.explain" | translate }} -

-
-
- {{ TextInput(form.name, optional=False) }} -
-
-   -
-
-
-
- {{ TextInput(form.description, paragraph=True, optional=False) }} -
-
-   -
-
-
{# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
{{ Alert(message=None, level="error", vue_template=True) }} @@ -76,7 +36,7 @@
  • - +
  • -
    -
    - {{ SaveButton(text=('portfolios.applications.create_button_text' | translate)) }} -
    + + {% block next_button %} + {{ SaveButton(text=('portfolios.applications.create_button_text' | translate)) }} + {% endblock %} + - + {% endblock %} From 4c8bb0a35df17437049f758814bf07dd007e2324 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:33:01 -0400 Subject: [PATCH 06/13] change ulr_for arguments to new multistep form --- templates/portfolios/applications/index.html | 4 ++-- templates/portfolios/reports/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/portfolios/applications/index.html b/templates/portfolios/applications/index.html index 873078f5..bc7ae431 100644 --- a/templates/portfolios/applications/index.html +++ b/templates/portfolios/applications/index.html @@ -13,7 +13,7 @@
    Applications
    {% if can_create_applications %} - + {{ 'portfolios.applications.add_application_text' | translate }} {{ Icon("plus", classes="sidenav__link-icon") }} @@ -26,7 +26,7 @@ {{ EmptyState( 'This portfolio doesn’t have any applications', action_label='Add a new application' if can_create_applications else None, - action_href=url_for('applications.new', portfolio_id=portfolio.id) if can_create_applications else None, + action_href=url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id) if can_create_applications else None, icon='cloud', sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.', add_perms=can_create_applications diff --git a/templates/portfolios/reports/index.html b/templates/portfolios/reports/index.html index 63f79b53..9cde86ae 100644 --- a/templates/portfolios/reports/index.html +++ b/templates/portfolios/reports/index.html @@ -141,7 +141,7 @@ {{ EmptyState( 'Nothing to report', action_label='Add a new application' if can_create_applications else None, - action_href=url_for('applications.new', portfolio_id=portfolio.id) if can_create_applications else None, + action_href=url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id) if can_create_applications else None, icon='chart', sub_message=message, add_perms=can_create_applications From 6b74766df9b920e2e17cb59e5cb7522140fe2023 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:34:01 -0400 Subject: [PATCH 07/13] Add translation for next button in new app form --- translations.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/translations.yaml b/translations.yaml index 0e87df0b..a88f1fe4 100644 --- a/translations.yaml +++ b/translations.yaml @@ -297,6 +297,7 @@ portfolios: add_another_environment: Add another environment app_settings_text: App settings create_button_text: Create + next_button_text: "Next: Environments" create_new_env: Create a new environment. create_new_env_info: Creating an environment gives you access to the Cloud Service Provider. This environment will function within the constraints of the task order, and any costs will be billed against the portfolio. csp_console_text: CSP console From 8c8f0be7613c40c3f0761c6df6c939dba41cb59d Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:34:52 -0400 Subject: [PATCH 08/13] Allow application creation without environments --- atst/domain/applications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atst/domain/applications.py b/atst/domain/applications.py index 0c063332..41444f59 100644 --- a/atst/domain/applications.py +++ b/atst/domain/applications.py @@ -19,12 +19,13 @@ class Applications(BaseDomainClass): resource_name = "application" @classmethod - def create(cls, user, portfolio, name, description, environment_names): + def create(cls, user, portfolio, name, description, environment_names=None): application = Application( portfolio=portfolio, name=name, description=description ) db.session.add(application) + if environment_names: Environments.create_many(user, application, environment_names) db.session.commit() From 2dd24aa286068efb98e60815cfa0fae6c77b291e Mon Sep 17 00:00:00 2001 From: graham-dds Date: Tue, 24 Sep 2019 10:08:22 -0400 Subject: [PATCH 09/13] Allow application creation without environments --- atst/domain/applications.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atst/domain/applications.py b/atst/domain/applications.py index 41444f59..0a37f2a6 100644 --- a/atst/domain/applications.py +++ b/atst/domain/applications.py @@ -49,7 +49,10 @@ class Applications(BaseDomainClass): application.name = new_data["name"] if "description" in new_data: application.description = new_data["description"] - + if "environment_names" in new_data: + Environments.create_many( + g.current_user, application, new_data["environment_names"] + ) db.session.add(application) db.session.commit() From 0bdbe755e3e2c22f2775e58222b2ca7d3b389ac7 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Tue, 24 Sep 2019 10:09:03 -0400 Subject: [PATCH 10/13] Allow the ability to update environment names --- atst/domain/applications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atst/domain/applications.py b/atst/domain/applications.py index 0a37f2a6..01cfd2b3 100644 --- a/atst/domain/applications.py +++ b/atst/domain/applications.py @@ -1,4 +1,5 @@ from . import BaseDomainClass +from flask import g from atst.database import db from atst.domain.application_roles import ApplicationRoles from atst.domain.environment_roles import EnvironmentRoles @@ -26,7 +27,7 @@ class Applications(BaseDomainClass): db.session.add(application) if environment_names: - Environments.create_many(user, application, environment_names) + Environments.create_many(user, application, environment_names) db.session.commit() return application From 66f606d951ee747ebac8578cccff57f1e4f31efc Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 11:57:47 -0400 Subject: [PATCH 11/13] add permissions to new application routes --- atst/routes/applications/new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atst/routes/applications/new.py b/atst/routes/applications/new.py index 05893ed0..40477666 100644 --- a/atst/routes/applications/new.py +++ b/atst/routes/applications/new.py @@ -34,6 +34,7 @@ def render_new_application_form( @applications_bp.route( "/portfolios//applications//step_1" ) +@user_can(Permissions.CREATE_APPLICATION, message="view create new application form") def view_new_application_step_1(portfolio_id, application_id=None): return render_new_application_form( "portfolios/applications/new/step_1.html", @@ -90,6 +91,7 @@ def create_or_update_new_application_step_1(portfolio_id, application_id=None): @applications_bp.route( "/portfolios//applications//step_2" ) +@user_can(Permissions.CREATE_APPLICATION, message="view create new application form") def view_new_application_step_2(portfolio_id, application_id): return render_new_application_form( "portfolios/applications/new/step_2.html", From a96c2b3cdde3a698de370f6ce5be17be5784a153 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Mon, 23 Sep 2019 14:30:19 -0400 Subject: [PATCH 12/13] Update / create new tests for application creation --- tests/routes/applications/test_index.py | 4 +- tests/routes/applications/test_new.py | 97 +++++++++++++++++++--- tests/test_access.py | 104 ++++++++++++++++++++++-- 3 files changed, 185 insertions(+), 20 deletions(-) diff --git a/tests/routes/applications/test_index.py b/tests/routes/applications/test_index.py index 614d8d89..fd9bf646 100644 --- a/tests/routes/applications/test_index.py +++ b/tests/routes/applications/test_index.py @@ -44,7 +44,7 @@ def test_user_with_permission_has_add_application_link(client, user_session): url_for("applications.portfolio_applications", portfolio_id=portfolio.id) ) assert ( - url_for("applications.create", portfolio_id=portfolio.id) + url_for("applications.create_new_application_step_1", portfolio_id=portfolio.id) in response.data.decode() ) @@ -58,7 +58,7 @@ def test_user_without_permission_has_no_add_application_link(client, user_sessio url_for("applications.portfolio_applications", portfolio_id=portfolio.id) ) assert ( - url_for("applications.create", portfolio_id=portfolio.id) + url_for("applications.create_new_application_step_1", portfolio_id=portfolio.id) not in response.data.decode() ) diff --git a/tests/routes/applications/test_new.py b/tests/routes/applications/test_new.py index 6c9e086d..22c86969 100644 --- a/tests/routes/applications/test_new.py +++ b/tests/routes/applications/test_new.py @@ -1,21 +1,96 @@ from flask import url_for -from tests.factories import PortfolioFactory +from tests.factories import PortfolioFactory, ApplicationFactory +from atst.domain.applications import Applications -def test_creating_application(client, user_session): +def test_get_name_and_description_form(client, user_session): + portfolio = PortfolioFactory.create() + user_session(portfolio.owner) + response = client.get( + url_for("applications.view_new_application_step_1", portfolio_id=portfolio.id) + ) + assert response.status_code == 200 + + +def test_get_name_and_description_form_for_update(client, user_session): + name = "My Test Application" + description = "This is the description of the test application." + application = ApplicationFactory.create(name=name, description=description) + user_session(application.portfolio.owner) + response = client.get( + url_for( + "applications.view_new_application_step_1", + portfolio_id=application.portfolio.id, + application_id=application.id, + ) + ) + assert response.status_code == 200 + assert name in response.data.decode() + assert description in response.data.decode() + + +def test_post_name_and_description(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", - }, + url_for( + "applications.create_new_application_step_1", portfolio_id=portfolio.id + ), + data={"name": "Test Application", "description": "This is only a test"}, ) assert response.status_code == 302 assert len(portfolio.applications) == 1 - assert len(portfolio.applications[0].environments) == 3 + assert portfolio.applications[0].name == "Test Application" + assert portfolio.applications[0].description == "This is only a test" + + +def test_post_name_and_description_for_update(client, session, user_session): + application = ApplicationFactory.create() + user_session(application.portfolio.owner) + response = client.post( + url_for( + "applications.update_new_application_step_1", + portfolio_id=application.portfolio.id, + application_id=application.id, + ), + data={"name": "Test Application", "description": "This is only a test"}, + ) + assert response.status_code == 302 + + session.refresh(application) + assert application.name == "Test Application" + assert application.description == "This is only a test" + + +def test_get_environments(client, user_session): + application = ApplicationFactory.create() + user_session(application.portfolio.owner) + response = client.get( + url_for( + "applications.view_new_application_step_2", + portfolio_id=application.portfolio.id, + application_id=application.id, + ) + ) + assert response.status_code == 200 + + +def test_post_environments(client, session, user_session): + application = ApplicationFactory.create(environments=[]) + user_session(application.portfolio.owner) + response = client.post( + url_for( + "applications.update_new_application_step_2", + portfolio_id=application.portfolio.id, + application_id=application.id, + ), + data={ + "environment_names-0": "development", + "environment_names-1": "staging", + "environment_names-2": "production", + }, + ) + assert response.status_code == 302 + session.refresh(application) + assert len(application.environments) == 3 diff --git a/tests/test_access.py b/tests/test_access.py index 779ef7f6..f8ef43a2 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -222,17 +222,105 @@ def test_applications_access_environment_access(get_url_assert_status): get_url_assert_status(ccpo, url, 404) -# applications.create -def test_applications_create_access(post_url_assert_status): +# applications.view_new_application_step_1 +def test_applications_get_application_step_1(get_url_assert_status): ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) owner = user_with() rando = user_with() portfolio = PortfolioFactory.create(owner=owner) - url = url_for("applications.create", portfolio_id=portfolio.id) - post_url_assert_status(ccpo, url, 200) - post_url_assert_status(owner, url, 200) - post_url_assert_status(rando, url, 404) + url = url_for("applications.view_new_application_step_1", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# applications.view_new_application_step_1 +def test_applications_get_application_step_1_update(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + application = ApplicationFactory.create(portfolio=portfolio) + + url = url_for( + "applications.view_new_application_step_1", + portfolio_id=portfolio.id, + application_id=application.id, + ) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# applications.create_or_update_new_application_step_1 +def test_applications_post_application_step_1(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + application = ApplicationFactory.create(portfolio=portfolio) + step_1_form_data = { + "name": "Test Application", + "description": "This is only a test", + } + + url = url_for( + "applications.create_new_application_step_1", portfolio_id=portfolio.id + ) + post_url_assert_status(ccpo, url, 302, data=step_1_form_data) + post_url_assert_status(owner, url, 302, data=step_1_form_data) + post_url_assert_status(rando, url, 404, data=step_1_form_data) + + url = url_for( + "applications.update_new_application_step_1", + portfolio_id=portfolio.id, + application_id=application.id, + ) + post_url_assert_status(ccpo, url, 302, data=step_1_form_data) + post_url_assert_status(owner, url, 302, data=step_1_form_data) + post_url_assert_status(rando, url, 404, data=step_1_form_data) + + +# applications.view_new_application_step_2 +def test_applications_get_application_step_2(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + application = ApplicationFactory.create(portfolio=portfolio) + + url = url_for( + "applications.view_new_application_step_2", + portfolio_id=portfolio.id, + application_id=application.id, + ) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# applications.update_new_application_step_2 +def test_applications_post_application_step_2(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + application = ApplicationFactory.create(portfolio=portfolio) + step_2_form_data = { + "environment_names-0": "development", + "environment_names-1": "staging", + "environment_names-2": "production", + } + + url = url_for( + "applications.update_new_application_step_1", + portfolio_id=portfolio.id, + application_id=application.id, + ) + post_url_assert_status(ccpo, url, 302, data=step_2_form_data) + post_url_assert_status(owner, url, 302, data=step_2_form_data) + post_url_assert_status(rando, url, 404, data=step_2_form_data) # portfolios.invite_member @@ -319,7 +407,9 @@ def test_applications_new_access(get_url_assert_status): rando = user_with() portfolio = PortfolioFactory.create(owner=owner) - url = url_for("applications.new", portfolio_id=portfolio.id) + url = url_for( + "applications.create_new_application_step_1", portfolio_id=portfolio.id + ) get_url_assert_status(ccpo, url, 200) get_url_assert_status(owner, url, 200) get_url_assert_status(rando, url, 404) From dbc851258fda64fc70c1b1cda25f9089f9a6c103 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Tue, 24 Sep 2019 09:56:17 -0400 Subject: [PATCH 13/13] Update .secrets.baseline --- .secrets.baseline | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index e6096961..3e7ecf38 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$", "lines": null }, - "generated_at": "2019-09-20T19:20:43Z", + "generated_at": "2019-09-24T13:51:51Z", "plugins_used": [ { "base64_limit": 4.5, @@ -199,5 +199,5 @@ } ] }, - "version": "0.12.6" + "version": "0.12.5" }