diracx-cli 0.0.1a15__tar.gz → 0.0.1a17__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.
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/PKG-INFO +1 -1
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/internal/__init__.py +20 -14
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/internal/legacy.py +6 -4
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/jobs.py +48 -10
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/utils.py +9 -1
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/PKG-INFO +1 -1
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/integration_test.yaml +29 -29
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/test_cssync.py +1 -1
- diracx_cli-0.0.1a17/tests/test_jobs.py +122 -0
- diracx_cli-0.0.1a15/tests/test_jobs.py +0 -13
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/README.md +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/pyproject.toml +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/setup.cfg +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/__init__.py +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/__main__.py +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/config.py +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx/cli/py.typed +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/SOURCES.txt +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/dependency_links.txt +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/entry_points.txt +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/requires.txt +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/src/diracx_cli.egg-info/top_level.txt +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/integration_test.cfg +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/integration_test_buggy.cfg +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/integration_test_secret.cfg +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/test_legacy.py +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/test_internal.py +0 -0
- {diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/test_login.py +0 -0
|
@@ -4,7 +4,7 @@ from typing import Annotated, Optional
|
|
|
4
4
|
import git
|
|
5
5
|
import typer
|
|
6
6
|
import yaml
|
|
7
|
-
from pydantic import
|
|
7
|
+
from pydantic import TypeAdapter
|
|
8
8
|
|
|
9
9
|
from diracx.core.config import ConfigSource, ConfigSourceUrl
|
|
10
10
|
from diracx.core.config.schema import (
|
|
@@ -25,13 +25,11 @@ app.add_typer(legacy.app, name="legacy")
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@app.command()
|
|
28
|
-
def generate_cs(
|
|
29
|
-
config_repo: str,
|
|
30
|
-
):
|
|
28
|
+
def generate_cs(config_repo: str):
|
|
31
29
|
"""Generate a minimal DiracX configuration repository"""
|
|
32
|
-
# TODO: The use of
|
|
33
|
-
config_repo =
|
|
34
|
-
if config_repo.scheme != "git+file":
|
|
30
|
+
# TODO: The use of TypeAdapter should be moved in to typer itself
|
|
31
|
+
config_repo = TypeAdapter(ConfigSourceUrl).validate_python(config_repo)
|
|
32
|
+
if config_repo.scheme != "git+file" or config_repo.path is None:
|
|
35
33
|
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
36
34
|
repo_path = Path(config_repo.path)
|
|
37
35
|
if repo_path.exists() and list(repo_path.iterdir()):
|
|
@@ -62,8 +60,10 @@ def add_vo(
|
|
|
62
60
|
):
|
|
63
61
|
"""Add a registry entry (vo) to an existing configuration repository"""
|
|
64
62
|
|
|
65
|
-
# TODO: The use of
|
|
66
|
-
config_repo =
|
|
63
|
+
# TODO: The use of TypeAdapter should be moved in to typer itself
|
|
64
|
+
config_repo = TypeAdapter(ConfigSourceUrl).validate_python(config_repo)
|
|
65
|
+
if config_repo.scheme != "git+file" or config_repo.path is None:
|
|
66
|
+
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
67
67
|
repo_path = Path(config_repo.path)
|
|
68
68
|
|
|
69
69
|
# A VO should at least contain a default group
|
|
@@ -104,8 +104,10 @@ def add_group(
|
|
|
104
104
|
):
|
|
105
105
|
"""Add a group to an existing vo in the configuration repository"""
|
|
106
106
|
|
|
107
|
-
# TODO: The use of
|
|
108
|
-
config_repo =
|
|
107
|
+
# TODO: The use of TypeAdapter should be moved in to typer itself
|
|
108
|
+
config_repo = TypeAdapter(ConfigSourceUrl).validate_python(config_repo)
|
|
109
|
+
if config_repo.scheme != "git+file" or config_repo.path is None:
|
|
110
|
+
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
109
111
|
repo_path = Path(config_repo.path)
|
|
110
112
|
|
|
111
113
|
new_group = GroupConfig(Properties=set(properties), Quota=None, Users=set())
|
|
@@ -139,8 +141,10 @@ def add_user(
|
|
|
139
141
|
):
|
|
140
142
|
"""Add a user to an existing vo and group"""
|
|
141
143
|
|
|
142
|
-
# TODO: The use of
|
|
143
|
-
config_repo =
|
|
144
|
+
# TODO: The use of TypeAdapter should be moved in to typer itself
|
|
145
|
+
config_repo = TypeAdapter(ConfigSourceUrl).validate_python(config_repo)
|
|
146
|
+
if config_repo.scheme != "git+file" or config_repo.path is None:
|
|
147
|
+
raise NotImplementedError("Only git+file:// URLs are supported")
|
|
144
148
|
|
|
145
149
|
repo_path = Path(config_repo.path)
|
|
146
150
|
|
|
@@ -184,6 +188,8 @@ def update_config_and_commit(repo_path: Path, config: Config, message: str):
|
|
|
184
188
|
repo = git.Repo(repo_path)
|
|
185
189
|
yaml_path = repo_path / "default.yml"
|
|
186
190
|
typer.echo(f"Writing back configuration to {yaml_path}", err=True)
|
|
187
|
-
yaml_path.write_text(
|
|
191
|
+
yaml_path.write_text(
|
|
192
|
+
yaml.safe_dump(config.model_dump(exclude_unset=True, mode="json"))
|
|
193
|
+
)
|
|
188
194
|
repo.index.add([yaml_path.relative_to(repo_path)])
|
|
189
195
|
repo.index.commit(message)
|
|
@@ -79,14 +79,16 @@ def cs_sync(old_file: Path, new_file: Path):
|
|
|
79
79
|
|
|
80
80
|
_apply_fixes(raw)
|
|
81
81
|
|
|
82
|
-
config = Config.
|
|
83
|
-
new_file.write_text(
|
|
82
|
+
config = Config.model_validate(raw)
|
|
83
|
+
new_file.write_text(
|
|
84
|
+
yaml.safe_dump(config.model_dump(exclude_unset=True, mode="json"))
|
|
85
|
+
)
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
def _apply_fixes(raw):
|
|
87
89
|
"""Modify raw in place to make any layout changes between the old and new structure"""
|
|
88
90
|
|
|
89
|
-
conv_config = ConversionConfig.
|
|
91
|
+
conv_config = ConversionConfig.model_validate(raw["DiracX"]["CsSync"])
|
|
90
92
|
|
|
91
93
|
raw.pop("DiracX", None)
|
|
92
94
|
# Remove dips specific parts from the CS
|
|
@@ -119,7 +121,7 @@ def _apply_fixes(raw):
|
|
|
119
121
|
|
|
120
122
|
for vo, vo_meta in conv_config.VOs.items():
|
|
121
123
|
raw["Registry"][vo] = {
|
|
122
|
-
"IdP": vo_meta.IdP,
|
|
124
|
+
"IdP": vo_meta.IdP.model_dump(),
|
|
123
125
|
"DefaultGroup": vo_meta.DefaultGroup,
|
|
124
126
|
"Users": {},
|
|
125
127
|
"Groups": {},
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
__all__ = ("app",)
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
|
|
7
|
+
import re
|
|
8
|
+
from typing import Annotated, cast
|
|
8
9
|
|
|
9
10
|
from rich.console import Console
|
|
10
11
|
from rich.table import Table
|
|
@@ -52,29 +53,66 @@ async def search(
|
|
|
52
53
|
],
|
|
53
54
|
condition: Annotated[list[SearchSpec], Option(parser=parse_condition)] = [],
|
|
54
55
|
all: bool = False,
|
|
56
|
+
page: int = 1,
|
|
57
|
+
per_page: int = 10,
|
|
55
58
|
):
|
|
56
59
|
async with DiracClient() as api:
|
|
57
|
-
jobs = await api.jobs.search(
|
|
60
|
+
jobs, content_range = await api.jobs.search(
|
|
58
61
|
parameters=None if all else parameter,
|
|
59
62
|
search=condition if condition else None,
|
|
63
|
+
page=page,
|
|
64
|
+
per_page=per_page,
|
|
65
|
+
cls=lambda _, jobs, headers: (
|
|
66
|
+
jobs,
|
|
67
|
+
ContentRange(headers.get("Content-Range", "jobs")),
|
|
68
|
+
),
|
|
60
69
|
)
|
|
61
|
-
display(jobs, "jobs")
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
display(jobs, cast(ContentRange, content_range))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ContentRange:
|
|
75
|
+
unit: str | None = None
|
|
76
|
+
start: int | None = None
|
|
77
|
+
end: int | None = None
|
|
78
|
+
total: int | None = None
|
|
79
|
+
|
|
80
|
+
def __init__(self, header: str):
|
|
81
|
+
if match := re.fullmatch(r"(\w+) (\d+-\d+|\*)/(\d+|\*)", header):
|
|
82
|
+
self.unit, range, total = match.groups()
|
|
83
|
+
self.total = int(total)
|
|
84
|
+
if range != "*":
|
|
85
|
+
self.start, self.end = map(int, range.split("-"))
|
|
86
|
+
elif match := re.fullmatch(r"\w+", header):
|
|
87
|
+
self.unit = match.group()
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def caption(self):
|
|
91
|
+
if self.start is None and self.end is None:
|
|
92
|
+
range_str = "all"
|
|
93
|
+
else:
|
|
94
|
+
range_str = (
|
|
95
|
+
f"{self.start if self.start is not None else 'unknown'}-"
|
|
96
|
+
f"{self.end if self.end is not None else 'unknown'} "
|
|
97
|
+
f"of {self.total or 'unknown'}"
|
|
98
|
+
)
|
|
99
|
+
return f"Showing {range_str} {self.unit}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def display(data, content_range: ContentRange):
|
|
65
103
|
output_format = get_diracx_preferences().output_format
|
|
66
104
|
match output_format:
|
|
67
105
|
case OutputFormats.JSON:
|
|
68
106
|
print(json.dumps(data, indent=2))
|
|
69
107
|
case OutputFormats.RICH:
|
|
70
|
-
display_rich(data,
|
|
108
|
+
display_rich(data, content_range)
|
|
71
109
|
case _:
|
|
72
110
|
raise NotImplementedError(output_format)
|
|
73
111
|
|
|
74
112
|
|
|
75
|
-
def display_rich(data,
|
|
113
|
+
def display_rich(data, content_range: ContentRange) -> None:
|
|
76
114
|
if not data:
|
|
77
|
-
print(f"No {unit} found")
|
|
115
|
+
print(f"No {content_range.unit} found")
|
|
78
116
|
return
|
|
79
117
|
|
|
80
118
|
console = Console()
|
|
@@ -83,7 +121,7 @@ def display_rich(data, unit: str) -> None:
|
|
|
83
121
|
table = Table(
|
|
84
122
|
"Parameter",
|
|
85
123
|
"Value",
|
|
86
|
-
caption=
|
|
124
|
+
caption=content_range.caption,
|
|
87
125
|
caption_justify="right",
|
|
88
126
|
)
|
|
89
127
|
for job in data:
|
|
@@ -93,7 +131,7 @@ def display_rich(data, unit: str) -> None:
|
|
|
93
131
|
else:
|
|
94
132
|
table = Table(
|
|
95
133
|
*columns,
|
|
96
|
-
caption=
|
|
134
|
+
caption=content_range.caption,
|
|
97
135
|
caption_justify="right",
|
|
98
136
|
)
|
|
99
137
|
for job in data:
|
|
@@ -6,6 +6,8 @@ from asyncio import run
|
|
|
6
6
|
from functools import wraps
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
|
+
from azure.core.exceptions import ClientAuthenticationError
|
|
10
|
+
from rich import print
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class AsyncTyper(typer.Typer):
|
|
@@ -13,7 +15,13 @@ class AsyncTyper(typer.Typer):
|
|
|
13
15
|
def decorator(async_func):
|
|
14
16
|
@wraps(async_func)
|
|
15
17
|
def sync_func(*_args, **_kwargs):
|
|
16
|
-
|
|
18
|
+
try:
|
|
19
|
+
return run(async_func(*_args, **_kwargs))
|
|
20
|
+
except ClientAuthenticationError:
|
|
21
|
+
print(
|
|
22
|
+
":x: [bold red]You are not authenticated. Log in with:[/bold red] "
|
|
23
|
+
"[bold] dirac login [OPTIONS] [VO] [/bold]"
|
|
24
|
+
)
|
|
17
25
|
|
|
18
26
|
self.command(*args, **kwargs)(sync_func)
|
|
19
27
|
return async_func
|
|
@@ -52,20 +52,20 @@ Registry:
|
|
|
52
52
|
DefaultGroup: jenkins_user
|
|
53
53
|
Groups:
|
|
54
54
|
jenkins_fcadmin:
|
|
55
|
-
Properties:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Users:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
Properties:
|
|
56
|
+
- FileCatalogManagement
|
|
57
|
+
- NormalUser
|
|
58
|
+
Users:
|
|
59
|
+
- 26dbe36e-cf5c-4c52-a834-29a1c904ef74
|
|
60
|
+
- a95ab678-3fa4-41b9-b863-fe62ce8064ce
|
|
61
|
+
- e2cb28ec-1a1e-40ee-a56d-d899b79879ce
|
|
62
62
|
jenkins_user:
|
|
63
|
-
Properties:
|
|
64
|
-
|
|
65
|
-
Users:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
Properties:
|
|
64
|
+
- NormalUser
|
|
65
|
+
Users:
|
|
66
|
+
- 26dbe36e-cf5c-4c52-a834-29a1c904ef74
|
|
67
|
+
- a95ab678-3fa4-41b9-b863-fe62ce8064ce
|
|
68
|
+
- e2cb28ec-1a1e-40ee-a56d-d899b79879ce
|
|
69
69
|
IdP:
|
|
70
70
|
ClientID: 995ed3b9-d5bd-49d3-a7f4-7fc7dbd5a0cd
|
|
71
71
|
URL: https://jenkins.invalid/
|
|
@@ -94,23 +94,23 @@ Registry:
|
|
|
94
94
|
DefaultGroup: dirac_user
|
|
95
95
|
Groups:
|
|
96
96
|
dirac_admin:
|
|
97
|
-
Properties:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Users:
|
|
106
|
-
|
|
97
|
+
Properties:
|
|
98
|
+
- AlarmsManagement
|
|
99
|
+
- CSAdministrator
|
|
100
|
+
- FullDelegation
|
|
101
|
+
- JobAdministrator
|
|
102
|
+
- Operator
|
|
103
|
+
- ProxyManagement
|
|
104
|
+
- ServiceAdministrator
|
|
105
|
+
Users:
|
|
106
|
+
- 26b14fc9-6d40-4ca5-b014-6234eaf0fb6e
|
|
107
107
|
dirac_user:
|
|
108
|
-
Properties:
|
|
109
|
-
|
|
110
|
-
Users:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
Properties:
|
|
109
|
+
- NormalUser
|
|
110
|
+
Users:
|
|
111
|
+
- 26b14fc9-6d40-4ca5-b014-6234eaf0fb6e
|
|
112
|
+
- d3adc733-6588-4d6f-8581-5986b02d0c87
|
|
113
|
+
- ff2152ff-34f4-4739-b106-3def37e291e3
|
|
114
114
|
IdP:
|
|
115
115
|
ClientID: 072afab5-ed92-46e0-a61d-4ecbc96e0770
|
|
116
116
|
URL: https://vo.invalid/
|
|
@@ -31,7 +31,7 @@ def test_cs_sync(tmp_path, monkeypatch):
|
|
|
31
31
|
actual_output = yaml.safe_load(output_file.read_text())
|
|
32
32
|
expected_output = yaml.safe_load((file_path / "integration_test.yaml").read_text())
|
|
33
33
|
assert actual_output == expected_output
|
|
34
|
-
Config.
|
|
34
|
+
Config.model_validate(actual_output)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def test_disabled_vos_empty(tmp_path, monkeypatch):
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from pytest import raises
|
|
9
|
+
|
|
10
|
+
from diracx import cli
|
|
11
|
+
from diracx.core.models import ScalarSearchSpec
|
|
12
|
+
from diracx.core.preferences import get_diracx_preferences
|
|
13
|
+
|
|
14
|
+
TEST_JDL = """
|
|
15
|
+
Arguments = "jobDescription.xml -o LogLevel=INFO";
|
|
16
|
+
Executable = "dirac-jobexec";
|
|
17
|
+
JobGroup = jobGroup;
|
|
18
|
+
JobName = jobName;
|
|
19
|
+
JobType = User;
|
|
20
|
+
LogLevel = INFO;
|
|
21
|
+
OutputSandbox =
|
|
22
|
+
{
|
|
23
|
+
Script1_CodeOutput.log,
|
|
24
|
+
std.err,
|
|
25
|
+
std.out
|
|
26
|
+
};
|
|
27
|
+
Priority = 1;
|
|
28
|
+
Site = ANY;
|
|
29
|
+
StdError = std.err;
|
|
30
|
+
StdOutput = std.out;
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
async def jdl_file():
|
|
36
|
+
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as temp_file:
|
|
37
|
+
temp_file.write(TEST_JDL)
|
|
38
|
+
temp_file.flush()
|
|
39
|
+
yield temp_file.name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def test_submit(with_cli_login, jdl_file, capfd):
|
|
43
|
+
"""Test submitting a job using a JDL file."""
|
|
44
|
+
|
|
45
|
+
with open(jdl_file, "r") as temp_file:
|
|
46
|
+
await cli.jobs.submit([temp_file])
|
|
47
|
+
|
|
48
|
+
cap = capfd.readouterr()
|
|
49
|
+
assert cap.err == ""
|
|
50
|
+
assert "Inserted 1 jobs with ids" in cap.out
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def test_search(with_cli_login, jdl_file, capfd):
|
|
54
|
+
"""Test searching for jobs."""
|
|
55
|
+
|
|
56
|
+
# Submit 20 jobs
|
|
57
|
+
with open(jdl_file, "r") as temp_file:
|
|
58
|
+
await cli.jobs.submit([temp_file] * 20)
|
|
59
|
+
|
|
60
|
+
cap = capfd.readouterr()
|
|
61
|
+
|
|
62
|
+
# By default the output should be in JSON format as capfd is not a TTY
|
|
63
|
+
await cli.jobs.search()
|
|
64
|
+
cap = capfd.readouterr()
|
|
65
|
+
assert cap.err == ""
|
|
66
|
+
jobs = json.loads(cap.out)
|
|
67
|
+
|
|
68
|
+
# There should be 10 jobs by default
|
|
69
|
+
assert len(jobs) == 10
|
|
70
|
+
assert "JobID" in jobs[0]
|
|
71
|
+
assert "JobGroup" in jobs[0]
|
|
72
|
+
|
|
73
|
+
# Change per-page to a very large number to get all the jobs at once: the caption should change
|
|
74
|
+
await cli.jobs.search(per_page=9999)
|
|
75
|
+
cap = capfd.readouterr()
|
|
76
|
+
assert cap.err == ""
|
|
77
|
+
jobs = json.loads(cap.out)
|
|
78
|
+
|
|
79
|
+
# There should be 20 jobs at least now
|
|
80
|
+
assert len(jobs) >= 20
|
|
81
|
+
assert "JobID" in cap.out
|
|
82
|
+
assert "JobGroup" in cap.out
|
|
83
|
+
|
|
84
|
+
# Search for a job that doesn't exist
|
|
85
|
+
condition = ScalarSearchSpec(parameter="Status", operator="eq", value="nonexistent")
|
|
86
|
+
await cli.jobs.search(condition=[condition])
|
|
87
|
+
cap = capfd.readouterr()
|
|
88
|
+
assert cap.err == ""
|
|
89
|
+
assert "[]" == cap.out.strip()
|
|
90
|
+
|
|
91
|
+
# Switch to RICH output
|
|
92
|
+
get_diracx_preferences.cache_clear()
|
|
93
|
+
os.environ["DIRACX_OUTPUT_FORMAT"] = "RICH"
|
|
94
|
+
|
|
95
|
+
await cli.jobs.search()
|
|
96
|
+
cap = capfd.readouterr()
|
|
97
|
+
assert cap.err == ""
|
|
98
|
+
|
|
99
|
+
with raises(json.JSONDecodeError):
|
|
100
|
+
json.loads(cap.out)
|
|
101
|
+
|
|
102
|
+
assert "JobID" in cap.out
|
|
103
|
+
assert "JobGroup" in cap.out
|
|
104
|
+
assert "Showing 0-9 of " in cap.out
|
|
105
|
+
|
|
106
|
+
# Change per-page to a very large number to get all the jobs at once: the caption should change
|
|
107
|
+
await cli.jobs.search(per_page=9999)
|
|
108
|
+
cap = capfd.readouterr()
|
|
109
|
+
assert cap.err == ""
|
|
110
|
+
|
|
111
|
+
with raises(json.JSONDecodeError):
|
|
112
|
+
json.loads(cap.out)
|
|
113
|
+
|
|
114
|
+
assert "JobID" in cap.out
|
|
115
|
+
assert "JobGroup" in cap.out
|
|
116
|
+
assert "Showing all jobs" in cap.out
|
|
117
|
+
|
|
118
|
+
# Search for a job that doesn't exist
|
|
119
|
+
await cli.jobs.search(condition=[condition])
|
|
120
|
+
cap = capfd.readouterr()
|
|
121
|
+
assert cap.err == ""
|
|
122
|
+
assert "No jobs found" in cap.out
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
|
|
5
|
-
from diracx import cli
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def test_search(with_cli_login, capfd):
|
|
9
|
-
await cli.jobs.search()
|
|
10
|
-
cap = capfd.readouterr()
|
|
11
|
-
assert cap.err == ""
|
|
12
|
-
# By default the output should be in JSON format as capfd is not a TTY
|
|
13
|
-
json.loads(cap.out)
|
|
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
|
{diracx_cli-0.0.1a15 → diracx_cli-0.0.1a17}/tests/legacy/cs_sync/integration_test_secret.cfg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|