Merge branch 'staging' into alpha-numeric-to-numbers

This commit is contained in:
dandds
2020-01-28 11:24:40 -05:00
committed by GitHub
72 changed files with 2692 additions and 1182 deletions

View File

@@ -1,22 +1,35 @@
import pytest
from unittest.mock import Mock
from uuid import uuid4
from tests.factories import ApplicationFactory, EnvironmentFactory
from tests.mock_azure import AUTH_CREDENTIALS, mock_azure
from atst.domain.csp.cloud import AzureCloudProvider
from atst.domain.csp.cloud.models import (
BillingInstructionCSPPayload,
BillingInstructionCSPResult,
BillingProfileCreationCSPPayload,
BillingProfileCreationCSPResult,
BillingProfileTenantAccessCSPPayload,
BillingProfileTenantAccessCSPResult,
BillingProfileVerificationCSPPayload,
BillingProfileVerificationCSPResult,
TaskOrderBillingCreationCSPPayload,
TaskOrderBillingCreationCSPResult,
TaskOrderBillingVerificationCSPPayload,
TaskOrderBillingVerificationCSPResult,
TenantCSPPayload,
TenantCSPResult,
)
from tests.mock_azure import mock_azure, AUTH_CREDENTIALS
from tests.factories import EnvironmentFactory, ApplicationFactory
creds = {
"home_tenant_id": "tenant_id",
"client_id": "client_id",
"secret_key": "secret_key",
}
BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31"
# TODO: Directly test create subscription, provide all args √
# TODO: Test create environment (create management group with parent)
# TODO: Test create application (create manageemnt group with parent)
# Create reusable mock for mocking the management group calls for multiple services
#
@pytest.mark.skip()
def test_create_subscription_succeeds(mock_azure: AzureCloudProvider):
environment = EnvironmentFactory.create()
@@ -51,14 +64,12 @@ def test_create_subscription_succeeds(mock_azure: AzureCloudProvider):
assert result == subscription_id
@pytest.mark.skip()
def mock_management_group_create(mock_azure, spec_dict):
mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = Mock(
**spec_dict
)
@pytest.mark.skip()
def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
environment = EnvironmentFactory.create()
@@ -71,7 +82,6 @@ def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
assert result.id == "Test Id"
@pytest.mark.skip()
def test_create_application_succeeds(mock_azure: AzureCloudProvider):
application = ApplicationFactory.create()
@@ -82,7 +92,6 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider):
assert result.id == "Test Id"
@pytest.mark.skip()
def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider):
environment_id = str(uuid4())
@@ -97,7 +106,6 @@ def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider):
assert result.get("csp_user_id") == csp_user_id
@pytest.mark.skip()
def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider):
subscription_id = str(uuid4())
management_group_id = str(uuid4())
@@ -121,3 +129,287 @@ def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider):
policy_definition_name=properties.get("displayName"),
parameters=mock_policy_definition,
)
def test_create_tenant(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.json.return_value = {
"objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
"tenantId": "60ff9d34-82bf-4f21-b565-308ef0533435",
"userId": "1153801116406515559",
}
mock_result.status_code = 200
mock_azure.sdk.requests.post.return_value = mock_result
payload = TenantCSPPayload(
**dict(
creds=creds,
user_id="admin",
password="JediJan13$coot", # pragma: allowlist secret
domain_name="jediccpospawnedtenant2",
first_name="Tedry",
last_name="Tenet",
country_code="US",
password_recovery_email_address="thomas@promptworks.com",
)
)
result = mock_azure.create_tenant(payload)
body: TenantCSPResult = result.get("body")
assert body.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435"
def test_create_billing_profile_creation(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.headers = {
"Location": "http://retry-url",
"Retry-After": "10",
}
mock_result.status_code = 202
mock_azure.sdk.requests.post.return_value = mock_result
payload = BillingProfileCreationCSPPayload(
**dict(
address=dict(
address_line_1="123 S Broad Street, Suite 2400",
company_name="Promptworks",
city="Philadelphia",
region="PA",
country="US",
postal_code="19109",
),
creds=creds,
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
billing_profile_display_name="Test Billing Profile",
billing_account_name=BILLING_ACCOUNT_NAME,
)
)
result = mock_azure.create_billing_profile_creation(payload)
body: BillingProfileCreationCSPResult = result.get("body")
assert body.billing_profile_retry_after == 10
def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.status_code = 200
mock_result.json.return_value = {
"id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB",
"name": "KQWI-W2SU-BG7-TGB",
"properties": {
"address": {
"addressLine1": "123 S Broad Street, Suite 2400",
"city": "Philadelphia",
"companyName": "Promptworks",
"country": "US",
"postalCode": "19109",
"region": "PA",
},
"currency": "USD",
"displayName": "First Portfolio Billing Profile",
"enabledAzurePlans": [],
"hasReadAccess": True,
"invoiceDay": 5,
"invoiceEmailOptIn": False,
"invoiceSections": [
{
"id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/invoiceSections/6HMZ-2HLO-PJA-TGB",
"name": "6HMZ-2HLO-PJA-TGB",
"properties": {"displayName": "First Portfolio Billing Profile"},
"type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections",
}
],
},
"type": "Microsoft.Billing/billingAccounts/billingProfiles",
}
mock_azure.sdk.requests.get.return_value = mock_result
payload = BillingProfileVerificationCSPPayload(
**dict(
creds=creds,
billing_profile_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview",
)
)
result = mock_azure.create_billing_profile_verification(payload)
body: BillingProfileVerificationCSPResult = result.get("body")
assert body.billing_profile_name == "KQWI-W2SU-BG7-TGB"
assert (
body.billing_profile_properties.billing_profile_display_name
== "First Portfolio Billing Profile"
)
def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.status_code = 201
mock_result.json.return_value = {
"id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/billingRoleAssignments/40000000-aaaa-bbbb-cccc-100000000000_0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
"name": "40000000-aaaa-bbbb-cccc-100000000000_0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
"properties": {
"createdOn": "2020-01-14T14:39:26.3342192+00:00",
"createdByPrincipalId": "82e2b376-3297-4096-8743-ed65b3be0b03",
"principalId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
"principalTenantId": "60ff9d34-82bf-4f21-b565-308ef0533435",
"roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/billingRoleDefinitions/40000000-aaaa-bbbb-cccc-100000000000",
"scope": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB",
},
"type": "Microsoft.Billing/billingRoleAssignments",
}
mock_azure.sdk.requests.post.return_value = mock_result
payload = BillingProfileTenantAccessCSPPayload(
**dict(
creds=creds,
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
user_object_id="0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
billing_profile_name="KQWI-W2SU-BG7-TGB",
)
)
result = mock_azure.create_billing_profile_tenant_access(payload)
body: BillingProfileTenantAccessCSPResult = result.get("body")
assert (
body.billing_role_assignment_name
== "40000000-aaaa-bbbb-cccc-100000000000_0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d"
)
def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.status_code = 202
mock_result.headers = {
"Location": "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview",
"Retry-After": "10",
}
mock_azure.sdk.requests.patch.return_value = mock_result
payload = TaskOrderBillingCreationCSPPayload(
**dict(
creds=creds,
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
billing_profile_name="KQWI-W2SU-BG7-TGB",
)
)
result = mock_azure.create_task_order_billing_creation(payload)
body: TaskOrderBillingCreationCSPResult = result.get("body")
assert (
body.task_order_billing_verify_url
== "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview"
)
def test_create_task_order_billing_verification(mock_azure):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.status_code = 200
mock_result.json.return_value = {
"id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB",
"name": "KQWI-W2SU-BG7-TGB",
"properties": {
"address": {
"addressLine1": "123 S Broad Street, Suite 2400",
"city": "Philadelphia",
"companyName": "Promptworks",
"country": "US",
"postalCode": "19109",
"region": "PA",
},
"currency": "USD",
"displayName": "Test Billing Profile",
"enabledAzurePlans": [
{
"productId": "DZH318Z0BPS6",
"skuId": "0001",
"skuDescription": "Microsoft Azure Plan",
}
],
"hasReadAccess": True,
"invoiceDay": 5,
"invoiceEmailOptIn": False,
"invoiceSections": [
{
"id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/invoiceSections/CHCO-BAAR-PJA-TGB",
"name": "CHCO-BAAR-PJA-TGB",
"properties": {"displayName": "Test Billing Profile"},
"type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections",
}
],
},
"type": "Microsoft.Billing/billingAccounts/billingProfiles",
}
mock_azure.sdk.requests.get.return_value = mock_result
payload = TaskOrderBillingVerificationCSPPayload(
**dict(
creds=creds,
task_order_billing_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview",
)
)
result = mock_azure.create_task_order_billing_verification(payload)
body: TaskOrderBillingVerificationCSPResult = result.get("body")
assert body.billing_profile_name == "KQWI-W2SU-BG7-TGB"
assert (
body.billing_profile_enabled_plan_details.enabled_azure_plans[0].get("skuId")
== "0001"
)
def test_create_billing_instruction(mock_azure: AzureCloudProvider):
mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = {
"accessToken": "TOKEN"
}
mock_result = Mock()
mock_result.status_code = 200
mock_result.json.return_value = {
"name": "TO1:CLIN001",
"properties": {
"amount": 1000.0,
"endDate": "2020-03-01T00:00:00+00:00",
"startDate": "2020-01-01T00:00:00+00:00",
},
"type": "Microsoft.Billing/billingAccounts/billingProfiles/billingInstructions",
}
mock_azure.sdk.requests.put.return_value = mock_result
payload = BillingInstructionCSPPayload(
**dict(
creds=creds,
initial_clin_amount=1000.00,
initial_clin_start_date="2020/1/1",
initial_clin_end_date="2020/3/1",
initial_clin_type="1",
initial_task_order_id="TO1",
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
billing_profile_name="KQWI-W2SU-BG7-TGB",
)
)
result = mock_azure.create_billing_instruction(payload)
body: BillingInstructionCSPResult = result.get("body")
assert body.reported_clin_name == "TO1:CLIN001"

View File

@@ -1,4 +1,4 @@
from atst.domain.csp.policy import AzurePolicyManager, AzurePolicy
from atst.domain.csp.cloud.policy import AzurePolicyManager, AzurePolicy
def test_portfolio_definitions():

View File

@@ -1,16 +1,24 @@
import pytest
import re
from unittest import mock
from tests.factories import (
PortfolioFactory,
PortfolioStateMachineFactory,
CLINFactory,
)
from atst.models import FSMStates
from atst.models import FSMStates, PortfolioStateMachine, TaskOrder
from atst.models.mixins.state_machines import AzureStages, StageStates, compose_state
from atst.models.portfolio import Portfolio
from atst.models.portfolio_state_machine import get_stage_csp_class
# TODO: Write failure case tests
@pytest.fixture(scope="function")
def portfolio():
portfolio = PortfolioFactory.create()
# TODO: setup clin/to as active/funded/ready
portfolio = CLINFactory.create().task_order.portfolio
return portfolio
@@ -19,18 +27,132 @@ def test_fsm_creation(portfolio):
assert sm.portfolio
def test_fsm_transition_start(portfolio):
def test_state_machine_trigger_next_transition(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
sm.trigger_next_transition()
assert sm.current_state == FSMStates.STARTING
sm.trigger_next_transition()
assert sm.current_state == FSMStates.STARTED
def test_state_machine_compose_state(portfolio):
PortfolioStateMachineFactory.create(portfolio=portfolio)
assert (
compose_state(AzureStages.TENANT, StageStates.CREATED)
== FSMStates.TENANT_CREATED
)
def test_state_machine_valid_data_classes_for_stages(portfolio):
PortfolioStateMachineFactory.create(portfolio=portfolio)
for stage in AzureStages:
assert get_stage_csp_class(stage.name.lower(), "payload") is not None
assert get_stage_csp_class(stage.name.lower(), "result") is not None
def test_state_machine_initialization(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
for stage in AzureStages:
# check that all stages have a 'create' and 'fail' triggers
stage_name = stage.name.lower()
for trigger_prefix in ["create", "fail"]:
assert hasattr(sm, trigger_prefix + "_" + stage_name)
# check that machine
in_progress_triggers = sm.machine.get_triggers(stage.name + "_IN_PROGRESS")
assert [
"reset",
"fail",
"finish_" + stage_name,
"fail_" + stage_name,
] == in_progress_triggers
started_triggers = sm.machine.get_triggers("STARTED")
create_trigger = next(
filter(
lambda trigger: trigger.startswith("create_"),
sm.machine.get_triggers(FSMStates.STARTED.name),
),
None,
)
assert ["reset", "fail", create_trigger] == started_triggers
@mock.patch("atst.domain.csp.cloud.MockCloudProvider")
def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio):
mock_cloud_provider._authorize.return_value = None
mock_cloud_provider._maybe_raise.return_value = None
sm: PortfolioStateMachine = PortfolioStateMachineFactory.create(portfolio=portfolio)
assert sm.portfolio
assert sm.state == FSMStates.UNSTARTED
# next_state does not create the trigger callbacks !!!
# sm.next_state()
sm.init()
assert sm.state == FSMStates.STARTING
sm.start()
assert sm.state == FSMStates.STARTED
sm.create_tenant(a=1, b=2)
assert sm.state == FSMStates.TENANT_CREATED
expected_states = [
FSMStates.TENANT_CREATED,
FSMStates.BILLING_PROFILE_CREATION_CREATED,
FSMStates.BILLING_PROFILE_VERIFICATION_CREATED,
FSMStates.BILLING_PROFILE_TENANT_ACCESS_CREATED,
FSMStates.TASK_ORDER_BILLING_CREATION_CREATED,
FSMStates.TASK_ORDER_BILLING_VERIFICATION_CREATED,
FSMStates.BILLING_INSTRUCTION_CREATED,
]
# Should source all creds for portfolio? might be easier to manage than per-step specific ones
creds = {"username": "mock-cloud", "password": "shh"} # pragma: allowlist secret
if portfolio.csp_data is not None:
csp_data = portfolio.csp_data
else:
csp_data = {}
ppoc = portfolio.owner
user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower()
domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower()
initial_task_order: TaskOrder = portfolio.task_orders[0]
initial_clin = initial_task_order.sorted_clins[0]
portfolio_data = {
"user_id": user_id,
"password": "jklfsdNCVD83nklds2#202", # pragma: allowlist secret
"domain_name": domain_name,
"first_name": ppoc.first_name,
"last_name": ppoc.last_name,
"country_code": "US",
"password_recovery_email_address": ppoc.email,
"address": { # TODO: TBD if we're sourcing this from data or config
"company_name": "",
"address_line_1": "",
"city": "",
"region": "",
"country": "",
"postal_code": "",
},
"billing_profile_display_name": "My Billing Profile",
"initial_clin_amount": initial_clin.obligated_amount,
"initial_clin_start_date": initial_clin.start_date.strftime("%Y/%m/%d"),
"initial_clin_end_date": initial_clin.end_date.strftime("%Y/%m/%d"),
"initial_clin_type": initial_clin.number,
"initial_task_order_id": initial_task_order.number,
}
config = {"billing_account_name": "billing_account_name"}
for expected_state in expected_states:
collected_data = dict(
list(csp_data.items()) + list(portfolio_data.items()) + list(config.items())
)
sm.trigger_next_transition(creds=creds, csp_data=collected_data)
assert sm.state == expected_state
if portfolio.csp_data is not None:
csp_data = portfolio.csp_data
else:
csp_data = {}

View File

@@ -8,6 +8,7 @@ AZURE_CONFIG = {
"AZURE_SECRET_KEY": "MOCK",
"AZURE_TENANT_ID": "MOCK",
"AZURE_POLICY_LOCATION": "policies",
"AZURE_VAULT_URL": "http://vault",
}
AUTH_CREDENTIALS = {
@@ -53,16 +54,38 @@ def mock_policy():
return Mock(spec=policy)
def mock_adal():
import adal
return Mock(spec=adal)
def mock_requests():
import requests
return Mock(spec=requests)
def mock_secrets():
from azure.keyvault import secrets
return Mock(spec=secrets)
class MockAzureSDK(object):
def __init__(self):
from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD
self.subscription = mock_subscription()
self.authorization = mock_authorization()
self.policy = mock_policy()
self.adal = mock_adal()
self.managementgroups = mock_managementgroups()
self.graphrbac = mock_graphrbac()
self.credentials = mock_credentials()
self.policy = mock_policy()
self.secrets = mock_secrets()
self.requests = mock_requests()
# may change to a JEDI cloud
self.cloud = AZURE_PUBLIC_CLOUD

View File

@@ -12,7 +12,7 @@ from atst.domain.application_roles import ApplicationRoles
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.invitations import ApplicationInvitations
from atst.domain.common import Paginator
from atst.domain.csp.cloud import GeneralCSPException
from atst.domain.csp.cloud.exceptions import GeneralCSPException
from atst.domain.permission_sets import PermissionSets
from atst.models.application_role import Status as ApplicationRoleStatus
from atst.models.environment_role import CSPRole, EnvironmentRole