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.
Files changed (98) hide show
  1. glpi_python_client/__init__.py +36 -0
  2. glpi_python_client/auth/__init__.py +11 -0
  3. glpi_python_client/auth/auth.py +310 -0
  4. glpi_python_client/auth/tests/test_auth.py +189 -0
  5. glpi_python_client/clients/__init__.py +18 -0
  6. glpi_python_client/clients/api_v1_session.py +460 -0
  7. glpi_python_client/clients/api_v2_client.py +317 -0
  8. glpi_python_client/clients/async_api_v2_client.py +236 -0
  9. glpi_python_client/clients/tests/__init__.py +5 -0
  10. glpi_python_client/clients/tests/test_api_v1_session.py +85 -0
  11. glpi_python_client/clients/tests/test_api_v2_client.py +349 -0
  12. glpi_python_client/clients/tests/test_async_api_v2_client.py +257 -0
  13. glpi_python_client/clients/v2/__init__.py +8 -0
  14. glpi_python_client/clients/v2/async_/__init__.py +12 -0
  15. glpi_python_client/clients/v2/async_/api.py +29 -0
  16. glpi_python_client/clients/v2/async_/directory.py +88 -0
  17. glpi_python_client/clients/v2/async_/documents.py +144 -0
  18. glpi_python_client/clients/v2/async_/team.py +125 -0
  19. glpi_python_client/clients/v2/async_/tests/__init__.py +5 -0
  20. glpi_python_client/clients/v2/async_/tests/test_directory.py +43 -0
  21. glpi_python_client/clients/v2/async_/tests/test_documents.py +41 -0
  22. glpi_python_client/clients/v2/async_/tests/test_team.py +44 -0
  23. glpi_python_client/clients/v2/async_/tests/test_tickets.py +174 -0
  24. glpi_python_client/clients/v2/async_/tests/test_timeline.py +126 -0
  25. glpi_python_client/clients/v2/async_/tickets.py +312 -0
  26. glpi_python_client/clients/v2/async_/timeline.py +312 -0
  27. glpi_python_client/clients/v2/async_/transport.py +251 -0
  28. glpi_python_client/clients/v2/common/__init__.py +6 -0
  29. glpi_python_client/clients/v2/common/client_config.py +219 -0
  30. glpi_python_client/clients/v2/common/constants.py +45 -0
  31. glpi_python_client/clients/v2/common/errors.py +23 -0
  32. glpi_python_client/clients/v2/common/filters.py +30 -0
  33. glpi_python_client/clients/v2/common/payloads.py +57 -0
  34. glpi_python_client/clients/v2/common/request_http.py +195 -0
  35. glpi_python_client/clients/v2/common/response_payloads.py +76 -0
  36. glpi_python_client/clients/v2/common/ticket_search.py +113 -0
  37. glpi_python_client/clients/v2/sync/__init__.py +12 -0
  38. glpi_python_client/clients/v2/sync/api.py +29 -0
  39. glpi_python_client/clients/v2/sync/directory.py +90 -0
  40. glpi_python_client/clients/v2/sync/documents.py +144 -0
  41. glpi_python_client/clients/v2/sync/team.py +125 -0
  42. glpi_python_client/clients/v2/sync/tests/__init__.py +5 -0
  43. glpi_python_client/clients/v2/sync/tests/test_directory.py +57 -0
  44. glpi_python_client/clients/v2/sync/tests/test_documents.py +99 -0
  45. glpi_python_client/clients/v2/sync/tests/test_team.py +64 -0
  46. glpi_python_client/clients/v2/sync/tests/test_tickets.py +430 -0
  47. glpi_python_client/clients/v2/sync/tests/test_timeline.py +77 -0
  48. glpi_python_client/clients/v2/sync/tests/test_transport.py +89 -0
  49. glpi_python_client/clients/v2/sync/tickets.py +312 -0
  50. glpi_python_client/clients/v2/sync/timeline.py +308 -0
  51. glpi_python_client/clients/v2/sync/transport.py +246 -0
  52. glpi_python_client/content/__init__.py +11 -0
  53. glpi_python_client/content/conversion.py +58 -0
  54. glpi_python_client/content/records/__init__.py +84 -0
  55. glpi_python_client/content/records/core/__init__.py +6 -0
  56. glpi_python_client/content/records/core/document_links.py +100 -0
  57. glpi_python_client/content/records/core/normalization.py +53 -0
  58. glpi_python_client/content/records/core/references.py +98 -0
  59. glpi_python_client/content/records/core/scalars.py +83 -0
  60. glpi_python_client/content/records/parsers/__init__.py +6 -0
  61. glpi_python_client/content/records/parsers/directory.py +62 -0
  62. glpi_python_client/content/records/parsers/documents.py +49 -0
  63. glpi_python_client/content/records/parsers/team.py +58 -0
  64. glpi_python_client/content/records/parsers/tests/__init__.py +5 -0
  65. glpi_python_client/content/records/parsers/tests/test_tickets.py +35 -0
  66. glpi_python_client/content/records/parsers/tests/test_timeline.py +20 -0
  67. glpi_python_client/content/records/parsers/tickets.py +96 -0
  68. glpi_python_client/content/records/parsers/timeline.py +119 -0
  69. glpi_python_client/content/tests/__init__.py +5 -0
  70. glpi_python_client/content/tests/test_conversion.py +13 -0
  71. glpi_python_client/models/__init__.py +30 -0
  72. glpi_python_client/models/_base.py +22 -0
  73. glpi_python_client/models/_payload.py +79 -0
  74. glpi_python_client/models/_shared.py +37 -0
  75. glpi_python_client/models/glpi/__init__.py +27 -0
  76. glpi_python_client/models/glpi/_document.py +59 -0
  77. glpi_python_client/models/glpi/_followup.py +77 -0
  78. glpi_python_client/models/glpi/_location.py +53 -0
  79. glpi_python_client/models/glpi/_solution.py +57 -0
  80. glpi_python_client/models/glpi/_task.py +41 -0
  81. glpi_python_client/models/glpi/_team_member.py +33 -0
  82. glpi_python_client/models/glpi/_ticket.py +303 -0
  83. glpi_python_client/models/glpi/_user.py +92 -0
  84. glpi_python_client/models/glpi/tests/__init__.py +5 -0
  85. glpi_python_client/models/glpi/tests/test__document.py +12 -0
  86. glpi_python_client/models/glpi/tests/test__followup.py +31 -0
  87. glpi_python_client/models/glpi/tests/test__location.py +12 -0
  88. glpi_python_client/models/glpi/tests/test__solution.py +11 -0
  89. glpi_python_client/models/glpi/tests/test__ticket.py +59 -0
  90. glpi_python_client/models/glpi/tests/test__user.py +29 -0
  91. glpi_python_client/py.typed +0 -0
  92. glpi_python_client/testing/__init__.py +27 -0
  93. glpi_python_client/testing/fixtures.py +52 -0
  94. glpi_python_client/testing/utils.py +149 -0
  95. glpi_python_client-0.1.0.dist-info/METADATA +144 -0
  96. glpi_python_client-0.1.0.dist-info/RECORD +98 -0
  97. glpi_python_client-0.1.0.dist-info/WHEEL +4 -0
  98. 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()