azure csp unit tests WIP

This commit is contained in:
Philip Kalinsky 2020-02-11 14:11:11 -05:00
parent 50ab045353
commit 441e81ba8d
10 changed files with 355 additions and 149 deletions

View File

@ -54,6 +54,7 @@ selenium = "*"
honcho = "*" honcho = "*"
blinker = "*" blinker = "*"
pytest-mock = "*" pytest-mock = "*"
requests-mock = "*"
detect-secrets = "*" detect-secrets = "*"
beautifulsoup4 = "*" beautifulsoup4 = "*"
mypy = "*" mypy = "*"

151
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "44296f145fcb42cff5fadf14a706ec9598f4436ccbdf05e1d69fcd8316c89e8d" "sha256": "dcc985866bfecd1e2abcd40722a657f0da223c3c9c11aee32fd4b532eca31cb8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -26,10 +26,10 @@
}, },
"alembic": { "alembic": {
"hashes": [ "hashes": [
"sha256:d412982920653db6e5a44bfd13b1d0db5685cbaaccaf226195749c706e1e862a" "sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.3" "version": "==1.4.0"
}, },
"amqp": { "amqp": {
"hashes": [ "hashes": [
@ -47,10 +47,10 @@
}, },
"azure-core": { "azure-core": {
"hashes": [ "hashes": [
"sha256:b8ccbd901d085048e4e3e72627b066923c5bd3780e4c43cf9cf9948aee9bdf9e", "sha256:8bdb12b8e937c5bdf495faadf7741ea1958436e2d24c9c5ca7bd7a5ca7a9e42f",
"sha256:e2cd99f0c0aef12c168d498cb5bc47a3a45c8ab08112183e3ec97e4dcb33ceb9" "sha256:bcfc4502c4cfbdcbe82301119439f52542fa4a42dfc1dadd647bba8b01819823"
], ],
"version": "==1.2.1" "version": "==1.2.2"
}, },
"azure-graphrbac": { "azure-graphrbac": {
"hashes": [ "hashes": [
@ -116,11 +116,11 @@
}, },
"azure-mgmt-resource": { "azure-mgmt-resource": {
"hashes": [ "hashes": [
"sha256:455a10bbae15673c7879d7515b38e1548cb1a8982dd35029ab3192565262c573", "sha256:a77707bad5551bd558da450045cd2f7097fb8cbaf68610a510a9e413f8a9cf3e",
"sha256:c2ad10cab63999c0a88ee498bc36200ee7f6e6e5d4bf82712bde882eda11146f" "sha256:d90b7d8f237b71b54cfd06480dc1ecd7dac81b22301bf2f4ead98a53cf269b6a"
], ],
"index": "pypi", "index": "pypi",
"version": "==8.0.0" "version": "==8.0.1"
}, },
"azure-mgmt-subscription": { "azure-mgmt-subscription": {
"hashes": [ "hashes": [
@ -186,41 +186,36 @@
}, },
"cffi": { "cffi": {
"hashes": [ "hashes": [
"sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff",
"sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b",
"sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac",
"sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0",
"sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384",
"sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26",
"sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6",
"sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b",
"sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e",
"sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd",
"sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2",
"sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66",
"sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc",
"sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8",
"sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55",
"sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4",
"sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5",
"sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d",
"sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78",
"sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa",
"sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793",
"sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f",
"sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a",
"sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f",
"sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30",
"sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f",
"sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3",
"sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"
"sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e",
"sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410",
"sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25",
"sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b",
"sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"
], ],
"version": "==1.13.2" "version": "==1.14.0"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
@ -296,11 +291,11 @@
}, },
"flask-wtf": { "flask-wtf": {
"hashes": [ "hashes": [
"sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2",
"sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.14.2" "version": "==0.14.3"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@ -333,10 +328,10 @@
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668"
], ],
"version": "==2.11.1" "version": "==3.0.0a1"
}, },
"kombu": { "kombu": {
"hashes": [ "hashes": [
@ -685,10 +680,10 @@
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
"sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50",
"sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e"
], ],
"version": "==2.1.0" "version": "==2.2.0"
} }
}, },
"develop": { "develop": {
@ -699,14 +694,6 @@
], ],
"version": "==1.4.3" "version": "==1.4.3"
}, },
"appnope": {
"hashes": [
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
],
"markers": "sys_platform == 'darwin'",
"version": "==0.1.0"
},
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
@ -871,10 +858,10 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42", "sha256:99c77677f31f255e130f3fed4c8e0eebb35f1a09df98ff965fff6774f71688cf",
"sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245" "sha256:99cd0403cecd8a13b95d2e045b9fcaa7837137fcc5ec3105f2c413305d82c143"
], ],
"version": "==3.0.5" "version": "==3.0.7"
}, },
"honcho": { "honcho": {
"hashes": [ "hashes": [
@ -944,10 +931,10 @@
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668"
], ],
"version": "==2.11.1" "version": "==3.0.0a1"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
@ -1056,10 +1043,10 @@
}, },
"parso": { "parso": {
"hashes": [ "hashes": [
"sha256:1376bdc8cb81377ca481976933773295218a2df47d3e1182ba76d372b1acb128", "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
"sha256:597f36de5102a8db05ffdf7ecdc761838b86565a4a111604c6e78beaedf1b045" "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095"
], ],
"version": "==0.6.0" "version": "==0.6.1"
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
@ -1158,11 +1145,11 @@
}, },
"pytest-flask": { "pytest-flask": {
"hashes": [ "hashes": [
"sha256:283730b469604ecb94caac28df99a40b7c785b828dd8d3323596718b51dfaeb2", "sha256:9001f6128c5c4a0d243ce46c117f3691052828d2faf39ac151b8388657dce447",
"sha256:d874781b622210d8c5d8061cdb091cb059fcb12203125110bd8e6f9256ccbf49" "sha256:cbd8c5b9f8f1b83e9c159ac4294964807c4934317a5fba181739ac15e1b823e6"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.15.0" "version": "==0.15.1"
}, },
"pytest-mock": { "pytest-mock": {
"hashes": [ "hashes": [
@ -1230,6 +1217,14 @@
"index": "pypi", "index": "pypi",
"version": "==2.22.0" "version": "==2.22.0"
}, },
"requests-mock": {
"hashes": [
"sha256:510df890afe08d36eca5bb16b4aa6308a6f85e3159ad3013bac8b9de7bd5a010",
"sha256:88d3402dd8b3c69a9e4f9d3a73ad11b15920c6efd36bc27bf1f701cf4a8e4646"
],
"index": "pypi",
"version": "==1.7.0"
},
"rope": { "rope": {
"hashes": [ "hashes": [
"sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203", "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203",
@ -1270,10 +1265,10 @@
}, },
"stevedore": { "stevedore": {
"hashes": [ "hashes": [
"sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730", "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b",
"sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14" "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b"
], ],
"version": "==1.31.0" "version": "==1.32.0"
}, },
"text-unidecode": { "text-unidecode": {
"hashes": [ "hashes": [
@ -1361,10 +1356,10 @@
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
"sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50",
"sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e"
], ],
"version": "==2.1.0" "version": "==2.2.0"
} }
} }
} }

View File

@ -2,6 +2,7 @@ import json
from secrets import token_urlsafe from secrets import token_urlsafe
from typing import Any, Dict from typing import Any, Dict
from uuid import uuid4 from uuid import uuid4
import pydantic
from atst.utils import sha256_hex from atst.utils import sha256_hex
@ -274,31 +275,17 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
result = self.sdk.requests.post( result = self.sdk.requests.post(
f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview", f"{self.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant?api-version=2020-01-01-preview",
json=create_tenant_body, json=create_tenant_body,
headers=create_tenant_headers, headers=create_tenant_headers,
timeout=30, timeout=30,
) )
result.raise_for_status() result.raise_for_status()
result_dict = result.json()
tenant_id = result_dict.get("tenantId")
tenant_admin_username = (
f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com"
)
self.update_tenant_creds(
tenant_id,
KeyVaultCredentials(
tenant_id=tenant_id,
tenant_admin_username=tenant_admin_username,
tenant_admin_password=payload.password,
),
)
return TenantCSPResult(domain_name=payload.domain_name, **result_dict)
except self.sdk.requests.ConnectionError: except self.sdk.requests.ConnectionError:
app.logger.error( #app.logger.error(
f"Could not create tenant. Connection Error", exc_info=1, # f"Could not create tenant. Connection Error", exc_info=1,
) #)
raise ConnectionException("connection error creating tenant") raise ConnectionException("connection error creating tenant")
except self.sdk.requests.Timeout: except self.sdk.requests.Timeout:
app.logger.error( app.logger.error(
@ -308,6 +295,27 @@ class AzureCloudProvider(CloudProviderInterface):
except self.sdk.requests.HTTPError: except self.sdk.requests.HTTPError:
raise UnknownServerException("azure application error creating tenant") raise UnknownServerException("azure application error creating tenant")
result_dict = result.json()
tenant_id = result_dict.get("tenantId")
tenant_admin_username = (
f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com"
)
try:
creds = KeyVaultCredentials(
tenant_id=tenant_id,
tenant_admin_username=tenant_admin_username,
tenant_admin_password=payload.password,
)
except pydantic.ValidationError as val_exc:
return
self.update_tenant_creds(
tenant_id,
creds,
)
return TenantCSPResult(domain_name=payload.domain_name, **result_dict)
def create_billing_profile_creation( def create_billing_profile_creation(
self, payload: BillingProfileCreationCSPPayload self, payload: BillingProfileCreationCSPPayload
): ):
@ -330,6 +338,7 @@ class AzureCloudProvider(CloudProviderInterface):
billing_account_create_url, billing_account_create_url,
json=create_billing_account_body, json=create_billing_account_body,
headers=create_billing_account_headers, headers=create_billing_account_headers,
timeout=30,
) )
result.raise_for_status() result.raise_for_status()
if result.status_code == 202: if result.status_code == 202:
@ -366,7 +375,9 @@ class AzureCloudProvider(CloudProviderInterface):
} }
try: try:
result = self.sdk.requests.get( result = self.sdk.requests.get(
payload.billing_profile_verify_url, headers=auth_header payload.billing_profile_verify_url,
headers=auth_header,
timeout=30,
) )
result.raise_for_status() result.raise_for_status()
@ -407,7 +418,12 @@ class AzureCloudProvider(CloudProviderInterface):
url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview" url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/createBillingRoleAssignment?api-version=2019-10-01-preview"
try: try:
result = self.sdk.requests.post(url, headers=headers, json=request_body) result = self.sdk.requests.post(
url,
headers=headers,
json=request_body,
timeout=30,
)
if result.status_code == 201: if result.status_code == 201:
return BillingProfileTenantAccessCSPResult(**result.json()) return BillingProfileTenantAccessCSPResult(**result.json())
@ -444,7 +460,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
result = self.sdk.requests.patch( result = self.sdk.requests.patch(
url, headers=request_headers, json=request_body url, headers=request_headers, json=request_body, timeout=30,
) )
result.raise_for_status() result.raise_for_status()
@ -482,7 +498,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
result = self.sdk.requests.get( result = self.sdk.requests.get(
payload.task_order_billing_verify_url, headers=auth_header payload.task_order_billing_verify_url, headers=auth_header, timeout=30,
) )
result.raise_for_status() result.raise_for_status()
@ -527,7 +543,7 @@ class AzureCloudProvider(CloudProviderInterface):
} }
try: try:
result = self.sdk.requests.put(url, headers=auth_header, json=request_body) result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30)
result.raise_for_status() result.raise_for_status()
return BillingInstructionCSPResult(**result.json()) return BillingInstructionCSPResult(**result.json())
@ -564,7 +580,7 @@ class AzureCloudProvider(CloudProviderInterface):
} }
try: try:
result = self.sdk.requests.put(url, headers=auth_header, json=request_body) result = self.sdk.requests.put(url, headers=auth_header, json=request_body, timeout=30)
if result.status_code in [200, 202]: if result.status_code in [200, 202]:
# 202 has location/retry after headers # 202 has location/retry after headers
return SubscriptionCreationCSPResult(**result.headers, **result.json()) return SubscriptionCreationCSPResult(**result.headers, **result.json())
@ -600,7 +616,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
result = self.sdk.requests.get( result = self.sdk.requests.get(
payload.subscription_verify_url, headers=auth_header payload.subscription_verify_url, headers=auth_header, timeout=30
) )
result.raise_for_status() result.raise_for_status()
@ -643,6 +659,7 @@ class AzureCloudProvider(CloudProviderInterface):
product_purchase_url, product_purchase_url,
json=create_product_purchase_body, json=create_product_purchase_body,
headers=create_product_purchase_headers, headers=create_product_purchase_headers,
timeout=30,
) )
result.raise_for_status() result.raise_for_status()
@ -680,7 +697,7 @@ class AzureCloudProvider(CloudProviderInterface):
} }
try: try:
result = self.sdk.requests.get( result = self.sdk.requests.get(
payload.product_purchase_verify_url, headers=auth_header payload.product_purchase_verify_url, headers=auth_header, timeout=30
) )
result.raise_for_status() result.raise_for_status()
@ -728,7 +745,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.put( response = self.sdk.requests.put(
url, headers=auth_header, json=request_body url, headers=auth_header, json=request_body, timeout=30
) )
response.raise_for_status() response.raise_for_status()
@ -772,7 +789,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.put( response = self.sdk.requests.put(
url, headers=auth_header, json=request_body url, headers=auth_header, json=request_body, timeout=30,
) )
response.raise_for_status() response.raise_for_status()
return TenantPrincipalOwnershipCSPResult(**response.json()) return TenantPrincipalOwnershipCSPResult(**response.json())
@ -809,7 +826,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.post( response = self.sdk.requests.post(
url, json=request_body, headers=auth_header url, json=request_body, headers=auth_header, timeout=30
) )
response.raise_for_status() response.raise_for_status()
return TenantPrincipalAppCSPResult(**response.json()) return TenantPrincipalAppCSPResult(**response.json())
@ -846,7 +863,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.post( response = self.sdk.requests.post(
url, json=request_body, headers=auth_header url, json=request_body, headers=auth_header, timeout=30
) )
response.raise_for_status() response.raise_for_status()
return TenantPrincipalCSPResult(**response.json()) return TenantPrincipalCSPResult(**response.json())
@ -887,7 +904,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.post( response = self.sdk.requests.post(
url, json=request_body, headers=auth_header url, json=request_body, headers=auth_header, timeout=30
) )
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
@ -932,7 +949,7 @@ class AzureCloudProvider(CloudProviderInterface):
url = f"{self.graph_resource}/beta/roleManagement/directory/roleDefinitions" url = f"{self.graph_resource}/beta/roleManagement/directory/roleDefinitions"
try: try:
response = self.sdk.requests.get(url, headers=auth_header) response = self.sdk.requests.get(url, headers=auth_header, timeout=30)
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
@ -986,7 +1003,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.post( response = self.sdk.requests.post(
url, headers=auth_header, json=request_body url, headers=auth_header, json=request_body, timeout=30
) )
response.raise_for_status() response.raise_for_status()
return PrincipalAdminRoleCSPResult(**response.json()) return PrincipalAdminRoleCSPResult(**response.json())
@ -1104,7 +1121,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.post( response = self.sdk.requests.post(
url, headers=auth_header, json=request_body url, headers=auth_header, json=request_body, timeout=30
) )
response.raise_for_status() response.raise_for_status()
@ -1138,7 +1155,7 @@ class AzureCloudProvider(CloudProviderInterface):
try: try:
response = self.sdk.requests.patch( response = self.sdk.requests.patch(
url, headers=auth_header, json=request_body url, headers=auth_header, json=request_body, timeout=30
) )
response.raise_for_status() response.raise_for_status()
@ -1257,7 +1274,7 @@ class AzureCloudProvider(CloudProviderInterface):
} }
url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01" url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"
try: try:
result = self.sdk.requests.post(url, headers=auth_header) result = self.sdk.requests.post(url, headers=auth_header, timeout=30)
result.raise_for_status() result.raise_for_status()
if not result.ok: if not result.ok:
raise AuthenticationException("Failed to elevate access") raise AuthenticationException("Failed to elevate access")
@ -1337,6 +1354,7 @@ class AzureCloudProvider(CloudProviderInterface):
f"{self.sdk.cloud.endpoints.resource_manager}{payload.invoice_section_id}{cost_mgmt_url}", f"{self.sdk.cloud.endpoints.resource_manager}{payload.invoice_section_id}{cost_mgmt_url}",
json=request_body, json=request_body,
headers=headers, headers=headers,
timeout=30,
) )
result.raise_for_status() result.raise_for_status()
if result.ok: if result.ok:

View File

@ -122,10 +122,6 @@ class MockCloudProvider(CloudProviderInterface):
payload is an instance of TenantCSPPayload data class payload is an instance of TenantCSPPayload data class
""" """
self._authorize("admin")
self._delay(1, 5)
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)

View File

@ -123,15 +123,21 @@ class FSMMixin:
] ]
def fail_stage(self, stage): def fail_stage(self, stage):
fail_trigger = "fail" + stage fail_trigger = f"fail_{stage}"
if fail_trigger in self.machine.get_triggers(self.current_state.name): if fail_trigger in self.machine.get_triggers(self.current_state.name):
self.trigger(fail_trigger) self.trigger(fail_trigger)
app.logger.info( app.logger.info(
f"calling fail trigger '{fail_trigger}' for '{self.__repr__()}'" f"calling fail trigger '{fail_trigger}' for '{self.__repr__()}'"
) )
else:
app.logger.info(
f"could not locate fail trigger '{fail_trigger}' for '{self.__repr__()}'"
)
def finish_stage(self, stage): def finish_stage(self, stage):
finish_trigger = "finish_" + stage finish_trigger = f"finish_{stage}"
if finish_trigger in self.machine.get_triggers(self.current_state.name): if finish_trigger in self.machine.get_triggers(self.current_state.name):
app.logger.info( app.logger.info(
f"calling finish trigger '{finish_trigger}' for '{self.__repr__()}'" f"calling finish trigger '{finish_trigger}' for '{self.__repr__()}'"

View File

@ -15,13 +15,31 @@ from atst.database import db
from atst.models.types import Id from atst.models.types import Id
from atst.models.base import Base from atst.models.base import Base
import atst.models.mixins as mixins import atst.models.mixins as mixins
from atst.models.mixins.state_machines import FSMStates, AzureStages, _build_transitions from atst.models.mixins.state_machines import (
FSMStates,
AzureStages,
StageStates,
_build_transitions
)
class StateMachineMisconfiguredError(Exception):
def __init__(self, class_details):
self.class_details = class_details
@property
def message(self):
return self.class_details
def _stage_to_classname(stage): def _stage_to_classname(stage):
return "".join(map(lambda word: word.capitalize(), stage.split("_"))) return "".join(map(lambda word: word.capitalize(), stage.split("_")))
def _stage_state_to_stage_name(state, stage_state):
return state.name.split(f"_{stage_state.name}")[0].lower()
def get_stage_csp_class(stage, class_type): def get_stage_csp_class(stage, class_type):
""" """
given a stage name and class_type return the class given a stage name and class_type return the class
@ -34,7 +52,7 @@ def get_stage_csp_class(stage, class_type):
importlib.import_module("atst.domain.csp.cloud.models"), cls_name importlib.import_module("atst.domain.csp.cloud.models"), cls_name
) )
except AttributeError: except AttributeError:
print("could not import CSP Result class <%s>" % cls_name) raise StateMachineMisconfiguredError(f"could not import CSP Payload/Result class {cls_name}")
@add_state_features(Tags) @add_state_features(Tags)
@ -74,7 +92,7 @@ class PortfolioStateMachine(
return f"<PortfolioStateMachine(state='{self.current_state.name}', portfolio='{self.portfolio.name}'" return f"<PortfolioStateMachine(state='{self.current_state.name}', portfolio='{self.portfolio.name}'"
@reconstructor @reconstructor
def attach_machine(self): def attach_machine(self, stages=AzureStages):
""" """
This is called as a result of a sqlalchemy query. This is called as a result of a sqlalchemy query.
Attach a machine depending on the current state. Attach a machine depending on the current state.
@ -86,7 +104,7 @@ class PortfolioStateMachine(
auto_transitions=False, auto_transitions=False,
after_state_change="after_state_change", after_state_change="after_state_change",
) )
states, transitions = _build_transitions(AzureStages) states, transitions = _build_transitions(stages)
self.machine.add_states(self.system_states + states) self.machine.add_states(self.system_states + states)
self.machine.add_transitions(self.system_transitions + transitions) self.machine.add_transitions(self.system_transitions + transitions)
@ -120,7 +138,7 @@ class PortfolioStateMachine(
app.logger.info( app.logger.info(
f"could not locate 'create trigger' for {self.__repr__()}" f"could not locate 'create trigger' for {self.__repr__()}"
) )
self.fail_stage(stage) self.trigger('fail')
elif self.current_state == FSMStates.FAILED: elif self.current_state == FSMStates.FAILED:
# get the first trigger that starts with 'create_' # get the first trigger that starts with 'create_'
@ -151,17 +169,16 @@ class PortfolioStateMachine(
if create_trigger is not None: if create_trigger is not None:
self.trigger(create_trigger, **kwargs) self.trigger(create_trigger, **kwargs)
def after_in_progress_callback(self, event):
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
def after_in_progress_callback(self, event):
# Accumulate payload w/ creds # Accumulate payload w/ creds
payload = event.kwargs.get("csp_data") payload = event.kwargs.get("csp_data")
current_stage = _stage_state_to_stage_name(self.current_state, StageStates.IN_PROGRESS)
payload_data_cls = get_stage_csp_class(stage, "payload") payload_data_cls = get_stage_csp_class(current_stage, "payload")
if not payload_data_cls: if not payload_data_cls:
app.logger.info(f"could not resolve payload data class for stage {stage}") app.logger.info(f"could not resolve payload data class for stage {current_stage}")
self.fail_stage(stage) self.fail_stage(current_stage)
try: try:
payload_data = payload_data_cls(**payload) payload_data = payload_data_cls(**payload)
except PydanticValidationError as exc: except PydanticValidationError as exc:
@ -171,13 +188,13 @@ class PortfolioStateMachine(
app.logger.info(exc.json()) app.logger.info(exc.json())
print(exc.json()) print(exc.json())
app.logger.info(payload) app.logger.info(payload)
self.fail_stage(stage) self.fail_stage(current_stage)
# TODO: Determine best place to do this, maybe @reconstructor # TODO: Determine best place to do this, maybe @reconstructor
self.csp = app.csp.cloud self.csp = app.csp.cloud
try: try:
func_name = f"create_{stage}" func_name = f"create_{current_stage}"
response = getattr(self.csp, func_name)(payload_data) response = getattr(self.csp, func_name)(payload_data)
if self.portfolio.csp_data is None: if self.portfolio.csp_data is None:
self.portfolio.csp_data = {} self.portfolio.csp_data = {}
@ -193,16 +210,16 @@ class PortfolioStateMachine(
print(exc.json()) print(exc.json())
app.logger.info(payload_data) app.logger.info(payload_data)
# TODO: Ensure that failing the stage does not preclude a Celery retry # TODO: Ensure that failing the stage does not preclude a Celery retry
self.fail_stage(stage) self.fail_stage(current_stage)
# TODO: catch and handle general CSP exception here # TODO: catch and handle general CSP exception here
except (ConnectionException, UnknownServerException) as exc: except (ConnectionException, UnknownServerException) as exc:
app.logger.error( app.logger.error(
f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1, f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1,
) )
# TODO: Ensure that failing the stage does not preclude a Celery retry # TODO: Ensure that failing the stage does not preclude a Celery retry
self.fail_stage(stage) self.fail_stage(current_stage)
self.finish_stage(stage) self.finish_stage(current_stage)
def is_csp_data_valid(self, event): def is_csp_data_valid(self, event):
""" """

View File

@ -1,13 +1,22 @@
import json import json
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from uuid import uuid4 from uuid import uuid4
import pendulum import pendulum
import pydantic import pydantic
import pytest import pytest
#import requests
from secrets import token_urlsafe
from tests.factories import ApplicationFactory, EnvironmentFactory from tests.factories import ApplicationFactory, EnvironmentFactory
from tests.mock_azure import AUTH_CREDENTIALS, mock_azure from tests.mock_azure import AUTH_CREDENTIALS, mock_azure
from atst.domain.csp.cloud.exceptions import (
AuthenticationException,
UserProvisioningException,
ConnectionException,
UnknownServerException,
SecretException,
)
from atst.domain.csp.cloud import AzureCloudProvider from atst.domain.csp.cloud import AzureCloudProvider
from atst.domain.csp.cloud.models import ( from atst.domain.csp.cloud.models import (
AdminRoleDefinitionCSPPayload, AdminRoleDefinitionCSPPayload,
@ -58,6 +67,7 @@ from atst.domain.csp.cloud.models import (
BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31" BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31"
def mock_management_group_create(mock_azure, spec_dict): 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_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = (
spec_dict spec_dict
@ -93,7 +103,6 @@ def mock_get_secret(azure, val=None):
return azure return azure
def test_create_application_succeeds(mock_azure: AzureCloudProvider): def test_create_application_succeeds(mock_azure: AzureCloudProvider):
application = ApplicationFactory.create() application = ApplicationFactory.create()
mock_management_group_create(mock_azure, {"id": "Test Id"}) mock_management_group_create(mock_azure, {"id": "Test Id"})
@ -132,8 +141,8 @@ def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider):
parameters=mock_policy_definition, parameters=mock_policy_definition,
) )
def test_create_tenant(mock_azure: AzureCloudProvider): def test_create_tenant(mock_azure: AzureCloudProvider):
mock_result = Mock() mock_result = Mock()
mock_result.json.return_value = { mock_result.json.return_value = {
"objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d", "objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
@ -141,7 +150,13 @@ def test_create_tenant(mock_azure: AzureCloudProvider):
"userId": "1153801116406515559", "userId": "1153801116406515559",
} }
mock_result.status_code = 200 mock_result.status_code = 200
mock_azure.sdk.requests.post.return_value = mock_result mock_azure.sdk.requests.post.return_value = mock_result
mock_azure.sdk.requests.post.side_effect = [
mock_azure.sdk.requests.exceptions.ConnectionError,
mock_result
]
payload = TenantCSPPayload( payload = TenantCSPPayload(
**dict( **dict(
user_id="admin", user_id="admin",
@ -154,10 +169,63 @@ def test_create_tenant(mock_azure: AzureCloudProvider):
) )
) )
mock_azure = mock_get_secret(mock_azure) mock_azure = mock_get_secret(mock_azure)
with pytest.raises(ConnectionException):
mock_azure.create_tenant(payload)
result: TenantCSPResult = mock_azure.create_tenant(payload) result: TenantCSPResult = mock_azure.create_tenant(payload)
assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435" assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435"
def test_create_tenant_req_mock(mock_azure: AzureCloudProvider, requests_mock):
url = f"{mock_azure.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant"
requests_mock.register_uri(
"POST", "?".join([url, "api-version=2020-01-01-preview"]),
json={
"objectId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
"tenantId": "60ff9d34-82bf-4f21-b565-308ef0533435",
"userId": "1153801116406515559",
},
)
mock_azure = mock_get_secret(mock_azure)
payload = TenantCSPPayload(
**dict(
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: TenantCSPResult = mock_azure.create_tenant(payload)
assert result.tenant_id == "60ff9d34-82bf-4f21-b565-308ef0533435"
def test_create_tenant_req_mock_fail(mock_azure: AzureCloudProvider, requests_mock):
url = f"{mock_azure.sdk.cloud.endpoints.resource_manager}providers/Microsoft.SignUp/createTenant"
requests_mock.register_uri(
"POST", "?".join([url, "api-version=2020-01-01-preview"]),
exc=requests.exceptions.ConnectionError,
)
mock_azure = mock_get_secret(mock_azure)
payload = TenantCSPPayload(
**dict(
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",
)
)
with pytest.raises(ConnectionException):
assert mock_azure.create_tenant(payload)
# def test_create_tenant_fails(mock_azure: AzureCloudProvider): # def test_create_tenant_fails(mock_azure: AzureCloudProvider):
# mock_result = Mock() # mock_result = Mock()
# mock_result.status_code = 200 # mock_result.status_code = 200

View File

@ -1,7 +1,14 @@
import pytest import pytest
from atst.domain.csp import MockCloudProvider from atst.domain.csp import MockCloudProvider
from atst.domain.csp.cloud.models import EnvironmentCSPPayload, EnvironmentCSPResult from atst.domain.csp.cloud.models import (
EnvironmentCSPPayload,
EnvironmentCSPResult,
TenantCSPPayload,
TenantCSPResult,
BillingProfileCreationCSPPayload,
BillingProfileCreationCSPResult,
)
from tests.factories import EnvironmentFactory, EnvironmentRoleFactory, UserFactory from tests.factories import EnvironmentFactory, EnvironmentRoleFactory, UserFactory
@ -28,6 +35,44 @@ def test_create_environment(mock_csp: MockCloudProvider):
assert isinstance(result, EnvironmentCSPResult) assert isinstance(result, EnvironmentCSPResult)
def test_create_tenant(mock_csp: MockCloudProvider):
payload = TenantCSPPayload(
**dict(
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_csp.create_tenant(payload)
assert isinstance(result, TenantCSPResult)
def test_create_billing_profile_creation(mock_csp: MockCloudProvider):
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",
),
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
billing_profile_display_name="Test Billing Profile",
billing_account_name="123123",
)
)
result = mock_csp.create_billing_profile_creation(payload)
assert isinstance(result, BillingProfileCreationCSPResult)
def test_create_or_update_user(mock_csp: MockCloudProvider): def test_create_or_update_user(mock_csp: MockCloudProvider):
env_role = EnvironmentRoleFactory.create() env_role = EnvironmentRoleFactory.create()
csp_user_id = mock_csp.create_or_update_user(CREDENTIALS, env_role, "csp_role_id") csp_user_id = mock_csp.create_or_update_user(CREDENTIALS, env_role, "csp_role_id")

View File

@ -1,6 +1,9 @@
import pytest import pytest
import pydantic
import re import re
from unittest import mock from unittest import mock
from enum import Enum
from tests.factories import ( from tests.factories import (
PortfolioStateMachineFactory, PortfolioStateMachineFactory,
@ -8,13 +11,29 @@ from tests.factories import (
) )
from atst.models import FSMStates, PortfolioStateMachine, TaskOrder from atst.models import FSMStates, PortfolioStateMachine, TaskOrder
from atst.models.mixins.state_machines import AzureStages, StageStates, compose_state from atst.domain.csp.cloud.models import BillingProfileCreationCSPPayload
from atst.models.mixins.state_machines import (
AzureStages,
StageStates,
compose_state,
_build_transitions,
_build_csp_states,
)
from atst.models.portfolio import Portfolio from atst.models.portfolio import Portfolio
from atst.models.portfolio_state_machine import get_stage_csp_class from atst.models.portfolio_state_machine import (
get_stage_csp_class,
_stage_to_classname,
_stage_state_to_stage_name,
StateMachineMisconfiguredError,
)
# TODO: Write failure case tests # TODO: Write failure case tests
class AzureStagesTest(Enum):
TENANT = "tenant"
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def portfolio(): def portfolio():
# TODO: setup clin/to as active/funded/ready # TODO: setup clin/to as active/funded/ready
@ -37,13 +56,34 @@ def test_state_machine_trigger_next_transition(portfolio):
assert sm.current_state == FSMStates.STARTED assert sm.current_state == FSMStates.STARTED
def test_state_machine_compose_state(portfolio): def test_state_machine_compose_state():
PortfolioStateMachineFactory.create(portfolio=portfolio)
assert ( assert (
compose_state(AzureStages.TENANT, StageStates.CREATED) compose_state(AzureStages.TENANT, StageStates.CREATED)
== FSMStates.TENANT_CREATED == FSMStates.TENANT_CREATED
) )
def test_stage_to_classname():
assert (_stage_to_classname(AzureStages.BILLING_PROFILE_CREATION.name) == "BillingProfileCreation")
def test_get_stage_csp_class():
csp_class = get_stage_csp_class(list(AzureStages)[0].name.lower(), "payload")
assert isinstance(csp_class, pydantic.main.ModelMetaclass)
def test_get_stage_csp_class_import_fail():
with pytest.raises(StateMachineMisconfiguredError):
csp_class = get_stage_csp_class("doesnotexist", "payload")
def test_build_transitions():
states, transitions = _build_transitions(AzureStagesTest)
assert [s.get('name').name for s in states] == ['TENANT_CREATED', 'TENANT_IN_PROGRESS', 'TENANT_FAILED']
assert [s.get('tags') for s in states] == [['TENANT', 'CREATED'], ['TENANT', 'IN_PROGRESS'], ['TENANT', 'FAILED']]
assert [t.get('trigger') for t in transitions] == ['create_tenant', 'finish_tenant', 'fail_tenant', 'resume_progress_tenant']
def test_build_csp_states():
states = _build_csp_states(AzureStagesTest)
assert list(states) == ['UNSTARTED', 'STARTING', 'STARTED', 'COMPLETED', 'FAILED', 'TENANT_CREATED', 'TENANT_IN_PROGRESS', 'TENANT_FAILED']
def test_state_machine_valid_data_classes_for_stages(portfolio): def test_state_machine_valid_data_classes_for_stages(portfolio):
PortfolioStateMachineFactory.create(portfolio=portfolio) PortfolioStateMachineFactory.create(portfolio=portfolio)
@ -52,6 +92,25 @@ def test_state_machine_valid_data_classes_for_stages(portfolio):
assert get_stage_csp_class(stage.name.lower(), "result") is not None assert get_stage_csp_class(stage.name.lower(), "result") is not None
def test_attach_machine(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
sm.machine = None
sm.attach_machine(stages=AzureStagesTest)
assert list(sm.machine.events) == ['init', 'start', 'reset', 'fail', 'create_tenant', 'finish_tenant', 'fail_tenant', 'resume_progress_tenant']
def test_fail_stage(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
sm.state = FSMStates.TENANT_IN_PROGRESS
sm.fail_stage('tenant')
assert sm.state == FSMStates.TENANT_FAILED
#import ipdb;ipdb.set_trace()
def test_stage_state_to_stage_name():
#sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
stage = _stage_state_to_stage_name(FSMStates.TENANT_IN_PROGRESS, StageStates.IN_PROGRESS)
assert stage == "tenant"
def test_state_machine_initialization(portfolio): def test_state_machine_initialization(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio) sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
@ -121,7 +180,7 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio):
csp_data = {} csp_data = {}
ppoc = portfolio.owner ppoc = portfolio.owner
user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower() user_id = "johndoe" # f"{ppoc.first_name[0]}{ppoc.last_name}".lower()
domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower() domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower()
initial_task_order: TaskOrder = portfolio.task_orders[0] initial_task_order: TaskOrder = portfolio.task_orders[0]
@ -131,10 +190,10 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio):
"user_id": user_id, "user_id": user_id,
"password": "jklfsdNCVD83nklds2#202", # pragma: allowlist secret "password": "jklfsdNCVD83nklds2#202", # pragma: allowlist secret
"domain_name": domain_name, "domain_name": domain_name,
"first_name": ppoc.first_name, "first_name": "123", # ppoc.first_name,
"last_name": ppoc.last_name, "last_name": "123", # ppoc.last_name,
"country_code": "US", "country_code": "US",
"password_recovery_email_address": ppoc.email, "password_recovery_email_address": "email@example.com", # ppoc.email,
"address": { # TODO: TBD if we're sourcing this from data or config "address": { # TODO: TBD if we're sourcing this from data or config
"company_name": "", "company_name": "",
"address_line_1": "", "address_line_1": "",
@ -163,3 +222,4 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio):
csp_data = portfolio.csp_data csp_data = portfolio.csp_data
else: else:
csp_data = {} csp_data = {}

View File

@ -79,7 +79,7 @@ def mock_adal():
def mock_requests(): def mock_requests():
import requests import requests
return Mock(spec=requests) return Mock(wraps=requests)
def mock_secrets(): def mock_secrets():