Skip to content

Commit b980ffc

Browse files
committed
feat: add a --seed option to the test command
1 parent 06be6aa commit b980ffc

4 files changed

Lines changed: 71 additions & 5 deletions

File tree

‎doc/changelog.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Added
88
^^^^^
99
- Support for Python 3.15.
1010
- ``test --strict`` fails on deviations from the recommendations of the RFCs.
11+
- ``test --seed`` sends the same generated values as a previous run. Each run prints its seed.
1112

1213
Changed
1314
^^^^^^^

‎doc/tutorial.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,22 @@ See the :doc:`scim2-tester documentation <scim2_tester:index>` for more details
257257
...
258258
SUCCESS check_replace_attribute User displayName
259259
...
260+
Seed: 2416347125
260261
261262
Each result shows the status of the check, its title, and the resource type and the attribute it is about.
262263
The passing results are green, the deviations from the recommendations of the RFCs are yellow,
263264
the failures are red and the skipped checks are gray.
264265
The command fails with the exit code 1 when a check fails.
265266
With ``--strict``, it also fails on deviations.
266267

268+
The checks send generated values, such as user names and email addresses.
269+
Each run generates new values, and prints its seed at the end.
270+
To send the same values again, for instance to replay a failure, pass the seed with ``--seed``:
271+
272+
.. code-block:: console
273+
274+
$ scim test --seed 2416347125
275+
267276
Errors
268277
------
269278

‎scim2_cli/test.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import secrets
12
import sys
23

34
import click
@@ -39,12 +40,18 @@
3940
is_flag=True,
4041
help="Fail on deviations from the recommendations of the RFCs.",
4142
)
43+
@click.option(
44+
"--seed",
45+
type=int,
46+
help="Send the same generated values as the run that printed this seed.",
47+
)
4248
def test_cli(
4349
ctx: click.Context,
4450
verbose: bool,
4551
check_status_code: bool,
4652
check_content_type: bool,
4753
strict: bool,
54+
seed: int | None,
4855
) -> None:
4956
"""Perform a server SCIM compliance check using :doc:`scim2-tester <scim2_tester:index>`.
5057
@@ -55,7 +62,9 @@ def test_cli(
5562
client = ctx.obj.client
5663
client.check_response_status_codes = check_status_code
5764
client.check_response_content_type = check_content_type
58-
results = check_server(client)
65+
if seed is None:
66+
seed = secrets.randbelow(2**32)
67+
results = check_server(client, seed=seed)
5968
click.echo(f"Performing a SCIM compliance check on {client.client.base_url} ...")
6069
failed = False
6170
for result in results:
@@ -77,4 +86,5 @@ def test_cli(
7786
if verbose and result.data:
7887
click.echo(f" {escape_control_characters(result.data)}")
7988

89+
click.echo(f"Seed: {seed}")
8090
sys.exit(1 if failed else 0)

‎tests/test_test.py‎

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@ def test_nominal(runner, httpserver):
2626
data="data2",
2727
),
2828
]
29-
with patch("scim2_cli.test.check_server", side_effect=[results]):
29+
with patch("scim2_cli.test.check_server", side_effect=[results]) as check_server:
3030
result = runner.invoke(
3131
cli,
3232
["--url", httpserver.url_for("/"), "test"],
3333
catch_exceptions=False,
3434
)
35+
seed = check_server.call_args.kwargs["seed"]
3536

3637
expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
3738
SUCCESS test1
3839
reason1
3940
ERROR test2
41+
Seed: {seed}
4042
"""
4143
assert result.output == expected_output
4244

@@ -59,12 +61,13 @@ def test_verbose(runner, httpserver):
5961
data="data2",
6062
),
6163
]
62-
with patch("scim2_cli.test.check_server", side_effect=[results]):
64+
with patch("scim2_cli.test.check_server", side_effect=[results]) as check_server:
6365
result = runner.invoke(
6466
cli,
6567
["--url", httpserver.url_for("/"), "test", "--verbose"],
6668
catch_exceptions=False,
6769
)
70+
seed = check_server.call_args.kwargs["seed"]
6871

6972
expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
7073
SUCCESS test1
@@ -73,6 +76,7 @@ def test_verbose(runner, httpserver):
7376
ERROR test2
7477
reason2
7578
data2
79+
Seed: {seed}
7680
"""
7781
assert result.output == expected_output
7882

@@ -97,7 +101,7 @@ def test_response_checks_options(runner, httpserver, options, expected):
97101
"""Test that the response check options are applied on the client."""
98102
checked = {}
99103

100-
def check_server(client):
104+
def check_server(client, seed):
101105
checked["status_codes"] = client.check_response_status_codes
102106
checked["content_type"] = client.check_response_content_type
103107
return []
@@ -140,7 +144,7 @@ def test_resource_type_and_attribute(runner, httpserver):
140144

141145
result = run_checks(runner, httpserver, results)
142146

143-
assert result.output.splitlines()[1:] == [
147+
assert result.output.splitlines()[1:-1] == [
144148
"SUCCESS discovery_methods",
145149
"SUCCESS object_creation User",
146150
"SUCCESS check_replace_attribute User name.familyName",
@@ -209,3 +213,45 @@ def test_status_color_follows_the_outcome(runner, httpserver, status, color):
209213
def test_every_outcome_has_a_color():
210214
"""Each outcome of scim2-tester has a color."""
211215
assert set(OUTCOME_COLORS) == set(Outcome)
216+
217+
218+
def test_seed_is_sent_and_printed(runner, httpserver):
219+
"""The seed passed with --seed generates the values, and is printed at the end."""
220+
with patch("scim2_cli.test.check_server", return_value=[]) as check_server:
221+
result = runner.invoke(
222+
cli,
223+
["--url", httpserver.url_for("/"), "test", "--seed", "1234"],
224+
catch_exceptions=False,
225+
)
226+
227+
assert check_server.call_args.kwargs["seed"] == 1234
228+
assert result.output.splitlines()[-1] == "Seed: 1234"
229+
230+
231+
def test_each_run_has_its_own_seed(runner, httpserver):
232+
"""Without --seed, each run draws a new seed and prints it."""
233+
seeds = []
234+
for _ in range(2):
235+
with patch("scim2_cli.test.check_server", return_value=[]) as check_server:
236+
result = runner.invoke(
237+
cli,
238+
["--url", httpserver.url_for("/"), "test"],
239+
catch_exceptions=False,
240+
)
241+
seed = check_server.call_args.kwargs["seed"]
242+
assert result.output.splitlines()[-1] == f"Seed: {seed}"
243+
seeds.append(seed)
244+
245+
assert seeds[0] != seeds[1]
246+
247+
248+
def test_seed_must_be_an_integer(runner, httpserver):
249+
"""A seed that is not an integer is a usage error."""
250+
with patch("scim2_cli.test.check_server") as check_server:
251+
result = runner.invoke(
252+
cli, ["--url", httpserver.url_for("/"), "test", "--seed", "abc"]
253+
)
254+
255+
assert result.exit_code == 2
256+
assert "'abc' is not a valid integer" in result.output
257+
check_server.assert_not_called()

0 commit comments

Comments
 (0)