proxygen-cli 2.0.15__tar.gz → 2.0.17__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.
Files changed (35) hide show
  1. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/PKG-INFO +10 -9
  2. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/README.md +8 -8
  3. proxygen_cli-2.0.17/proxygen_cli/cli/command_credentials.py +101 -0
  4. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_main.py +2 -5
  5. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_spec.py +40 -16
  6. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/credentials.py +34 -23
  7. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_credentials_test.py +64 -9
  8. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_spec_test.py +29 -3
  9. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/pyproject.toml +1 -1
  10. proxygen_cli-2.0.15/proxygen_cli/cli/command_credentials.py +0 -65
  11. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/__init__.py +0 -0
  12. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_docker.py +0 -0
  13. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_instance.py +0 -0
  14. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_secret.py +0 -0
  15. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_settings.py +0 -0
  16. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/auth.py +0 -0
  17. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/constants.py +0 -0
  18. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/dot_proxygen.py +0 -0
  19. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/output.py +0 -0
  20. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/proxygen_api.py +0 -0
  21. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/settings.py +0 -0
  22. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/spec.py +0 -0
  23. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/spec_server.py +0 -0
  24. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/version.py +0 -0
  25. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/__init__.py +0 -0
  26. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_instance_test.py +0 -0
  27. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_main_test.py +0 -0
  28. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_secret_test.py +0 -0
  29. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_settings_test.py +0 -0
  30. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/conftest.py +0 -0
  31. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/client.key +0 -0
  32. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/client.pem +0 -0
  33. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/secret.txt +0 -0
  34. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/mock_private_key.py +0 -0
  35. {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/validate_cli_version_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxygen-cli
3
- Version: 2.0.15
3
+ Version: 2.0.17
4
4
  Summary: CLI for interacting with NHSD APIM's proxygen service
5
5
  Home-page: https://github.com/NHSDigital/proxygen-cli
6
6
  Author: Ben Strutt
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
13
14
  Requires-Dist: click (>=8.1.3,<9.0.0)
14
15
  Requires-Dist: cryptography (>=36.0.1,<37.0.0)
15
16
  Requires-Dist: lxml (>=4.7.1,<5.0.0)
@@ -38,19 +39,19 @@ Type `proxygen` to see a list of available commands.
38
39
 
39
40
  ### Credentials
40
41
 
41
- All users should have individual credentials.
42
- `proxygen-cli` needs to know about them.
42
+ The CLI has its own client credentials, which need to be input.
43
+ Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
44
+
45
+ All users should also have individual credentials. `proxygen-cli` needs to know about them.
43
46
 
47
+ Simply execute the following command which will prompt you to enter your `client_id`, `client_secret`, `username`, and `password`:
44
48
  ```
45
- proxygen credentials set username <USERNAME>
46
- proxygen credentials set password <PASSWORD>
49
+ proxygen credentials set
47
50
  ```
48
51
 
49
- The CLI has its own client credentials, which need to be input.
50
- Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
52
+ If you need to update any credentials in the future, use the following command:
51
53
  ```
52
- proxgen credentials set client_id <CLIENT_ID>
53
- proxgen credentials set client_secret <CLIENT_SECRET>
54
+ proxygen credentials set <KEY> <VALUE>
54
55
  ```
55
56
 
56
57
 
@@ -14,19 +14,19 @@ Type `proxygen` to see a list of available commands.
14
14
 
15
15
  ### Credentials
16
16
 
17
- All users should have individual credentials.
18
- `proxygen-cli` needs to know about them.
17
+ The CLI has its own client credentials, which need to be input.
18
+ Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
19
+
20
+ All users should also have individual credentials. `proxygen-cli` needs to know about them.
19
21
 
22
+ Simply execute the following command which will prompt you to enter your `client_id`, `client_secret`, `username`, and `password`:
20
23
  ```
21
- proxygen credentials set username <USERNAME>
22
- proxygen credentials set password <PASSWORD>
24
+ proxygen credentials set
23
25
  ```
24
26
 
25
- The CLI has its own client credentials, which need to be input.
26
- Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
27
+ If you need to update any credentials in the future, use the following command:
27
28
  ```
28
- proxgen credentials set client_id <CLIENT_ID>
29
- proxgen credentials set client_secret <CLIENT_SECRET>
29
+ proxygen credentials set <KEY> <VALUE>
30
30
  ```
31
31
 
32
32
 
@@ -0,0 +1,101 @@
1
+ import json
2
+ import click
3
+ import yaml
4
+ import pydantic
5
+
6
+ from proxygen_cli.lib import output
7
+ from proxygen_cli.lib.credentials import (
8
+ Credentials, get_credentials, _yaml_credentials_file_source, create_yaml_credentials_file, initialise_credentials)
9
+ from proxygen_cli.lib.dot_proxygen import credentials_file
10
+
11
+ CHOICE_OF_CREDENTIAL_KEYS = click.Choice(Credentials.__fields__.keys())
12
+
13
+
14
+ @click.group()
15
+ def credentials():
16
+ """Get/set credentials."""
17
+
18
+
19
+ @credentials.command()
20
+ def list():
21
+ """
22
+ List all credentials values.
23
+ """
24
+ if not _yaml_credentials_file_source(None):
25
+ click.echo("Credentials file does not exist. Please run 'proxygen credentials set'")
26
+ exit()
27
+
28
+ creds = get_credentials()
29
+ output.print_spec(json.loads(creds.json(exclude_none=True)))
30
+
31
+
32
+ @credentials.command()
33
+ @click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
34
+ def get(key):
35
+ """
36
+ Read a value from your credentials.
37
+ """
38
+ if not _yaml_credentials_file_source(None):
39
+ click.echo("Credentials file does not exist. Please run 'proxygen credentials set'")
40
+ exit()
41
+
42
+ creds = get_credentials()
43
+ click.echo(getattr(creds, key))
44
+
45
+
46
+ @credentials.command()
47
+ @click.argument("custom_pairs", nargs=-1, metavar="KEY VALUE", required=False)
48
+ @click.option("--force", is_flag=True, help="Force re-entry of standard credentials")
49
+ def set(custom_pairs, force):
50
+ """
51
+ Write a value to your credentials.
52
+ """
53
+ if not _yaml_credentials_file_source(None):
54
+ create_yaml_credentials_file()
55
+
56
+ current_credentials = _yaml_credentials_file_source(None)
57
+
58
+ # Check if base credentials are set
59
+ base_credentials_set = all(
60
+ current_credentials.get(field) is not None
61
+ for field in ["client_id", "client_secret", "username", "password"]
62
+ )
63
+
64
+ if not base_credentials_set or force:
65
+ client_id = click.prompt("Enter client_id")
66
+ client_secret = click.prompt("Enter client_secret", default="", show_default=False)
67
+ username = click.prompt("Enter username", default="", show_default=False)
68
+ password = click.prompt("Enter password", default="", show_default=False)
69
+
70
+ current_credentials["client_id"] = client_id
71
+ current_credentials["client_secret"] = client_secret
72
+ current_credentials["username"] = username
73
+ current_credentials["password"] = password
74
+
75
+ # Prompt for individual custom key-value pairs
76
+ for i in range(0, len(custom_pairs), 2):
77
+ key, value = custom_pairs[i:i + 2]
78
+ current_credentials[key] = value
79
+
80
+ try:
81
+ new_credentials = json.loads(Credentials(**current_credentials).json(exclude_none=True))
82
+ except pydantic.ValidationError as e:
83
+ errors = json.loads(e.json())
84
+ raise click.BadParameter("\n".join(error["msg"] for error in errors))
85
+
86
+ with credentials_file().open("w") as f:
87
+ yaml.safe_dump(new_credentials, f)
88
+
89
+ initialise_credentials()
90
+
91
+
92
+ @credentials.command()
93
+ @click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
94
+ def rm(key):
95
+ """
96
+ Delete a value from your credentials.
97
+ """
98
+ current_credentials = _yaml_credentials_file_source(None)
99
+ current_credentials.pop(key, None)
100
+ with credentials_file().open("w") as f:
101
+ yaml.safe_dump(current_credentials, f)
@@ -1,10 +1,7 @@
1
- import sys
2
- from typing import get_args
3
- from urllib import parse
4
1
  import click
5
2
  from proxygen_cli.lib.settings import SETTINGS
6
3
 
7
- from proxygen_cli.lib import constants, proxygen_api, settings, output, version
4
+ from proxygen_cli.lib import proxygen_api, output
8
5
  from proxygen_cli.cli import (
9
6
  command_credentials,
10
7
  command_settings,
@@ -23,7 +20,7 @@ def main():
23
20
  main.add_command(command_settings.settings)
24
21
  main.add_command(command_credentials.credentials)
25
22
  main.add_command(command_instance.instance)
26
- main.add_command(command_spec.spec_cmd, name="spec")
23
+ main.add_command(command_spec.spec)
27
24
  main.add_command(command_secret.secret)
28
25
  main.add_command(command_docker.docker)
29
26
 
@@ -3,34 +3,58 @@ from typing import get_args
3
3
  import click
4
4
  from yaspin import yaspin
5
5
 
6
- from proxygen_cli.lib import output, proxygen_api, spec, spec_server
6
+ from proxygen_cli.lib import output, proxygen_api, spec as lib_spec, \
7
+ spec_server
7
8
  from proxygen_cli.lib.constants import LITERAL_ENVS
8
9
  from proxygen_cli.lib.settings import SETTINGS
9
10
 
10
11
  CHOICE_OF_ENVS = click.Choice(get_args(LITERAL_ENVS))
11
12
  PUBLISH_SPEC_POP_KEYS = ["x-nhsd-apim"] # Don't publish deployment information
12
13
 
14
+ HELP_NO_CONFIRM = "Do not prompt for confirmation."
15
+ HELP_UAT = "Target the UAT instance of API catalogue."
16
+
17
+ UAT_KWARGS = {
18
+ 'default': False,
19
+ 'help': HELP_UAT,
20
+ 'is_flag': True
21
+ }
22
+
23
+ NO_CONFIRM_KWARGS = {
24
+ 'is_flag': True,
25
+ 'show_default': True,
26
+ 'help': HELP_NO_CONFIRM,
27
+ }
28
+
13
29
 
14
30
  @click.group()
15
31
  @click.option(
16
- "--api", default=SETTINGS.api, help="Override the default API", show_default=True
32
+ "--api",
33
+ default=SETTINGS.api,
34
+ help="Override the default API",
35
+ show_default=True
36
+ )
37
+ @click.option(
38
+ "--uat",
39
+ **UAT_KWARGS
17
40
  )
18
41
  @click.pass_context
19
- def spec_cmd(ctx, api):
42
+ def spec(ctx, api, uat):
43
+ """Publish/update/delete specifications on the API catalogue."""
20
44
  ctx.ensure_object(dict)
21
45
  ctx.obj["api"] = api
46
+ ctx.obj['uat'] = uat
22
47
 
23
48
 
24
- @spec_cmd.command()
49
+ @spec.command()
25
50
  @click.argument("spec_file")
26
51
  @click.option(
27
52
  "--no-confirm",
28
- is_flag=True,
29
- show_default=True,
30
- help="Do not prompt for confirmation.",
53
+ **NO_CONFIRM_KWARGS
31
54
  )
32
55
  @click.option(
33
- "--uat", default=False, help="Spec for UAT environment", is_flag=True
56
+ "--uat",
57
+ **UAT_KWARGS
34
58
  )
35
59
  @click.pass_context
36
60
  def publish(ctx, spec_file, no_confirm, uat):
@@ -39,7 +63,7 @@ def publish(ctx, spec_file, no_confirm, uat):
39
63
  """
40
64
 
41
65
  api = ctx.obj["api"]
42
- paas_open_api = spec.resolve(spec_file, pop_keys=PUBLISH_SPEC_POP_KEYS)
66
+ paas_open_api = lib_spec.resolve(spec_file, pop_keys=PUBLISH_SPEC_POP_KEYS)
43
67
 
44
68
  if not no_confirm:
45
69
  output.print_spec(paas_open_api)
@@ -69,9 +93,10 @@ def serve(spec_file):
69
93
  spec_server.serve(spec_file, pop_keys=PUBLISH_SPEC_POP_KEYS)
70
94
 
71
95
 
72
- @spec_cmd.command()
96
+ @spec.command()
73
97
  @click.option(
74
- "--uat", default=False, help="Spec for UAT environment", is_flag=True
98
+ "--uat",
99
+ **UAT_KWARGS
75
100
  )
76
101
  @click.pass_context
77
102
  def get(ctx, uat):
@@ -83,15 +108,14 @@ def get(ctx, uat):
83
108
  output.print_spec(result)
84
109
 
85
110
 
86
- @spec_cmd.command()
111
+ @spec.command()
87
112
  @click.option(
88
113
  "--no-confirm",
89
- is_flag=True,
90
- show_default=True,
91
- help="Do not prompt for confirmation.",
114
+ **NO_CONFIRM_KWARGS
92
115
  )
93
116
  @click.option(
94
- "--uat", default=False, help="Spec for UAT environment", is_flag=True
117
+ "--uat",
118
+ **UAT_KWARGS
95
119
  )
96
120
  @click.pass_context
97
121
  def delete(ctx, no_confirm, uat):
@@ -3,6 +3,7 @@ import json
3
3
  from typing import Optional
4
4
  import yaml
5
5
  import sys
6
+ import os
6
7
 
7
8
  import click
8
9
  from pydantic import (
@@ -21,6 +22,14 @@ def _yaml_credentials_file_source(_):
21
22
  return credentials or {}
22
23
 
23
24
 
25
+ def create_yaml_credentials_file():
26
+ file_path = os.path.expanduser("~/.proxygen/credentials.yaml")
27
+
28
+ if not os.path.exists(file_path):
29
+ with open(file_path, 'w'):
30
+ pass
31
+
32
+
24
33
  class Credentials(BaseSettings):
25
34
  base_url: AnyHttpUrl = (
26
35
  "https://identity.prod.api.platform.nhs.uk/realms/api-producers"
@@ -28,9 +37,9 @@ class Credentials(BaseSettings):
28
37
  private_key_path: Optional[str] = None
29
38
  key_id: Optional[str] = None
30
39
  client_id: str
31
- client_secret: str = None
32
- username: str = None
33
- password: str = None
40
+ client_secret: Optional[str] = None
41
+ username: Optional[str] = None
42
+ password: Optional[str] = None
34
43
 
35
44
  @validator("username", "password", "client_secret", "client_id")
36
45
  def validate_humans_users(cls, value, values):
@@ -47,12 +56,12 @@ class Credentials(BaseSettings):
47
56
  )
48
57
  with private_key_file.open() as f:
49
58
  return f.read()
50
-
59
+
51
60
  @validator("key_id")
52
61
  def validate_kid(cls, kid, values):
53
62
  """
54
- If authenticating using a machine (with an associated jwks configured in Keycloak) key, then the key id is required
55
- for newer versions of Keycloak.
63
+ If authenticating using a machine (with an associated jwks configured in Keycloak) key,
64
+ then the key id is required for newer versions of Keycloak.
56
65
  """
57
66
  if values.get("private_key_path") and not kid:
58
67
  raise ValueError("Private key specified with no associated Key ID (KID)")
@@ -65,7 +74,7 @@ class Credentials(BaseSettings):
65
74
  So take string.
66
75
  """
67
76
  return cls._validate_private_key_path(private_key_path)
68
-
77
+
69
78
  @staticmethod
70
79
  def _validate_private_key_path(private_key_path):
71
80
  """
@@ -104,24 +113,26 @@ class Credentials(BaseSettings):
104
113
 
105
114
 
106
115
  _CREDENTIALS = None
107
- try:
108
- _CREDENTIALS = Credentials()
109
- except ValidationError as e: # pragma: no cover
110
-
111
- errors = json.loads(e.json())
112
- print("*" * 100, file=sys.stderr)
113
- print(
114
- "Warning: Credentials invalid or not configured. See `proxygen credentials`.",
115
- file=sys.stderr,
116
- )
117
- details = " " + "\n ".join(
118
- f"{error['loc'][0]}: {error['msg']}" for error in errors
119
- )
120
- print(details, file=sys.stderr)
121
- print("*" * 100, file=sys.stderr)
122
- _CREDENTIALS = None
116
+ def initialise_credentials():
117
+ global _CREDENTIALS
118
+ try:
119
+ _CREDENTIALS = Credentials()
120
+ except ValidationError as e:
121
+ errors = json.loads(e.json())
122
+ print("*" * 100, file=sys.stderr)
123
+ print(
124
+ "Warning: Credentials invalid or not configured. See `proxygen credentials`.",
125
+ file=sys.stderr,
126
+ )
127
+ details = " " + "\n ".join(
128
+ f"{error['loc'][0]}: {error['msg']}" for error in errors
129
+ )
130
+ print(details, file=sys.stderr)
131
+ print("*" * 100, file=sys.stderr)
132
+ _CREDENTIALS = None
123
133
 
124
134
 
125
135
  def get_credentials():
136
+ initialise_credentials()
126
137
  global _CREDENTIALS
127
138
  return _CREDENTIALS
@@ -1,5 +1,6 @@
1
1
  from unittest.mock import patch
2
2
 
3
+ import yaml
3
4
  import pytest
4
5
  from click.testing import CliRunner
5
6
  from pydantic.error_wrappers import ValidationError
@@ -72,7 +73,7 @@ def test_private_key_id_missing(patch_config):
72
73
  """
73
74
  mock_credentials = get_test_credentials()
74
75
 
75
- with patch_config(credentials=mock_credentials),\
76
+ with patch_config(credentials=mock_credentials), \
76
77
  patch(("proxygen_cli.lib.credentials.Credentials."
77
78
  "_validate_private_key_path")) as mock_creds:
78
79
  mock_creds.return_value = True
@@ -107,19 +108,73 @@ def test_set_credential(patch_config, credentials_file):
107
108
  assert credentials_file() == expected_credentials
108
109
 
109
110
 
110
- def test_set_invalid_setting():
111
- runner = CliRunner()
112
- result = runner.invoke(cmd_credentials.set, ["invalid", "new-username"])
111
+ def test_update_credentials(patch_config, credentials_file):
112
+ mock_credentials = get_test_credentials()
113
+
114
+ # Convert mock_credentials to a dictionary
115
+ mock_credentials_dict = yaml.safe_load(mock_credentials)
113
116
 
114
- assert "'invalid' is not one of 'base_url'" in result.output.strip()
117
+ # Set an initial value for the username field
118
+ initial_username = "old-username"
119
+ mock_credentials_dict["username"] = initial_username
115
120
 
121
+ with patch_config(credentials=mock_credentials):
122
+ runner = CliRunner()
123
+ runner.invoke(cmd_credentials.set, ["username", "new-username123"])
116
124
 
117
- def test_set_invalid_private_key_path():
118
- runner = CliRunner()
119
- result = runner.invoke(
120
- cmd_credentials.set, ["private_key_path", "invalid_private_key_path"]
125
+ expected_credentials = "\n".join(
126
+ [
127
+ "base_url: https://mock-keycloak-url.nhs.uk",
128
+ "client_id: mock-api-client",
129
+ "client_secret: 1a2f4g5",
130
+ "password: mock-password",
131
+ "username: new-username123", # Updated value
132
+ ]
133
+ )
134
+
135
+ # Check the file has been written to
136
+ assert credentials_file() == expected_credentials
137
+
138
+
139
+ def test_add_key_id(patch_config, credentials_file):
140
+ mock_credentials = get_test_credentials()
141
+
142
+ with patch_config(credentials=mock_credentials):
143
+ runner = CliRunner()
144
+ runner.invoke(cmd_credentials.set, ["key_id", "test_kid"])
145
+
146
+ expected_credentials = "\n".join(
147
+ [
148
+ "base_url: https://mock-keycloak-url.nhs.uk",
149
+ "client_id: mock-api-client",
150
+ "client_secret: 1a2f4g5",
151
+ "key_id: test_kid",
152
+ "password: mock-password",
153
+ "username: mock-user",
154
+ ]
121
155
  )
122
156
 
157
+ # Check the file has been written to
158
+ assert credentials_file() == expected_credentials
159
+
160
+
161
+ def test_set_invalid_setting(patch_config):
162
+ mock_credentials = get_test_credentials()
163
+ with patch_config(credentials=mock_credentials):
164
+ runner = CliRunner()
165
+ result = runner.invoke(cmd_credentials.set, ["invalid", "new-username"])
166
+
167
+ assert "Error: Invalid value: extra fields not permitted" in result.output.strip()
168
+
169
+
170
+ def test_set_invalid_private_key_path(patch_config):
171
+ mock_credentials = get_test_credentials()
172
+ with patch_config(credentials=mock_credentials):
173
+ runner = CliRunner()
174
+ result = runner.invoke(
175
+ cmd_credentials.set, ["private_key_path", "invalid_private_key_path"]
176
+ )
177
+
123
178
  assert ".proxygen/invalid_private_key_path does not exist" in result.output.strip()
124
179
 
125
180
 
@@ -3,9 +3,11 @@ Tests around the spec CLI command.
3
3
  """
4
4
  from urllib.parse import urlparse
5
5
  from click.testing import CliRunner
6
+ import pytest
6
7
 
7
8
  from proxygen_cli.cli.command_spec import get as spec_get, \
8
- delete as spec_delete, publish as spec_publish
9
+ delete as spec_delete, publish as spec_publish, spec as spec_base, \
10
+ serve as spec_serve
9
11
 
10
12
 
11
13
  def test_publish_spec_std(patch_request, patch_access_token,
@@ -15,7 +17,7 @@ def test_publish_spec_std(patch_request, patch_access_token,
15
17
  """
16
18
 
17
19
  runner = CliRunner()
18
- with patch_access_token(), patch_request(200, "TEST") as patched_request,\
20
+ with patch_access_token(), patch_request(200, "TEST") as patched_request, \
19
21
  patch_spec_resolver(), patch_click_confirm():
20
22
 
21
23
  runner.invoke(spec_publish, ["NoFile"], obj={"api": "mock-api"})
@@ -32,7 +34,7 @@ def test_publish_spec_uat(patch_request, patch_access_token,
32
34
  """
33
35
 
34
36
  runner = CliRunner()
35
- with patch_access_token(), patch_request(200, "TEST") as patched_request,\
37
+ with patch_access_token(), patch_request(200, "TEST") as patched_request, \
36
38
  patch_spec_resolver(), patch_click_confirm():
37
39
 
38
40
  runner.invoke(
@@ -110,6 +112,30 @@ def test_delete_spec_uat(patch_request, patch_access_token,
110
112
  assert path == "/apis/mock-api/spec/uat"
111
113
 
112
114
 
115
+ @pytest.mark.parametrize("cmd, uat_expected", [
116
+ (spec_get, True),
117
+ (spec_delete, True),
118
+ (spec_publish, True),
119
+ (spec_serve, False),
120
+ (spec_base, True)
121
+ ])
122
+ def test_uat_option_in_expected_command_help(cmd, uat_expected):
123
+ """
124
+ Ensure the --uat option is only available for the appropriate spec
125
+ commands.
126
+ """
127
+
128
+ runner = CliRunner()
129
+ result = runner.invoke(cmd, ["--help"])
130
+
131
+ if uat_expected:
132
+ assert "--uat" in result.output, \
133
+ f"--uat option was expected but not found for command {cmd}"
134
+ else:
135
+ assert "--uat" not in result.output, \
136
+ f"--uat option was not expected but found for command {cmd}"
137
+
138
+
113
139
  def _extract_request_path(mocked_request, method='GET'):
114
140
  """
115
141
  Extract the path from the first mocked call to .get() in the provided
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "proxygen-cli"
3
- version = "2.0.15"
3
+ version = "2.0.17"
4
4
  description = "CLI for interacting with NHSD APIM's proxygen service"
5
5
  authors = ["Ben Strutt <ben.strutt1@nhs.net>"]
6
6
  readme = "README.md"
@@ -1,65 +0,0 @@
1
- import json
2
- import pydantic
3
- import yaml
4
- import click
5
-
6
- from proxygen_cli.lib import output
7
- from proxygen_cli.lib.credentials import Credentials, get_credentials, _yaml_credentials_file_source
8
- from proxygen_cli.lib.dot_proxygen import credentials_file
9
-
10
- CHOICE_OF_CREDENTIAL_KEYS = click.Choice(Credentials.__fields__.keys())
11
-
12
-
13
- @click.group()
14
- def credentials():
15
- """Get/set credentials."""
16
-
17
-
18
- @credentials.command()
19
- def list():
20
- """
21
- List all credentials values.
22
- """
23
- creds = get_credentials()
24
- output.print_spec(json.loads(creds.json(exclude_none=True)))
25
-
26
- @credentials.command()
27
- @click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
28
- def get(key):
29
- """
30
- Read a value from your credentials.
31
- """
32
- creds = get_credentials()
33
- click.echo(getattr(creds, key))
34
-
35
-
36
- @credentials.command()
37
- @click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
38
- @click.argument("value")
39
- def set(key, value):
40
- """
41
- Write a value to your credentials.
42
- """
43
- current_credentials = _yaml_credentials_file_source(None)
44
- current_credentials[key] = value
45
- try:
46
- new_credentials = json.loads(Credentials(**current_credentials).json(exclude_none=True))
47
- except pydantic.ValidationError as e:
48
- errors = json.loads(e.json())
49
- raise click.BadParameter("\n".join(error["msg"] for error in errors))
50
- with credentials_file().open("w") as f:
51
- yaml.safe_dump(new_credentials, f)
52
-
53
-
54
-
55
-
56
- @credentials.command()
57
- @click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
58
- def rm(key):
59
- """
60
- Delete a value from your credentials.
61
- """
62
- current_credentials = _yaml_credentials_file_source(None)
63
- current_credentials.pop(key, None)
64
- with credentials_file().open("w") as f:
65
- yaml.safe_dump(current_credentials, f)