karrio-server-graph 2025.5__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 (39) hide show
  1. karrio/server/graph/__init__.py +1 -0
  2. karrio/server/graph/admin.py +3 -0
  3. karrio/server/graph/apps.py +5 -0
  4. karrio/server/graph/forms.py +57 -0
  5. karrio/server/graph/management/__init__.py +0 -0
  6. karrio/server/graph/management/commands/__init__.py +0 -0
  7. karrio/server/graph/management/commands/export_schema.py +9 -0
  8. karrio/server/graph/migrations/0001_initial.py +37 -0
  9. karrio/server/graph/migrations/0002_auto_20210512_1353.py +22 -0
  10. karrio/server/graph/migrations/__init__.py +0 -0
  11. karrio/server/graph/models.py +44 -0
  12. karrio/server/graph/schema.py +44 -0
  13. karrio/server/graph/schemas/__init__.py +2 -0
  14. karrio/server/graph/schemas/base/__init__.py +385 -0
  15. karrio/server/graph/schemas/base/inputs.py +612 -0
  16. karrio/server/graph/schemas/base/mutations.py +1033 -0
  17. karrio/server/graph/schemas/base/types.py +1406 -0
  18. karrio/server/graph/serializers.py +388 -0
  19. karrio/server/graph/templates/graphql/graphiql.html +142 -0
  20. karrio/server/graph/templates/karrio/email_change_email.html +13 -0
  21. karrio/server/graph/templates/karrio/email_change_email.txt +13 -0
  22. karrio/server/graph/templates/karrio/password_reset_email.html +14 -0
  23. karrio/server/graph/tests/__init__.py +9 -0
  24. karrio/server/graph/tests/base.py +153 -0
  25. karrio/server/graph/tests/test_carrier_connections.py +239 -0
  26. karrio/server/graph/tests/test_metafield.py +404 -0
  27. karrio/server/graph/tests/test_partial_shipments.py +603 -0
  28. karrio/server/graph/tests/test_rate_sheets.py +354 -0
  29. karrio/server/graph/tests/test_registration.py +209 -0
  30. karrio/server/graph/tests/test_templates.py +677 -0
  31. karrio/server/graph/tests/test_user_info.py +71 -0
  32. karrio/server/graph/urls.py +10 -0
  33. karrio/server/graph/utils.py +308 -0
  34. karrio/server/graph/views.py +91 -0
  35. karrio/server/settings/graph.py +7 -0
  36. karrio_server_graph-2025.5.dist-info/METADATA +29 -0
  37. karrio_server_graph-2025.5.dist-info/RECORD +39 -0
  38. karrio_server_graph-2025.5.dist-info/WHEEL +5 -0
  39. karrio_server_graph-2025.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,603 @@
1
+ import json
2
+ from unittest.mock import ANY, patch
3
+ from django.urls import reverse
4
+ from rest_framework import status
5
+ from karrio.core.models import RateDetails, ChargeDetails
6
+ from karrio.server.graph.tests.base import GraphTestCase, Result
7
+ import karrio.server.manager.models as manager
8
+
9
+
10
+ class TestPartialShipmentMutation(GraphTestCase):
11
+ def setUp(self) -> None:
12
+ super().setUp()
13
+ # Create a draft shipment using the REST API
14
+ self.shipment = self._create_draft_shipment()
15
+
16
+ def _create_draft_shipment(self):
17
+ """Create a draft shipment using the REST API without service (no rates fetched)"""
18
+ url = reverse("karrio.server.manager:shipment-list")
19
+
20
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
21
+ mock.return_value = RETURNED_RATES_VALUE
22
+ response = self.client.post(url, DRAFT_SHIPMENT_DATA)
23
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
24
+ response_data = json.loads(response.content)
25
+ return response_data
26
+
27
+ def test_partial_update_options_with_none_values(self):
28
+ """Test updating options with explicit None values to remove existing options"""
29
+ shipment_id = self.shipment["id"]
30
+
31
+ response = self.query(
32
+ """
33
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
34
+ partial_shipment_update(input: $data) {
35
+ shipment {
36
+ id
37
+ options
38
+ }
39
+ errors {
40
+ field
41
+ messages
42
+ }
43
+ }
44
+ }
45
+ """,
46
+ operation_name="partial_shipment_update",
47
+ variables={
48
+ "data": {
49
+ "id": shipment_id,
50
+ "options": {
51
+ "insurance": None,
52
+ "signature_confirmation": None,
53
+ "currency": "USD",
54
+ "new_option": "test_value",
55
+ },
56
+ }
57
+ },
58
+ )
59
+
60
+ self.assertResponseNoErrors(response)
61
+ self.assertDictEqual(response.data, OPTIONS_UPDATE_RESPONSE)
62
+
63
+ def test_partial_update_shipper_address_fields(self):
64
+ """Test updating random fields within shipper address"""
65
+ shipment_id = self.shipment["id"]
66
+
67
+ response = self.query(
68
+ """
69
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
70
+ partial_shipment_update(input: $data) {
71
+ shipment {
72
+ id
73
+ shipper {
74
+ id
75
+ company_name
76
+ person_name
77
+ phone_number
78
+ email
79
+ city
80
+ postal_code
81
+ }
82
+ }
83
+ errors {
84
+ field
85
+ messages
86
+ }
87
+ }
88
+ }
89
+ """,
90
+ operation_name="partial_shipment_update",
91
+ variables={
92
+ "data": {
93
+ "id": shipment_id,
94
+ "shipper": {
95
+ "company_name": "Updated Corp Inc.",
96
+ "email": "updated@example.com",
97
+ "phone_number": "555-123-4567",
98
+ },
99
+ }
100
+ },
101
+ )
102
+
103
+ self.assertResponseNoErrors(response)
104
+ self.assertDictEqual(response.data, SHIPPER_UPDATE_RESPONSE)
105
+
106
+ def test_partial_update_recipient_address_fields(self):
107
+ """Test updating random fields within recipient address"""
108
+ shipment_id = self.shipment["id"]
109
+
110
+ response = self.query(
111
+ """
112
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
113
+ partial_shipment_update(input: $data) {
114
+ shipment {
115
+ id
116
+ recipient {
117
+ id
118
+ company_name
119
+ person_name
120
+ address_line1
121
+ city
122
+ state_code
123
+ residential
124
+ }
125
+ }
126
+ errors {
127
+ field
128
+ messages
129
+ }
130
+ }
131
+ }
132
+ """,
133
+ operation_name="partial_shipment_update",
134
+ variables={
135
+ "data": {
136
+ "id": shipment_id,
137
+ "recipient": {
138
+ "person_name": "John Updated",
139
+ "address_line1": "456 Updated St",
140
+ "residential": True,
141
+ },
142
+ }
143
+ },
144
+ )
145
+
146
+ self.assertResponseNoErrors(response)
147
+ self.assertDictEqual(response.data, RECIPIENT_UPDATE_RESPONSE)
148
+
149
+ def test_partial_update_payment_and_metadata(self):
150
+ """Test updating payment information and metadata"""
151
+ shipment_id = self.shipment["id"]
152
+
153
+ response = self.query(
154
+ """
155
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
156
+ partial_shipment_update(input: $data) {
157
+ shipment {
158
+ id
159
+ payment {
160
+ paid_by
161
+ currency
162
+ account_number
163
+ }
164
+ metadata
165
+ reference
166
+ }
167
+ errors {
168
+ field
169
+ messages
170
+ }
171
+ }
172
+ }
173
+ """,
174
+ operation_name="partial_shipment_update",
175
+ variables={
176
+ "data": {
177
+ "id": shipment_id,
178
+ "payment": {"paid_by": "recipient", "account_number": "123456789"},
179
+ "metadata": {"customer_id": "CUST123", "order_number": "ORD456"},
180
+ "reference": "REF789",
181
+ }
182
+ },
183
+ )
184
+
185
+ self.assertResponseNoErrors(response)
186
+ self.assertDictEqual(response.data, PAYMENT_METADATA_UPDATE_RESPONSE)
187
+
188
+ def test_partial_update_parcel_information(self):
189
+ """Test updating parcel information"""
190
+ shipment_id = self.shipment["id"]
191
+
192
+ response = self.query(
193
+ """
194
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
195
+ partial_shipment_update(input: $data) {
196
+ shipment {
197
+ id
198
+ parcels {
199
+ id
200
+ weight
201
+ weight_unit
202
+ package_preset
203
+ description
204
+ reference_number
205
+ }
206
+ }
207
+ errors {
208
+ field
209
+ messages
210
+ }
211
+ }
212
+ }
213
+ """,
214
+ operation_name="partial_shipment_update",
215
+ variables={
216
+ "data": {
217
+ "id": shipment_id,
218
+ "parcels": [
219
+ {
220
+ "weight": 2.5,
221
+ "weight_unit": "LB",
222
+ "description": "Updated parcel description",
223
+ "package_preset": "canadapost_corrugated_medium_box",
224
+ }
225
+ ],
226
+ }
227
+ },
228
+ )
229
+
230
+ self.assertResponseNoErrors(response)
231
+ self.assertDictEqual(response.data, PARCEL_UPDATE_RESPONSE)
232
+
233
+ def test_partial_update_label_type_and_options_merge(self):
234
+ """Test updating label type and ensuring options are properly merged"""
235
+ shipment_id = self.shipment["id"]
236
+
237
+ # First, set some initial options
238
+ self.query(
239
+ """
240
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
241
+ partial_shipment_update(input: $data) {
242
+ shipment {
243
+ id
244
+ options
245
+ }
246
+ }
247
+ }
248
+ """,
249
+ operation_name="partial_shipment_update",
250
+ variables={
251
+ "data": {
252
+ "id": shipment_id,
253
+ "options": {
254
+ "insurance": 100,
255
+ "signature_confirmation": True,
256
+ "currency": "CAD",
257
+ },
258
+ }
259
+ },
260
+ )
261
+
262
+ # Now update label type and add more options
263
+ response = self.query(
264
+ """
265
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
266
+ partial_shipment_update(input: $data) {
267
+ shipment {
268
+ id
269
+ label_type
270
+ options
271
+ }
272
+ errors {
273
+ field
274
+ messages
275
+ }
276
+ }
277
+ }
278
+ """,
279
+ operation_name="partial_shipment_update",
280
+ variables={
281
+ "data": {
282
+ "id": shipment_id,
283
+ "label_type": "ZPL",
284
+ "options": {"delivery_confirmation": True, "priority": "high"},
285
+ }
286
+ },
287
+ )
288
+
289
+ self.assertResponseNoErrors(response)
290
+ self.assertDictEqual(response.data, LABEL_OPTIONS_MERGE_RESPONSE)
291
+
292
+ def test_full_shipment_update_simulation(self):
293
+ """Test updating multiple fields at once to simulate frontend save operation"""
294
+ shipment_id = self.shipment["id"]
295
+
296
+ # Set the shipment ID dynamically in the test data
297
+ full_update_data = FULL_UPDATE_DATA.copy()
298
+ full_update_data["data"]["id"] = shipment_id
299
+
300
+ response = self.query(
301
+ """
302
+ mutation partial_shipment_update($data: PartialShipmentMutationInput!) {
303
+ partial_shipment_update(input: $data) {
304
+ shipment {
305
+ id
306
+ status
307
+ shipper {
308
+ company_name
309
+ person_name
310
+ email
311
+ }
312
+ recipient {
313
+ company_name
314
+ person_name
315
+ email
316
+ }
317
+ parcels {
318
+ weight
319
+ weight_unit
320
+ description
321
+ }
322
+ payment {
323
+ paid_by
324
+ currency
325
+ }
326
+ options
327
+ metadata
328
+ reference
329
+ label_type
330
+ }
331
+ errors {
332
+ field
333
+ messages
334
+ }
335
+ }
336
+ }
337
+ """,
338
+ operation_name="partial_shipment_update",
339
+ variables=full_update_data,
340
+ )
341
+
342
+ self.assertResponseNoErrors(response)
343
+ self.assertDictEqual(response.data, FULL_UPDATE_SIMULATION_RESPONSE)
344
+
345
+
346
+ # Test data
347
+ DRAFT_SHIPMENT_DATA = {
348
+ "recipient": {
349
+ "address_line1": "125 Church St",
350
+ "person_name": "John Poop",
351
+ "company_name": "A corp.",
352
+ "phone_number": "514 000 0000",
353
+ "city": "Moncton",
354
+ "country_code": "CA",
355
+ "postal_code": "E1C4Z8",
356
+ "residential": False,
357
+ "state_code": "NB",
358
+ },
359
+ "shipper": {
360
+ "address_line1": "5840 Oak St",
361
+ "person_name": "Jane Doe",
362
+ "company_name": "B corp.",
363
+ "phone_number": "514 000 9999",
364
+ "city": "Vancouver",
365
+ "country_code": "CA",
366
+ "postal_code": "V6M2V9",
367
+ "residential": False,
368
+ "state_code": "BC",
369
+ },
370
+ "parcels": [
371
+ {
372
+ "weight": 1,
373
+ "weight_unit": "KG",
374
+ "package_preset": "canadapost_corrugated_small_box",
375
+ }
376
+ ],
377
+ "payment": {"currency": "CAD", "paid_by": "sender"},
378
+ # Note: No service specified, so this creates a draft without rates
379
+ }
380
+
381
+ RETURNED_RATES_VALUE = [
382
+ [
383
+ RateDetails(
384
+ carrier_id="canadapost",
385
+ carrier_name="canadapost",
386
+ currency="CAD",
387
+ transit_days=2,
388
+ service="canadapost_priority",
389
+ total_charge=106.71,
390
+ extra_charges=[
391
+ ChargeDetails(amount=13.92, currency="CAD", name="Duty and taxes"),
392
+ ChargeDetails(amount=2.7, currency="CAD", name="Fuel surcharge"),
393
+ ChargeDetails(amount=-11.74, currency="CAD", name="SMB Savings"),
394
+ ChargeDetails(amount=-9.04, currency="CAD", name="Discount"),
395
+ ChargeDetails(amount=101.83, currency="CAD", name="Base surcharge"),
396
+ ],
397
+ )
398
+ ],
399
+ [],
400
+ ]
401
+
402
+ FULL_UPDATE_DATA = {
403
+ "data": {
404
+ "id": None, # Will be set dynamically in the test
405
+ "shipper": {
406
+ "company_name": "Full Update Shipper Corp",
407
+ "person_name": "Updated Shipper",
408
+ "email": "shipper@fullupdate.com",
409
+ },
410
+ "recipient": {
411
+ "company_name": "Full Update Recipient Corp",
412
+ "person_name": "Updated Recipient",
413
+ "email": "recipient@fullupdate.com",
414
+ },
415
+ "parcels": [
416
+ {"weight": 3.0, "weight_unit": "KG", "description": "Full update parcel"}
417
+ ],
418
+ "payment": {"paid_by": "third_party", "currency": "USD"},
419
+ "options": {"insurance": 250, "priority": "express"},
420
+ "metadata": {"full_update": True, "test_case": "full_update_simulation"},
421
+ "reference": "FULL_UPDATE_REF",
422
+ "label_type": "PNG",
423
+ }
424
+ }
425
+
426
+ # Expected responses for assertDictEqual
427
+ OPTIONS_UPDATE_RESPONSE = {
428
+ "data": {
429
+ "partial_shipment_update": {
430
+ "shipment": {
431
+ "id": ANY,
432
+ "options": {
433
+ "shipping_date": ANY,
434
+ "shipment_date": ANY,
435
+ "currency": "USD",
436
+ "new_option": "test_value",
437
+ },
438
+ },
439
+ "errors": None,
440
+ }
441
+ }
442
+ }
443
+
444
+ SHIPPER_UPDATE_RESPONSE = {
445
+ "data": {
446
+ "partial_shipment_update": {
447
+ "shipment": {
448
+ "id": ANY,
449
+ "shipper": {
450
+ "id": ANY,
451
+ "company_name": "Updated Corp Inc.",
452
+ "person_name": "Jane Doe",
453
+ "phone_number": "555-123-4567",
454
+ "email": "updated@example.com",
455
+ "city": "Vancouver",
456
+ "postal_code": "V6M2V9",
457
+ },
458
+ },
459
+ "errors": None,
460
+ }
461
+ }
462
+ }
463
+
464
+ RECIPIENT_UPDATE_RESPONSE = {
465
+ "data": {
466
+ "partial_shipment_update": {
467
+ "shipment": {
468
+ "id": ANY,
469
+ "recipient": {
470
+ "id": ANY,
471
+ "company_name": "A corp.",
472
+ "person_name": "John Updated",
473
+ "address_line1": "456 Updated St",
474
+ "city": "Moncton",
475
+ "state_code": "NB",
476
+ "residential": True,
477
+ },
478
+ },
479
+ "errors": None,
480
+ }
481
+ }
482
+ }
483
+
484
+ PAYMENT_METADATA_UPDATE_RESPONSE = {
485
+ "data": {
486
+ "partial_shipment_update": {
487
+ "shipment": {
488
+ "id": ANY,
489
+ "payment": {
490
+ "paid_by": "recipient",
491
+ "currency": None,
492
+ "account_number": "123456789",
493
+ },
494
+ "metadata": {
495
+ "customer_id": "CUST123",
496
+ "order_number": "ORD456",
497
+ },
498
+ "reference": "REF789",
499
+ },
500
+ "errors": None,
501
+ }
502
+ }
503
+ }
504
+
505
+ PARCEL_UPDATE_RESPONSE = {
506
+ "data": {
507
+ "partial_shipment_update": {
508
+ "shipment": {
509
+ "id": ANY,
510
+ "parcels": [
511
+ {
512
+ "id": ANY,
513
+ "weight": 1.0,
514
+ "weight_unit": "KG",
515
+ "package_preset": "canadapost_corrugated_small_box",
516
+ "description": None,
517
+ "reference_number": ANY,
518
+ },
519
+ {
520
+ "id": ANY,
521
+ "weight": 2.5,
522
+ "weight_unit": "LB",
523
+ "package_preset": "canadapost_corrugated_medium_box",
524
+ "description": "Updated parcel description",
525
+ "reference_number": ANY,
526
+ },
527
+ ],
528
+ },
529
+ "errors": None,
530
+ }
531
+ }
532
+ }
533
+
534
+ LABEL_OPTIONS_MERGE_RESPONSE = {
535
+ "data": {
536
+ "partial_shipment_update": {
537
+ "shipment": {
538
+ "id": ANY,
539
+ "label_type": "ZPL",
540
+ "options": {
541
+ "shipping_date": ANY,
542
+ "shipment_date": ANY,
543
+ "currency": "CAD",
544
+ "insurance": 100,
545
+ "signature_confirmation": True,
546
+ "delivery_confirmation": True,
547
+ "priority": "high",
548
+ },
549
+ },
550
+ "errors": None,
551
+ }
552
+ }
553
+ }
554
+
555
+ FULL_UPDATE_SIMULATION_RESPONSE = {
556
+ "data": {
557
+ "partial_shipment_update": {
558
+ "shipment": {
559
+ "id": ANY,
560
+ "status": "draft",
561
+ "shipper": {
562
+ "company_name": "Full Update Shipper Corp",
563
+ "person_name": "Updated Shipper",
564
+ "email": "shipper@fullupdate.com",
565
+ },
566
+ "recipient": {
567
+ "company_name": "Full Update Recipient Corp",
568
+ "person_name": "Updated Recipient",
569
+ "email": "recipient@fullupdate.com",
570
+ },
571
+ "parcels": [
572
+ {
573
+ "weight": 1.0,
574
+ "weight_unit": "KG",
575
+ "description": None,
576
+ },
577
+ {
578
+ "weight": 3.0,
579
+ "weight_unit": "KG",
580
+ "description": "Full update parcel",
581
+ },
582
+ ],
583
+ "payment": {
584
+ "paid_by": "third_party",
585
+ "currency": "USD",
586
+ },
587
+ "options": {
588
+ "shipping_date": ANY,
589
+ "shipment_date": ANY,
590
+ "insurance": 250,
591
+ "priority": "express",
592
+ },
593
+ "metadata": {
594
+ "full_update": True,
595
+ "test_case": "full_update_simulation",
596
+ },
597
+ "reference": "FULL_UPDATE_REF",
598
+ "label_type": "PNG",
599
+ },
600
+ "errors": None,
601
+ }
602
+ }
603
+ }