{{ "portfolios.header" | translate }}
-{{ "portfolios.new.title" | translate }}
-{{ "portfolios.header" | translate }}
+{{ 'portfolios.new.title' | translate }}
+90 + payload = ManagementGroupCSPPayload( + tenant_id="any-old-id", + management_group_name=name, + display_name="Council of Naboo", + parent_id="Galactic_Senate", + ) + assert len(payload.management_group_name) == 90 + + +def test_ManagementGroupCSPPayload_display_name(): + # shortens display_name to fit + name = "Council of Naboo".ljust(95, "1") + assert len(name) > 90 + payload = ManagementGroupCSPPayload( + tenant_id="any-old-id", display_name=name, parent_id="Galactic_Senate" + ) + assert len(payload.display_name) == 90 + + +def test_ManagementGroupCSPPayload_parent_id(): + full_path = f"{AZURE_MGMNT_PATH}Galactic_Senate" + # adds full path + payload = ManagementGroupCSPPayload( + tenant_id="any-old-id", + display_name="Council of Naboo", + parent_id="Galactic_Senate", + ) + assert payload.parent_id == full_path + # keeps full path + payload = ManagementGroupCSPPayload( + tenant_id="any-old-id", display_name="Council of Naboo", parent_id=full_path + ) + assert payload.parent_id == full_path + + +def test_ManagementGroupCSPResponse_id(): + full_id = "/path/to/naboo-123" + response = ManagementGroupCSPResponse( + **{"id": "/path/to/naboo-123", "other": "stuff"} + ) + assert response.id == full_id + + +def test_KeyVaultCredentials_enforce_admin_creds(): + with pytest.raises(ValidationError): + KeyVaultCredentials(tenant_id="an id", tenant_admin_username="C3PO") + assert KeyVaultCredentials( + tenant_id="an id", + tenant_admin_username="C3PO", + tenant_admin_password="beep boop", + ) + + +def test_KeyVaultCredentials_enforce_sp_creds(): + with pytest.raises(ValidationError): + KeyVaultCredentials(tenant_id="an id", tenant_sp_client_id="C3PO") + assert KeyVaultCredentials( + tenant_id="an id", tenant_sp_client_id="C3PO", tenant_sp_key="beep boop" + ) + + +def test_KeyVaultCredentials_enforce_root_creds(): + with pytest.raises(ValidationError): + KeyVaultCredentials(root_tenant_id="an id", root_sp_client_id="C3PO") + assert KeyVaultCredentials( + root_tenant_id="an id", root_sp_client_id="C3PO", root_sp_key="beep boop" + ) diff --git a/tests/domain/test_applications.py b/tests/domain/test_applications.py index 9fda3114..02dd3124 100644 --- a/tests/domain/test_applications.py +++ b/tests/domain/test_applications.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta import pytest from uuid import uuid4 @@ -196,3 +197,20 @@ def test_update_does_not_duplicate_names_within_portfolio(): with pytest.raises(AlreadyExistsError): Applications.update(dupe_application, {"name": name}) + + +def test_get_applications_pending_creation(): + now = datetime.now() + later = now + timedelta(minutes=30) + + portfolio1 = PortfolioFactory.create(state="COMPLETED") + app_ready = ApplicationFactory.create(portfolio=portfolio1) + + app_done = ApplicationFactory.create(portfolio=portfolio1, cloud_id="123456") + + portfolio2 = PortfolioFactory.create(state="UNSTARTED") + app_not_ready = ApplicationFactory.create(portfolio=portfolio2) + + uuids = Applications.get_applications_pending_creation() + + assert [app_ready.id] == uuids diff --git a/tests/domain/test_task_orders.py b/tests/domain/test_task_orders.py index 49b9bae6..42da74e6 100644 --- a/tests/domain/test_task_orders.py +++ b/tests/domain/test_task_orders.py @@ -71,7 +71,7 @@ def test_update_adds_clins(): def test_update_does_not_duplicate_clins(): task_order = TaskOrderFactory.create( - number="3453453456", create_clins=[{"number": "123"}, {"number": "456"}] + number="3453453456123", create_clins=[{"number": "123"}, {"number": "456"}] ) clins = [ { @@ -93,7 +93,7 @@ def test_update_does_not_duplicate_clins(): ] task_order = TaskOrders.update( task_order_id=task_order.id, - number="0000000000", + number="0000000000000", clins=clins, pdf={"filename": "sample.pdf", "object_name": "1234567"}, ) @@ -170,3 +170,11 @@ def test_update_enforces_unique_number(): dupe_task_order = TaskOrderFactory.create() with pytest.raises(AlreadyExistsError): TaskOrders.update(dupe_task_order.id, task_order.number, [], None) + + +def test_allows_alphanumeric_number(): + portfolio = PortfolioFactory.create() + valid_to_numbers = ["1234567890123", "ABC1234567890"] + + for number in valid_to_numbers: + assert TaskOrders.create(portfolio.id, number, [], None) diff --git a/tests/factories.py b/tests/factories.py index d9af7c40..b7a63243 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -7,6 +7,7 @@ import datetime from atst.forms import data from atst.models import * +from atst.models.mixins.state_machines import FSMStates from atst.domain.invitations import PortfolioInvitations from atst.domain.permission_sets import PermissionSets @@ -121,6 +122,7 @@ class PortfolioFactory(Base): owner = kwargs.pop("owner", UserFactory.create()) members = kwargs.pop("members", []) with_task_orders = kwargs.pop("task_orders", []) + state = kwargs.pop("state", None) portfolio = super()._create(model_class, *args, **kwargs) @@ -161,6 +163,12 @@ class PortfolioFactory(Base): permission_sets=perms_set, ) + if state: + state = getattr(FSMStates, state) + fsm = PortfolioStateMachineFactory.create(state=state, portfolio=portfolio) + # setting it in the factory is not working for some reason + fsm.state = state + portfolio.applications = applications portfolio.task_orders = task_orders return portfolio diff --git a/tests/forms/test_task_order.py b/tests/forms/test_task_order.py index 97759c81..ae4fd3c6 100644 --- a/tests/forms/test_task_order.py +++ b/tests/forms/test_task_order.py @@ -112,3 +112,37 @@ def test_no_number(): http_request_form_data = {} form = TaskOrderForm(http_request_form_data) assert form.data["number"] is None + + +def test_number_allows_alphanumeric(): + valid_to_numbers = ["1234567890123", "ABC1234567890"] + + for number in valid_to_numbers: + form = TaskOrderForm({"number": number}) + assert form.validate() + + +def test_number_allows_between_13_and_17_characters(): + valid_to_numbers = ["123456789012345", "ABCDEFG1234567890"] + + for number in valid_to_numbers: + form = TaskOrderForm({"number": number}) + assert form.validate() + + +def test_number_strips_dashes(): + valid_to_numbers = ["123-456789-012345", "ABCD-EFG12345-67890"] + + for number in valid_to_numbers: + form = TaskOrderForm({"number": number}) + assert form.validate() + assert not "-" in form.number.data + + +def test_number_case_coerces_all_caps(): + valid_to_numbers = ["12345678012345", "AbcEFg1234567890"] + + for number in valid_to_numbers: + form = TaskOrderForm({"number": number}) + assert form.validate() + assert form.number.data == number.upper() diff --git a/tests/mock_azure.py b/tests/mock_azure.py index 7fa67667..4f37848e 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -72,6 +72,12 @@ def mock_secrets(): return Mock(spec=secrets) +def mock_identity(): + import azure.identity as identity + + return Mock(spec=identity) + + class MockAzureSDK(object): def __init__(self): from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD @@ -88,6 +94,7 @@ class MockAzureSDK(object): self.requests = mock_requests() # may change to a JEDI cloud self.cloud = AZURE_PUBLIC_CLOUD + self.identity = mock_identity() @pytest.fixture(scope="function") diff --git a/tests/render_vue_component.py b/tests/render_vue_component.py index 62106a67..49fb6ee9 100644 --- a/tests/render_vue_component.py +++ b/tests/render_vue_component.py @@ -35,6 +35,7 @@ class TaskOrderPdfForm(Form): class TaskOrderForm(Form): pdf = FormField(TaskOrderPdfForm, label="task_order_pdf") + number = StringField(label="task_order_number", default="number") @pytest.fixture @@ -63,6 +64,12 @@ def multi_checkbox_input_macro(env): return getattr(multi_checkbox_template.module, "MultiCheckboxInput") +@pytest.fixture +def text_input_macro(env): + text_input_template = env.get_template("components/text_input.html") + return getattr(text_input_template.module, "TextInput") + + @pytest.fixture def initial_value_form(scope="function"): return InitialValueForm() @@ -170,3 +177,10 @@ def test_make_pop_date_range(env, app): index=1, ) write_template(pop_date_range, "pop_date_range.html") + + +def test_make_text_input_template(text_input_macro, task_order_form): + text_input_to_number = text_input_macro( + task_order_form.number, validation="taskOrderNumber" + ) + write_template(text_input_to_number, "text_input_to_number.html") diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 4855ad83..9929a992 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -158,7 +158,7 @@ def test_task_orders_form_step_two_add_number(client, user_session, task_order): def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order): user_session(task_order.portfolio.owner) - form_data = {"number": "1234567890"} + form_data = {"number": "abc-1234567890"} response = client.post( url_for( "task_orders.submit_form_step_two_add_number", task_order_id=task_order.id @@ -167,7 +167,7 @@ def test_task_orders_submit_form_step_two_add_number(client, user_session, task_ ) assert response.status_code == 302 - assert task_order.number == "1234567890" + assert task_order.number == "ABC1234567890" # pragma: allowlist secret def test_task_orders_submit_form_step_two_enforces_unique_number( @@ -194,7 +194,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to( client, user_session, task_order ): user_session(task_order.portfolio.owner) - form_data = {"number": "0000000000"} + form_data = {"number": "0000000000000"} original_number = task_order.number response = client.post( url_for( @@ -203,7 +203,7 @@ def test_task_orders_submit_form_step_two_add_number_existing_to( data=form_data, ) assert response.status_code == 302 - assert task_order.number == "0000000000" + assert task_order.number == "0000000000000" assert task_order.number != original_number diff --git a/tests/test_access.py b/tests/test_access.py index b0dac527..f8879024 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -663,7 +663,7 @@ def test_task_orders_new_get_routes(get_url_assert_status): def test_task_orders_new_post_routes(post_url_assert_status): post_routes = [ ("task_orders.submit_form_step_one_add_pdf", {"pdf": ""}), - ("task_orders.submit_form_step_two_add_number", {"number": "1234567890"}), + ("task_orders.submit_form_step_two_add_number", {"number": "1234567890123"}), ( "task_orders.submit_form_step_three_add_clins", { diff --git a/tests/test_jobs.py b/tests/test_jobs.py index ff8e4602..2ac5f408 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -8,9 +8,9 @@ from atst.domain.csp.cloud import MockCloudProvider from atst.domain.portfolios import Portfolios from atst.jobs import ( - RecordEnvironmentFailure, - RecordEnvironmentRoleFailure, + RecordFailure, dispatch_create_environment, + dispatch_create_application, dispatch_create_atat_admin_user, dispatch_provision_portfolio, dispatch_provision_user, @@ -18,6 +18,7 @@ from atst.jobs import ( do_provision_user, do_provision_portfolio, do_create_environment, + do_create_application, do_create_atat_admin_user, ) from atst.models.utils import claim_for_update @@ -27,9 +28,10 @@ from tests.factories import ( EnvironmentRoleFactory, PortfolioFactory, PortfolioStateMachineFactory, + ApplicationFactory, ApplicationRoleFactory, ) -from atst.models import CSPRole, EnvironmentRole, ApplicationRoleStatus +from atst.models import CSPRole, EnvironmentRole, ApplicationRoleStatus, JobFailure @pytest.fixture(autouse=True, scope="function") @@ -43,8 +45,17 @@ def portfolio(): return portfolio -def test_environment_job_failure(celery_app, celery_worker): - @celery_app.task(bind=True, base=RecordEnvironmentFailure) +def _find_failure(session, entity, id_): + return ( + session.query(JobFailure) + .filter(JobFailure.entity == entity) + .filter(JobFailure.entity_id == id_) + .one() + ) + + +def test_environment_job_failure(session, celery_app, celery_worker): + @celery_app.task(bind=True, base=RecordFailure) def _fail_hard(self, environment_id=None): raise ValueError("something bad happened") @@ -56,13 +67,12 @@ def test_environment_job_failure(celery_app, celery_worker): with pytest.raises(ValueError): task.get() - assert environment.job_failures - job_failure = environment.job_failures[0] + job_failure = _find_failure(session, "environment", str(environment.id)) assert job_failure.task == task -def test_environment_role_job_failure(celery_app, celery_worker): - @celery_app.task(bind=True, base=RecordEnvironmentRoleFailure) +def test_environment_role_job_failure(session, celery_app, celery_worker): + @celery_app.task(bind=True, base=RecordFailure) def _fail_hard(self, environment_role_id=None): raise ValueError("something bad happened") @@ -74,8 +84,7 @@ def test_environment_role_job_failure(celery_app, celery_worker): with pytest.raises(ValueError): task.get() - assert role.job_failures - job_failure = role.job_failures[0] + job_failure = _find_failure(session, "environment_role", str(role.id)) assert job_failure.task == task @@ -99,6 +108,24 @@ def test_create_environment_job_is_idempotent(csp, session): csp.create_environment.assert_not_called() +def test_create_application_job(session, csp): + portfolio = PortfolioFactory.create( + csp_data={"tenant_id": str(uuid4()), "root_management_group_id": str(uuid4())} + ) + application = ApplicationFactory.create(portfolio=portfolio, cloud_id=None) + do_create_application(csp, application.id) + session.refresh(application) + + assert application.cloud_id + + +def test_create_application_job_is_idempotent(csp): + application = ApplicationFactory.create(cloud_id=uuid4()) + do_create_application(csp, application.id) + + csp.create_application.assert_not_called() + + def test_create_atat_admin_user(csp, session): environment = EnvironmentFactory.create(cloud_id="something") do_create_atat_admin_user(csp, environment.id) @@ -139,6 +166,21 @@ def test_dispatch_create_environment(session, monkeypatch): mock.delay.assert_called_once_with(environment_id=e1.id) +def test_dispatch_create_application(monkeypatch): + portfolio = PortfolioFactory.create(state="COMPLETED") + app = ApplicationFactory.create(portfolio=portfolio) + + mock = Mock() + monkeypatch.setattr("atst.jobs.create_application", mock) + + # When dispatch_create_application is called + dispatch_create_application.run() + + # It should cause the create_application task to be called once + # with the application id + mock.delay.assert_called_once_with(application_id=app.id) + + def test_dispatch_create_atat_admin_user(session, monkeypatch): portfolio = PortfolioFactory.create( applications=[ diff --git a/tests/utils/test_hash.py b/tests/utils/test_hash.py new file mode 100644 index 00000000..5cfb8489 --- /dev/null +++ b/tests/utils/test_hash.py @@ -0,0 +1,16 @@ +import random +import re +import string + +from atst.utils import sha256_hex + + +def test_sha256_hex(): + sample = "".join( + random.choices(string.ascii_uppercase + string.digits, k=random.randrange(200)) + ) + hashed = sha256_hex(sample) + assert re.match("^[a-zA-Z0-9]+$", hashed) + assert len(hashed) == 64 + hashed_again = sha256_hex(sample) + assert hashed == hashed_again diff --git a/translations.yaml b/translations.yaml index b3caebc7..df583059 100644 --- a/translations.yaml +++ b/translations.yaml @@ -84,6 +84,31 @@ email: application_invite: "{inviter_name} has invited you to a JEDI cloud application" portfolio_invite: "{inviter_name} has invited you to a JEDI cloud portfolio" environment_ready: JEDI cloud environment ready +empty_state: + applications: + header: + edit: You don’t have any Applications yet + view: This portfolio has no Applications + message: + edit: You can manage multiple Applications within a single Portfolio as long as the funding sources are the same. + view: A Portfolio member with Edit Application permissions can add Applications to this Portfolio. + button_text: Create Your First Application + applications_reporting: + header: + edit: Nothing to report. + view: Nothing to report. + message: + edit: This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started. + view: This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments. + button_text: Add a new application + task_orders: + header: + edit: Add approved task orders + view: This Portfolio has no Task Orders + message: + edit: Upload your approved Task Order here. You are required to confirm you have the appropriate signature. You will have the ability to add additional approved Task Orders with more funding to this Portfolio in the future. + view: A Portfolio member with Edit Funding permissions can fund this Portfolio with approved Task Orders. + button_text: Add Task Order flash: application: created: @@ -370,11 +395,6 @@ portfolios: add_member: Add Team Member add_another_environment: Add another environment create_button: Create Application - empty_state: - header: You don't have any Applications yet - message: You can manage multiple Applications within a single Portfolio as long as the funding sources are the same. - button_text: Create Your First Application - view_only_text: Contact your portfolio administrator to add an application. new: step_1_header: Name and Describe New Application step_1_button_text: "Next: Add Environments" @@ -417,6 +437,7 @@ portfolios: add_subscription: Add new subscription blank_slate: This Application has no environments disabled: ": Access Suspended" + funding_alert: "Application environments will not be created until the {name} portfolio is funded." environments_heading: Application Environments existing_application_title: "{application_name} Application Settings" member_count: "{count} Members" @@ -482,12 +503,6 @@ portfolios: header: Funding Duration tooltip: Funding duration is the period of time that there is a valid task order funding the portfolio. estimate_warning: Reports displayed in JEDI are estimates and not a system of record. - empty_state: - message: Nothing to report. - sub_message: - can_create_applications: This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started. - cannot_create_applications: This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments. - action_label: "Add a new application" total_value: header: Total Portfolio Value tooltip: Total portfolio value is all obligated and projected funds for all task orders in this portfolio. @@ -558,11 +573,6 @@ task_orders: sticky_header_text: "Add a Task Order" sticky_header_review_text: Review Changes sticky_header_context: "Step {step} of 5" - empty_state: - header: Add approved task orders - message: Upload your approved Task Order here. You are required to confirm you have the appropriate signature. You will have the ability to add additional approved Task Orders with more funding to this Portfolio in the future. - button_text: Add Task Order - view_only_text: Contact your portfolio administrator to add a Task Order. sign: digital_signature_description: I confirm the uploaded Task Order is signed by the appropriate, duly warranted Agency Contracting Officer who authorized me to upload the Task Order. confirmation_description: I confirm that the information entered here in matches that of the submitted Task Order. diff --git a/uitests/Application_Index_with_App.html b/uitests/Application_Index_with_App.html index 2c2c120c..c7efd48a 100644 --- a/uitests/Application_Index_with_App.html +++ b/uitests/Application_Index_with_App.html @@ -169,7 +169,7 @@ Imported from: AT-AT CI - New Portfolio-->
assertText | css=.sticky-cta-text > h3 | -*Create New Portfolio* | +*Name and Describe Portfolio* | |
waitForPageToLoad | @@ -192,7 +192,7 @@ Imported from: AT-AT CI - New Portfolio-->||||
type | css=#name | -Tatooine Energy Maintenance Systems | +Tatooine Energy Maintenance Systems ${alphanumeric} | |
waitForPageToLoad | @@ -291,29 +291,12 @@ Imported from: AT-AT CI - Portfolio Settings--> Imported from: AT-AT CI - Portfolio Settings-->||||
waitForElementPresent | -css=.panel__content > p:nth-of-type(2) | +css=th.table-cell--third | ||
assertElementPresent | -css=.panel__content > p:nth-of-type(2) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=td.name | -- | ||
assertElementPresent | -css=td.name | +css=th.table-cell--third | ||
assertText | css=button.usa-button.usa-button-primary.usa-button-big | -Save | -||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=button.usa-button.usa-button-primary | -- | ||
assertText | -css=button.usa-button.usa-button-primary | -*Update* | -||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=input.usa-button.usa-button-primary | -- | ||
assertText | -css=input.usa-button.usa-button-primary | -Save | +Save Changes | |
waitForPageToLoad | @@ -375,12 +324,12 @@ Imported from: AT-AT CI - Portfolio Settings-->||||
waitForElementPresent | -css=a.icon-link.modal-link | +css=a.usa-button.usa-button-secondary.add-new-button | ||
click | -css=a.icon-link.modal-link | +css=a.usa-button.usa-button-secondary.add-new-button | ||
waitForElementPresent | -css=#add-port-mem > div > div:nth-of-type(1) > h1 | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ||
assertText | -css=#add-port-mem > div > div:nth-of-type(1) > h1 | -*Invite new portfolio member* | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Add Manager* |
waitForPageToLoad | @@ -487,13 +436,13 @@ Imported from: AT-AT CI - Portfolio Settings-->||||
waitForElementPresent | -css=#add-port-mem > div > div:nth-of-type(2) > h1 | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ||
assertText | -css=#add-port-mem > div > div:nth-of-type(2) > h1 | -*Assign member permissions* | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Set Portfolio Permissions* |
waitForPageToLoad | @@ -503,12 +452,12 @@ Imported from: AT-AT CI - Portfolio Settings-->||||
waitForElementPresent | -css=#permission_sets-perms_app_mgmt | +css=#perms_app_mgmt-None | ||
click | -css=#permission_sets-perms_app_mgmt | +css=#perms_app_mgmt-None | ||
waitForElementPresent | -css=#permission_sets-perms_app_mgmt > option:nth-of-type(1) | +css=#perms_funding-None | ||
click | -css=#permission_sets-perms_app_mgmt > option:nth-of-type(1) | +css=#perms_funding-None | ||
waitForElementPresent | -css=#permission_sets-perms_funding | +css=#perms_reporting-None | ||
click | -css=#permission_sets-perms_funding | +css=#perms_reporting-None | ||
waitForElementPresent | -css=#permission_sets-perms_funding > option:nth-of-type(1) | -- | ||
click | -css=#permission_sets-perms_funding > option:nth-of-type(1) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=#permission_sets-perms_reporting | -- | ||
click | -css=#permission_sets-perms_reporting | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=#permission_sets-perms_reporting > option:nth-of-type(1) | -- | ||
click | -css=#permission_sets-perms_reporting > option:nth-of-type(1) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=#permission_sets-perms_portfolio_mgmt | +css=#perms_portfolio_mgmt-None | ||
type | -css=#permission_sets-perms_portfolio_mgmt | +css=#perms_portfolio_mgmt-None | edit_portfolio_admin | |
waitForElementPresent | -css=#permission_sets-perms_portfolio_mgmt > option:nth-of-type(2) | -- | ||
click | -css=#permission_sets-perms_portfolio_mgmt > option:nth-of-type(2) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | css=input[type="submit"].action-group__action | |||
waitForElementPresent | -css=table.atat-table > tbody > tr:nth-of-type(2) > td.name | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | ++ | |
assertText | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | +*invite pending* | +||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=.usa-alert-body | ++ | ||
assertText | +css=.usa-alert-body | +*Brandon Buchannan's invitation has been sent + +Brandon Buchannan's access to this Portfolio is pending until they sign in for the first time.* | +||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 | ++ | ||
click | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 | ++ | ||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item-toggle-content.toggle-menu__toggle > a:nth-of-type(1) | ++ | ||
click | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item-toggle-content.toggle-menu__toggle > a:nth-of-type(1) | ++ | ||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=.portfolio-content > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 | |||
assertElementPresent | -css=table.atat-table > tbody > tr:nth-of-type(2) > td.name | +css=.portfolio-content > div:nth-of-type(3) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 | ||
waitForElementPresent | -css=.usa-alert-body > p:nth-of-type(2) | +css=.portfolio-perms > div:nth-of-type(2) > .usa-input.input__inline-fields.checked > fieldset.usa-input__choices > legend > label | ++ | |
click | +css=.portfolio-perms > div:nth-of-type(2) > .usa-input.input__inline-fields.checked > fieldset.usa-input__choices > legend > label | ++ | ||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=.portfolio-perms > div:nth-of-type(4) > .usa-input.input__inline-fields > fieldset.usa-input__choices > legend > label | ++ | ||
click | +css=.portfolio-perms > div:nth-of-type(4) > .usa-input.input__inline-fields > fieldset.usa-input__choices > legend > label | ++ | ||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=.action-group__action.usa-button | ++ | ||
click | +css=.action-group__action.usa-button | ++ | ||
waitForPageToLoad | ++ | + | ||
waitForElementPresent | +css=h3.usa-alert-heading | |||
assertText | -css=.usa-alert-body > p:nth-of-type(2) | -*You have successfully invited Brandon Buchannan to the portfolio.* | -||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=select[name="members_permissions-1-perms_app_mgmt"] | -- | ||
type | -css=select[name="members_permissions-1-perms_app_mgmt"] | -edit_portfolio_application_management | -||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=select[name="members_permissions-1-perms_app_mgmt"] > option:nth-of-type(2) | -- | ||
click | -css=select[name="members_permissions-1-perms_app_mgmt"] > option:nth-of-type(2) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=select[name="members_permissions-1-perms_reporting"] | -- | ||
type | -css=select[name="members_permissions-1-perms_reporting"] | -edit_portfolio_reports | -||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=select[name="members_permissions-1-perms_reporting"] > option:nth-of-type(2) | -- | ||
click | -css=select[name="members_permissions-1-perms_reporting"] > option:nth-of-type(2) | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=input[type="submit"] | -- | ||
click | -css=input[type="submit"] | -- | ||
waitForPageToLoad | -- | - | ||
waitForElementPresent | -css=.usa-alert.usa-alert-success > .usa-alert-body > h3.usa-alert-heading | -- | ||
assertText | -css=.usa-alert.usa-alert-success > .usa-alert-body > h3.usa-alert-heading | +css=h3.usa-alert-heading | *Success!* | |
waitForElementPresent | -css=.usa-alert-body > p:nth-of-type(2) | +css=.usa-alert-text | ||
assertText | -css=.usa-alert-body > p:nth-of-type(2) | -*You have successfully updated access permissions for members of Tatooine Energy Maintenance Systems.* | +css=.usa-alert-text | +*You have successfully updated access permissions for* |
Resend Portfolio Member Invite | +||
waitForPageToLoad | ++ | + |
open | +/login-dev?username=brandon | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/user"] > .topbar__link-label | ++ |
assertText | +css=a[href="/user"] > .topbar__link-label | +*Brandon Buchannan* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/logout"] > .topbar__link-label | ++ |
click | +css=a[href="/logout"] > .topbar__link-label | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.col > .usa-alert.usa-alert-info:nth-of-type(2) > .usa-alert-body > h3.usa-alert-heading | ++ |
assertText | +css=.col > .usa-alert.usa-alert-info:nth-of-type(2) > .usa-alert-body > h3.usa-alert-heading | +*Logged out* | +
waitForPageToLoad | ++ | + |
open | +/login-dev | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.home__content > h1 | ++ |
assertText | +css=.home__content > h1 | +JEDI Cloud Services | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/portfolios/new"] | ++ |
click | +css=a[href="/portfolios/new"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.portfolio-header__name > h1 | ++ |
assertText | +css=.portfolio-header__name > h1 | +*New Portfolio* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.sticky-cta-text > h3 | ++ |
assertText | +css=.sticky-cta-text > h3 | +*Name and Describe Portfolio* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#name | ++ |
type | +css=#name | +Tatooine Energy Maintenance Systems ${alphanumeric} | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=fieldset.usa-input__choices > ul > li:nth-of-type(5) > label | ++ |
click | +css=fieldset.usa-input__choices > ul > li:nth-of-type(5) > label | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="submit"] | ++ |
click | +css=input[type="submit"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.empty-state > h3 | ++ |
assertText | +css=.empty-state > h3 | +*You don't have any Applications yet* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.icon.icon--cog > svg | ++ |
click | +css=.icon.icon--cog > svg | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.portfolio-header__name > h1 | ++ |
assertText | +css=.portfolio-header__name > h1 | +*Tatooine Energy Maintenance Systems* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=th.table-cell--third | ++ |
assertElementPresent | +css=th.table-cell--third | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=button.usa-button.usa-button-primary.usa-button-big | ++ |
assertText | +css=button.usa-button.usa-button-primary.usa-button-big | +Save Changes | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a.usa-button.usa-button-secondary.add-new-button | ++ |
click | +css=a.usa-button.usa-button-secondary.add-new-button | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ++ |
assertText | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Add Manager* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-first_name | ++ |
type | +css=#user_data-first_name | +Brandon | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-last_name | ++ |
type | +css=#user_data-last_name | +Buchannan | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-email | ++ |
type | +css=#user_data-email | +jay+brandon@promptworks.com | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-dod_id | ++ |
type | +css=#user_data-dod_id | +3456789012 | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="button"] | ++ |
click | +css=input[type="button"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ++ |
assertText | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Set Portfolio Permissions* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_app_mgmt-None | ++ |
click | +css=#perms_app_mgmt-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_funding-None | ++ |
click | +css=#perms_funding-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_reporting-None | ++ |
click | +css=#perms_reporting-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_portfolio_mgmt-None | ++ |
type | +css=#perms_portfolio_mgmt-None | +edit_portfolio_admin | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="submit"].action-group__action | ++ |
click | +css=input[type="submit"].action-group__action | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | ++ |
assertText | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | +*invite pending* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.usa-alert-body | ++ |
assertText | +css=.usa-alert-body | +*Brandon Buchannan's invitation has been sent + +Brandon Buchannan's access to this Portfolio is pending until they sign in for the first time.* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 > path | ++ |
click | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 > path | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item-toggle-content.toggle-menu__toggle > a:nth-of-type(2) | ++ |
click | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item-toggle-content.toggle-menu__toggle > a:nth-of-type(2) | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.portfolio-content > div:nth-of-type(4) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 | ++ |
assertText | +css=.portfolio-content > div:nth-of-type(4) > .modal.form-content--app-mem > .modal__container > .modal__dialog > .modal__body > .modal__form--header > h1 | +*Verify Member Information* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.action-group__action.usa-button | ++ |
click | +css=.action-group__action.usa-button | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.usa-alert-text | ++ |
assertText | +css=.usa-alert-text | +*jay+brandon@promptworks.com has been sent an invitation to access this Portfolio* | +
Revoke Portfolio Member Invite | +||
waitForPageToLoad | ++ | + |
open | +/login-dev?username=brandon | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/user"] > .topbar__link-label | ++ |
assertText | +css=a[href="/user"] > .topbar__link-label | +*Brandon Buchannan* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/logout"] > .topbar__link-label | ++ |
click | +css=a[href="/logout"] > .topbar__link-label | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.col > .usa-alert.usa-alert-info:nth-of-type(2) > .usa-alert-body > h3.usa-alert-heading | ++ |
assertText | +css=.col > .usa-alert.usa-alert-info:nth-of-type(2) > .usa-alert-body > h3.usa-alert-heading | +*Logged out* | +
waitForPageToLoad | ++ | + |
open | +/login-dev | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.home__content > h1 | ++ |
assertText | +css=.home__content > h1 | +JEDI Cloud Services | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a[href="/portfolios/new"] | ++ |
click | +css=a[href="/portfolios/new"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.portfolio-header__name > h1 | ++ |
assertText | +css=.portfolio-header__name > h1 | +*New Portfolio* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.sticky-cta-text > h3 | ++ |
assertText | +css=.sticky-cta-text > h3 | +*Name and Describe Portfolio* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#name | ++ |
type | +css=#name | +Tatooine Energy Maintenance Systems ${alphanumeric} | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=fieldset.usa-input__choices > ul > li:nth-of-type(5) > label | ++ |
click | +css=fieldset.usa-input__choices > ul > li:nth-of-type(5) > label | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="submit"] | ++ |
click | +css=input[type="submit"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.empty-state > h3 | ++ |
assertText | +css=.empty-state > h3 | +*You don't have any Applications yet* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.icon.icon--cog > svg | ++ |
click | +css=.icon.icon--cog > svg | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.portfolio-header__name > h1 | ++ |
assertText | +css=.portfolio-header__name > h1 | +*Tatooine Energy Maintenance Systems* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=th.table-cell--third | ++ |
assertElementPresent | +css=th.table-cell--third | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=button.usa-button.usa-button-primary.usa-button-big | ++ |
assertText | +css=button.usa-button.usa-button-primary.usa-button-big | +Save Changes | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=a.usa-button.usa-button-secondary.add-new-button | ++ |
click | +css=a.usa-button.usa-button-secondary.add-new-button | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ++ |
assertText | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Add Manager* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-first_name | ++ |
type | +css=#user_data-first_name | +Brandon | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-last_name | ++ |
type | +css=#user_data-last_name | +Buchannan | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-email | ++ |
type | +css=#user_data-email | +jay+brandon@promptworks.com | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#user_data-dod_id | ++ |
type | +css=#user_data-dod_id | +3456789012 | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="button"] | ++ |
click | +css=input[type="button"] | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#add-portfolio-manager > div > div > div.member-form > h2 | ++ |
assertText | +css=#add-portfolio-manager > div > div > div.member-form > h2 | +*Set Portfolio Permissions* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_app_mgmt-None | ++ |
click | +css=#perms_app_mgmt-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_funding-None | ++ |
click | +css=#perms_funding-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_reporting-None | ++ |
click | +css=#perms_reporting-None | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=#perms_portfolio_mgmt-None | ++ |
type | +css=#perms_portfolio_mgmt-None | +edit_portfolio_admin | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=input[type="submit"].action-group__action | ++ |
click | +css=input[type="submit"].action-group__action | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | ++ |
assertText | +css=table.atat-table > tbody > tr > td > span.label.label--success.label--below | +*invite pending* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.usa-alert-body | ++ |
assertText | +css=.usa-alert-body | +*Brandon Buchannan's invitation has been sent + +Brandon Buchannan's access to this Portfolio is pending until they sign in for the first time.* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 | ++ |
click | +css=table.atat-table > tbody > tr:nth-of-type(2) > td.toggle-menu__container > .toggle-menu > .accordion-table__item__toggler > .icon.icon--ellipsis > svg.svg-inline--fa.fa-ellipsis-h.fa-w-16 | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=.accordion-table__item-toggle-content > a:nth-of-type(3) | ++ |
click | +css=.accordion-table__item-toggle-content > a:nth-of-type(3) | ++ |
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=form[action] > h1 | ++ |
assertText | +css=form[action] > h1 | +*Revoke Invite* | +
waitForPageToLoad | ++ | + |
waitForElementPresent | +css=button[type="submit"].action-group__action | ++ |
click | +css=button[type="submit"].action-group__action | ++ |