bt-cli 0.4.9__tar.gz → 0.4.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.gitignore +1 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/CLAUDE.md +1 -1
- {bt_cli-0.4.9 → bt_cli-0.4.11}/PKG-INFO +1 -1
- {bt_cli-0.4.9 → bt_cli-0.4.11}/pyproject.toml +1 -1
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/cli.py +5 -5
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/csv_utils.py +2 -2
- bt_cli-0.4.11/tests/integration/conftest.py +214 -0
- bt_cli-0.4.11/tests/integration/helpers.py +75 -0
- bt_cli-0.4.11/tests/integration/test_epmw_lifecycle.py +242 -0
- bt_cli-0.4.11/tests/integration/test_pra_lifecycle.py +283 -0
- bt_cli-0.4.11/tests/integration/test_pws_lifecycle.py +309 -0
- bt_cli-0.4.9/.claude/settings.local.json +0 -128
- bt_cli-0.4.9/bt.spec +0 -57
- bt_cli-0.4.9/tests/integration/conftest.py +0 -96
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.env.example +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/README.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/assets/cli-help.png +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/assets/cli-output.png +0 -0
- /bt_cli-0.4.9/bt-admin.spec → /bt_cli-0.4.11/bt-cli.spec +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/bt_entry.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/conftest.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.9 → bt_cli-0.4.11}/tests/pws-smoke-test.sh +0 -0
|
@@ -552,9 +552,9 @@ def _get_skills_path() -> Optional[str]:
|
|
|
552
552
|
if bundle_path.exists():
|
|
553
553
|
return str(bundle_path)
|
|
554
554
|
|
|
555
|
-
# Try importlib.resources
|
|
555
|
+
# Try importlib.resources (files() works with directories in 3.9+)
|
|
556
556
|
try:
|
|
557
|
-
if sys.version_info >= (3,
|
|
557
|
+
if sys.version_info >= (3, 9):
|
|
558
558
|
from importlib.resources import files
|
|
559
559
|
data_path = files("bt_cli.data").joinpath("skills")
|
|
560
560
|
if data_path.is_dir():
|
|
@@ -564,7 +564,7 @@ def _get_skills_path() -> Optional[str]:
|
|
|
564
564
|
with path("bt_cli.data", "skills") as p:
|
|
565
565
|
if p.exists():
|
|
566
566
|
return str(p)
|
|
567
|
-
except (ImportError, ModuleNotFoundError, TypeError, FileNotFoundError):
|
|
567
|
+
except (ImportError, ModuleNotFoundError, TypeError, FileNotFoundError, IsADirectoryError):
|
|
568
568
|
pass
|
|
569
569
|
|
|
570
570
|
# Fall back to source directory
|
|
@@ -587,9 +587,9 @@ def _get_claude_md_path() -> Optional[str]:
|
|
|
587
587
|
if bundle_path.exists():
|
|
588
588
|
return str(bundle_path)
|
|
589
589
|
|
|
590
|
-
# Try importlib.resources
|
|
590
|
+
# Try importlib.resources (files() available in 3.9+)
|
|
591
591
|
try:
|
|
592
|
-
if sys.version_info >= (3,
|
|
592
|
+
if sys.version_info >= (3, 9):
|
|
593
593
|
from importlib.resources import files
|
|
594
594
|
data_path = files("bt_cli.data").joinpath("CLAUDE.md")
|
|
595
595
|
if data_path.is_file():
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import csv
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def read_csv(file_path: str) -> list[dict[str, Any]]:
|
|
@@ -73,7 +73,7 @@ def parse_bool(value: str) -> bool:
|
|
|
73
73
|
return value.lower().strip() in ('true', 'yes', '1', 'y')
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def parse_int(value: str, default: int
|
|
76
|
+
def parse_int(value: str, default: Optional[int] = None) -> Optional[int]:
|
|
77
77
|
"""Parse integer from CSV string.
|
|
78
78
|
|
|
79
79
|
Args:
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Integration test fixtures.
|
|
2
|
+
|
|
3
|
+
These fixtures provide real API clients when credentials are configured.
|
|
4
|
+
Tests are automatically skipped if required environment variables are missing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def pytest_configure(config):
|
|
13
|
+
"""Register integration marker."""
|
|
14
|
+
config.addinivalue_line(
|
|
15
|
+
"markers", "integration: mark test as integration test requiring real API credentials"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def pws_integration_config():
|
|
21
|
+
"""Load PWS config from environment, skip if not available.
|
|
22
|
+
|
|
23
|
+
Requires BT_PWS_API_URL and either:
|
|
24
|
+
- BT_PWS_API_KEY (for API key auth)
|
|
25
|
+
- BT_PWS_CLIENT_ID and BT_PWS_CLIENT_SECRET (for OAuth)
|
|
26
|
+
"""
|
|
27
|
+
from bt_cli.core.config import load_pws_config
|
|
28
|
+
|
|
29
|
+
if not os.getenv("BT_PWS_API_URL"):
|
|
30
|
+
pytest.skip("PWS credentials not configured (BT_PWS_API_URL not set)")
|
|
31
|
+
|
|
32
|
+
if not os.getenv("BT_PWS_API_KEY") and not (
|
|
33
|
+
os.getenv("BT_PWS_CLIENT_ID") and os.getenv("BT_PWS_CLIENT_SECRET")
|
|
34
|
+
):
|
|
35
|
+
pytest.skip("PWS credentials not configured (missing API key or OAuth credentials)")
|
|
36
|
+
|
|
37
|
+
return load_pws_config()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def pra_integration_config():
|
|
42
|
+
"""Load PRA config from environment, skip if not available.
|
|
43
|
+
|
|
44
|
+
Requires:
|
|
45
|
+
- BT_PRA_API_URL
|
|
46
|
+
- BT_PRA_CLIENT_ID
|
|
47
|
+
- BT_PRA_CLIENT_SECRET
|
|
48
|
+
"""
|
|
49
|
+
from bt_cli.core.config import load_pra_config
|
|
50
|
+
|
|
51
|
+
if not os.getenv("BT_PRA_API_URL"):
|
|
52
|
+
pytest.skip("PRA credentials not configured (BT_PRA_API_URL not set)")
|
|
53
|
+
|
|
54
|
+
if not os.getenv("BT_PRA_CLIENT_ID") or not os.getenv("BT_PRA_CLIENT_SECRET"):
|
|
55
|
+
pytest.skip("PRA credentials not configured (missing OAuth credentials)")
|
|
56
|
+
|
|
57
|
+
return load_pra_config()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def epmw_integration_config():
|
|
62
|
+
"""Load EPMW config from environment, skip if not available.
|
|
63
|
+
|
|
64
|
+
Requires:
|
|
65
|
+
- BT_EPM_API_URL
|
|
66
|
+
- BT_EPM_CLIENT_ID
|
|
67
|
+
- BT_EPM_CLIENT_SECRET
|
|
68
|
+
"""
|
|
69
|
+
from bt_cli.core.config import load_epmw_config
|
|
70
|
+
|
|
71
|
+
if not os.getenv("BT_EPM_API_URL"):
|
|
72
|
+
pytest.skip("EPMW credentials not configured (BT_EPM_API_URL not set)")
|
|
73
|
+
|
|
74
|
+
if not os.getenv("BT_EPM_CLIENT_ID") or not os.getenv("BT_EPM_CLIENT_SECRET"):
|
|
75
|
+
pytest.skip("EPMW credentials not configured (missing OAuth credentials)")
|
|
76
|
+
|
|
77
|
+
return load_epmw_config()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.fixture
|
|
81
|
+
def entitle_integration_config():
|
|
82
|
+
"""Load Entitle config from environment, skip if not available.
|
|
83
|
+
|
|
84
|
+
Requires:
|
|
85
|
+
- BT_ENTITLE_API_URL
|
|
86
|
+
- BT_ENTITLE_API_KEY
|
|
87
|
+
"""
|
|
88
|
+
from bt_cli.core.config import load_entitle_config
|
|
89
|
+
|
|
90
|
+
if not os.getenv("BT_ENTITLE_API_URL"):
|
|
91
|
+
pytest.skip("Entitle credentials not configured (BT_ENTITLE_API_URL not set)")
|
|
92
|
+
|
|
93
|
+
if not os.getenv("BT_ENTITLE_API_KEY"):
|
|
94
|
+
pytest.skip("Entitle credentials not configured (BT_ENTITLE_API_KEY not set)")
|
|
95
|
+
|
|
96
|
+
return load_entitle_config()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# =============================================================================
|
|
100
|
+
# Auto-Discovery Fixtures for PWS
|
|
101
|
+
# =============================================================================
|
|
102
|
+
|
|
103
|
+
@pytest.fixture
|
|
104
|
+
def pws_workgroup_id(pws_integration_config):
|
|
105
|
+
"""Auto-discover first available workgroup ID."""
|
|
106
|
+
from bt_cli.pws.client.base import FullPasswordSafeClient
|
|
107
|
+
|
|
108
|
+
with FullPasswordSafeClient(pws_integration_config) as client:
|
|
109
|
+
client.authenticate()
|
|
110
|
+
workgroups = client.list_workgroups()
|
|
111
|
+
if not workgroups:
|
|
112
|
+
pytest.skip("No workgroups available")
|
|
113
|
+
return workgroups[0]["ID"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def pws_linux_platform_id(pws_integration_config):
|
|
118
|
+
"""Auto-discover Linux platform ID."""
|
|
119
|
+
from bt_cli.pws.client.base import FullPasswordSafeClient
|
|
120
|
+
|
|
121
|
+
with FullPasswordSafeClient(pws_integration_config) as client:
|
|
122
|
+
client.authenticate()
|
|
123
|
+
platforms = client.list_platforms()
|
|
124
|
+
for p in platforms:
|
|
125
|
+
if "Linux" in p.get("Name", ""):
|
|
126
|
+
return p["PlatformID"]
|
|
127
|
+
pytest.skip("No Linux platform found")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.fixture
|
|
131
|
+
def pws_windows_platform_id(pws_integration_config):
|
|
132
|
+
"""Auto-discover Windows platform ID."""
|
|
133
|
+
from bt_cli.pws.client.base import FullPasswordSafeClient
|
|
134
|
+
|
|
135
|
+
with FullPasswordSafeClient(pws_integration_config) as client:
|
|
136
|
+
client.authenticate()
|
|
137
|
+
platforms = client.list_platforms()
|
|
138
|
+
for p in platforms:
|
|
139
|
+
if "Windows" in p.get("Name", "") and "Domain" not in p.get("Name", ""):
|
|
140
|
+
return p["PlatformID"]
|
|
141
|
+
pytest.skip("No Windows platform found")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.fixture
|
|
145
|
+
def pws_functional_account_id(pws_integration_config):
|
|
146
|
+
"""Auto-discover first available functional account ID."""
|
|
147
|
+
from bt_cli.pws.client.base import FullPasswordSafeClient
|
|
148
|
+
|
|
149
|
+
with FullPasswordSafeClient(pws_integration_config) as client:
|
|
150
|
+
client.authenticate()
|
|
151
|
+
accounts = client.list_functional_accounts()
|
|
152
|
+
if not accounts:
|
|
153
|
+
pytest.skip("No functional accounts available")
|
|
154
|
+
return accounts[0]["FunctionalAccountID"]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =============================================================================
|
|
158
|
+
# Auto-Discovery Fixtures for PRA
|
|
159
|
+
# =============================================================================
|
|
160
|
+
|
|
161
|
+
@pytest.fixture
|
|
162
|
+
def pra_jumpoint_id(pra_integration_config):
|
|
163
|
+
"""Auto-discover first available jumpoint ID."""
|
|
164
|
+
from bt_cli.pra.client.base import PRAClient
|
|
165
|
+
|
|
166
|
+
with PRAClient(pra_integration_config) as client:
|
|
167
|
+
# PRA uses automatic OAuth authentication
|
|
168
|
+
jumpoints = client.list_jumpoints()
|
|
169
|
+
if not jumpoints:
|
|
170
|
+
pytest.skip("No jumpoints available")
|
|
171
|
+
return jumpoints[0]["id"]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@pytest.fixture
|
|
175
|
+
def pra_jump_group_id(pra_integration_config):
|
|
176
|
+
"""Auto-discover first available jump group ID."""
|
|
177
|
+
from bt_cli.pra.client.base import PRAClient
|
|
178
|
+
|
|
179
|
+
with PRAClient(pra_integration_config) as client:
|
|
180
|
+
# PRA uses automatic OAuth authentication
|
|
181
|
+
groups = client.list_jump_groups()
|
|
182
|
+
if not groups:
|
|
183
|
+
pytest.skip("No jump groups available")
|
|
184
|
+
return groups[0]["id"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# =============================================================================
|
|
188
|
+
# Auto-Discovery Fixtures for EPMW
|
|
189
|
+
# =============================================================================
|
|
190
|
+
|
|
191
|
+
@pytest.fixture
|
|
192
|
+
def epmw_policy_id(epmw_integration_config):
|
|
193
|
+
"""Auto-discover first available policy ID."""
|
|
194
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
195
|
+
|
|
196
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
197
|
+
# EPMW uses auto-auth via OAuth token on each request
|
|
198
|
+
policies = client.list_policies()
|
|
199
|
+
if not policies:
|
|
200
|
+
pytest.skip("No policies available")
|
|
201
|
+
return policies[0]["id"]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.fixture
|
|
205
|
+
def epmw_computer_id(epmw_integration_config):
|
|
206
|
+
"""Auto-discover first available computer ID."""
|
|
207
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
208
|
+
|
|
209
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
210
|
+
# EPMW uses auto-auth via OAuth token on each request
|
|
211
|
+
computers = client.list_computers()
|
|
212
|
+
if not computers:
|
|
213
|
+
pytest.skip("No computers available")
|
|
214
|
+
return computers[0]["id"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Shared utilities for integration tests."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Callable, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def unique_name(prefix: str) -> str:
|
|
9
|
+
"""Generate unique test resource name.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
prefix: Resource type prefix (e.g., 'safe', 'system', 'jump')
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Unique name like 'pytest-safe-a1b2c3d4'
|
|
16
|
+
"""
|
|
17
|
+
return f"pytest-{prefix}-{uuid.uuid4().hex[:8]}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextmanager
|
|
21
|
+
def cleanup_on_exit(delete_func: Callable[[Any], Any], resource_id: Any):
|
|
22
|
+
"""Context manager to ensure resource cleanup even on test failure.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
delete_func: Function to call for deletion
|
|
26
|
+
resource_id: ID to pass to delete function
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
with cleanup_on_exit(client.delete_safe, safe_id):
|
|
30
|
+
# do stuff with safe
|
|
31
|
+
pass
|
|
32
|
+
# safe is deleted even if exception occurs
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
yield
|
|
36
|
+
finally:
|
|
37
|
+
try:
|
|
38
|
+
delete_func(resource_id)
|
|
39
|
+
except Exception:
|
|
40
|
+
pass # Best effort cleanup
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ResourceTracker:
|
|
44
|
+
"""Track created resources for cleanup at end of test.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
tracker = ResourceTracker()
|
|
48
|
+
tracker.add(client.delete_safe, safe_id)
|
|
49
|
+
tracker.add(client.delete_folder, folder_id)
|
|
50
|
+
# ... test code ...
|
|
51
|
+
tracker.cleanup() # Deletes in reverse order
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self._resources: list[tuple[Callable, Any]] = []
|
|
56
|
+
|
|
57
|
+
def add(self, delete_func: Callable[[Any], Any], resource_id: Any) -> None:
|
|
58
|
+
"""Register a resource for cleanup."""
|
|
59
|
+
self._resources.append((delete_func, resource_id))
|
|
60
|
+
|
|
61
|
+
def cleanup(self) -> None:
|
|
62
|
+
"""Delete all tracked resources in reverse order (LIFO)."""
|
|
63
|
+
while self._resources:
|
|
64
|
+
delete_func, resource_id = self._resources.pop()
|
|
65
|
+
try:
|
|
66
|
+
delete_func(resource_id)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass # Best effort cleanup
|
|
69
|
+
|
|
70
|
+
def __enter__(self):
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
74
|
+
self.cleanup()
|
|
75
|
+
return False # Don't suppress exceptions
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""EPMW CRUD lifecycle integration tests.
|
|
2
|
+
|
|
3
|
+
These tests create, verify, and clean up real resources in EPM Windows.
|
|
4
|
+
Run with: pytest tests/integration/test_epmw_lifecycle.py -v
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from tests.integration.helpers import unique_name, ResourceTracker
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.integration
|
|
13
|
+
class TestGroupLifecycle:
|
|
14
|
+
"""Test EPMW Group CRUD lifecycle."""
|
|
15
|
+
|
|
16
|
+
def test_group_lifecycle(self, epmw_integration_config):
|
|
17
|
+
"""Create group → update → delete."""
|
|
18
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
19
|
+
|
|
20
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
21
|
+
# EPMW uses auto-auth via OAuth token on each request
|
|
22
|
+
|
|
23
|
+
# CREATE (returns just the group ID string)
|
|
24
|
+
group_name = unique_name("group")
|
|
25
|
+
group_id = client.create_group({
|
|
26
|
+
"name": group_name,
|
|
27
|
+
"description": "Integration test group",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# VERIFY exists
|
|
32
|
+
groups = client.list_groups()
|
|
33
|
+
assert any(g["id"] == group_id for g in groups), "Group not found"
|
|
34
|
+
|
|
35
|
+
# GET details
|
|
36
|
+
retrieved = client.get_group(group_id)
|
|
37
|
+
assert retrieved["name"] == group_name
|
|
38
|
+
|
|
39
|
+
# UPDATE (may fail with 405 if API doesn't support PUT)
|
|
40
|
+
new_desc = "Updated integration test group"
|
|
41
|
+
try:
|
|
42
|
+
client.update_group(group_id, {
|
|
43
|
+
"name": group_name,
|
|
44
|
+
"description": new_desc,
|
|
45
|
+
})
|
|
46
|
+
# VERIFY update
|
|
47
|
+
updated = client.get_group(group_id)
|
|
48
|
+
assert updated["description"] == new_desc
|
|
49
|
+
except Exception as e:
|
|
50
|
+
if "405" in str(e):
|
|
51
|
+
pass # API doesn't support update, skip verification
|
|
52
|
+
else:
|
|
53
|
+
raise
|
|
54
|
+
|
|
55
|
+
finally:
|
|
56
|
+
# DELETE (may fail with 405 if API doesn't support it)
|
|
57
|
+
try:
|
|
58
|
+
client.delete_group(group_id)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if "405" in str(e):
|
|
61
|
+
# API doesn't support delete, test passed for create/update
|
|
62
|
+
return
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
# VERIFY deleted
|
|
66
|
+
groups = client.list_groups()
|
|
67
|
+
assert not any(g["id"] == group_id for g in groups), "Group still exists"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.integration
|
|
71
|
+
class TestGroupWithPolicyLifecycle:
|
|
72
|
+
"""Test Group with Policy assignment lifecycle."""
|
|
73
|
+
|
|
74
|
+
def test_group_policy_assignment(self, epmw_integration_config, epmw_policy_id):
|
|
75
|
+
"""Create group → assign policy → verify → delete."""
|
|
76
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
77
|
+
|
|
78
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
79
|
+
# EPMW uses auto-auth via OAuth token
|
|
80
|
+
|
|
81
|
+
# CREATE group (returns just the group ID string)
|
|
82
|
+
group_name = unique_name("group")
|
|
83
|
+
group_id = client.create_group({
|
|
84
|
+
"name": group_name,
|
|
85
|
+
"description": "Policy assignment test",
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# ASSIGN policy to group (skip if API doesn't support it)
|
|
90
|
+
try:
|
|
91
|
+
client.assign_policy_to_group(group_id, epmw_policy_id)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
if "404" in str(e) or "405" in str(e):
|
|
94
|
+
pytest.skip(f"Policy assignment not supported: {e}")
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
# VERIFY assignment (get group and check policy)
|
|
98
|
+
retrieved = client.get_group(group_id)
|
|
99
|
+
# Policy assignment might be reflected in group data
|
|
100
|
+
assert retrieved is not None
|
|
101
|
+
|
|
102
|
+
finally:
|
|
103
|
+
# DELETE group (may fail with 405)
|
|
104
|
+
try:
|
|
105
|
+
client.delete_group(group_id)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass # Best effort cleanup
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.mark.integration
|
|
111
|
+
class TestComputerOperations:
|
|
112
|
+
"""Test Computer archive/unarchive operations.
|
|
113
|
+
|
|
114
|
+
Note: We can't create computers via API (they're agent-enrolled),
|
|
115
|
+
so we test archive/unarchive on existing computers if available.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def test_computer_archive_unarchive(self, epmw_integration_config, epmw_computer_id):
|
|
119
|
+
"""Archive computer → verify → unarchive → verify."""
|
|
120
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
121
|
+
|
|
122
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
123
|
+
# EPMW uses auto-auth via OAuth token
|
|
124
|
+
|
|
125
|
+
# Get initial state (field is 'archived' not 'isArchived')
|
|
126
|
+
computer = client.get_computer(epmw_computer_id)
|
|
127
|
+
initial_archived = computer.get("archived", False)
|
|
128
|
+
|
|
129
|
+
# Only test if not already archived
|
|
130
|
+
if initial_archived:
|
|
131
|
+
pytest.skip("Computer already archived, skipping test")
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
# ARCHIVE
|
|
135
|
+
client.archive_computer(epmw_computer_id)
|
|
136
|
+
|
|
137
|
+
# VERIFY archived (API may process async, skip if not immediate)
|
|
138
|
+
archived = client.get_computer(epmw_computer_id)
|
|
139
|
+
if not archived.get("archived", False):
|
|
140
|
+
pytest.skip("Archive operation may be async or not supported")
|
|
141
|
+
|
|
142
|
+
finally:
|
|
143
|
+
# UNARCHIVE (restore original state)
|
|
144
|
+
try:
|
|
145
|
+
client.unarchive_computer(epmw_computer_id)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass # Best effort
|
|
148
|
+
|
|
149
|
+
# VERIFY unarchived
|
|
150
|
+
restored = client.get_computer(epmw_computer_id)
|
|
151
|
+
assert not restored.get("archived", True), "Computer still archived"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.integration
|
|
155
|
+
class TestUserLifecycle:
|
|
156
|
+
"""Test EPMW User CRUD lifecycle.
|
|
157
|
+
|
|
158
|
+
Note: User creation may require specific permissions.
|
|
159
|
+
Tests will skip if permissions are insufficient.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def test_user_lifecycle(self, epmw_integration_config):
|
|
163
|
+
"""Create user → enable → disable → delete."""
|
|
164
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
165
|
+
|
|
166
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
167
|
+
# EPMW uses auto-auth via OAuth token
|
|
168
|
+
|
|
169
|
+
# CREATE user (may fail with 400/403 if not permitted)
|
|
170
|
+
username = unique_name("user")
|
|
171
|
+
try:
|
|
172
|
+
user = client.create_user({
|
|
173
|
+
"username": f"{username}@test.local",
|
|
174
|
+
"firstName": "Test",
|
|
175
|
+
"lastName": "User",
|
|
176
|
+
"email": f"{username}@example.com",
|
|
177
|
+
})
|
|
178
|
+
except Exception as e:
|
|
179
|
+
if "permission" in str(e).lower() or "400" in str(e) or "403" in str(e):
|
|
180
|
+
pytest.skip(f"User creation not permitted: {e}")
|
|
181
|
+
raise
|
|
182
|
+
|
|
183
|
+
user_id = user["id"]
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# VERIFY exists
|
|
187
|
+
users = client.list_users()
|
|
188
|
+
assert any(u["id"] == user_id for u in users), "User not found"
|
|
189
|
+
|
|
190
|
+
# DISABLE
|
|
191
|
+
client.disable_user(user_id)
|
|
192
|
+
|
|
193
|
+
# VERIFY disabled
|
|
194
|
+
disabled = client.get_user(user_id)
|
|
195
|
+
assert disabled.get("isDisabled", False), "User not disabled"
|
|
196
|
+
|
|
197
|
+
# ENABLE
|
|
198
|
+
client.enable_user(user_id)
|
|
199
|
+
|
|
200
|
+
# VERIFY enabled
|
|
201
|
+
enabled = client.get_user(user_id)
|
|
202
|
+
assert not enabled.get("isDisabled", True), "User still disabled"
|
|
203
|
+
|
|
204
|
+
finally:
|
|
205
|
+
# DELETE
|
|
206
|
+
try:
|
|
207
|
+
# Note: EPMW may not have delete_user, may need to disable instead
|
|
208
|
+
if hasattr(client, 'delete_user'):
|
|
209
|
+
client.delete_user(user_id)
|
|
210
|
+
else:
|
|
211
|
+
# Just disable if can't delete
|
|
212
|
+
client.disable_user(user_id)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass # Best effort cleanup
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.integration
|
|
218
|
+
class TestAdminRequestOperations:
|
|
219
|
+
"""Test Admin Request approval/denial.
|
|
220
|
+
|
|
221
|
+
Note: These tests require pending admin requests to exist.
|
|
222
|
+
They will skip if no requests are available.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def test_list_admin_requests(self, epmw_integration_config):
|
|
226
|
+
"""List admin requests (read-only test)."""
|
|
227
|
+
from bt_cli.epmw.client.base import EPMWClient
|
|
228
|
+
|
|
229
|
+
with EPMWClient(epmw_integration_config) as client:
|
|
230
|
+
# EPMW uses auto-auth via OAuth token
|
|
231
|
+
|
|
232
|
+
# LIST requests
|
|
233
|
+
requests = client.list_admin_requests()
|
|
234
|
+
|
|
235
|
+
# Just verify we can list (may be empty)
|
|
236
|
+
assert isinstance(requests, list)
|
|
237
|
+
|
|
238
|
+
# If there are pending requests, verify structure
|
|
239
|
+
if requests:
|
|
240
|
+
req = requests[0]
|
|
241
|
+
# Structure has nested fields like requestInfo, accessDecision
|
|
242
|
+
assert "requestInfo" in req or "id" in req
|