glpi-python-client 0.1.0__py3-none-any.whl
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.
- glpi_python_client/__init__.py +36 -0
- glpi_python_client/auth/__init__.py +11 -0
- glpi_python_client/auth/auth.py +310 -0
- glpi_python_client/auth/tests/test_auth.py +189 -0
- glpi_python_client/clients/__init__.py +18 -0
- glpi_python_client/clients/api_v1_session.py +460 -0
- glpi_python_client/clients/api_v2_client.py +317 -0
- glpi_python_client/clients/async_api_v2_client.py +236 -0
- glpi_python_client/clients/tests/__init__.py +5 -0
- glpi_python_client/clients/tests/test_api_v1_session.py +85 -0
- glpi_python_client/clients/tests/test_api_v2_client.py +349 -0
- glpi_python_client/clients/tests/test_async_api_v2_client.py +257 -0
- glpi_python_client/clients/v2/__init__.py +8 -0
- glpi_python_client/clients/v2/async_/__init__.py +12 -0
- glpi_python_client/clients/v2/async_/api.py +29 -0
- glpi_python_client/clients/v2/async_/directory.py +88 -0
- glpi_python_client/clients/v2/async_/documents.py +144 -0
- glpi_python_client/clients/v2/async_/team.py +125 -0
- glpi_python_client/clients/v2/async_/tests/__init__.py +5 -0
- glpi_python_client/clients/v2/async_/tests/test_directory.py +43 -0
- glpi_python_client/clients/v2/async_/tests/test_documents.py +41 -0
- glpi_python_client/clients/v2/async_/tests/test_team.py +44 -0
- glpi_python_client/clients/v2/async_/tests/test_tickets.py +174 -0
- glpi_python_client/clients/v2/async_/tests/test_timeline.py +126 -0
- glpi_python_client/clients/v2/async_/tickets.py +312 -0
- glpi_python_client/clients/v2/async_/timeline.py +312 -0
- glpi_python_client/clients/v2/async_/transport.py +251 -0
- glpi_python_client/clients/v2/common/__init__.py +6 -0
- glpi_python_client/clients/v2/common/client_config.py +219 -0
- glpi_python_client/clients/v2/common/constants.py +45 -0
- glpi_python_client/clients/v2/common/errors.py +23 -0
- glpi_python_client/clients/v2/common/filters.py +30 -0
- glpi_python_client/clients/v2/common/payloads.py +57 -0
- glpi_python_client/clients/v2/common/request_http.py +195 -0
- glpi_python_client/clients/v2/common/response_payloads.py +76 -0
- glpi_python_client/clients/v2/common/ticket_search.py +113 -0
- glpi_python_client/clients/v2/sync/__init__.py +12 -0
- glpi_python_client/clients/v2/sync/api.py +29 -0
- glpi_python_client/clients/v2/sync/directory.py +90 -0
- glpi_python_client/clients/v2/sync/documents.py +144 -0
- glpi_python_client/clients/v2/sync/team.py +125 -0
- glpi_python_client/clients/v2/sync/tests/__init__.py +5 -0
- glpi_python_client/clients/v2/sync/tests/test_directory.py +57 -0
- glpi_python_client/clients/v2/sync/tests/test_documents.py +99 -0
- glpi_python_client/clients/v2/sync/tests/test_team.py +64 -0
- glpi_python_client/clients/v2/sync/tests/test_tickets.py +430 -0
- glpi_python_client/clients/v2/sync/tests/test_timeline.py +77 -0
- glpi_python_client/clients/v2/sync/tests/test_transport.py +89 -0
- glpi_python_client/clients/v2/sync/tickets.py +312 -0
- glpi_python_client/clients/v2/sync/timeline.py +308 -0
- glpi_python_client/clients/v2/sync/transport.py +246 -0
- glpi_python_client/content/__init__.py +11 -0
- glpi_python_client/content/conversion.py +58 -0
- glpi_python_client/content/records/__init__.py +84 -0
- glpi_python_client/content/records/core/__init__.py +6 -0
- glpi_python_client/content/records/core/document_links.py +100 -0
- glpi_python_client/content/records/core/normalization.py +53 -0
- glpi_python_client/content/records/core/references.py +98 -0
- glpi_python_client/content/records/core/scalars.py +83 -0
- glpi_python_client/content/records/parsers/__init__.py +6 -0
- glpi_python_client/content/records/parsers/directory.py +62 -0
- glpi_python_client/content/records/parsers/documents.py +49 -0
- glpi_python_client/content/records/parsers/team.py +58 -0
- glpi_python_client/content/records/parsers/tests/__init__.py +5 -0
- glpi_python_client/content/records/parsers/tests/test_tickets.py +35 -0
- glpi_python_client/content/records/parsers/tests/test_timeline.py +20 -0
- glpi_python_client/content/records/parsers/tickets.py +96 -0
- glpi_python_client/content/records/parsers/timeline.py +119 -0
- glpi_python_client/content/tests/__init__.py +5 -0
- glpi_python_client/content/tests/test_conversion.py +13 -0
- glpi_python_client/models/__init__.py +30 -0
- glpi_python_client/models/_base.py +22 -0
- glpi_python_client/models/_payload.py +79 -0
- glpi_python_client/models/_shared.py +37 -0
- glpi_python_client/models/glpi/__init__.py +27 -0
- glpi_python_client/models/glpi/_document.py +59 -0
- glpi_python_client/models/glpi/_followup.py +77 -0
- glpi_python_client/models/glpi/_location.py +53 -0
- glpi_python_client/models/glpi/_solution.py +57 -0
- glpi_python_client/models/glpi/_task.py +41 -0
- glpi_python_client/models/glpi/_team_member.py +33 -0
- glpi_python_client/models/glpi/_ticket.py +303 -0
- glpi_python_client/models/glpi/_user.py +92 -0
- glpi_python_client/models/glpi/tests/__init__.py +5 -0
- glpi_python_client/models/glpi/tests/test__document.py +12 -0
- glpi_python_client/models/glpi/tests/test__followup.py +31 -0
- glpi_python_client/models/glpi/tests/test__location.py +12 -0
- glpi_python_client/models/glpi/tests/test__solution.py +11 -0
- glpi_python_client/models/glpi/tests/test__ticket.py +59 -0
- glpi_python_client/models/glpi/tests/test__user.py +29 -0
- glpi_python_client/py.typed +0 -0
- glpi_python_client/testing/__init__.py +27 -0
- glpi_python_client/testing/fixtures.py +52 -0
- glpi_python_client/testing/utils.py +149 -0
- glpi_python_client-0.1.0.dist-info/METADATA +144 -0
- glpi_python_client-0.1.0.dist-info/RECORD +98 -0
- glpi_python_client-0.1.0.dist-info/WHEEL +4 -0
- glpi_python_client-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from glpi_python_client import GlpiClient, GlpiTeamMember
|
|
8
|
+
from glpi_python_client.testing.utils import FakeResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _empty_response() -> FakeResponse:
|
|
12
|
+
return FakeResponse(status_code=204, payload={}, text="", content=b"")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_add_team_member_returns_none(
|
|
16
|
+
client_factory: Callable[..., GlpiClient],
|
|
17
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
18
|
+
) -> None:
|
|
19
|
+
client = client_factory()
|
|
20
|
+
|
|
21
|
+
def fake_post_request(
|
|
22
|
+
endpoint: str,
|
|
23
|
+
payload: dict[str, object] | None = None,
|
|
24
|
+
skip_entity: bool = False,
|
|
25
|
+
) -> FakeResponse:
|
|
26
|
+
assert endpoint == "Assistance/Ticket/321/TeamMember"
|
|
27
|
+
assert skip_entity is False
|
|
28
|
+
assert payload == {"type": "User", "id": 99, "role": "1"}
|
|
29
|
+
return FakeResponse()
|
|
30
|
+
|
|
31
|
+
monkeypatch.setattr(client, "_post_request", fake_post_request)
|
|
32
|
+
try:
|
|
33
|
+
client.add_team_member(
|
|
34
|
+
"321",
|
|
35
|
+
GlpiTeamMember(member_type="User", member_id=99, role="1"),
|
|
36
|
+
)
|
|
37
|
+
finally:
|
|
38
|
+
client.close()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_remove_team_member_returns_none(
|
|
42
|
+
client_factory: Callable[..., GlpiClient],
|
|
43
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
44
|
+
) -> None:
|
|
45
|
+
client = client_factory()
|
|
46
|
+
|
|
47
|
+
def fake_delete_request(
|
|
48
|
+
endpoint: str,
|
|
49
|
+
payload: dict[str, object] | None = None,
|
|
50
|
+
skip_entity: bool = False,
|
|
51
|
+
) -> FakeResponse:
|
|
52
|
+
assert endpoint == "Assistance/Ticket/321/TeamMember"
|
|
53
|
+
assert payload == {"type": "User", "id": 99, "role": "1"}
|
|
54
|
+
assert skip_entity is False
|
|
55
|
+
return _empty_response()
|
|
56
|
+
|
|
57
|
+
monkeypatch.setattr(client, "_delete_request", fake_delete_request)
|
|
58
|
+
try:
|
|
59
|
+
client.remove_team_member(
|
|
60
|
+
321,
|
|
61
|
+
GlpiTeamMember(member_type="User", member_id=99, role="1"),
|
|
62
|
+
)
|
|
63
|
+
finally:
|
|
64
|
+
client.close()
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from glpi_python_client import GlpiClient, GlpiTicket
|
|
9
|
+
from glpi_python_client.testing.utils import (
|
|
10
|
+
FakeResponse,
|
|
11
|
+
SearchResponse,
|
|
12
|
+
TicketResponse,
|
|
13
|
+
make_ticket_record,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _empty_response() -> FakeResponse:
|
|
18
|
+
return FakeResponse(status_code=204, payload={}, text="", content=b"")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_get_ticket_record_excludes_deleted_ticket_by_default(
|
|
22
|
+
client_factory: Callable[..., GlpiClient],
|
|
23
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
24
|
+
) -> None:
|
|
25
|
+
client = client_factory()
|
|
26
|
+
|
|
27
|
+
def fake_get_request(
|
|
28
|
+
endpoint: str,
|
|
29
|
+
params: dict[str, object] | None = None,
|
|
30
|
+
skip_entity: bool = False,
|
|
31
|
+
) -> TicketResponse:
|
|
32
|
+
assert endpoint == "Assistance/Ticket/123"
|
|
33
|
+
assert params is None
|
|
34
|
+
assert skip_entity is False
|
|
35
|
+
return TicketResponse(make_ticket_record(id=123, is_deleted=1))
|
|
36
|
+
|
|
37
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
38
|
+
try:
|
|
39
|
+
with pytest.raises(ValueError, match="deleted"):
|
|
40
|
+
client.get_ticket_record("123")
|
|
41
|
+
finally:
|
|
42
|
+
client.close()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_get_ticket_record_can_include_deleted_ticket(
|
|
46
|
+
client_factory: Callable[..., GlpiClient],
|
|
47
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
48
|
+
) -> None:
|
|
49
|
+
client = client_factory()
|
|
50
|
+
|
|
51
|
+
def fake_get_request(
|
|
52
|
+
endpoint: str,
|
|
53
|
+
params: dict[str, object] | None = None,
|
|
54
|
+
skip_entity: bool = False,
|
|
55
|
+
) -> TicketResponse:
|
|
56
|
+
assert endpoint == "Assistance/Ticket/123"
|
|
57
|
+
assert params is None
|
|
58
|
+
assert skip_entity is False
|
|
59
|
+
return TicketResponse(make_ticket_record(id=123, is_deleted=1))
|
|
60
|
+
|
|
61
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
62
|
+
try:
|
|
63
|
+
ticket = client.get_ticket_record("123", include_deleted_ticket=True)
|
|
64
|
+
|
|
65
|
+
assert ticket.id == "123"
|
|
66
|
+
finally:
|
|
67
|
+
client.close()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_get_ticket_record_accepts_integer_identifier(
|
|
71
|
+
client_factory: Callable[..., GlpiClient],
|
|
72
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
73
|
+
) -> None:
|
|
74
|
+
client = client_factory()
|
|
75
|
+
|
|
76
|
+
def fake_get_request(
|
|
77
|
+
endpoint: str,
|
|
78
|
+
params: dict[str, object] | None = None,
|
|
79
|
+
skip_entity: bool = False,
|
|
80
|
+
) -> TicketResponse:
|
|
81
|
+
assert endpoint == "Assistance/Ticket/123"
|
|
82
|
+
assert params is None
|
|
83
|
+
assert skip_entity is False
|
|
84
|
+
return TicketResponse(make_ticket_record(id=123))
|
|
85
|
+
|
|
86
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
87
|
+
try:
|
|
88
|
+
ticket = client.get_ticket_record(123)
|
|
89
|
+
|
|
90
|
+
assert ticket.id == "123"
|
|
91
|
+
finally:
|
|
92
|
+
client.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_create_ticket_does_not_inject_package_defaults(
|
|
96
|
+
client_factory: Callable[..., GlpiClient],
|
|
97
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
98
|
+
) -> None:
|
|
99
|
+
client = client_factory()
|
|
100
|
+
payload_data: dict[str, object] | None = None
|
|
101
|
+
|
|
102
|
+
def fake_post_request(
|
|
103
|
+
endpoint: str,
|
|
104
|
+
payload: dict[str, object] | None = None,
|
|
105
|
+
skip_entity: bool = False,
|
|
106
|
+
) -> FakeResponse:
|
|
107
|
+
nonlocal payload_data
|
|
108
|
+
assert endpoint == "Assistance/Ticket"
|
|
109
|
+
assert skip_entity is False
|
|
110
|
+
assert payload is not None
|
|
111
|
+
payload_data = payload
|
|
112
|
+
return FakeResponse()
|
|
113
|
+
|
|
114
|
+
monkeypatch.setattr(client, "_post_request", fake_post_request)
|
|
115
|
+
try:
|
|
116
|
+
created = client.create_ticket(GlpiTicket(name="Need help"))
|
|
117
|
+
|
|
118
|
+
assert created == "321"
|
|
119
|
+
assert payload_data == {"name": "Need help"}
|
|
120
|
+
finally:
|
|
121
|
+
client.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_create_ticket_accepts_public_extra_payload(
|
|
125
|
+
client_factory: Callable[..., GlpiClient],
|
|
126
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
127
|
+
) -> None:
|
|
128
|
+
client = client_factory()
|
|
129
|
+
payload_data: dict[str, object] | None = None
|
|
130
|
+
|
|
131
|
+
def fake_post_request(
|
|
132
|
+
endpoint: str,
|
|
133
|
+
payload: dict[str, object] | None = None,
|
|
134
|
+
skip_entity: bool = False,
|
|
135
|
+
) -> FakeResponse:
|
|
136
|
+
nonlocal payload_data
|
|
137
|
+
assert endpoint == "Assistance/Ticket"
|
|
138
|
+
assert skip_entity is False
|
|
139
|
+
assert payload is not None
|
|
140
|
+
payload_data = payload
|
|
141
|
+
return FakeResponse()
|
|
142
|
+
|
|
143
|
+
monkeypatch.setattr(client, "_post_request", fake_post_request)
|
|
144
|
+
try:
|
|
145
|
+
created = client.create_ticket(
|
|
146
|
+
GlpiTicket(
|
|
147
|
+
name="Need help",
|
|
148
|
+
extra_payload={"_room_code": "PAR-3F-12"},
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
assert created == "321"
|
|
153
|
+
assert payload_data == {
|
|
154
|
+
"name": "Need help",
|
|
155
|
+
"_room_code": "PAR-3F-12",
|
|
156
|
+
}
|
|
157
|
+
finally:
|
|
158
|
+
client.close()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_create_ticket_requires_name(
|
|
162
|
+
client_factory: Callable[..., GlpiClient],
|
|
163
|
+
) -> None:
|
|
164
|
+
client = client_factory()
|
|
165
|
+
try:
|
|
166
|
+
with pytest.raises(ValueError, match="ticket creation requires a name"):
|
|
167
|
+
client.create_ticket(GlpiTicket(content="Body only"))
|
|
168
|
+
finally:
|
|
169
|
+
client.close()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_update_ticket_returns_none(
|
|
173
|
+
client_factory: Callable[..., GlpiClient],
|
|
174
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
175
|
+
) -> None:
|
|
176
|
+
client = client_factory()
|
|
177
|
+
|
|
178
|
+
def fake_update_request(
|
|
179
|
+
endpoint: str,
|
|
180
|
+
payload: dict[str, object] | None = None,
|
|
181
|
+
skip_entity: bool = False,
|
|
182
|
+
) -> FakeResponse:
|
|
183
|
+
assert endpoint == "Assistance/Ticket/321"
|
|
184
|
+
assert skip_entity is False
|
|
185
|
+
assert payload == {"name": "Updated"}
|
|
186
|
+
response = FakeResponse()
|
|
187
|
+
response.status_code = 204
|
|
188
|
+
response.text = ""
|
|
189
|
+
response.content = b""
|
|
190
|
+
return response
|
|
191
|
+
|
|
192
|
+
monkeypatch.setattr(client, "_update_request", fake_update_request)
|
|
193
|
+
try:
|
|
194
|
+
client.update_ticket("321", GlpiTicket(name="Updated"))
|
|
195
|
+
finally:
|
|
196
|
+
client.close()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_delete_ticket_returns_none(
|
|
200
|
+
client_factory: Callable[..., GlpiClient],
|
|
201
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
202
|
+
) -> None:
|
|
203
|
+
client = client_factory()
|
|
204
|
+
|
|
205
|
+
def fake_delete_request(
|
|
206
|
+
endpoint: str,
|
|
207
|
+
payload: dict[str, object] | None = None,
|
|
208
|
+
skip_entity: bool = False,
|
|
209
|
+
) -> FakeResponse:
|
|
210
|
+
assert endpoint == "Assistance/Ticket/321"
|
|
211
|
+
assert payload is None
|
|
212
|
+
assert skip_entity is False
|
|
213
|
+
return _empty_response()
|
|
214
|
+
|
|
215
|
+
monkeypatch.setattr(client, "_delete_request", fake_delete_request)
|
|
216
|
+
try:
|
|
217
|
+
client.delete_ticket(321)
|
|
218
|
+
finally:
|
|
219
|
+
client.close()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_search_ticket_records_returns_full_list_by_default(
|
|
223
|
+
client_factory: Callable[..., GlpiClient],
|
|
224
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
225
|
+
) -> None:
|
|
226
|
+
client = client_factory()
|
|
227
|
+
requests: list[dict[str, object]] = []
|
|
228
|
+
responses = iter(
|
|
229
|
+
[
|
|
230
|
+
SearchResponse(
|
|
231
|
+
[
|
|
232
|
+
make_ticket_record(id=1, name="Ticket 1"),
|
|
233
|
+
make_ticket_record(id=2, name="Ticket 2"),
|
|
234
|
+
],
|
|
235
|
+
status_code=206,
|
|
236
|
+
headers={"Content-Range": "0-1/3"},
|
|
237
|
+
),
|
|
238
|
+
SearchResponse(
|
|
239
|
+
[make_ticket_record(id=3, name="Ticket 3")],
|
|
240
|
+
status_code=200,
|
|
241
|
+
headers={"Content-Range": "2-2/3"},
|
|
242
|
+
),
|
|
243
|
+
]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def fake_get_request(
|
|
247
|
+
endpoint: str,
|
|
248
|
+
params: dict[str, object] | None = None,
|
|
249
|
+
skip_entity: bool = False,
|
|
250
|
+
) -> SearchResponse:
|
|
251
|
+
assert endpoint == "Assistance/Ticket"
|
|
252
|
+
assert skip_entity is False
|
|
253
|
+
requests.append(dict(params or {}))
|
|
254
|
+
return next(responses)
|
|
255
|
+
|
|
256
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
257
|
+
try:
|
|
258
|
+
tickets = client.search_ticket_records()
|
|
259
|
+
|
|
260
|
+
assert [ticket.id for ticket in tickets] == ["1", "2", "3"]
|
|
261
|
+
assert [request["start"] for request in requests] == [0, 2]
|
|
262
|
+
assert all("limit" not in request for request in requests)
|
|
263
|
+
finally:
|
|
264
|
+
client.close()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.parametrize(
|
|
268
|
+
("include_deleted_ticket", "expected_ids"),
|
|
269
|
+
[(False, ["1"]), (True, ["1", "2"])],
|
|
270
|
+
)
|
|
271
|
+
def test_search_ticket_records_controls_deleted_ticket_results(
|
|
272
|
+
client_factory: Callable[..., GlpiClient],
|
|
273
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
274
|
+
include_deleted_ticket: bool,
|
|
275
|
+
expected_ids: list[str],
|
|
276
|
+
) -> None:
|
|
277
|
+
client = client_factory()
|
|
278
|
+
responses = iter(
|
|
279
|
+
[
|
|
280
|
+
SearchResponse(
|
|
281
|
+
[
|
|
282
|
+
make_ticket_record(id=1, name="Ticket 1"),
|
|
283
|
+
make_ticket_record(id=2, name="Ticket 2", is_deleted=1),
|
|
284
|
+
],
|
|
285
|
+
headers={"Content-Range": "0-1/2"},
|
|
286
|
+
),
|
|
287
|
+
]
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def fake_get_request(
|
|
291
|
+
endpoint: str,
|
|
292
|
+
params: dict[str, object] | None = None,
|
|
293
|
+
skip_entity: bool = False,
|
|
294
|
+
) -> SearchResponse:
|
|
295
|
+
assert endpoint == "Assistance/Ticket"
|
|
296
|
+
assert params is not None
|
|
297
|
+
assert params["start"] == 0
|
|
298
|
+
assert "is_deleted" in str(params.get("fields"))
|
|
299
|
+
assert skip_entity is False
|
|
300
|
+
return next(responses)
|
|
301
|
+
|
|
302
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
303
|
+
try:
|
|
304
|
+
if include_deleted_ticket:
|
|
305
|
+
tickets = client.search_ticket_records(include_deleted_ticket=True)
|
|
306
|
+
else:
|
|
307
|
+
tickets = client.search_ticket_records()
|
|
308
|
+
|
|
309
|
+
assert [ticket.id for ticket in tickets] == expected_ids
|
|
310
|
+
finally:
|
|
311
|
+
client.close()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_search_ticket_records_returns_lazy_batches_when_requested(
|
|
315
|
+
client_factory: Callable[..., GlpiClient],
|
|
316
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
317
|
+
) -> None:
|
|
318
|
+
client = client_factory()
|
|
319
|
+
requests: list[dict[str, object]] = []
|
|
320
|
+
responses = iter(
|
|
321
|
+
[
|
|
322
|
+
SearchResponse(
|
|
323
|
+
[
|
|
324
|
+
make_ticket_record(id=1, name="Ticket 1"),
|
|
325
|
+
make_ticket_record(id=2, name="Ticket 2"),
|
|
326
|
+
],
|
|
327
|
+
status_code=206,
|
|
328
|
+
headers={"Content-Range": "0-1/3"},
|
|
329
|
+
),
|
|
330
|
+
SearchResponse(
|
|
331
|
+
[make_ticket_record(id=3, name="Ticket 3")],
|
|
332
|
+
status_code=200,
|
|
333
|
+
headers={"Content-Range": "2-2/3"},
|
|
334
|
+
),
|
|
335
|
+
]
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def fake_get_request(
|
|
339
|
+
endpoint: str,
|
|
340
|
+
params: dict[str, object] | None = None,
|
|
341
|
+
skip_entity: bool = False,
|
|
342
|
+
) -> SearchResponse:
|
|
343
|
+
assert endpoint == "Assistance/Ticket"
|
|
344
|
+
assert skip_entity is False
|
|
345
|
+
requests.append(dict(params or {}))
|
|
346
|
+
return next(responses)
|
|
347
|
+
|
|
348
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
349
|
+
try:
|
|
350
|
+
batches = client.search_ticket_records(batch_size=2)
|
|
351
|
+
|
|
352
|
+
assert requests == []
|
|
353
|
+
assert [[ticket.id for ticket in batch] for batch in batches] == [
|
|
354
|
+
["1", "2"],
|
|
355
|
+
["3"],
|
|
356
|
+
]
|
|
357
|
+
assert [request["start"] for request in requests] == [0, 2]
|
|
358
|
+
assert [request["limit"] for request in requests] == [2, 2]
|
|
359
|
+
finally:
|
|
360
|
+
client.close()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_search_ticket_records_follows_server_default_pagination(
|
|
364
|
+
client_factory: Callable[..., GlpiClient],
|
|
365
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
366
|
+
) -> None:
|
|
367
|
+
client = client_factory()
|
|
368
|
+
requests: list[dict[str, object]] = []
|
|
369
|
+
responses = iter(
|
|
370
|
+
[
|
|
371
|
+
SearchResponse(
|
|
372
|
+
[
|
|
373
|
+
make_ticket_record(id=1, name="Ticket 1"),
|
|
374
|
+
make_ticket_record(id=2, name="Ticket 2"),
|
|
375
|
+
]
|
|
376
|
+
),
|
|
377
|
+
SearchResponse([make_ticket_record(id=3, name="Ticket 3")]),
|
|
378
|
+
]
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def fake_get_request(
|
|
382
|
+
endpoint: str,
|
|
383
|
+
params: dict[str, object] | None = None,
|
|
384
|
+
skip_entity: bool = False,
|
|
385
|
+
) -> SearchResponse:
|
|
386
|
+
assert endpoint == "Assistance/Ticket"
|
|
387
|
+
assert skip_entity is False
|
|
388
|
+
requests.append(dict(params or {}))
|
|
389
|
+
return next(responses)
|
|
390
|
+
|
|
391
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
392
|
+
try:
|
|
393
|
+
tickets = client.search_ticket_records()
|
|
394
|
+
|
|
395
|
+
assert [ticket.id for ticket in tickets] == ["1", "2", "3"]
|
|
396
|
+
assert [request["start"] for request in requests] == [0, 2]
|
|
397
|
+
assert all("limit" not in request for request in requests)
|
|
398
|
+
finally:
|
|
399
|
+
client.close()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_sync_client_can_run_inside_running_event_loop(
|
|
403
|
+
client_factory: Callable[..., GlpiClient],
|
|
404
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
405
|
+
) -> None:
|
|
406
|
+
async def run_test() -> None:
|
|
407
|
+
client = client_factory()
|
|
408
|
+
|
|
409
|
+
def fake_get_request(
|
|
410
|
+
endpoint: str,
|
|
411
|
+
params: dict[str, object] | None = None,
|
|
412
|
+
skip_entity: bool = False,
|
|
413
|
+
) -> SearchResponse:
|
|
414
|
+
assert endpoint == "Assistance/Ticket"
|
|
415
|
+
assert skip_entity is False
|
|
416
|
+
assert params is not None
|
|
417
|
+
return SearchResponse(
|
|
418
|
+
[make_ticket_record(id=1, name="Ticket 1")],
|
|
419
|
+
headers={"Content-Range": "0-0/1"},
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
monkeypatch.setattr(client, "_get_request", fake_get_request)
|
|
423
|
+
try:
|
|
424
|
+
tickets = client.search_ticket_records()
|
|
425
|
+
|
|
426
|
+
assert [ticket.id for ticket in tickets] == ["1"]
|
|
427
|
+
finally:
|
|
428
|
+
client.close()
|
|
429
|
+
|
|
430
|
+
asyncio.run(run_test())
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from glpi_python_client import GlpiClient, GlpiFollowup
|
|
8
|
+
from glpi_python_client.testing.utils import FakeResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _empty_response() -> FakeResponse:
|
|
12
|
+
return FakeResponse(status_code=204, payload={}, text="", content=b"")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_create_followup_accepts_followup_id_response_key(
|
|
16
|
+
client_factory: Callable[..., GlpiClient],
|
|
17
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
18
|
+
) -> None:
|
|
19
|
+
client = client_factory()
|
|
20
|
+
|
|
21
|
+
def fake_post_request(
|
|
22
|
+
endpoint: str,
|
|
23
|
+
payload: dict[str, object] | None = None,
|
|
24
|
+
skip_entity: bool = False,
|
|
25
|
+
) -> FakeResponse:
|
|
26
|
+
assert endpoint == "Assistance/Ticket/321/Timeline/Followup"
|
|
27
|
+
assert skip_entity is False
|
|
28
|
+
assert payload == {
|
|
29
|
+
"content": "<p>Followup body</p>",
|
|
30
|
+
"is_private": False,
|
|
31
|
+
}
|
|
32
|
+
return FakeResponse(payload={"followup_id": "654"})
|
|
33
|
+
|
|
34
|
+
monkeypatch.setattr(client, "_post_request", fake_post_request)
|
|
35
|
+
try:
|
|
36
|
+
created = client.create_followup(
|
|
37
|
+
321,
|
|
38
|
+
GlpiFollowup(content="Followup body"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert created == "654"
|
|
42
|
+
finally:
|
|
43
|
+
client.close()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
("method_name", "args", "expected_endpoint"),
|
|
48
|
+
[
|
|
49
|
+
("delete_followup", (321, 654), "Assistance/Ticket/321/Timeline/Followup/654"),
|
|
50
|
+
("delete_solution", (321, 987), "Assistance/Ticket/321/Timeline/Solution/987"),
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
def test_delete_timeline_operations_return_none(
|
|
54
|
+
client_factory: Callable[..., GlpiClient],
|
|
55
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
56
|
+
method_name: str,
|
|
57
|
+
args: tuple[object, ...],
|
|
58
|
+
expected_endpoint: str,
|
|
59
|
+
) -> None:
|
|
60
|
+
client = client_factory()
|
|
61
|
+
|
|
62
|
+
def fake_delete_request(
|
|
63
|
+
endpoint: str,
|
|
64
|
+
payload: dict[str, object] | None = None,
|
|
65
|
+
skip_entity: bool = False,
|
|
66
|
+
) -> FakeResponse:
|
|
67
|
+
assert endpoint == expected_endpoint
|
|
68
|
+
assert payload is None
|
|
69
|
+
assert skip_entity is False
|
|
70
|
+
return _empty_response()
|
|
71
|
+
|
|
72
|
+
monkeypatch.setattr(client, "_delete_request", fake_delete_request)
|
|
73
|
+
try:
|
|
74
|
+
operation = getattr(client, method_name)
|
|
75
|
+
operation(*args)
|
|
76
|
+
finally:
|
|
77
|
+
client.close()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from glpi_python_client import GlpiClient, GlpiTeamMember
|
|
9
|
+
from glpi_python_client.testing.utils import FakeResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _empty_response() -> FakeResponse:
|
|
13
|
+
return FakeResponse(status_code=204, payload={}, text="", content=b"")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_client_normalizes_glpi_api_url_and_headers(
|
|
17
|
+
client_factory: Callable[..., GlpiClient],
|
|
18
|
+
) -> None:
|
|
19
|
+
client = client_factory(glpi_entity=2, glpi_profile=3, entity_recursive=True)
|
|
20
|
+
try:
|
|
21
|
+
client._auth.access_token = "token"
|
|
22
|
+
|
|
23
|
+
headers = client._get_headers()
|
|
24
|
+
|
|
25
|
+
assert client.glpi_api_url == "https://glpi.example.test/api.php"
|
|
26
|
+
assert headers["Authorization"] == "Bearer token"
|
|
27
|
+
assert headers["GLPI-Entity"] == "2"
|
|
28
|
+
assert headers["GLPI-Profile"] == "3"
|
|
29
|
+
assert headers["GLPI-Entity-Recursive"] == "true"
|
|
30
|
+
finally:
|
|
31
|
+
client.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_delete_ticket_omits_json_content_type_without_payload(
|
|
35
|
+
client_factory: Callable[..., GlpiClient],
|
|
36
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
37
|
+
) -> None:
|
|
38
|
+
client = client_factory()
|
|
39
|
+
requests: list[dict[str, object]] = []
|
|
40
|
+
|
|
41
|
+
def ensure_token() -> None:
|
|
42
|
+
client._auth.access_token = "token"
|
|
43
|
+
|
|
44
|
+
def delete(url: str, **kwargs: object) -> FakeResponse:
|
|
45
|
+
requests.append({"url": url, **kwargs})
|
|
46
|
+
return _empty_response()
|
|
47
|
+
|
|
48
|
+
monkeypatch.setattr(client._auth, "ensure_token", ensure_token)
|
|
49
|
+
monkeypatch.setattr(client._session, "delete", delete)
|
|
50
|
+
try:
|
|
51
|
+
client.delete_ticket("321")
|
|
52
|
+
|
|
53
|
+
headers = cast(dict[str, str], requests[0]["headers"])
|
|
54
|
+
assert requests[0]["url"] == (
|
|
55
|
+
"https://glpi.example.test/api.php/Assistance/Ticket/321"
|
|
56
|
+
)
|
|
57
|
+
assert "Content-Type" not in headers
|
|
58
|
+
assert requests[0]["json"] is None
|
|
59
|
+
finally:
|
|
60
|
+
client.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_remove_team_member_keeps_json_content_type_with_payload(
|
|
64
|
+
client_factory: Callable[..., GlpiClient],
|
|
65
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
66
|
+
) -> None:
|
|
67
|
+
client = client_factory()
|
|
68
|
+
requests: list[dict[str, object]] = []
|
|
69
|
+
|
|
70
|
+
def ensure_token() -> None:
|
|
71
|
+
client._auth.access_token = "token"
|
|
72
|
+
|
|
73
|
+
def delete(url: str, **kwargs: object) -> FakeResponse:
|
|
74
|
+
requests.append({"url": url, **kwargs})
|
|
75
|
+
return _empty_response()
|
|
76
|
+
|
|
77
|
+
monkeypatch.setattr(client._auth, "ensure_token", ensure_token)
|
|
78
|
+
monkeypatch.setattr(client._session, "delete", delete)
|
|
79
|
+
try:
|
|
80
|
+
client.remove_team_member(
|
|
81
|
+
"321",
|
|
82
|
+
GlpiTeamMember(member_type="User", member_id=99, role="1"),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
headers = cast(dict[str, str], requests[0]["headers"])
|
|
86
|
+
assert headers["Content-Type"] == "application/json"
|
|
87
|
+
assert requests[0]["json"] == {"type": "User", "id": 99, "role": "1"}
|
|
88
|
+
finally:
|
|
89
|
+
client.close()
|