zen-cli 0.1.4__tar.gz → 0.1.6__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.
- {zen_cli-0.1.4 → zen_cli-0.1.6}/PKG-INFO +2 -2
- {zen_cli-0.1.4 → zen_cli-0.1.6}/pyproject.toml +9 -3
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/deployment/create.py +5 -1
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/cmd_util.py +1 -1
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/logger.py +1 -1
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/auth.py +19 -10
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/csp.py +2 -2
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/ez_client.py +1 -1
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/login_support.py +3 -0
- zen_cli-0.1.6/src/zen_cli/support/test_utils.py +155 -0
- zen_cli-0.1.6/tests/blackbox/__init__.py +0 -0
- zen_cli-0.1.6/tests/blackbox/test_api.py +9 -0
- zen_cli-0.1.6/tests/blackbox/test_auth.py +20 -0
- zen_cli-0.1.6/tests/blackbox/test_completion.py +8 -0
- zen_cli-0.1.6/tests/blackbox/test_context.py +17 -0
- zen_cli-0.1.6/tests/blackbox/test_credential.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_deployment.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_entitlement.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_errors.py +88 -0
- zen_cli-0.1.6/tests/blackbox/test_group.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_io.py +88 -0
- zen_cli-0.1.6/tests/blackbox/test_login.py +27 -0
- zen_cli-0.1.6/tests/blackbox/test_logout.py +6 -0
- zen_cli-0.1.6/tests/blackbox/test_mcp.py +8 -0
- zen_cli-0.1.6/tests/blackbox/test_network.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_org.py +23 -0
- zen_cli-0.1.6/tests/blackbox/test_pool.py +33 -0
- zen_cli-0.1.6/tests/blackbox/test_profile.py +55 -0
- zen_cli-0.1.6/tests/blackbox/test_provider.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_root.py +14 -0
- zen_cli-0.1.6/tests/blackbox/test_sag.py +11 -0
- zen_cli-0.1.6/tests/blackbox/test_task.py +8 -0
- zen_cli-0.1.6/tests/blackbox/test_vm.py +8 -0
- zen_cli-0.1.6/tests/blackbox/util.py +31 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/tests/test_device_login.py +43 -1
- {zen_cli-0.1.4 → zen_cli-0.1.6}/.gitignore +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/LICENSE +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/README.md +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/__main__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cli.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/activity/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/activity/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/activity/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/api.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/auth/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/auth/details.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/auth/refresh.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/auth/status.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/auth/token.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/completion.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/credential/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/credential/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/credential/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/credential/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/credential/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/deployment/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/deployment/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/deployment/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/deployment/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/entitlement/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/entitlement/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/entitlement/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/entitlement/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/entitlement/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/group/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/group/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/group/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/group/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/group/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/login.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/logout.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/mcp.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/network/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/network/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/network/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/network/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/network/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/notification/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/notification/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/notification/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/org/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/org/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/org/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/org/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/org/use.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/pool/use.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/profile/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/profile/init.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/provider/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/provider/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/provider/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/provider/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/provider/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/sag/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/sag/create.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/sag/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/sag/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/sag/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/cancel.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/duplicate.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/log.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/namespaces.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/use.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/task/wait.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/upgrade.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/delete.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/get.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/list.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/poweroff.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/poweron.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/restart.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/shutdown.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/use.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/cmds/vm/wait.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/config/default.yaml +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/config/int.yaml +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/config/prod.yaml +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/config/stg.yaml +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/_init.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/built_in_cmds/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/built_in_cmds/_ut.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/built_in_cmds/context.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/built_in_cmds/profile.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/cli_options.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/cli_processor.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/config.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/context.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/data_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/dispatcher.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/extension.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/fn_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/fstore.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/jsondot.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/profile.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/profile_store.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/recent.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/state.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/telemetry.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/template_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/timeutil.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/ctxp/var_template.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/activity.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/api.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/credential.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/deployment.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/entitlement.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/group.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/network.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/notification.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/org.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/pool.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/provider.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/sag.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/task.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/resources/vm.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/mcp/server.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/activity.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/api.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/broker/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/broker/entitlement.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/broker/pool_group.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/network.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/pool.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/provider.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/sag.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/cockpit/vm.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/credential.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/deployment.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/notification.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/org.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/service/task.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/__init__.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/cli_options.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/client_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/init.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/mdc.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/payload_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/requtil.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/utils.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/sglib/zds_client.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/auth.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/payload.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/profile.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/task.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/use_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/support/vm.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/exec_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/exit.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/param_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/patch_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/query_util.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/readonly.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/src/zen_cli/util/versions.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/tests/test_auth_status.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/tests/test_cli.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/tests/test_mcp.py +0 -0
- {zen_cli-0.1.4 → zen_cli-0.1.6}/tests/test_payload.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: zen-cli
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: CLI for Zen Desktop Service. Designed for AI and Human.
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/zen-cli/
|
|
6
6
|
Author: zen-cli authors
|
|
7
7
|
License-Expression: MIT
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "zen-cli"
|
|
7
|
-
version = "0.1.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.1.6"
|
|
8
|
+
description = "CLI for Zen Desktop Service. Designed for AI and Human."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
@@ -44,7 +44,7 @@ include = ["src/zen_cli", "tests", "README.md", "LICENSE"]
|
|
|
44
44
|
target-version = "py312"
|
|
45
45
|
line-length = 120
|
|
46
46
|
src = ["src"]
|
|
47
|
-
exclude = ["cli-ref"]
|
|
47
|
+
exclude = ["cli-ref", "src/zen_cli/support/test_utils2.py"]
|
|
48
48
|
|
|
49
49
|
[tool.ruff.lint]
|
|
50
50
|
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
@@ -53,6 +53,7 @@ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
|
53
53
|
python_version = "3.12"
|
|
54
54
|
strict = true
|
|
55
55
|
files = ["src"]
|
|
56
|
+
exclude = ["test_utils2\\.py"]
|
|
56
57
|
|
|
57
58
|
[[tool.mypy.overrides]]
|
|
58
59
|
module = ["mcp", "mcp.*"]
|
|
@@ -67,9 +68,14 @@ module = [
|
|
|
67
68
|
"yaml",
|
|
68
69
|
"httpx2",
|
|
69
70
|
"httpx2.*",
|
|
71
|
+
"coloredlogs",
|
|
70
72
|
]
|
|
71
73
|
ignore_missing_imports = true
|
|
72
74
|
|
|
73
75
|
[[tool.mypy.overrides]]
|
|
74
76
|
module = ["zen_cli.cmds", "zen_cli.cmds.*", "zen_cli.ctxp", "zen_cli.ctxp.*"]
|
|
75
77
|
disallow_untyped_decorators = false
|
|
78
|
+
|
|
79
|
+
[[tool.mypy.overrides]]
|
|
80
|
+
module = ["zen_cli.service.task"]
|
|
81
|
+
disallow_subclassing_any = false
|
|
@@ -12,4 +12,8 @@ from zen_cli.support.payload import payload_options, run_create
|
|
|
12
12
|
@payload_options
|
|
13
13
|
def create(org: str, data_arg: str | None, json_arg: str | None) -> Any:
|
|
14
14
|
"""Create an express deployment from a JSON payload."""
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
def _create(payload: Any) -> Any:
|
|
17
|
+
return deployment.create(payload, org_id=cli.get_org_id(org))
|
|
18
|
+
|
|
19
|
+
return run_create("deployment", _create, data_arg, json_arg)
|
|
@@ -40,7 +40,7 @@ def subprocess_args(include_stdout: bool = True) -> dict[str, Any]:
|
|
|
40
40
|
# when run from Pyinstaller with the ``--noconsole`` option. Avoid this
|
|
41
41
|
# distraction.
|
|
42
42
|
si = subprocess.STARTUPINFO()
|
|
43
|
-
si.dwFlags |= subprocess
|
|
43
|
+
si.dwFlags |= getattr(subprocess, "STARTF_USESHOWWINDOW", 1)
|
|
44
44
|
# Windows doesn't search the path by default. Pass it an environment so
|
|
45
45
|
# it will.
|
|
46
46
|
env = os.environ
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
from logging.handlers import RotatingFileHandler
|
|
6
6
|
from typing import Any, ClassVar, TextIO
|
|
7
7
|
|
|
8
|
-
import coloredlogs
|
|
8
|
+
import coloredlogs
|
|
9
9
|
|
|
10
10
|
LOG_FORMAT_SIMPLE = "%(levelname).4s %(asctime)s %(name)-16s %(message)s"
|
|
11
11
|
LOG_FORMAT_LONG = (
|
|
@@ -54,6 +54,8 @@ def refresh_oauth_token(
|
|
|
54
54
|
refresh_token = old_oauth_token.get("refresh_token")
|
|
55
55
|
if not refresh_token:
|
|
56
56
|
raise Exception("CSP auth refresh failed: missing refresh_token")
|
|
57
|
+
if not client_id:
|
|
58
|
+
client_id = old_oauth_token.get("client_id")
|
|
57
59
|
if not client_id:
|
|
58
60
|
from .login_support import identify_client_id
|
|
59
61
|
|
|
@@ -64,16 +66,23 @@ def refresh_oauth_token(
|
|
|
64
66
|
"refresh_token": refresh_token,
|
|
65
67
|
"client_id": client_id,
|
|
66
68
|
}
|
|
69
|
+
post_kwargs: dict[str, Any] = {
|
|
70
|
+
"headers": {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"},
|
|
71
|
+
"timeout": 20,
|
|
72
|
+
}
|
|
67
73
|
if client_secret:
|
|
68
74
|
data["client_secret"] = client_secret
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
# Confidential clients only. Native/device apps are OIDC_AUTH_METHOD_TYPE_NONE;
|
|
76
|
+
# HTTP Basic with an empty secret makes ZITADEL reject the refresh as invalid_client.
|
|
77
|
+
post_kwargs["auth"] = (client_id, client_secret)
|
|
78
|
+
resp = httpx2.post(url, data=data, **post_kwargs)
|
|
79
|
+
if not resp.is_success:
|
|
80
|
+
try:
|
|
81
|
+
body = resp.json()
|
|
82
|
+
desc = body.get("error_description") or body.get("error") or resp.text
|
|
83
|
+
except Exception:
|
|
84
|
+
desc = resp.text or str(resp.status_code)
|
|
85
|
+
raise Exception(f"CSP auth refresh failed: {desc}")
|
|
77
86
|
new_token = resp.json()
|
|
78
87
|
log.debug(f"New auth token: {new_token}")
|
|
79
88
|
if not new_token or not new_token.get("access_token"):
|
|
@@ -193,7 +202,7 @@ def _populate_token_with_cache(auth_config: Any, force_refresh: bool = False, ve
|
|
|
193
202
|
print("Provider: csp.")
|
|
194
203
|
try:
|
|
195
204
|
csp_url = _cfg_get(auth_config, "url")
|
|
196
|
-
client_id = _cfg_get(auth_config, "clientId", "client_id")
|
|
205
|
+
client_id = token.get("client_id") or _cfg_get(auth_config, "clientId", "client_id")
|
|
197
206
|
if not client_id and _cfg_get(auth_config, "device"):
|
|
198
207
|
from .login_support import identify_device_client_id
|
|
199
208
|
|
|
@@ -205,7 +214,7 @@ def _populate_token_with_cache(auth_config: Any, force_refresh: bool = False, ve
|
|
|
205
214
|
client_secret=_cfg_get(auth_config, "clientSecret", "client_secret"),
|
|
206
215
|
)
|
|
207
216
|
except Exception as e:
|
|
208
|
-
log.
|
|
217
|
+
log.warning("Failed to refresh OAuth token: %s", e)
|
|
209
218
|
token = None
|
|
210
219
|
else:
|
|
211
220
|
if verbose:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, cast
|
|
4
4
|
|
|
5
5
|
import httpx2
|
|
6
6
|
|
|
@@ -103,7 +103,7 @@ class CspClient:
|
|
|
103
103
|
"Accept": "application/json",
|
|
104
104
|
}
|
|
105
105
|
# <no org id for this API>
|
|
106
|
-
resp = self._client.post("/oauth/v2/token", headers=headers, data=f"api_token={api_token}")
|
|
106
|
+
resp = self._client.post("/oauth/v2/token", headers=headers, data=cast(Any, f"api_token={api_token}"))
|
|
107
107
|
self._oauth_token = resp.json()
|
|
108
108
|
return self._oauth_token
|
|
109
109
|
|
|
@@ -148,7 +148,7 @@ def _parse_resp(resp: httpx2.Response) -> Any:
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def _is_404(e: httpx2.HTTPStatusError) -> bool:
|
|
151
|
-
return e.response.status_code == 404
|
|
151
|
+
return bool(e.response.status_code == 404)
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
def on404ReturnNone(func: Callable[..., Any]) -> Any:
|
|
@@ -205,6 +205,8 @@ def do_oauth2_pkce(csp_url: str, client_id: str, org_id: str) -> Any:
|
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
log.debug("token: %s", token)
|
|
208
|
+
if isinstance(token, dict):
|
|
209
|
+
token.setdefault("client_id", client_id)
|
|
208
210
|
return token
|
|
209
211
|
|
|
210
212
|
|
|
@@ -295,6 +297,7 @@ def poll_device_access_token(
|
|
|
295
297
|
token = resp.json()
|
|
296
298
|
if not isinstance(token, dict) or not token.get("access_token"):
|
|
297
299
|
raise CtxpException("Device-code login failed: token response had no access_token.")
|
|
300
|
+
token.setdefault("client_id", client_id)
|
|
298
301
|
return token
|
|
299
302
|
err, desc = _oauth_error(resp)
|
|
300
303
|
if err == "authorization_pending":
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
from unittest import TestCase
|
|
10
|
+
|
|
11
|
+
_SRC_DIR = str(Path(__file__).resolve().parents[2])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _zen_prefix() -> str:
|
|
15
|
+
exe_dir = Path(sys.executable).parent
|
|
16
|
+
for candidate in (
|
|
17
|
+
exe_dir / "Scripts" / "zen.exe",
|
|
18
|
+
exe_dir / "Scripts" / "zen",
|
|
19
|
+
exe_dir / "zen.exe",
|
|
20
|
+
exe_dir / "zen",
|
|
21
|
+
):
|
|
22
|
+
if candidate.is_file():
|
|
23
|
+
return f'"{candidate}"'
|
|
24
|
+
return f'"{sys.executable}" -m zen_cli'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _zen_cmd(cmd: str) -> str:
|
|
28
|
+
"""Rewrite a `zen ...` command so tests work when `zen` is not on PATH."""
|
|
29
|
+
if cmd == "zen" or cmd.startswith("zen "):
|
|
30
|
+
return _zen_prefix() + cmd[3:]
|
|
31
|
+
return cmd
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _test_env() -> dict[str, str]:
|
|
35
|
+
env = os.environ.copy()
|
|
36
|
+
env["ZEN_CLI_TELEMETRY"] = "false"
|
|
37
|
+
env["HCS_CLI_CHECK_UPGRADE"] = "false"
|
|
38
|
+
existing = env.get("PYTHONPATH", "")
|
|
39
|
+
env["PYTHONPATH"] = _SRC_DIR if not existing else _SRC_DIR + os.pathsep + existing
|
|
40
|
+
return env
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _run(cmd: str, stdin_payload: str | None = None) -> tuple[str, str, int]:
|
|
44
|
+
env = _test_env()
|
|
45
|
+
p = subprocess.run(
|
|
46
|
+
_zen_cmd(cmd),
|
|
47
|
+
input="" if stdin_payload is None else stdin_payload,
|
|
48
|
+
shell=True,
|
|
49
|
+
text=True,
|
|
50
|
+
check=False,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
env=env,
|
|
53
|
+
)
|
|
54
|
+
return p.stdout, p.stderr, p.returncode
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_test_profile_name = "__ctxp_ut"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_TEST_PROFILE = {
|
|
61
|
+
"description": "unit-test",
|
|
62
|
+
"readonly": False,
|
|
63
|
+
"csp": {"url": "https://csp.example.invalid"},
|
|
64
|
+
"zds": {"mdcs": {"ut": {"url": "https://zds.example.invalid"}}},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _create_test_profile() -> None:
|
|
69
|
+
p = subprocess.Popen(
|
|
70
|
+
_zen_cmd(f"zen profile create {_test_profile_name}"),
|
|
71
|
+
shell=True,
|
|
72
|
+
text=True,
|
|
73
|
+
stdin=subprocess.PIPE,
|
|
74
|
+
stdout=subprocess.PIPE,
|
|
75
|
+
stderr=subprocess.PIPE,
|
|
76
|
+
env=_test_env(),
|
|
77
|
+
)
|
|
78
|
+
p.communicate(input=json.dumps(_TEST_PROFILE))
|
|
79
|
+
p.wait()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _delete_test_profile() -> None:
|
|
83
|
+
_run(f"zen profile delete {_test_profile_name} -y")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_profile_ready = False
|
|
87
|
+
_prev_profile: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _restore_profiles() -> None:
|
|
91
|
+
_delete_test_profile()
|
|
92
|
+
if _prev_profile and _prev_profile != _test_profile_name:
|
|
93
|
+
_run(f"zen profile use {_prev_profile}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _ensure_test_profile() -> None:
|
|
97
|
+
global _profile_ready, _prev_profile
|
|
98
|
+
if _profile_ready:
|
|
99
|
+
return
|
|
100
|
+
stdout, _, code = _run("zen profile name")
|
|
101
|
+
_prev_profile = stdout.strip().strip('"') if code == 0 else None
|
|
102
|
+
_delete_test_profile()
|
|
103
|
+
_create_test_profile()
|
|
104
|
+
atexit.register(_restore_profiles)
|
|
105
|
+
_profile_ready = True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CliTest(TestCase):
|
|
109
|
+
@classmethod
|
|
110
|
+
def setUpClass(cls) -> None:
|
|
111
|
+
_ensure_test_profile()
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def NON_EMPTY_STRING(cls, data: Any) -> None:
|
|
115
|
+
if data is None or len(data) == 0:
|
|
116
|
+
raise Exception("Expect non-empty output, but got empty.")
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def NON_EMPTY_JSON(cls, data: Any) -> None:
|
|
120
|
+
if data is None or len(data) == 0:
|
|
121
|
+
raise Exception("Expect non-empty json, but got empty.")
|
|
122
|
+
d = json.loads(data)
|
|
123
|
+
if len(d) == 0:
|
|
124
|
+
raise Exception("Expect non-empty json, but got empty dict.")
|
|
125
|
+
|
|
126
|
+
def verify(
|
|
127
|
+
self,
|
|
128
|
+
cmd: str,
|
|
129
|
+
verify_output: Callable[[str], None] | str | None = None,
|
|
130
|
+
expected_return_codes: list[int] | int | None = None,
|
|
131
|
+
stdin_payload: str | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
stdout, stderr, returncode = _run(cmd, stdin_payload)
|
|
134
|
+
try:
|
|
135
|
+
if expected_return_codes is None:
|
|
136
|
+
codes = [0]
|
|
137
|
+
elif isinstance(expected_return_codes, int):
|
|
138
|
+
codes = [expected_return_codes]
|
|
139
|
+
else:
|
|
140
|
+
codes = expected_return_codes
|
|
141
|
+
self.assertIn(returncode, codes, "Invalid return code.")
|
|
142
|
+
|
|
143
|
+
combined_output = stdout or ""
|
|
144
|
+
if stderr:
|
|
145
|
+
combined_output = f"{combined_output}\n{stderr}" if combined_output else stderr
|
|
146
|
+
|
|
147
|
+
if verify_output is not None:
|
|
148
|
+
if callable(verify_output):
|
|
149
|
+
verify_output(combined_output)
|
|
150
|
+
else:
|
|
151
|
+
self.assertEqual(combined_output, verify_output)
|
|
152
|
+
except Exception:
|
|
153
|
+
print("<DUMP STDOUT>", stdout)
|
|
154
|
+
print("<DUMP STDERR>", stderr)
|
|
155
|
+
raise
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TestApi(CliTest):
|
|
5
|
+
def test_requires_path(self) -> None:
|
|
6
|
+
self.verify("zen api", expected_return_codes=[2])
|
|
7
|
+
|
|
8
|
+
def test_path_must_start_with_slash(self) -> None:
|
|
9
|
+
self.verify("zen api org/v1/org-details", expected_return_codes=[2])
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest, _test_profile_name
|
|
2
|
+
|
|
3
|
+
from .util import contains, json_has
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestAuth(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen auth --help", contains("status", "details", "token", "refresh"))
|
|
9
|
+
|
|
10
|
+
def test_status_logged_out(self) -> None:
|
|
11
|
+
self.verify(
|
|
12
|
+
f"zen -p {_test_profile_name} auth status",
|
|
13
|
+
json_has(logged_in=False, ttl=0),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def test_details_when_logged_out(self) -> None:
|
|
17
|
+
self.verify(f"zen -p {_test_profile_name} auth details", expected_return_codes=[1])
|
|
18
|
+
|
|
19
|
+
def test_token_when_logged_out(self) -> None:
|
|
20
|
+
self.verify(f"zen -p {_test_profile_name} auth token", expected_return_codes=[1])
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest, _test_profile_name
|
|
2
|
+
|
|
3
|
+
from .util import contains, json_has
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestContext(CliTest):
|
|
7
|
+
def test_list(self) -> None:
|
|
8
|
+
self.verify(f"zen -p {_test_profile_name} context list", CliTest.NON_EMPTY_STRING)
|
|
9
|
+
|
|
10
|
+
def test_roundtrip(self) -> None:
|
|
11
|
+
name = "bb_ctx"
|
|
12
|
+
try:
|
|
13
|
+
self.verify(f"zen -p {_test_profile_name} context set {name} k=v", None)
|
|
14
|
+
self.verify(f"zen -p {_test_profile_name} context get {name}", json_has(k="v"))
|
|
15
|
+
self.verify(f"zen -p {_test_profile_name} context list", contains(name))
|
|
16
|
+
finally:
|
|
17
|
+
self.verify(f"zen -p {_test_profile_name} context delete {name}", None)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCredential(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen credential --help", contains("create", "delete", "list", "get"))
|
|
9
|
+
|
|
10
|
+
def test_create_requires_payload(self) -> None:
|
|
11
|
+
self.verify("zen credential create", expected_return_codes=[2])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDeployment(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen deployment --help", contains("create", "delete", "list", "get"))
|
|
9
|
+
|
|
10
|
+
def test_create_requires_payload(self) -> None:
|
|
11
|
+
self.verify("zen deployment create", expected_return_codes=[2])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestEntitlement(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen entitlement --help", contains("create", "delete", "list", "get"))
|
|
9
|
+
|
|
10
|
+
def test_create_requires_payload(self) -> None:
|
|
11
|
+
self.verify("zen entitlement create", expected_return_codes=[2])
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestFrameworkErrors(CliTest):
|
|
7
|
+
"""Generic error handling from ctxp (_process_result, print_error, panic, error_details)."""
|
|
8
|
+
|
|
9
|
+
def test_click_missing_argument(self) -> None:
|
|
10
|
+
self.verify(
|
|
11
|
+
"zen cli-test echo-obj",
|
|
12
|
+
contains("Missing argument 'ID'"),
|
|
13
|
+
expected_return_codes=[2],
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def test_click_unknown_option(self) -> None:
|
|
17
|
+
self.verify(
|
|
18
|
+
"zen cli-test echo-obj foo --not-a-flag",
|
|
19
|
+
contains("No such option"),
|
|
20
|
+
expected_return_codes=[2],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def test_click_unknown_command(self) -> None:
|
|
24
|
+
self.verify("zen nosuchcommand", contains("No such command"), expected_return_codes=[2])
|
|
25
|
+
|
|
26
|
+
def test_error_tuple_prints_reason_and_exit_code(self) -> None:
|
|
27
|
+
self.verify(
|
|
28
|
+
"zen cli-test echo-error --reason boom --code 3",
|
|
29
|
+
contains("boom"),
|
|
30
|
+
expected_return_codes=[3],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def test_ctxp_error_tuple_prints_exception_and_exit_code(self) -> None:
|
|
34
|
+
self.verify(
|
|
35
|
+
"zen cli-test echo-error --reason boom --code 4 --ctxp-error",
|
|
36
|
+
contains("CtxpException: boom"),
|
|
37
|
+
expected_return_codes=[4],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def test_error_return_code_zero_is_rejected(self) -> None:
|
|
41
|
+
self.verify(
|
|
42
|
+
"zen cli-test echo-error --reason boom --code 0",
|
|
43
|
+
contains("return_code must not be 0"),
|
|
44
|
+
expected_return_codes=[1],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def test_ctxp_exception_prints_cause_chain_without_traceback(self) -> None:
|
|
48
|
+
def check(data: str) -> None:
|
|
49
|
+
if "CtxpException: pseudo-error" not in data:
|
|
50
|
+
raise AssertionError(data)
|
|
51
|
+
if "Caused by: KeyError: 'missing'" not in data:
|
|
52
|
+
raise AssertionError(data)
|
|
53
|
+
if "Traceback (most recent call last)" in data:
|
|
54
|
+
raise AssertionError(f"user error should not dump a traceback:\n{data}")
|
|
55
|
+
|
|
56
|
+
self.verify(
|
|
57
|
+
"zen cli-test echo-exception --reason missing --type CtxpException",
|
|
58
|
+
check,
|
|
59
|
+
expected_return_codes=[1],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_generic_exception_prints_cause_chain(self) -> None:
|
|
63
|
+
self.verify(
|
|
64
|
+
"zen cli-test echo-exception --reason x --type Exception",
|
|
65
|
+
contains("Exception: pseudo-error", "Caused by: KeyError: 'x'"),
|
|
66
|
+
expected_return_codes=[1],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_program_error_prints_traceback(self) -> None:
|
|
70
|
+
self.verify(
|
|
71
|
+
"zen cli-test echo-exception --reason missing --type TypeError",
|
|
72
|
+
contains("Traceback (most recent call last)", "TypeError", "pseudo-error"),
|
|
73
|
+
expected_return_codes=[1],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def test_http_status_error_includes_body(self) -> None:
|
|
77
|
+
self.verify(
|
|
78
|
+
"zen cli-test echo-exception --reason body --type httpx2.HTTPStatusError",
|
|
79
|
+
contains("HTTPStatusError", "400", "body"),
|
|
80
|
+
expected_return_codes=[1],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def test_panic_from_missing_profile(self) -> None:
|
|
84
|
+
self.verify(
|
|
85
|
+
"zen profile get no-such-profile-xyz",
|
|
86
|
+
contains("Profile not found"),
|
|
87
|
+
expected_return_codes=[1],
|
|
88
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestGroup(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen group --help", contains("create", "delete", "list", "get"))
|
|
9
|
+
|
|
10
|
+
def test_create_requires_payload(self) -> None:
|
|
11
|
+
self.verify("zen group create", expected_return_codes=[2])
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains, json_equals, json_has
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCommonIo(CliTest):
|
|
7
|
+
"""Shared I/O flags from ctxp.cli_options, applied by cli_processor._default_io."""
|
|
8
|
+
|
|
9
|
+
def test_help_lists_io_flags_and_examples(self) -> None:
|
|
10
|
+
self.verify(
|
|
11
|
+
"zen cli-test echo-obj --help",
|
|
12
|
+
contains(
|
|
13
|
+
"--output",
|
|
14
|
+
"--field",
|
|
15
|
+
"--exclude-field",
|
|
16
|
+
"--ids",
|
|
17
|
+
"--first",
|
|
18
|
+
"Examples:",
|
|
19
|
+
"-o table",
|
|
20
|
+
"--field id,name",
|
|
21
|
+
"--ids",
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def test_default_output_is_json(self) -> None:
|
|
26
|
+
self.verify("zen cli-test echo-obj foo --opt bar", json_has(id="foo", opt="bar"))
|
|
27
|
+
|
|
28
|
+
def test_output_json_compact(self) -> None:
|
|
29
|
+
self.verify(
|
|
30
|
+
"zen cli-test echo-obj foo --opt bar -o json-compact",
|
|
31
|
+
json_equals({"id": "foo", "opt": "bar"}),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def test_output_yaml_and_yml_alias(self) -> None:
|
|
35
|
+
self.verify("zen cli-test echo-obj foo --opt bar -o yaml", contains("id: foo", "opt: bar"))
|
|
36
|
+
self.verify("zen cli-test echo-obj foo --opt bar -o yml", contains("id: foo", "opt: bar"))
|
|
37
|
+
|
|
38
|
+
def test_output_table_and_t_alias(self) -> None:
|
|
39
|
+
self.verify("zen cli-test echo-obj-list a b --opt x -o table", contains("Id", "Opt", "a", "b", "x"))
|
|
40
|
+
self.verify("zen cli-test echo-obj-list a --opt x -o t", contains("Id", "Opt", "a", "x"))
|
|
41
|
+
|
|
42
|
+
def test_output_text_object_and_list(self) -> None:
|
|
43
|
+
self.verify("zen cli-test echo-obj foo --opt bar -o text", json_has(id="foo", opt="bar"))
|
|
44
|
+
self.verify("zen cli-test echo-str-list a b -o text", contains("a", "b"))
|
|
45
|
+
|
|
46
|
+
def test_output_case_insensitive(self) -> None:
|
|
47
|
+
self.verify("zen cli-test echo-obj foo --opt bar -o JSON", json_has(id="foo", opt="bar"))
|
|
48
|
+
|
|
49
|
+
def test_invalid_output_is_usage_error(self) -> None:
|
|
50
|
+
self.verify(
|
|
51
|
+
"zen cli-test echo-obj foo --opt bar -o not-a-format",
|
|
52
|
+
contains("Invalid value for '--output'"),
|
|
53
|
+
expected_return_codes=[2],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def test_field_keeps_only_named_keys(self) -> None:
|
|
57
|
+
self.verify("zen cli-test echo-obj foo --opt bar --field id", json_equals({"id": "foo"}))
|
|
58
|
+
self.verify(
|
|
59
|
+
"zen cli-test echo-obj-list a b --opt x --field id",
|
|
60
|
+
json_equals([{"id": "a"}, {"id": "b"}]),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def test_exclude_field_drops_named_keys(self) -> None:
|
|
64
|
+
self.verify("zen cli-test echo-obj foo --opt bar --exclude-field opt", json_equals({"id": "foo"}))
|
|
65
|
+
|
|
66
|
+
def test_ids_prints_id_only(self) -> None:
|
|
67
|
+
self.verify("zen cli-test echo-obj foo --opt bar --ids", json_equals("foo"))
|
|
68
|
+
self.verify("zen cli-test echo-obj-list a b --opt x --ids", json_equals(["a", "b"]))
|
|
69
|
+
|
|
70
|
+
def test_ids_and_field_are_rejected(self) -> None:
|
|
71
|
+
self.verify(
|
|
72
|
+
"zen cli-test echo-obj foo --opt bar --ids --field id",
|
|
73
|
+
contains("--ids and --fields should not be used together."),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def test_first_returns_first_list_item(self) -> None:
|
|
77
|
+
self.verify("zen cli-test echo-obj-list a b c --opt x --first", json_has(id="a", opt="x"))
|
|
78
|
+
|
|
79
|
+
def test_first_on_empty_list_prints_nothing(self) -> None:
|
|
80
|
+
self.verify("zen cli-test echo-obj-list --opt x --first", None)
|
|
81
|
+
|
|
82
|
+
def test_none_result_prints_nothing(self) -> None:
|
|
83
|
+
self.verify("zen cli-test echo-none", None)
|
|
84
|
+
|
|
85
|
+
def test_scalar_and_list_json(self) -> None:
|
|
86
|
+
self.verify("zen cli-test echo-int 7", contains("7"))
|
|
87
|
+
self.verify("zen cli-test echo-str hello", contains("hello"))
|
|
88
|
+
self.verify("zen cli-test echo-int-list 1 2 3 -o json-compact", json_equals([1, 2, 3]))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from zen_cli.support.test_utils import CliTest
|
|
2
|
+
|
|
3
|
+
from .util import contains
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestLogin(CliTest):
|
|
7
|
+
def test_help(self) -> None:
|
|
8
|
+
self.verify("zen login --help", contains("--device-code", "--browser"))
|
|
9
|
+
|
|
10
|
+
def test_help_has_no_details(self) -> None:
|
|
11
|
+
def check(data: str) -> None:
|
|
12
|
+
if "--details" in data or "-d," in data:
|
|
13
|
+
raise AssertionError(f"login help should not list --details:\n{data}")
|
|
14
|
+
if "--device-code" not in data:
|
|
15
|
+
raise AssertionError(data)
|
|
16
|
+
|
|
17
|
+
self.verify("zen login --help", check)
|
|
18
|
+
|
|
19
|
+
def test_exclusive_methods(self) -> None:
|
|
20
|
+
self.verify(
|
|
21
|
+
"zen login --device-code --browser",
|
|
22
|
+
contains("Specify only one"),
|
|
23
|
+
expected_return_codes=[1],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def test_client_id_needs_secret(self) -> None:
|
|
27
|
+
self.verify("zen login --client-id demo", expected_return_codes=[1])
|