pipecatcloud 0.3.1__tar.gz → 0.4.0__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.
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/CHANGELOG.md +27 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/PKG-INFO +1 -1
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/deploy_utils.py +23 -3
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/api.py +0 -1
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/agent.py +8 -19
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/deploy.py +2 -8
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/docker.py +6 -1
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/config.py +5 -1
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/entry_point.py +10 -4
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/SOURCES.txt +2 -1
- pipecatcloud-0.4.0/tests/conftest.py +17 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_api_client.py +0 -2
- pipecatcloud-0.4.0/tests/test_config_file.py +48 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_krisp_viva.py +1 -9
- pipecatcloud-0.3.1/tests/test_managed_keys.py +0 -426
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/format.yml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.gitignore +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.pre-commit-config.yaml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/LICENSE +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/README.md +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pyproject.toml +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pytest.ini +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/scripts/fix-ruff.sh +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/scripts/pre-commit.sh +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/setup.cfg +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/build_utils.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/regions.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/build.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/constants.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/__init__.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_agent_stop.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_deployment_status.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_regions.py +0 -0
- {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/uv.lock +0 -0
|
@@ -5,6 +5,33 @@ All notable changes to **Pipecat Cloud** will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-03-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`--config-file` flag**: Specify an alternate deploy config file on any
|
|
13
|
+
command that uses a config file (`deploy`, `agent sessions`, `agent start`,
|
|
14
|
+
`agent stop`, `docker build-push`). Enables multi-environment workflows
|
|
15
|
+
without renaming files or setting environment variables.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Renamed `--config` to `--show-cli-config` for clarity — it displays internal
|
|
20
|
+
CLI state (credentials, active org), not deploy configuration.
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
|
|
24
|
+
- Removed `--enable-managed-keys` CLI option from `deploy` command.
|
|
25
|
+
|
|
26
|
+
- Removed `enable_managed_keys` from `pcc-deploy.toml` configuration.
|
|
27
|
+
|
|
28
|
+
- Removed "Managed Keys" status display from `agent status` output.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- CLI no longer crashes on a malformed credentials file. It warns and allows
|
|
33
|
+
`auth login` to run so the user can fix it.
|
|
34
|
+
|
|
8
35
|
## [0.3.1] - 2026-03-13
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -10,10 +10,13 @@ from datetime import datetime, timezone
|
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from typing import Callable, List, Optional
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
import toml
|
|
15
|
+
import typer
|
|
14
16
|
from attr import dataclass, field
|
|
15
17
|
from loguru import logger
|
|
16
18
|
|
|
19
|
+
from pipecatcloud.cli import PIPECAT_DEPLOY_CONFIG_PATH
|
|
17
20
|
from pipecatcloud.constants import KRISP_VIVA_MODELS, KrispVivaAudioFilter
|
|
18
21
|
from pipecatcloud.exception import ConfigFileError
|
|
19
22
|
|
|
@@ -280,7 +283,6 @@ class DeployConfigParams:
|
|
|
280
283
|
region: Optional[str] = None
|
|
281
284
|
scaling: ScalingParams = ScalingParams()
|
|
282
285
|
enable_krisp: bool = False
|
|
283
|
-
enable_managed_keys: bool = False
|
|
284
286
|
docker_config: dict = field(factory=dict)
|
|
285
287
|
build_config: BuildConfig = field(factory=BuildConfig) # Cloud build configuration
|
|
286
288
|
agent_profile: Optional[str] = None
|
|
@@ -303,7 +305,6 @@ class DeployConfigParams:
|
|
|
303
305
|
"region": self.region,
|
|
304
306
|
"scaling": self.scaling.to_dict() if self.scaling else None,
|
|
305
307
|
"enable_krisp": self.enable_krisp,
|
|
306
|
-
"enable_managed_keys": self.enable_managed_keys,
|
|
307
308
|
"docker_config": self.docker_config,
|
|
308
309
|
"build_config": self.build_config.to_dict() if self.build_config else None,
|
|
309
310
|
"agent_profile": self.agent_profile,
|
|
@@ -363,7 +364,6 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
|
|
|
363
364
|
"region",
|
|
364
365
|
"scaling",
|
|
365
366
|
"enable_krisp",
|
|
366
|
-
"enable_managed_keys",
|
|
367
367
|
"docker",
|
|
368
368
|
"build",
|
|
369
369
|
"agent_profile",
|
|
@@ -380,14 +380,34 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
|
|
|
380
380
|
raise ConfigFileError(str(e))
|
|
381
381
|
|
|
382
382
|
|
|
383
|
+
CONFIG_FILE_OPTION: Optional[str] = typer.Option(
|
|
384
|
+
None,
|
|
385
|
+
"--config-file",
|
|
386
|
+
help=f"Path to deploy config file (default: {PIPECAT_DEPLOY_CONFIG_PATH})",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
383
390
|
def with_deploy_config(func: Callable) -> Callable:
|
|
384
391
|
"""
|
|
385
392
|
Decorator that loads the deploy config file and injects it into the function.
|
|
386
393
|
If the config file exists, it will be loaded and passed to the function as `deploy_config`.
|
|
394
|
+
|
|
395
|
+
If the wrapped function receives a `config_file` kwarg (from a --config-file typer option),
|
|
396
|
+
it will override the default deploy config path before loading.
|
|
387
397
|
"""
|
|
388
398
|
|
|
389
399
|
@functools.wraps(func)
|
|
390
400
|
def wrapper(*args, **kwargs):
|
|
401
|
+
config_file = kwargs.pop("config_file", None)
|
|
402
|
+
if config_file:
|
|
403
|
+
import pipecatcloud.cli.config as config_module
|
|
404
|
+
|
|
405
|
+
if not os.path.exists(config_file):
|
|
406
|
+
from pipecatcloud._utils.console_utils import console
|
|
407
|
+
|
|
408
|
+
console.error(f"Deploy config file not found: {config_file}")
|
|
409
|
+
raise typer.Exit(1)
|
|
410
|
+
config_module.deploy_config_path = config_file
|
|
391
411
|
try:
|
|
392
412
|
deploy_config = load_deploy_config_file()
|
|
393
413
|
kwargs["deploy_config"] = deploy_config
|
|
@@ -336,7 +336,6 @@ class _API:
|
|
|
336
336
|
"maxAgents": deploy_config.scaling.max_agents,
|
|
337
337
|
},
|
|
338
338
|
"enableKrisp": deploy_config.enable_krisp,
|
|
339
|
-
"enableIntegratedKeysProxy": deploy_config.enable_managed_keys, # API expects this field name
|
|
340
339
|
"agentProfile": deploy_config.agent_profile,
|
|
341
340
|
"krispViva": {
|
|
342
341
|
"audioFilter": deploy_config.krisp_viva.audio_filter,
|
|
@@ -28,7 +28,11 @@ from pipecatcloud._utils.console_utils import (
|
|
|
28
28
|
format_duration,
|
|
29
29
|
format_timestamp,
|
|
30
30
|
)
|
|
31
|
-
from pipecatcloud._utils.deploy_utils import
|
|
31
|
+
from pipecatcloud._utils.deploy_utils import (
|
|
32
|
+
CONFIG_FILE_OPTION,
|
|
33
|
+
DeployConfigParams,
|
|
34
|
+
with_deploy_config,
|
|
35
|
+
)
|
|
32
36
|
from pipecatcloud._utils.regions import get_region_codes, validate_region
|
|
33
37
|
from pipecatcloud.cli import PIPECAT_CLI_NAME
|
|
34
38
|
from pipecatcloud.cli.api import API
|
|
@@ -206,24 +210,6 @@ async def status(
|
|
|
206
210
|
str(data.get("updatedAt", "N/A")),
|
|
207
211
|
)
|
|
208
212
|
|
|
209
|
-
# Check for Managed Keys status
|
|
210
|
-
# API returns integratedKeysProxy but we display as "Managed Keys"
|
|
211
|
-
integrated_keys = (
|
|
212
|
-
data.get("deployment", {})
|
|
213
|
-
.get("manifest", {})
|
|
214
|
-
.get("spec", {})
|
|
215
|
-
.get("integratedKeysProxy", {})
|
|
216
|
-
)
|
|
217
|
-
if isinstance(integrated_keys, dict):
|
|
218
|
-
integrated_keys_enabled = integrated_keys.get("enabled", False)
|
|
219
|
-
else:
|
|
220
|
-
integrated_keys_enabled = bool(integrated_keys)
|
|
221
|
-
|
|
222
|
-
deployment_table.add_row(
|
|
223
|
-
"[bold]Managed Keys:[/bold]",
|
|
224
|
-
"[green]Enabled[/green]" if integrated_keys_enabled else "[dim]Disabled[/dim]",
|
|
225
|
-
)
|
|
226
|
-
|
|
227
213
|
# Check for Krisp VIVA status (reverse-mapped by API)
|
|
228
214
|
krisp_viva = data.get("krispViva")
|
|
229
215
|
krisp_viva_status = "[dim]Disabled[/dim]"
|
|
@@ -346,6 +332,7 @@ async def status(
|
|
|
346
332
|
@with_deploy_config
|
|
347
333
|
async def sessions(
|
|
348
334
|
deploy_config=typer.Option(None, hidden=True),
|
|
335
|
+
config_file: Optional[str] = CONFIG_FILE_OPTION,
|
|
349
336
|
agent_name: str = typer.Argument(
|
|
350
337
|
None, help="Name of the agent to list sessions for e.g. 'my-agent'", show_default=False
|
|
351
338
|
),
|
|
@@ -781,6 +768,7 @@ async def deployments(
|
|
|
781
768
|
@with_deploy_config
|
|
782
769
|
async def start(
|
|
783
770
|
deploy_config=typer.Option(None, hidden=True),
|
|
771
|
+
config_file: Optional[str] = CONFIG_FILE_OPTION,
|
|
784
772
|
agent_name: str = typer.Argument(None, help="Name of the agent to start e.g. 'my-agent'"),
|
|
785
773
|
force: bool = typer.Option(
|
|
786
774
|
False,
|
|
@@ -947,6 +935,7 @@ async def start(
|
|
|
947
935
|
@with_deploy_config
|
|
948
936
|
async def stop(
|
|
949
937
|
deploy_config=typer.Option(None, hidden=True),
|
|
938
|
+
config_file: Optional[str] = CONFIG_FILE_OPTION,
|
|
950
939
|
agent_name: str = typer.Argument(None, help="Name of the agent e.g. 'my-agent'"),
|
|
951
940
|
session_id: str = typer.Option(
|
|
952
941
|
...,
|
|
@@ -28,6 +28,7 @@ from pipecatcloud._utils.build_utils import (
|
|
|
28
28
|
)
|
|
29
29
|
from pipecatcloud._utils.console_utils import console
|
|
30
30
|
from pipecatcloud._utils.deploy_utils import (
|
|
31
|
+
CONFIG_FILE_OPTION,
|
|
31
32
|
BuildConfig,
|
|
32
33
|
DeployConfigParams,
|
|
33
34
|
KrispVivaConfig,
|
|
@@ -482,6 +483,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
482
483
|
@with_deploy_config
|
|
483
484
|
async def deploy(
|
|
484
485
|
deploy_config=typer.Option(None, hidden=True),
|
|
486
|
+
config_file: Optional[str] = CONFIG_FILE_OPTION,
|
|
485
487
|
agent_name: str = typer.Argument(
|
|
486
488
|
None, help="Name of the agent to deploy e.g. 'my-agent'", show_default=False
|
|
487
489
|
),
|
|
@@ -534,12 +536,6 @@ def create_deploy_command(app: typer.Typer):
|
|
|
534
536
|
help="[DEPRECATED] Enable Krisp integration for this deployment. Use --krisp-viva-audio-filter instead",
|
|
535
537
|
rich_help_panel="Deployment Configuration",
|
|
536
538
|
),
|
|
537
|
-
managed_keys: bool = typer.Option(
|
|
538
|
-
False,
|
|
539
|
-
"--enable-managed-keys",
|
|
540
|
-
help="Enable Managed Keys for this deployment",
|
|
541
|
-
rich_help_panel="Deployment Configuration",
|
|
542
|
-
),
|
|
543
539
|
krisp_viva_audio_filter: str = typer.Option(
|
|
544
540
|
None,
|
|
545
541
|
"--krisp-viva-audio-filter",
|
|
@@ -656,7 +652,6 @@ def create_deploy_command(app: typer.Typer):
|
|
|
656
652
|
max_agents=max_agents if max_agents is not None else partial_config.scaling.max_agents,
|
|
657
653
|
)
|
|
658
654
|
partial_config.enable_krisp = krisp or partial_config.enable_krisp
|
|
659
|
-
partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
|
|
660
655
|
partial_config.agent_profile = profile or partial_config.agent_profile
|
|
661
656
|
|
|
662
657
|
# Override build config from CLI args
|
|
@@ -797,7 +792,6 @@ def create_deploy_command(app: typer.Typer):
|
|
|
797
792
|
f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}",
|
|
798
793
|
f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}",
|
|
799
794
|
f"[bold white]Krisp VIVA:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.krisp_viva.audio_filter else '[green]Enabled (' + partial_config.krisp_viva.audio_filter + ')[/green]'}",
|
|
800
|
-
f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}",
|
|
801
795
|
"\n[dim]Scaling configuration:[/dim]",
|
|
802
796
|
]
|
|
803
797
|
)
|
|
@@ -13,7 +13,11 @@ from rich.panel import Panel
|
|
|
13
13
|
|
|
14
14
|
from pipecatcloud._utils.async_utils import synchronizer
|
|
15
15
|
from pipecatcloud._utils.console_utils import console
|
|
16
|
-
from pipecatcloud._utils.deploy_utils import
|
|
16
|
+
from pipecatcloud._utils.deploy_utils import (
|
|
17
|
+
CONFIG_FILE_OPTION,
|
|
18
|
+
DeployConfigParams,
|
|
19
|
+
with_deploy_config,
|
|
20
|
+
)
|
|
17
21
|
from pipecatcloud.cli import PIPECAT_CLI_NAME
|
|
18
22
|
|
|
19
23
|
docker_cli = typer.Typer(
|
|
@@ -180,6 +184,7 @@ def _build_image_name(
|
|
|
180
184
|
@with_deploy_config
|
|
181
185
|
async def build_push(
|
|
182
186
|
deploy_config=typer.Option(None, hidden=True),
|
|
187
|
+
config_file: Optional[str] = CONFIG_FILE_OPTION,
|
|
183
188
|
agent_name: str = typer.Argument(
|
|
184
189
|
None, help="Name of the agent to build image for e.g. 'my-agent'", show_default=False
|
|
185
190
|
),
|
|
@@ -35,11 +35,12 @@ def version_callback(value: bool):
|
|
|
35
35
|
raise typer.Exit()
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def show_config_callback(value: bool):
|
|
39
39
|
if value:
|
|
40
40
|
from rich.pretty import pprint
|
|
41
41
|
|
|
42
42
|
from pipecatcloud._utils.deploy_utils import load_deploy_config_file
|
|
43
|
+
from pipecatcloud.cli.config import deploy_config_path
|
|
43
44
|
|
|
44
45
|
# Print local config
|
|
45
46
|
pprint(config.to_dict())
|
|
@@ -48,11 +49,11 @@ def config_callback(value: bool):
|
|
|
48
49
|
try:
|
|
49
50
|
deploy_config = load_deploy_config_file()
|
|
50
51
|
if deploy_config:
|
|
51
|
-
console.print("Deploy config [dim](
|
|
52
|
+
console.print(f"Deploy config [dim]({deploy_config_path})[/dim]:")
|
|
52
53
|
console.print_json(data=deploy_config.to_dict())
|
|
53
54
|
except ConfigFileError as e:
|
|
54
55
|
console.error(
|
|
55
|
-
f"Malformed
|
|
56
|
+
f"Malformed {deploy_config_path} - Please correct errors and try again.\n\n{e}"
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
raise typer.Exit()
|
|
@@ -71,7 +72,12 @@ entrypoint_cli_typer = typer.Typer(
|
|
|
71
72
|
def cli(
|
|
72
73
|
ctx: typer.Context,
|
|
73
74
|
_version: bool = typer.Option(None, "--version", callback=version_callback, help="CLI version"),
|
|
74
|
-
|
|
75
|
+
_show_cli_config: bool = typer.Option(
|
|
76
|
+
None,
|
|
77
|
+
"--show-cli-config",
|
|
78
|
+
callback=show_config_callback,
|
|
79
|
+
help="Show CLI internal configuration (credentials, active org)",
|
|
80
|
+
),
|
|
75
81
|
):
|
|
76
82
|
pass
|
|
77
83
|
|
|
@@ -51,11 +51,12 @@ src/pipecatcloud/cli/commands/regions.py
|
|
|
51
51
|
src/pipecatcloud/cli/commands/secrets.py
|
|
52
52
|
src/pipecatcloud/smallwebrtc/session_manager.py
|
|
53
53
|
tests/__init__.py
|
|
54
|
+
tests/conftest.py
|
|
54
55
|
tests/test_agent_sessions.py
|
|
55
56
|
tests/test_agent_stop.py
|
|
56
57
|
tests/test_api_client.py
|
|
58
|
+
tests/test_config_file.py
|
|
57
59
|
tests/test_deployment_status.py
|
|
58
60
|
tests/test_docker_commands.py
|
|
59
61
|
tests/test_krisp_viva.py
|
|
60
|
-
tests/test_managed_keys.py
|
|
61
62
|
tests/test_regions.py
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global test fixtures.
|
|
3
|
+
|
|
4
|
+
Sets PIPECAT_CONFIG_PATH to an isolated temp file so tests never
|
|
5
|
+
read or corrupt the real user credentials. Provides a fake token
|
|
6
|
+
and org so @requires_login passes through.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
|
|
12
|
+
# Must be set before any pipecatcloud module is imported, since
|
|
13
|
+
# config.py reads the credentials file at module load time.
|
|
14
|
+
_tmp = tempfile.NamedTemporaryFile(suffix=".toml", delete=False, mode="w")
|
|
15
|
+
_tmp.write('token = "test-token"\norg = "test-org"\n')
|
|
16
|
+
_tmp.close()
|
|
17
|
+
os.environ["PIPECAT_CONFIG_PATH"] = _tmp.name
|
|
@@ -260,7 +260,6 @@ class TestAPIRegionWithOtherParameters:
|
|
|
260
260
|
region="ap",
|
|
261
261
|
secret_set="my-secrets",
|
|
262
262
|
image_credentials="my-creds",
|
|
263
|
-
enable_managed_keys=True,
|
|
264
263
|
)
|
|
265
264
|
|
|
266
265
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
@@ -274,7 +273,6 @@ class TestAPIRegionWithOtherParameters:
|
|
|
274
273
|
assert payload["region"] == "ap"
|
|
275
274
|
assert payload["secretSet"] == "my-secrets"
|
|
276
275
|
assert payload["imagePullSecretSet"] == "my-creds"
|
|
277
|
-
assert payload["enableIntegratedKeysProxy"] is True
|
|
278
276
|
|
|
279
277
|
|
|
280
278
|
class TestAPIProperties:
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the --config-file flag behavior via @with_deploy_config.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
14
|
+
|
|
15
|
+
from pipecatcloud._utils.deploy_utils import with_deploy_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@with_deploy_config
|
|
19
|
+
def dummy_command(deploy_config=None, config_file=None):
|
|
20
|
+
"""Minimal command decorated with @with_deploy_config."""
|
|
21
|
+
return deploy_config
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestConfigFileFlag:
|
|
25
|
+
"""Test --config-file override behavior in @with_deploy_config."""
|
|
26
|
+
|
|
27
|
+
def test_loads_alternate_config_file(self):
|
|
28
|
+
"""--config-file should load the specified file instead of the default."""
|
|
29
|
+
with tempfile.NamedTemporaryFile(suffix=".toml", mode="w", delete=False) as f:
|
|
30
|
+
f.write('agent_name = "alt-agent"\n')
|
|
31
|
+
f.flush()
|
|
32
|
+
try:
|
|
33
|
+
result = dummy_command(config_file=f.name)
|
|
34
|
+
assert result is not None
|
|
35
|
+
assert result.agent_name == "alt-agent"
|
|
36
|
+
finally:
|
|
37
|
+
os.unlink(f.name)
|
|
38
|
+
|
|
39
|
+
def test_nonexistent_config_file_exits(self):
|
|
40
|
+
"""--config-file pointing to missing file should exit with error."""
|
|
41
|
+
with pytest.raises(typer.Exit):
|
|
42
|
+
dummy_command(config_file="/nonexistent/path.toml")
|
|
43
|
+
|
|
44
|
+
def test_no_config_file_uses_default(self):
|
|
45
|
+
"""Without --config-file, falls back to default pcc-deploy.toml behavior."""
|
|
46
|
+
result = dummy_command(config_file=None)
|
|
47
|
+
# No pcc-deploy.toml in test dir, so deploy_config should be None
|
|
48
|
+
assert result is None
|
|
@@ -132,7 +132,6 @@ class TestDeployConfigIntegration:
|
|
|
132
132
|
agent_name="test-agent",
|
|
133
133
|
image="test:latest",
|
|
134
134
|
enable_krisp=True,
|
|
135
|
-
enable_managed_keys=True,
|
|
136
135
|
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
137
136
|
)
|
|
138
137
|
|
|
@@ -143,7 +142,6 @@ class TestDeployConfigIntegration:
|
|
|
143
142
|
assert result["agent_name"] == "test-agent"
|
|
144
143
|
assert result["image"] == "test:latest"
|
|
145
144
|
assert result["enable_krisp"] is True
|
|
146
|
-
assert result["enable_managed_keys"] is True
|
|
147
145
|
assert result["krisp_viva"]["audio_filter"] == "tel"
|
|
148
146
|
|
|
149
147
|
|
|
@@ -237,7 +235,6 @@ class TestTOMLConfiguration:
|
|
|
237
235
|
agent_name = "test-agent"
|
|
238
236
|
image = "test:latest"
|
|
239
237
|
enable_krisp = true
|
|
240
|
-
enable_managed_keys = true
|
|
241
238
|
secret_set = "my-secrets"
|
|
242
239
|
|
|
243
240
|
[scaling]
|
|
@@ -257,7 +254,6 @@ class TestTOMLConfiguration:
|
|
|
257
254
|
assert config.agent_name == "test-agent"
|
|
258
255
|
assert config.image == "test:latest"
|
|
259
256
|
assert config.enable_krisp is True
|
|
260
|
-
assert config.enable_managed_keys is True
|
|
261
257
|
assert config.secret_set == "my-secrets"
|
|
262
258
|
assert config.scaling.min_agents == 2
|
|
263
259
|
assert config.scaling.max_agents == 10
|
|
@@ -550,7 +546,6 @@ class TestBackwardCompatibility:
|
|
|
550
546
|
agent_name = "legacy-agent"
|
|
551
547
|
image = "legacy:latest"
|
|
552
548
|
enable_krisp = true
|
|
553
|
-
enable_managed_keys = true
|
|
554
549
|
|
|
555
550
|
[scaling]
|
|
556
551
|
min_agents = 1
|
|
@@ -569,7 +564,6 @@ class TestBackwardCompatibility:
|
|
|
569
564
|
assert config is not None
|
|
570
565
|
assert config.agent_name == "legacy-agent"
|
|
571
566
|
assert config.enable_krisp is True
|
|
572
|
-
assert config.enable_managed_keys is True
|
|
573
567
|
assert config.krisp_viva.audio_filter is None
|
|
574
568
|
|
|
575
569
|
|
|
@@ -612,13 +606,12 @@ class TestErrorHandling:
|
|
|
612
606
|
assert result["krisp_viva"]["audio_filter"] == "tel"
|
|
613
607
|
|
|
614
608
|
def test_all_features_enabled_together(self):
|
|
615
|
-
"""Krisp
|
|
609
|
+
"""Krisp and Krisp VIVA can be enabled together."""
|
|
616
610
|
# Arrange
|
|
617
611
|
config = DeployConfigParams(
|
|
618
612
|
agent_name="test-agent",
|
|
619
613
|
image="test:latest",
|
|
620
614
|
enable_krisp=True,
|
|
621
|
-
enable_managed_keys=True,
|
|
622
615
|
krisp_viva=KrispVivaConfig(audio_filter="pro"),
|
|
623
616
|
)
|
|
624
617
|
|
|
@@ -627,7 +620,6 @@ class TestErrorHandling:
|
|
|
627
620
|
|
|
628
621
|
# Assert
|
|
629
622
|
assert result["enable_krisp"] is True
|
|
630
|
-
assert result["enable_managed_keys"] is True
|
|
631
623
|
assert result["krisp_viva"]["audio_filter"] == "pro"
|
|
632
624
|
|
|
633
625
|
|
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Unit tests for managed keys feature.
|
|
3
|
-
|
|
4
|
-
Tests follow AAA pattern and focus on behaviors/outcomes rather than implementation details.
|
|
5
|
-
Covers data model, TOML parsing, CLI commands, and API integration.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from unittest.mock import AsyncMock, patch
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
|
|
12
|
-
# Import from source, not installed package
|
|
13
|
-
import sys
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
17
|
-
|
|
18
|
-
from pipecatcloud._utils.deploy_utils import DeployConfigParams, load_deploy_config_file
|
|
19
|
-
from pipecatcloud.api import _API
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class TestDeployConfigDataModel:
|
|
23
|
-
"""Test the DeployConfigParams data model with managed keys support."""
|
|
24
|
-
|
|
25
|
-
def test_default_keys_is_disabled(self):
|
|
26
|
-
"""When not specified, managed keys should default to disabled."""
|
|
27
|
-
# Arrange & Act
|
|
28
|
-
config = DeployConfigParams()
|
|
29
|
-
|
|
30
|
-
# Assert
|
|
31
|
-
assert config.enable_managed_keys is False
|
|
32
|
-
|
|
33
|
-
def test_can_enable_keys(self):
|
|
34
|
-
"""Integrated keys can be explicitly enabled."""
|
|
35
|
-
# Arrange & Act
|
|
36
|
-
config = DeployConfigParams(enable_managed_keys=True)
|
|
37
|
-
|
|
38
|
-
# Assert
|
|
39
|
-
assert config.enable_managed_keys is True
|
|
40
|
-
|
|
41
|
-
def test_keys_included_in_dict_representation(self):
|
|
42
|
-
"""Dictionary representation should include managed keys setting."""
|
|
43
|
-
# Arrange
|
|
44
|
-
config = DeployConfigParams(agent_name="test-agent", enable_managed_keys=True)
|
|
45
|
-
|
|
46
|
-
# Act
|
|
47
|
-
result = config.to_dict()
|
|
48
|
-
|
|
49
|
-
# Assert
|
|
50
|
-
assert "enable_managed_keys" in result
|
|
51
|
-
assert result["enable_managed_keys"] is True
|
|
52
|
-
|
|
53
|
-
def test_keys_preserves_other_settings(self):
|
|
54
|
-
"""Adding managed keys should not affect other configuration settings."""
|
|
55
|
-
# Arrange
|
|
56
|
-
config = DeployConfigParams(
|
|
57
|
-
agent_name="test-agent",
|
|
58
|
-
image="test:latest",
|
|
59
|
-
enable_krisp=True,
|
|
60
|
-
enable_managed_keys=True,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# Act
|
|
64
|
-
result = config.to_dict()
|
|
65
|
-
|
|
66
|
-
# Assert
|
|
67
|
-
assert result["agent_name"] == "test-agent"
|
|
68
|
-
assert result["image"] == "test:latest"
|
|
69
|
-
assert result["enable_krisp"] is True
|
|
70
|
-
assert result["enable_managed_keys"] is True
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class TestTOMLConfiguration:
|
|
74
|
-
"""Test TOML configuration file parsing with managed keys."""
|
|
75
|
-
|
|
76
|
-
@pytest.fixture
|
|
77
|
-
def temp_config_file(self, tmp_path):
|
|
78
|
-
"""Create a temporary TOML config file."""
|
|
79
|
-
config_path = tmp_path / "pcc-deploy.toml"
|
|
80
|
-
return config_path
|
|
81
|
-
|
|
82
|
-
def test_loads_keys_proxy_from_toml_when_enabled(self, temp_config_file):
|
|
83
|
-
"""TOML file with managed keys enabled should be parsed correctly."""
|
|
84
|
-
# Arrange
|
|
85
|
-
config_content = """
|
|
86
|
-
agent_name = "test-agent"
|
|
87
|
-
image = "test:latest"
|
|
88
|
-
enable_managed_keys = true
|
|
89
|
-
"""
|
|
90
|
-
temp_config_file.write_text(config_content)
|
|
91
|
-
|
|
92
|
-
# Act
|
|
93
|
-
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
94
|
-
config = load_deploy_config_file()
|
|
95
|
-
|
|
96
|
-
# Assert
|
|
97
|
-
assert config is not None
|
|
98
|
-
assert config.enable_managed_keys is True
|
|
99
|
-
|
|
100
|
-
def test_loads_keys_proxy_from_toml_when_disabled(self, temp_config_file):
|
|
101
|
-
"""TOML file with managed keys explicitly disabled should be parsed correctly."""
|
|
102
|
-
# Arrange
|
|
103
|
-
config_content = """
|
|
104
|
-
agent_name = "test-agent"
|
|
105
|
-
image = "test:latest"
|
|
106
|
-
enable_managed_keys = false
|
|
107
|
-
"""
|
|
108
|
-
temp_config_file.write_text(config_content)
|
|
109
|
-
|
|
110
|
-
# Act
|
|
111
|
-
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
112
|
-
config = load_deploy_config_file()
|
|
113
|
-
|
|
114
|
-
# Assert
|
|
115
|
-
assert config is not None
|
|
116
|
-
assert config.enable_managed_keys is False
|
|
117
|
-
|
|
118
|
-
def test_defaults_keys_proxy_when_not_in_toml(self, temp_config_file):
|
|
119
|
-
"""TOML file without managed keys setting should default to disabled."""
|
|
120
|
-
# Arrange
|
|
121
|
-
config_content = """
|
|
122
|
-
agent_name = "test-agent"
|
|
123
|
-
image = "test:latest"
|
|
124
|
-
"""
|
|
125
|
-
temp_config_file.write_text(config_content)
|
|
126
|
-
|
|
127
|
-
# Act
|
|
128
|
-
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
129
|
-
config = load_deploy_config_file()
|
|
130
|
-
|
|
131
|
-
# Assert
|
|
132
|
-
assert config is not None
|
|
133
|
-
assert config.enable_managed_keys is False
|
|
134
|
-
|
|
135
|
-
def test_preserves_other_settings_with_keys_proxy(self, temp_config_file):
|
|
136
|
-
"""Keys proxy setting should not interfere with other TOML settings."""
|
|
137
|
-
# Arrange
|
|
138
|
-
config_content = """
|
|
139
|
-
agent_name = "test-agent"
|
|
140
|
-
image = "test:latest"
|
|
141
|
-
enable_krisp = true
|
|
142
|
-
enable_managed_keys = true
|
|
143
|
-
secret_set = "my-secrets"
|
|
144
|
-
|
|
145
|
-
[scaling]
|
|
146
|
-
min_agents = 2
|
|
147
|
-
max_agents = 10
|
|
148
|
-
"""
|
|
149
|
-
temp_config_file.write_text(config_content)
|
|
150
|
-
|
|
151
|
-
# Act
|
|
152
|
-
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
153
|
-
config = load_deploy_config_file()
|
|
154
|
-
|
|
155
|
-
# Assert
|
|
156
|
-
assert config.agent_name == "test-agent"
|
|
157
|
-
assert config.image == "test:latest"
|
|
158
|
-
assert config.enable_krisp is True
|
|
159
|
-
assert config.enable_managed_keys is True
|
|
160
|
-
assert config.secret_set == "my-secrets"
|
|
161
|
-
assert config.scaling.min_agents == 2
|
|
162
|
-
assert config.scaling.max_agents == 10
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class TestAPIIntegration:
|
|
166
|
-
"""Test API client integration with managed keys."""
|
|
167
|
-
|
|
168
|
-
@pytest.fixture
|
|
169
|
-
def api_client(self):
|
|
170
|
-
"""Create an API client instance."""
|
|
171
|
-
return _API(token="test-token", is_cli=True)
|
|
172
|
-
|
|
173
|
-
@pytest.mark.asyncio
|
|
174
|
-
async def test_deploy_payload_includes_keys_proxy(self, api_client):
|
|
175
|
-
"""Deploy payload should include enableIntegratedKeys field."""
|
|
176
|
-
# Arrange
|
|
177
|
-
config = DeployConfigParams(
|
|
178
|
-
agent_name="test-agent", image="test:latest", enable_managed_keys=True
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
182
|
-
mock_request.return_value = {"success": True}
|
|
183
|
-
|
|
184
|
-
# Act
|
|
185
|
-
await api_client._deploy(config, "test-org", update=False)
|
|
186
|
-
|
|
187
|
-
# Assert
|
|
188
|
-
mock_request.assert_called_once()
|
|
189
|
-
payload = mock_request.call_args[1]["json"]
|
|
190
|
-
assert "enableIntegratedKeysProxy" in payload
|
|
191
|
-
assert payload["enableIntegratedKeysProxy"] is True
|
|
192
|
-
|
|
193
|
-
@pytest.mark.asyncio
|
|
194
|
-
async def test_update_payload_includes_keys_proxy(self, api_client):
|
|
195
|
-
"""Update payload should include enableIntegratedKeys field."""
|
|
196
|
-
# Arrange
|
|
197
|
-
config = DeployConfigParams(
|
|
198
|
-
agent_name="test-agent", image="test:latest", enable_managed_keys=True
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
202
|
-
mock_request.return_value = {"success": True}
|
|
203
|
-
|
|
204
|
-
# Act
|
|
205
|
-
await api_client._deploy(config, "test-org", update=True)
|
|
206
|
-
|
|
207
|
-
# Assert
|
|
208
|
-
mock_request.assert_called_once()
|
|
209
|
-
payload = mock_request.call_args[1]["json"]
|
|
210
|
-
assert "enableIntegratedKeysProxy" in payload
|
|
211
|
-
assert payload["enableIntegratedKeysProxy"] is True
|
|
212
|
-
|
|
213
|
-
@pytest.mark.asyncio
|
|
214
|
-
async def test_disabled_keys_proxy_sends_false(self, api_client):
|
|
215
|
-
"""When managed keys is disabled, API should send false value."""
|
|
216
|
-
# Arrange
|
|
217
|
-
config = DeployConfigParams(
|
|
218
|
-
agent_name="test-agent", image="test:latest", enable_managed_keys=False
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
222
|
-
mock_request.return_value = {"success": True}
|
|
223
|
-
|
|
224
|
-
# Act
|
|
225
|
-
await api_client._deploy(config, "test-org", update=False)
|
|
226
|
-
|
|
227
|
-
# Assert
|
|
228
|
-
mock_request.assert_called_once()
|
|
229
|
-
payload = mock_request.call_args[1]["json"]
|
|
230
|
-
assert "enableIntegratedKeysProxy" in payload
|
|
231
|
-
assert payload["enableIntegratedKeysProxy"] is False
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class TestAgentStatusDisplay:
|
|
235
|
-
"""Test agent status command displays managed keys status."""
|
|
236
|
-
|
|
237
|
-
@pytest.fixture
|
|
238
|
-
def mock_api(self):
|
|
239
|
-
"""Mock the API agent method."""
|
|
240
|
-
with patch("pipecatcloud.cli.commands.agent.API") as mock_api:
|
|
241
|
-
yield mock_api
|
|
242
|
-
|
|
243
|
-
def test_displays_enabled_keys_proxy(self, mock_api, capsys):
|
|
244
|
-
"""Agent status should display when managed keys is enabled."""
|
|
245
|
-
# Arrange
|
|
246
|
-
from pipecatcloud.cli.commands.agent import status
|
|
247
|
-
|
|
248
|
-
mock_api.agent = AsyncMock(
|
|
249
|
-
return_value=(
|
|
250
|
-
{
|
|
251
|
-
"ready": True,
|
|
252
|
-
"deployment": {
|
|
253
|
-
"manifest": {"spec": {"integratedKeysProxy": {"enabled": True}}}
|
|
254
|
-
},
|
|
255
|
-
"autoScaling": {
|
|
256
|
-
"minReplicas": 0,
|
|
257
|
-
"maxReplicas": 1,
|
|
258
|
-
}, # Avoid undefined variable bug
|
|
259
|
-
},
|
|
260
|
-
None,
|
|
261
|
-
)
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
# Act
|
|
265
|
-
status(agent_name="test-agent", organization="test-org")
|
|
266
|
-
|
|
267
|
-
# Assert
|
|
268
|
-
captured = capsys.readouterr()
|
|
269
|
-
assert "Managed Keys" in captured.out
|
|
270
|
-
assert "Enabled" in captured.out
|
|
271
|
-
|
|
272
|
-
def test_displays_disabled_keys_proxy(self, mock_api, capsys):
|
|
273
|
-
"""Agent status should display when managed keys is disabled."""
|
|
274
|
-
# Arrange
|
|
275
|
-
from pipecatcloud.cli.commands.agent import status
|
|
276
|
-
|
|
277
|
-
mock_api.agent = AsyncMock(
|
|
278
|
-
return_value=(
|
|
279
|
-
{
|
|
280
|
-
"ready": True,
|
|
281
|
-
"deployment": {"manifest": {"spec": {}}},
|
|
282
|
-
"autoScaling": {
|
|
283
|
-
"minReplicas": 0,
|
|
284
|
-
"maxReplicas": 1,
|
|
285
|
-
}, # Avoid undefined variable bug
|
|
286
|
-
},
|
|
287
|
-
None,
|
|
288
|
-
)
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
# Act
|
|
292
|
-
status(agent_name="test-agent", organization="test-org")
|
|
293
|
-
|
|
294
|
-
# Assert
|
|
295
|
-
captured = capsys.readouterr()
|
|
296
|
-
assert "Managed Keys" in captured.out
|
|
297
|
-
assert "Disabled" in captured.out
|
|
298
|
-
|
|
299
|
-
def test_handles_boolean_keys_proxy_value(self, mock_api, capsys):
|
|
300
|
-
"""Agent status should handle boolean value for backward compatibility."""
|
|
301
|
-
# Arrange
|
|
302
|
-
from pipecatcloud.cli.commands.agent import status
|
|
303
|
-
|
|
304
|
-
mock_api.agent = AsyncMock(
|
|
305
|
-
return_value=(
|
|
306
|
-
{
|
|
307
|
-
"ready": True,
|
|
308
|
-
"deployment": {"manifest": {"spec": {"integratedKeysProxy": True}}},
|
|
309
|
-
"autoScaling": {
|
|
310
|
-
"minReplicas": 0,
|
|
311
|
-
"maxReplicas": 1,
|
|
312
|
-
}, # Avoid undefined variable bug
|
|
313
|
-
},
|
|
314
|
-
None,
|
|
315
|
-
)
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
# Act
|
|
319
|
-
status(agent_name="test-agent", organization="test-org")
|
|
320
|
-
|
|
321
|
-
# Assert
|
|
322
|
-
captured = capsys.readouterr()
|
|
323
|
-
assert "Managed Keys" in captured.out
|
|
324
|
-
assert "Enabled" in captured.out
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
class TestBackwardCompatibility:
|
|
328
|
-
"""Test backward compatibility with existing deployments."""
|
|
329
|
-
|
|
330
|
-
def test_existing_config_without_keys_proxy_works(self):
|
|
331
|
-
"""Existing configs without managed keys field should work unchanged."""
|
|
332
|
-
# Arrange
|
|
333
|
-
config = DeployConfigParams(agent_name="test-agent", image="test:latest", enable_krisp=True)
|
|
334
|
-
|
|
335
|
-
# Act
|
|
336
|
-
result = config.to_dict()
|
|
337
|
-
|
|
338
|
-
# Assert
|
|
339
|
-
assert result["enable_krisp"] is True
|
|
340
|
-
assert result["enable_managed_keys"] is False
|
|
341
|
-
|
|
342
|
-
def test_api_response_without_keys_proxy_doesnt_crash(self):
|
|
343
|
-
"""Agent status should handle API responses without managed keys field."""
|
|
344
|
-
# Arrange
|
|
345
|
-
data = {"ready": True, "deployment": {"manifest": {"spec": {"image": "test:latest"}}}}
|
|
346
|
-
|
|
347
|
-
# Act - simulate the logic from agent.py
|
|
348
|
-
integrated_keys = (
|
|
349
|
-
data.get("deployment", {})
|
|
350
|
-
.get("manifest", {})
|
|
351
|
-
.get("spec", {})
|
|
352
|
-
.get("integratedKeysProxy", {})
|
|
353
|
-
)
|
|
354
|
-
if isinstance(integrated_keys, dict):
|
|
355
|
-
keys_proxy_enabled = integrated_keys.get("enabled", False)
|
|
356
|
-
else:
|
|
357
|
-
keys_proxy_enabled = bool(integrated_keys)
|
|
358
|
-
|
|
359
|
-
# Assert
|
|
360
|
-
assert keys_proxy_enabled is False
|
|
361
|
-
|
|
362
|
-
@pytest.fixture
|
|
363
|
-
def temp_legacy_config(self, tmp_path):
|
|
364
|
-
"""Create a legacy TOML config without managed keys."""
|
|
365
|
-
config_path = tmp_path / "pcc-deploy.toml"
|
|
366
|
-
config_content = """
|
|
367
|
-
agent_name = "legacy-agent"
|
|
368
|
-
image = "legacy:latest"
|
|
369
|
-
enable_krisp = true
|
|
370
|
-
|
|
371
|
-
[scaling]
|
|
372
|
-
min_instances = 1
|
|
373
|
-
max_instances = 5
|
|
374
|
-
"""
|
|
375
|
-
config_path.write_text(config_content)
|
|
376
|
-
return config_path
|
|
377
|
-
|
|
378
|
-
def test_legacy_toml_loads_with_defaults(self, temp_legacy_config):
|
|
379
|
-
"""Legacy TOML files should load with managed keys defaulting to disabled."""
|
|
380
|
-
# Arrange & Act
|
|
381
|
-
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_legacy_config)):
|
|
382
|
-
config = load_deploy_config_file()
|
|
383
|
-
|
|
384
|
-
# Assert
|
|
385
|
-
assert config is not None
|
|
386
|
-
assert config.agent_name == "legacy-agent"
|
|
387
|
-
assert config.enable_krisp is True
|
|
388
|
-
assert config.enable_managed_keys is False
|
|
389
|
-
assert config.scaling.min_agents == 1 # Converted from min_instances
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
class TestErrorHandling:
|
|
393
|
-
"""Test error handling and edge cases."""
|
|
394
|
-
|
|
395
|
-
@pytest.mark.asyncio
|
|
396
|
-
async def test_api_error_during_deploy_with_keys_proxy(self):
|
|
397
|
-
"""API errors during deployment should be handled properly."""
|
|
398
|
-
# Arrange
|
|
399
|
-
api = _API(token="test-token", is_cli=True)
|
|
400
|
-
config = DeployConfigParams(
|
|
401
|
-
agent_name="test-agent", image="test:latest", enable_managed_keys=True
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
with patch.object(api, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
405
|
-
mock_request.side_effect = Exception("Network error")
|
|
406
|
-
|
|
407
|
-
# Act & Assert
|
|
408
|
-
with pytest.raises(Exception, match="Network error"):
|
|
409
|
-
await api._deploy(config, "test-org", update=False)
|
|
410
|
-
|
|
411
|
-
def test_simultaneous_krisp_and_keys_proxy(self):
|
|
412
|
-
"""Both Krisp and Keys Proxy can be enabled simultaneously."""
|
|
413
|
-
# Arrange
|
|
414
|
-
config = DeployConfigParams(
|
|
415
|
-
agent_name="test-agent",
|
|
416
|
-
image="test:latest",
|
|
417
|
-
enable_krisp=True,
|
|
418
|
-
enable_managed_keys=True,
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
# Act
|
|
422
|
-
result = config.to_dict()
|
|
423
|
-
|
|
424
|
-
# Assert
|
|
425
|
-
assert result["enable_krisp"] is True
|
|
426
|
-
assert result["enable_managed_keys"] is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|