From 81f23ebc22cc405a70d1bf4c557558822253694a Mon Sep 17 00:00:00 2001 From: tomdds Date: Tue, 14 Jan 2020 15:04:13 -0500 Subject: [PATCH] Finish first passes at baseline tenant integration Add last of the integrations for setting up billing and reporting a CLIN. --- atst/domain/csp/cloud.py | 220 ++++++++++++++++++------ tests/domain/cloud/test_azure_csp.py | 241 +++++++++++++++++++++++---- 2 files changed, 385 insertions(+), 76 deletions(-) diff --git a/atst/domain/csp/cloud.py b/atst/domain/csp/cloud.py index 86c436e5..703dec53 100644 --- a/atst/domain/csp/cloud.py +++ b/atst/domain/csp/cloud.py @@ -231,15 +231,15 @@ class BillingProfileCSPPayload(BaseCSPPayload): class BillingProfileCreateCSPResult(AliasModel): - location: str + billing_profile_validate_url: str retry_after: int class Config: - fields = {"location": "Location", "retry_after": "Retry-After"} + fields = {"billing_profile_validate_url": "Location", "retry_after": "Retry-After"} class BillingProfileVerifyCSPPayload(BaseCSPPayload): - location: str + billing_profile_validate_url: str class BillingInvoiceSection(AliasModel): @@ -269,6 +269,70 @@ class BillingProfileCSPResult(AliasModel): } +class BillingRoleAssignmentCSPPayload(BaseCSPPayload): + tenant_id: str + user_object_id: str + billing_account_name: str + billing_profile_name: str + + +class BillingRoleAssignmentCSPResult(AliasModel): + billing_role_assignment_id: str + billing_role_assignment_name: str + + class Config: + fields = { + "billing_role_assignment_id": "id", + "billing_role_assignment_name": "name", + } + +class EnableTaskOrderBillingCSPPayload(BaseCSPPayload): + billing_account_name: str + billing_profile_name: str + +class EnableTaskOrderBillingCSPResult(AliasModel): + task_order_billing_validation_url: str + retry_after: int + + class Config: + fields = {"task_order_billing_validation_url": "Location", "retry_after": "Retry-After"} + +class VerifyTaskOrderBillingCSPPayload(BaseCSPPayload): + task_order_billing_validation_url: str + +class BillingProfileEnabledPlanDetails(AliasModel): + enabled_azure_plans: List[Dict] + + +class BillingProfileEnabledCSPResult(AliasModel): + billing_profile_id: str + billing_profile_name: str + billing_profile_enabled_plan_details: BillingProfileEnabledPlanDetails + + class Config: + fields = { + "billing_profile_id": "id", + "billing_profile_name": "name", + "billing_profile_enabled_plan_details": "properties", + } + +class ReportCLINCSPPayload(BaseCSPPayload): + amount: float + start_date: str + end_date: str + clin_type: str + task_order_id: str + billing_account_name: str + billing_profile_name: str + +class ReportCLINCSPResult(AliasModel): + reported_clin_name: str + + class Config: + fields = { + "reported_clin_name": "name", + } + class CloudProviderInterface: def root_creds(self) -> Dict: raise NotImplementedError() @@ -705,7 +769,6 @@ class AzureCloudProvider(CloudProviderInterface): create_tenant_body = payload.dict(by_alias=True) create_tenant_headers = { - "Content-Type": "application/json", "Authorization": f"Bearer {sp_token}", } @@ -730,7 +793,6 @@ class AzureCloudProvider(CloudProviderInterface): create_billing_account_body = payload.dict(by_alias=True) create_billing_account_headers = { - "Content-Type": "application/json", "Authorization": f"Bearer {sp_token}", } @@ -745,7 +807,11 @@ class AzureCloudProvider(CloudProviderInterface): ) if result.status_code == 202: + # 202 has location/retry after headers return self._ok(BillingProfileCreateCSPResult(**result.headers)) + elif result.status_code == 200: + # NB: Swagger docs imply call can sometimes resolve immediately + return self._ok(BillingProfileCSPResult(**result.json())) else: return self._error(result.json()) @@ -760,56 +826,116 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.get(payload.location, headers=auth_header) + result = self.sdk.requests.get(payload.billing_profile_validate_url, headers=auth_header) - if result.status_code == 200: + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(BillingProfileCreateCSPResult(**result.headers)) + elif result.status_code == 200: return self._ok(BillingProfileCSPResult(**result.json())) else: return self._error(result.json()) - def create_billing_owner(self, creds, tenant_admin_details): - # authenticate as tenant_admin - # create billing owner identity - - # TODO: Lookup response format - # Managed service identity? - response = {"id": "string"} - return self._ok({"billing_owner_id": response["id"]}) - - def assign_billing_owner(self, creds, billing_owner_id, tenant_id): - # TODO: Do we source role definition ID from config, api or self-defined? - # TODO: If from api, - """ - { - "principalId": "string", - "principalTenantId": "string", - "billingRoleDefinitionId": "string" - } - """ - - return self.ok() - - def report_clin(self, creds, clin_id, clin_amount, clin_start, clin_end, clin_to): - # should consumer be responsible for reporting each clin or - # should this take a list and manage the sequential reporting? - """ Payload - { - "enabledAzurePlans": [ - { - "skuId": "string" - } - ], - "clinBudget": { - "amount": 0, - "startDate": "2019-12-18T16:47:40.909Z", - "endDate": "2019-12-18T16:47:40.909Z", - "externalReferenceId": "string" + def grant_billing_profile_tenant_access(self, payload: BillingRoleAssignmentCSPPayload): + sp_token = self._get_sp_token(payload.creds) + request_body = { + "properties": { + "principalTenantId": payload.tenant_id, # from tenant creation + "principalId": payload.user_object_id, # from tenant creationn + "roleDefinitionId": f"/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/billingRoleDefinitions/40000000-aaaa-bbbb-cccc-100000000000", } } - """ - # we don't need any of the returned info for this - return self._ok() + headers = { + "Authorization": f"Bearer {sp_token}", + } + + url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview" + + result = self.sdk.requests.post(url, headers=headers, json=request_body) + if result.status_code == 201: + return self._ok(BillingRoleAssignmentCSPResult(**result.json())) + else: + return self._error(result.json()) + + def enable_task_order_billing(self, payload: EnableTaskOrderBillingCSPPayload): + sp_token = self._get_sp_token(payload.creds) + request_body = [ + { + "op": "replace", + "path": "/enabledAzurePlans", + "value": [ + { + "skuId": "0001" + } + ] + } + ] + + request_headers = { + "Authorization": f"Bearer {sp_token}", + } + + url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}?api-version=2019-10-01-preview" + + result = self.sdk.requests.patch(url, headers=request_headers, json=request_body) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(BillingProfileCreateCSPResult(**result.headers)) + elif result.status_code == 200: + return self._ok(BillingProfileEnabledCSPResult(**result.json())) + else: + return self._error(result.json()) + + def validate_task_order_billing_enabled(self, payload: VerifyTaskOrderBillingCSPPayload): + sp_token = self._get_sp_token(payload.creds) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for task order billing validation" + ) + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.get(payload.task_order_billing_validation_url, headers=auth_header) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(EnableTaskOrderBillingCSPResult(**result.headers)) + elif result.status_code == 200: + return self._ok(BillingProfileEnabledCSPResult(**result.json())) + else: + return self._error(result.json()) + + def report_clin(self, payload: ReportCLINCSPPayload): + sp_token = self._get_sp_token(payload.creds) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for task order billing validation" + ) + + request_body = { + "properties": { + "amount": payload.amount, + "startDate": payload.start_date, + "endDate": payload.end_date + } + } + + url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/instructions/{payload.task_order_id}:CLIN00{payload.clin_type}?api-version=2019-10-01-preview" + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.put(url, headers=auth_header, json=request_body) + + if result.status_code == 200: + return self._ok(ReportCLINCSPResult(**result.json())) + else: + return self._error(result.json()) def create_remote_admin(self, creds, tenant_details): # create app/service principal within tenant, with name constructed from tenant details diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 6b9a8b12..9e9809c7 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -12,6 +12,13 @@ from atst.domain.csp.cloud import ( BillingProfileCreateCSPResult, BillingProfileVerifyCSPPayload, BillingProfileCSPResult, + BillingRoleAssignmentCSPPayload, + BillingRoleAssignmentCSPResult, + EnableTaskOrderBillingCSPPayload, + VerifyTaskOrderBillingCSPPayload, + BillingProfileEnabledCSPResult, + ReportCLINCSPPayload, + ReportCLINCSPResult, ) from tests.mock_azure import mock_azure, AUTH_CREDENTIALS @@ -25,7 +32,7 @@ from tests.factories import EnvironmentFactory, ApplicationFactory # -@pytest.mark.skip() +@pytest.mark.skip("Skipping legacy azure integration tests") def test_create_subscription_succeeds(mock_azure: AzureCloudProvider): environment = EnvironmentFactory.create() @@ -60,14 +67,13 @@ 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() +@pytest.mark.skip("Skipping legacy azure integration tests") def test_create_environment_succeeds(mock_azure: AzureCloudProvider): environment = EnvironmentFactory.create() @@ -80,7 +86,7 @@ def test_create_environment_succeeds(mock_azure: AzureCloudProvider): assert result.id == "Test Id" -@pytest.mark.skip() +@pytest.mark.skip("Skipping legacy azure integration tests") def test_create_application_succeeds(mock_azure: AzureCloudProvider): application = ApplicationFactory.create() @@ -91,7 +97,7 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider): assert result.id == "Test Id" -@pytest.mark.skip() +@pytest.mark.skip("Skipping legacy azure integration tests") def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider): environment_id = str(uuid4()) @@ -106,7 +112,7 @@ def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider): assert result.get("csp_user_id") == csp_user_id -@pytest.mark.skip() +@pytest.mark.skip("Skipping legacy azure integration tests") def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider): subscription_id = str(uuid4()) management_group_id = str(uuid4()) @@ -148,33 +154,32 @@ def test_create_tenant(mock_azure: AzureCloudProvider): payload = TenantCSPPayload( **dict( creds={"username": "mock-cloud", "pass": "shh"}, - user_id="123", - password="123", - domain_name="123", - first_name="john", - last_name="doe", + user_id="admin", + password="JediJan13$coot", + domain_name="jediccpospawnedtenant2", + first_name="Tedry", + last_name="Tenet", country_code="US", - password_recovery_email_address="password@email.com", + password_recovery_email_address="thomas@promptworks.com", ) ) result = mock_azure.create_tenant(payload) - print(result) body: TenantCSPResult = result.get("body") assert body.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" def test_create_billing_profile(mock_azure: AzureCloudProvider): - # mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = { - # "accessToken": "TOKEN" - # } + 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 + 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 = BillingProfileCSPPayload( **dict( address=dict( @@ -191,7 +196,6 @@ def test_create_billing_profile(mock_azure: AzureCloudProvider): ) ) result = mock_azure.create_billing_profile(payload) - print(result) body: BillingProfileCreateCSPResult = result.get("body") assert body.retry_after == 10 @@ -204,8 +208,8 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): 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/XC36-GRNZ-BG7-TGB", - "name": "XC36-GRNZ-BG7-TGB", + "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", @@ -223,7 +227,7 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): "invoiceEmailOptIn": False, "invoiceSections": [ { - "id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/XC36-GRNZ-BG7-TGB/invoiceSections/6HMZ-2HLO-PJA-TGB", + "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", @@ -241,15 +245,194 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider): "password": "password", "tenant_id": "tenant_id", }, - location="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", + billing_profile_validate_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.validate_billing_profile_created(payload) body: BillingProfileCreateCSPResult = result.get("body") - assert body.billing_profile_name == "XC36-GRNZ-BG7-TGB" + assert body.billing_profile_name == "KQWI-W2SU-BG7-TGB" assert ( body.billing_profile_properties.display_name == "First Portfolio Billing Profile" ) + +def test_grant_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 = BillingRoleAssignmentCSPPayload( + **dict( + creds={ + "username": "username", + "password": "password", + "tenant_id": "tenant_id", + }, + 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.grant_billing_profile_tenant_access(payload) + body: BillingRoleAssignmentCSPResult = result.get("body") + assert ( + body.billing_role_assignment_name + == "40000000-aaaa-bbbb-cccc-100000000000_0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d" + ) + + +def test_enable_task_order_billing(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 = EnableTaskOrderBillingCSPPayload( + **dict( + creds={ + "username": "username", + "password": "password", + "tenant_id": "tenant_id", + }, + 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.enable_task_order_billing(payload) + body: BillingProfileCreateCSPResult = result.get("body") + assert ( + body.billing_profile_validate_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_validate_task_order_billing_enabled(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 = VerifyTaskOrderBillingCSPPayload( + **dict( + creds={ + "username": "username", + "password": "password", + "tenant_id": "tenant_id", + }, + task_order_billing_validation_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.validate_task_order_billing_enabled(payload) + body: BillingProfileEnabledCSPResult = 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_report_clin(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 = ReportCLINCSPPayload( + **dict( + creds={}, + amount=1000.00, + start_date="2020/1/1", + end_date="2020/3/1", + clin_type="1", + 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.report_clin(payload) + body: ReportCLINCSPResult = result.get("body") + assert body.reported_clin_name == "TO1:CLIN001" +