diff --git a/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py b/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py new file mode 100644 index 00000000..cc0be2f3 --- /dev/null +++ b/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py @@ -0,0 +1,251 @@ +"""Add AADP Purchase Steps + +Revision ID: 50979d8ef680 +Revises: cd7e3f9a5d64 +Create Date: 2020-01-30 17:00:27.916639 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "50979d8ef680" # pragma: allowlist secret +down_revision = "cd7e3f9a5d64" # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "portfolio_state_machines", + "state", + type_=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "PRODUCT_PURCHASE_CREATED", + "PRODUCT_PURCHASE_IN_PROGRESS", + "PRODUCT_PURCHASE_FAILED", + "PRODUCT_PURCHASE_VERIFICATION_CREATED", + "PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS", + "PRODUCT_PURCHASE_VERIFICATION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_type=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_nullable=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "portfolio_state_machines", + "state", + type_=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_type=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "PRODUCT_PURCHASE_CREATED", + "PRODUCT_PURCHASE_IN_PROGRESS", + "PRODUCT_PURCHASE_FAILED", + "PRODUCT_PURCHASE_VERIFICATION_CREATED", + "PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS", + "PRODUCT_PURCHASE_VERIFICATION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_nullable=False, + ) + # ### end Alembic commands ### diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 0e0511b8..13b474e6 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -26,6 +26,10 @@ from .models import ( BillingProfileVerificationCSPResult, KeyVaultCredentials, ManagementGroupCSPResponse, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, PrincipalAdminRoleCSPPayload, PrincipalAdminRoleCSPResult, TaskOrderBillingCreationCSPPayload, @@ -94,6 +98,7 @@ class AzureCloudProvider(CloudProviderInterface): self.ps_client_id = config["POWERSHELL_CLIENT_ID"] self.owner_role_def_id = config["AZURE_OWNER_ROLE_DEF_ID"] self.graph_resource = config["AZURE_GRAPH_RESOURCE"] + self.default_aadp_qty = config["AZURE_AADP_QTY"] if azure_sdk_provider is None: self.sdk = AzureSDKProvider() @@ -526,6 +531,70 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): + sp_token = self._get_root_provisioning_token() + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for aad premium product purchase" + ) + + create_product_purchase_body = { + "type": "AADPremium", + "sku": "AADP1", + "productProperties": {"beneficiaryTenantId": payload.tenant_id,}, + "quantity": self.default_aadp_qty, + } + create_product_purchase_headers = { + "Authorization": f"Bearer {sp_token}", + } + + product_purchase_url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/purchaseProduct?api-version=2019-10-01-preview" + + result = self.sdk.requests.post( + product_purchase_url, + json=create_product_purchase_body, + headers=create_product_purchase_headers, + ) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(ProductPurchaseCSPResult(**result.headers)) + elif result.status_code == 200: + # NB: Swagger docs imply call can sometimes resolve immediately + return self._ok(ProductPurchaseVerificationCSPResult(**result.json())) + else: + return self._error(result.json()) + + def create_product_purchase_verification( + self, payload: ProductPurchaseVerificationCSPPayload + ): + sp_token = self._get_root_provisioning_token() + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for aad premium product purchase validation" + ) + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.get( + payload.product_purchase_verify_url, headers=auth_header + ) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(ProductPurchaseCSPResult(**result.headers)) + elif result.status_code == 200: + premium_purchase_date = result.json()["properties"]["purchaseDate"] + return self._ok( + ProductPurchaseVerificationCSPResult( + premium_purchase_date=premium_purchase_date + ) + ) + else: + return self._error(result.json()) + def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): mgmt_token = self._get_elevated_management_token(payload.tenant_id) @@ -799,7 +868,7 @@ class AzureCloudProvider(CloudProviderInterface): def _get_root_provisioning_token(self): creds = self._source_creds() return self._get_sp_token( - creds.tenant_id, creds.root_sp_client_id, creds.root_sp_key + creds.root_tenant_id, creds.root_sp_client_id, creds.root_sp_key ) def _get_sp_token(self, tenant_id, client_id, secret_key): diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 7baa2003..dce01f4e 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -25,6 +25,10 @@ from .models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, PrincipalAdminRoleCSPPayload, PrincipalAdminRoleCSPResult, SubscriptionCreationCSPPayload, @@ -315,11 +319,33 @@ class MockCloudProvider(CloudProviderInterface): } ) - def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + return ProductPurchaseCSPResult( + **dict( + product_purchase_verify_url="https://zombo.com", + product_purchase_retry_after=10, + ) + ) + + def create_product_purchase_verification( + self, payload: ProductPurchaseVerificationCSPPayload + ): + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + + return ProductPurchaseVerificationCSPResult( + **dict(premium_purchase_date="2020-01-30T18:57:05.981Z") + ) + + def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) return TenantAdminOwnershipCSPResult(**dict(id="admin_owner_assignment_id")) def create_tenant_principal_ownership( diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index db3efa05..efd4b8cf 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -450,3 +450,27 @@ class SuscriptionVerificationCSPResult(AliasModel): class Config: fields = {"subscription_id": "subscriptionLink"} + + +class ProductPurchaseCSPPayload(BaseCSPPayload): + billing_account_name: str + billing_profile_name: str + + +class ProductPurchaseCSPResult(AliasModel): + product_purchase_verify_url: str + product_purchase_retry_after: int + + class Config: + fields = { + "product_purchase_verify_url": "Location", + "product_purchase_retry_after": "Retry-After", + } + + +class ProductPurchaseVerificationCSPPayload(BaseCSPPayload): + product_purchase_verify_url: str + + +class ProductPurchaseVerificationCSPResult(AliasModel): + premium_purchase_date: str diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index 889b5b8f..a7edf268 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -17,6 +17,8 @@ class AzureStages(Enum): TASK_ORDER_BILLING_CREATION = "task order billing creation" TASK_ORDER_BILLING_VERIFICATION = "task order billing verification" BILLING_INSTRUCTION = "billing instruction" + PRODUCT_PURCHASE = "purchase aad premium product" + PRODUCT_PURCHASE_VERIFICATION = "purchase aad premium product verification" TENANT_PRINCIPAL_APP = "tenant principal application" TENANT_PRINCIPAL = "tenant principal" TENANT_PRINCIPAL_CREDENTIAL = "tenant principal credential" diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 0d84f2a4..eef5620e 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -20,6 +20,10 @@ from atst.domain.csp.cloud.models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, SubscriptionCreationCSPPayload, SubscriptionCreationCSPResult, SubscriptionVerificationCSPPayload, @@ -308,6 +312,7 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): 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" @@ -410,6 +415,78 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): assert body.reported_clin_name == "TO1:CLIN001" +def test_create_product_purchase(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.post.return_value = mock_result + + payload = ProductPurchaseCSPPayload( + **dict( + tenant_id="6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4", + 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_product_purchase(payload) + body: ProductPurchaseCSPResult = result.get("body") + assert ( + body.product_purchase_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_product_purchase_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/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME/invoiceSections/INVOICESECTION/products/29386e29-a025-faae-f70b-b1cbbc266600", + "name": "29386e29-a025-faae-f70b-b1cbbc266600", + "properties": { + "availabilityId": "C07TTFC7Q9XK", + "billingProfileId": "/providers/Microsoft.Billing/billingAccounts/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME", + "billingProfileDisplayName": "ATAT Billing Profile", + "endDate": "01/30/2021", + "invoiceSectionId": "/providers/Microsoft.Billing/billingAccounts/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME/invoiceSections/INVOICESECTION", + "invoiceSectionDisplayName": "ATAT Billing Profile", + "productType": "Azure Active Directory Premium P1", + "productTypeId": "C07TTFC7Q9XK", + "skuId": "0002", + "skuDescription": "Azure Active Directory Premium P1", + "purchaseDate": "01/31/2020", + "quantity": 5, + "status": "AutoRenew", + }, + "type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections/products", + } + + mock_azure.sdk.requests.get.return_value = mock_result + + payload = ProductPurchaseVerificationCSPPayload( + **dict( + tenant_id="6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4", + product_purchase_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_product_purchase_verification(payload) + body: ProductPurchaseVerificationCSPResult = result.get("body") + assert body.premium_purchase_date == "01/31/2020" + + def test_create_tenant_principal_app(mock_azure: AzureCloudProvider): with patch.object( AzureCloudProvider, diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 9480c8a0..2c27cfd8 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -104,6 +104,8 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): FSMStates.TASK_ORDER_BILLING_CREATION_CREATED, FSMStates.TASK_ORDER_BILLING_VERIFICATION_CREATED, FSMStates.BILLING_INSTRUCTION_CREATED, + FSMStates.PRODUCT_PURCHASE_CREATED, + FSMStates.PRODUCT_PURCHASE_VERIFICATION_CREATED, FSMStates.TENANT_PRINCIPAL_APP_CREATED, FSMStates.TENANT_PRINCIPAL_CREATED, FSMStates.TENANT_PRINCIPAL_CREDENTIAL_CREATED, diff --git a/tests/mock_azure.py b/tests/mock_azure.py index 438ae855..ce85a396 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -12,6 +12,7 @@ AZURE_CONFIG = { "POWERSHELL_CLIENT_ID": "MOCK", "AZURE_OWNER_ROLE_DEF_ID": "MOCK", "AZURE_GRAPH_RESOURCE": "MOCK", + "AZURE_AADP_QTY": 5, } AUTH_CREDENTIALS = {