mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
chore: update asserts in every unit test
This commit is contained in:
@@ -11,9 +11,6 @@ from prowler.providers.vercel.exceptions.exceptions import (
|
||||
|
||||
MAX_WORKERS = 10
|
||||
|
||||
# Vercel API base URL
|
||||
VERCEL_API_BASE = "https://api.vercel.com"
|
||||
|
||||
|
||||
class VercelService:
|
||||
"""Base class for Vercel services to share provider context and HTTP client."""
|
||||
@@ -32,6 +29,7 @@ class VercelService:
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
)
|
||||
self._base_url = provider.session.base_url
|
||||
self._team_id = provider.session.team_id
|
||||
|
||||
# Thread pool for parallel API calls
|
||||
@@ -58,7 +56,7 @@ class VercelService:
|
||||
if self._team_id and "teamId" not in params:
|
||||
params["teamId"] = self._team_id
|
||||
|
||||
url = f"{VERCEL_API_BASE}{path}"
|
||||
url = f"{self._base_url}{path}"
|
||||
max_retries = self.audit_config.get("max_retries", 3)
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
|
||||
@@ -11,6 +11,7 @@ class VercelSession(BaseModel):
|
||||
|
||||
token: str
|
||||
team_id: Optional[str] = None
|
||||
base_url: str = "https://api.vercel.com"
|
||||
http_session: Any = Field(default=None, exclude=True)
|
||||
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@ from prowler.providers.vercel.models import (
|
||||
VercelTeamInfo,
|
||||
)
|
||||
|
||||
# Vercel API base URL
|
||||
VERCEL_API_BASE = "https://api.vercel.com"
|
||||
|
||||
|
||||
class VercelProvider(Provider):
|
||||
"""Vercel provider."""
|
||||
@@ -189,7 +186,9 @@ class VercelProvider(Provider):
|
||||
params = {"teamId": session.team_id} if session.team_id else {}
|
||||
|
||||
# Get user info
|
||||
response = http.get(f"{VERCEL_API_BASE}/v2/user", params=params, timeout=30)
|
||||
response = http.get(
|
||||
f"{session.base_url}/v2/user", params=params, timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
user_data = response.json().get("user", {})
|
||||
|
||||
@@ -202,7 +201,7 @@ class VercelProvider(Provider):
|
||||
if session.team_id:
|
||||
params = {"teamId": session.team_id}
|
||||
team_response = http.get(
|
||||
f"{VERCEL_API_BASE}/v2/teams/{session.team_id}",
|
||||
f"{session.base_url}/v2/teams/{session.team_id}",
|
||||
params=params,
|
||||
timeout=30,
|
||||
)
|
||||
@@ -254,7 +253,7 @@ class VercelProvider(Provider):
|
||||
if session.team_id:
|
||||
params["teamId"] = session.team_id
|
||||
response = session.http_session.get(
|
||||
f"{VERCEL_API_BASE}/v2/user", params=params, timeout=30
|
||||
f"{session.base_url}/v2/user", params=params, timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 401:
|
||||
|
||||
@@ -33,12 +33,13 @@ class Test_authentication_no_stale_tokens:
|
||||
def test_token_active_recently(self):
|
||||
token_id = "tok_1"
|
||||
token_name = "Recent Token"
|
||||
active_at = datetime.now(timezone.utc) - timedelta(days=10)
|
||||
authentication_client = mock.MagicMock
|
||||
authentication_client.tokens = {
|
||||
token_id: VercelAuthToken(
|
||||
id=token_id,
|
||||
name=token_name,
|
||||
active_at=datetime.now(timezone.utc) - timedelta(days=10),
|
||||
active_at=active_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,17 +63,23 @@ class Test_authentication_no_stale_tokens:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "PASS"
|
||||
assert "was last active on" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) was last active on {active_at.strftime('%Y-%m-%d %H:%M UTC')} (within the last 90 days)."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
def test_token_stale_90_days(self):
|
||||
token_id = "tok_2"
|
||||
token_name = "Stale Token"
|
||||
active_at = datetime.now(timezone.utc) - timedelta(days=120)
|
||||
days_inactive = (datetime.now(timezone.utc) - active_at).days
|
||||
authentication_client = mock.MagicMock
|
||||
authentication_client.tokens = {
|
||||
token_id: VercelAuthToken(
|
||||
id=token_id,
|
||||
name=token_name,
|
||||
active_at=datetime.now(timezone.utc) - timedelta(days=120),
|
||||
active_at=active_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,7 +103,11 @@ class Test_authentication_no_stale_tokens:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "FAIL"
|
||||
assert "has not been used for" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) has not been used for {days_inactive} days (last active: {active_at.strftime('%Y-%m-%d %H:%M UTC')}). Threshold is 90 days."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
def test_token_no_activity(self):
|
||||
token_id = "tok_3"
|
||||
@@ -130,4 +141,8 @@ class Test_authentication_no_stale_tokens:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "FAIL"
|
||||
assert "no recorded activity" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) has no recorded activity and is considered stale."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
@@ -33,12 +33,13 @@ class Test_authentication_token_not_expired:
|
||||
def test_token_not_expired(self):
|
||||
token_id = "tok_1"
|
||||
token_name = "My Token"
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(days=30)
|
||||
authentication_client = mock.MagicMock
|
||||
authentication_client.tokens = {
|
||||
token_id: VercelAuthToken(
|
||||
id=token_id,
|
||||
name=token_name,
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(days=30),
|
||||
expires_at=expires_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,17 +63,22 @@ class Test_authentication_token_not_expired:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "PASS"
|
||||
assert "is valid and expires" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) is valid and expires on {expires_at.strftime('%Y-%m-%d %H:%M UTC')}."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
def test_token_expired(self):
|
||||
token_id = "tok_2"
|
||||
token_name = "Old Token"
|
||||
expires_at = datetime.now(timezone.utc) - timedelta(days=1)
|
||||
authentication_client = mock.MagicMock
|
||||
authentication_client.tokens = {
|
||||
token_id: VercelAuthToken(
|
||||
id=token_id,
|
||||
name=token_name,
|
||||
expires_at=datetime.now(timezone.utc) - timedelta(days=1),
|
||||
expires_at=expires_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,7 +102,11 @@ class Test_authentication_token_not_expired:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "FAIL"
|
||||
assert "has expired" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) has expired on {expires_at.strftime('%Y-%m-%d %H:%M UTC')}."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
def test_token_no_expiration(self):
|
||||
token_id = "tok_3"
|
||||
@@ -130,4 +140,8 @@ class Test_authentication_token_not_expired:
|
||||
assert result[0].resource_id == token_id
|
||||
assert result[0].resource_name == token_name
|
||||
assert result[0].status == "PASS"
|
||||
assert "does not have an expiration" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Token '{token_name}' ({token_id}) does not have an expiration date set and is currently valid."
|
||||
)
|
||||
assert result[0].team_id is None
|
||||
|
||||
@@ -69,7 +69,11 @@ class Test_deployment_preview_not_publicly_accessible:
|
||||
assert result[0].resource_id == DEPLOYMENT_ID
|
||||
assert result[0].resource_name == "my-app-abc123"
|
||||
assert result[0].status == "PASS"
|
||||
assert "has deployment protection configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Preview deployment my-app-abc123 ({DEPLOYMENT_ID}) has deployment protection configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_preview_not_protected(self):
|
||||
deployment_client = mock.MagicMock
|
||||
@@ -106,9 +110,10 @@ class Test_deployment_preview_not_publicly_accessible:
|
||||
assert result[0].resource_name == "my-app-abc123"
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have deployment protection configured"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Preview deployment my-app-abc123 ({DEPLOYMENT_ID}) does not have deployment protection configured and is publicly accessible."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_production_deployment_skipped(self):
|
||||
deployment_client = mock.MagicMock
|
||||
|
||||
@@ -70,7 +70,11 @@ class Test_deployment_production_uses_stable_target:
|
||||
assert result[0].resource_id == DEPLOYMENT_ID
|
||||
assert result[0].resource_name == "my-app-abc123"
|
||||
assert result[0].status == "PASS"
|
||||
assert "stable branch" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Production deployment my-app-abc123 ({DEPLOYMENT_ID}) is sourced from stable branch 'main'."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_non_stable_branch(self):
|
||||
deployment_client = mock.MagicMock
|
||||
@@ -107,7 +111,11 @@ class Test_deployment_production_uses_stable_target:
|
||||
assert result[0].resource_id == DEPLOYMENT_ID
|
||||
assert result[0].resource_name == "my-app-abc123"
|
||||
assert result[0].status == "FAIL"
|
||||
assert "instead of a" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Production deployment my-app-abc123 ({DEPLOYMENT_ID}) is sourced from branch 'feature-xyz' instead of a configured stable branch (main, master)."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_non_production_skipped(self):
|
||||
deployment_client = mock.MagicMock
|
||||
|
||||
@@ -64,7 +64,11 @@ class Test_domain_dns_properly_configured:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has DNS properly configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} has DNS properly configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_not_configured(self):
|
||||
domain_client = mock.MagicMock
|
||||
@@ -97,4 +101,8 @@ class Test_domain_dns_properly_configured:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have DNS properly configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} does not have DNS properly configured. The domain may not be resolving to Vercel's infrastructure."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -66,7 +66,11 @@ class Test_domain_no_wildcard_dns_exposure:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no wildcard DNS records" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} has no wildcard DNS records."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_has_wildcard_records(self):
|
||||
domain_client = mock.MagicMock
|
||||
@@ -105,4 +109,8 @@ class Test_domain_no_wildcard_dns_exposure:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "wildcard DNS" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} has 1 wildcard DNS record(s): *.example.com. This may expose unintended subdomains."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -64,8 +64,10 @@ class Test_domain_ssl_certificate_valid:
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
"has a valid SSL certificate provisioned" in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} has a valid SSL certificate provisioned."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_ssl_missing(self):
|
||||
domain_client = mock.MagicMock
|
||||
@@ -100,6 +102,7 @@ class Test_domain_ssl_certificate_valid:
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have an SSL certificate provisioned"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} does not have an SSL certificate provisioned."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -64,7 +64,8 @@ class Test_domain_verified:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "is verified" in result[0].status_extended
|
||||
assert result[0].status_extended == f"Domain {DOMAIN_NAME} is verified."
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_not_verified(self):
|
||||
domain_client = mock.MagicMock
|
||||
@@ -97,4 +98,8 @@ class Test_domain_verified:
|
||||
assert result[0].resource_id == DOMAIN_ID
|
||||
assert result[0].resource_name == DOMAIN_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "is not verified" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Domain {DOMAIN_NAME} is not verified. The domain may not be serving traffic correctly."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_project_auto_expose_system_env_disabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "does not automatically expose" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not automatically expose system environment variables to the build process."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_auto_expose_enabled(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -96,4 +100,8 @@ class Test_project_auto_expose_system_env_disabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "automatically exposes system" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} automatically exposes system environment variables to the build process."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -68,7 +68,11 @@ class Test_project_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "deployment protection enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has deployment protection enabled with level 'standard' on preview deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_deployment_protection_disabled(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -103,7 +107,11 @@ class Test_project_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have deployment protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have deployment protection enabled on preview deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_deployment_protection_none(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -136,3 +144,8 @@ class Test_project_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have deployment protection enabled on preview deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_project_directory_listing_disabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has directory listing disabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has directory listing disabled."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_listing_enabled(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -96,4 +100,8 @@ class Test_project_directory_listing_disabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "has directory listing enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has directory listing enabled, which may expose the project's file structure to visitors."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -74,7 +74,11 @@ class Test_project_environment_no_overly_broad_target:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no environment variables targeting" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has no environment variables targeting all three environments simultaneously."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_var_targets_all_envs(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -115,4 +119,8 @@ class Test_project_environment_no_overly_broad_target:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "targeting all three environments" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has 1 environment variable(s) targeting all three environments: SHARED_VAR."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -73,7 +73,11 @@ class Test_project_environment_no_secrets_in_plain_type:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no secret-like environment variables" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has no secret-like environment variables stored as plain text."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_secret_key_plain(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -113,7 +117,11 @@ class Test_project_environment_no_secrets_in_plain_type:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "stored as plain text" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has 1 secret-like environment variable(s) stored as plain text: MY_API_KEY."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_non_secret_key_plain(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -153,4 +161,8 @@ class Test_project_environment_no_secrets_in_plain_type:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no secret-like environment variables" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has no secret-like environment variables stored as plain text."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -74,7 +74,11 @@ class Test_project_environment_production_vars_not_in_preview:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no sensitive production environment" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has no sensitive production environment variables leaking to preview deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_prod_and_preview_secret(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -115,7 +119,11 @@ class Test_project_environment_production_vars_not_in_preview:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "also targeting preview" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has 1 sensitive production environment variable(s) also targeting preview: DB_PASSWORD."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_prod_and_preview_plain(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -156,4 +164,8 @@ class Test_project_environment_production_vars_not_in_preview:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no sensitive production environment" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has no sensitive production environment variables leaking to preview deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -73,7 +73,11 @@ class Test_project_environment_sensitive_vars_encrypted:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "properly encrypted" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has all sensitive environment variables properly encrypted or uses no sensitive variables."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_sensitive_var_plain_text(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -113,8 +117,11 @@ class Test_project_environment_sensitive_vars_encrypted:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "plain text" in result[0].status_extended
|
||||
assert "API_KEY" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has 1 sensitive environment variable(s) stored as plain text: API_KEY."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_no_sensitive_vars(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -154,4 +161,8 @@ class Test_project_environment_sensitive_vars_encrypted:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "properly encrypted" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has all sensitive environment variables properly encrypted or uses no sensitive variables."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_project_git_fork_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has Git fork protection enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has Git fork protection enabled, preventing untrusted forks from accessing secrets."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_fork_protection_disabled(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -96,4 +100,8 @@ class Test_project_git_fork_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have Git fork protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have Git fork protection enabled, allowing forks to access environment variables and trigger deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_project_password_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has password protection configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has password protection configured to restrict access to deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_no_password_protection(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -96,4 +100,8 @@ class Test_project_password_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have password protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have password protection configured for deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -68,7 +68,11 @@ class Test_project_production_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "production deployment protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has production deployment protection enabled with level 'standard'."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_protection_none_level(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -103,7 +107,11 @@ class Test_project_production_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have deployment protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_protection_null(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -136,4 +144,8 @@ class Test_project_production_deployment_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have deployment protection" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have deployment protection enabled on production deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_project_skew_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has skew protection enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} has skew protection enabled, ensuring consistent deployment versions during rollouts."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_skew_protection_disabled(self):
|
||||
project_client = mock.MagicMock
|
||||
@@ -96,4 +100,8 @@ class Test_project_skew_protection_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have skew protection enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} does not have skew protection enabled, which may cause version mismatches during deployments."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -67,7 +67,11 @@ class Test_security_custom_rules_configured:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "custom firewall rule(s) configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) has 1 custom firewall rule(s) configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_no_custom_rules(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -103,5 +107,7 @@ class Test_security_custom_rules_configured:
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have any custom firewall rules" in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any custom firewall rules configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -67,7 +67,11 @@ class Test_security_ip_blocking_rules_configured:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "IP blocking rule(s) configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) has 1 IP blocking rule(s) configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_no_ip_rules(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -102,4 +106,8 @@ class Test_security_ip_blocking_rules_configured:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have any IP blocking rules" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any IP blocking rules configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -68,7 +68,11 @@ class Test_security_managed_rulesets_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "managed WAF rulesets enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) has managed WAF rulesets enabled."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_managed_rulesets_disabled(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -105,9 +109,10 @@ class Test_security_managed_rulesets_enabled:
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have managed WAF rulesets enabled"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have managed WAF rulesets enabled."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_managed_rulesets_plan_gated(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -143,5 +148,8 @@ class Test_security_managed_rulesets_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "MANUAL"
|
||||
assert "could not be assessed" in result[0].status_extended
|
||||
assert "Enterprise plan" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) could not be assessed for managed rulesets. Enterprise plan required to access this feature."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -67,7 +67,11 @@ class Test_security_rate_limiting_configured:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "rate limiting rule(s) configured" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) has 1 rate limiting rule(s) configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_no_rate_limiting(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -102,4 +106,8 @@ class Test_security_rate_limiting_configured:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have any rate limiting rules" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have any rate limiting rules configured."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -67,7 +67,11 @@ class Test_security_waf_enabled:
|
||||
assert result[0].resource_id == PROJECT_ID
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "Web Application Firewall enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) has the Web Application Firewall enabled."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
def test_waf_disabled(self):
|
||||
security_client = mock.MagicMock
|
||||
@@ -103,6 +107,7 @@ class Test_security_waf_enabled:
|
||||
assert result[0].resource_name == PROJECT_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have the Web Application Firewall enabled"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Project {PROJECT_NAME} ({PROJECT_ID}) does not have the Web Application Firewall enabled."
|
||||
)
|
||||
assert result[0].team_id == TEAM_ID
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_team_directory_sync_enabled:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "has directory sync (SCIM) enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has directory sync (SCIM) enabled for automated user provisioning."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_directory_sync_disabled(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -97,6 +101,7 @@ class Test_team_directory_sync_enabled:
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
"does not have directory sync (SCIM) enabled"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} does not have directory sync (SCIM) enabled. User provisioning and deprovisioning must be managed manually."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
@@ -75,7 +75,11 @@ class Test_team_member_no_stale_access:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no members with access older" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has no members with access older than 90 days requiring review."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_stale_member(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -116,7 +120,11 @@ class Test_team_member_no_stale_access:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "joined more than" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has 1 member(s) who joined more than 90 days ago and may require an access review."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_non_active_member_skipped(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -157,4 +165,8 @@ class Test_team_member_no_stale_access:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no members with access older" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has no members with access older than 90 days requiring review."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
@@ -75,7 +75,11 @@ class Test_team_member_role_least_privilege:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "within the recommended 20% threshold" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has 0 owner(s) out of 1 active members (0%), which is within the recommended 20% threshold."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_member_owner_role(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -116,4 +120,8 @@ class Test_team_member_role_least_privilege:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "exceeds the recommended 20% threshold" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has 1 owner(s) out of 1 active members (100%), which exceeds the recommended 20% threshold."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
@@ -75,7 +75,11 @@ class Test_team_no_stale_invitations:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "no stale pending invitations" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has no stale pending invitations older than 30 days."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_stale_invitation(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -116,5 +120,8 @@ class Test_team_no_stale_invitations:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "stale" in result[0].status_extended
|
||||
assert "pending invitation(s)" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has 1 stale pending invitation(s) older than 30 days."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
@@ -64,7 +64,10 @@ class Test_team_saml_sso_enabled:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert f"Team {TEAM_NAME} has SAML SSO enabled" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended == f"Team {TEAM_NAME} has SAML SSO enabled."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_saml_disabled(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -99,6 +102,7 @@ class Test_team_saml_sso_enabled:
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
f"Team {TEAM_NAME} does not have SAML SSO enabled"
|
||||
in result[0].status_extended
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} does not have SAML SSO enabled."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
@@ -63,7 +63,11 @@ class Test_team_saml_sso_enforced:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "PASS"
|
||||
assert "enforces SAML SSO" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} enforces SAML SSO for all members."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_saml_enabled_not_enforced(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -96,7 +100,11 @@ class Test_team_saml_sso_enforced:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not enforce it" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} has SAML SSO enabled but does not enforce it. Members can still authenticate without SSO."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
def test_saml_disabled(self):
|
||||
team_client = mock.MagicMock
|
||||
@@ -129,4 +137,8 @@ class Test_team_saml_sso_enforced:
|
||||
assert result[0].resource_id == TEAM_ID
|
||||
assert result[0].resource_name == TEAM_NAME
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have SAML SSO enforced" in result[0].status_extended
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Team {TEAM_NAME} does not have SAML SSO enforced."
|
||||
)
|
||||
assert result[0].team_id == ""
|
||||
|
||||
Reference in New Issue
Block a user