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,354 @@
1
+ import karrio.lib as lib
2
+ from unittest.mock import ANY
3
+ from karrio.server.graph.tests.base import GraphTestCase
4
+ import karrio.server.providers.models as providers
5
+
6
+
7
+ class TestRateSheets(GraphTestCase):
8
+ def setUp(self):
9
+ super().setUp()
10
+
11
+ # Create a test rate sheet
12
+ self.rate_sheet = providers.RateSheet.objects.create(
13
+ name="Test Rate Sheet",
14
+ carrier_name="ups",
15
+ slug="test_rate_sheet",
16
+ created_by=self.user,
17
+ )
18
+
19
+ # Create a test service
20
+ self.service = providers.ServiceLevel.objects.create(
21
+ service_name="UPS Standard",
22
+ service_code="ups_standard",
23
+ carrier_service_code="11",
24
+ currency="USD",
25
+ active=True,
26
+ zones=[
27
+ {
28
+ "rate": 10.00,
29
+ "label": "Zone 1",
30
+ "cities": ["New York", "Los Angeles"],
31
+ }
32
+ ],
33
+ created_by=self.user,
34
+ )
35
+ self.rate_sheet.services.add(self.service)
36
+
37
+ def test_query_rate_sheets(self):
38
+ response = self.query(
39
+ """
40
+ query get_rate_sheets {
41
+ rate_sheets {
42
+ edges {
43
+ node {
44
+ id
45
+ name
46
+ carrier_name
47
+ slug
48
+ services {
49
+ id
50
+ service_name
51
+ service_code
52
+ carrier_service_code
53
+ active
54
+ currency
55
+ zones {
56
+ rate
57
+ label
58
+ cities
59
+ postal_codes
60
+ country_codes
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ """,
68
+ operation_name="get_rate_sheets",
69
+ )
70
+ response_data = response.data
71
+
72
+ self.assertResponseNoErrors(response)
73
+ self.assertDictEqual(
74
+ lib.to_dict(response_data),
75
+ RATE_SHEETS_RESPONSE,
76
+ )
77
+
78
+ def test_create_rate_sheet(self):
79
+ response = self.query(
80
+ """
81
+ mutation create_rate_sheet($data: CreateRateSheetMutationInput!) {
82
+ create_rate_sheet(input: $data) {
83
+ rate_sheet {
84
+ id
85
+ name
86
+ carrier_name
87
+ services {
88
+ id
89
+ service_name
90
+ service_code
91
+ currency
92
+ zones {
93
+ rate
94
+ label
95
+ postal_codes
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ """,
102
+ operation_name="create_rate_sheet",
103
+ variables=CREATE_RATE_SHEET_DATA,
104
+ )
105
+ response_data = response.data
106
+
107
+ self.assertResponseNoErrors(response)
108
+ self.assertDictEqual(response_data, CREATE_RATE_SHEET_RESPONSE)
109
+
110
+ def test_update_rate_sheet(self):
111
+ response = self.query(
112
+ """
113
+ mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) {
114
+ update_rate_sheet(input: $data) {
115
+ rate_sheet {
116
+ id
117
+ name
118
+ services {
119
+ id
120
+ service_name
121
+ zones {
122
+ rate
123
+ label
124
+ country_codes
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ """,
131
+ operation_name="update_rate_sheet",
132
+ variables={
133
+ "data": {
134
+ "id": self.rate_sheet.id,
135
+ "name": "Updated Rate Sheet",
136
+ "services": [
137
+ {
138
+ "id": self.service.id,
139
+ "service_name": "Updated Service",
140
+ "zones": [
141
+ {
142
+ "rate": 20.0,
143
+ "label": "Updated Zone",
144
+ "country_codes": ["US", "CA"],
145
+ }
146
+ ],
147
+ }
148
+ ],
149
+ },
150
+ },
151
+ )
152
+ response_data = response.data
153
+
154
+ self.assertResponseNoErrors(response)
155
+ self.assertDictEqual(
156
+ lib.to_dict(response_data),
157
+ UPDATE_RATE_SHEET_RESPONSE,
158
+ )
159
+
160
+ def test_update_service_zone(self):
161
+ response = self.query(
162
+ """
163
+ mutation update_zone($data: UpdateServiceZoneMutationInput!) {
164
+ update_service_zone(input: $data) {
165
+ rate_sheet {
166
+ id
167
+ services {
168
+ id
169
+ zones {
170
+ rate
171
+ label
172
+ country_codes
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ """,
179
+ operation_name="update_zone",
180
+ variables={
181
+ "data": {
182
+ "id": self.rate_sheet.id,
183
+ "service_id": self.service.id,
184
+ **UPDATE_ZONE_DATA["data"],
185
+ },
186
+ },
187
+ )
188
+ response_data = response.data
189
+
190
+ self.assertResponseNoErrors(response)
191
+ self.assertDictEqual(
192
+ lib.to_dict(response_data),
193
+ UPDATE_ZONE_RESPONSE,
194
+ )
195
+
196
+
197
+ RATE_SHEETS_RESPONSE = {
198
+ "data": {
199
+ "rate_sheets": {
200
+ "edges": [
201
+ {
202
+ "node": {
203
+ "carrier_name": "ups",
204
+ "id": ANY,
205
+ "name": "Test Rate Sheet",
206
+ "services": [
207
+ {
208
+ "active": True,
209
+ "carrier_service_code": "11",
210
+ "currency": "USD",
211
+ "id": ANY,
212
+ "service_code": "ups_standard",
213
+ "service_name": "UPS Standard",
214
+ "zones": [
215
+ {
216
+ "cities": ["New York", "Los Angeles"],
217
+ "label": "Zone 1",
218
+ "rate": 10.0,
219
+ }
220
+ ],
221
+ }
222
+ ],
223
+ "slug": "test_rate_sheet",
224
+ }
225
+ }
226
+ ]
227
+ }
228
+ }
229
+ }
230
+
231
+ CREATE_RATE_SHEET_DATA = {
232
+ "data": {
233
+ "name": "New Rate Sheet",
234
+ "carrier_name": "fedex",
235
+ "services": [
236
+ {
237
+ "service_name": "FedEx Ground",
238
+ "service_code": "fedex_ground",
239
+ "carrier_service_code": "FEDEX_GROUND",
240
+ "currency": "USD",
241
+ "zones": [
242
+ {
243
+ "rate": 15.0,
244
+ "label": "Zone A",
245
+ "postal_codes": ["12345", "67890"],
246
+ }
247
+ ],
248
+ }
249
+ ],
250
+ }
251
+ }
252
+
253
+ CREATE_RATE_SHEET_RESPONSE = {
254
+ "data": {
255
+ "create_rate_sheet": {
256
+ "rate_sheet": {
257
+ "id": ANY,
258
+ "name": "New Rate Sheet",
259
+ "carrier_name": "fedex",
260
+ "services": [
261
+ {
262
+ "id": ANY,
263
+ "service_name": "FedEx Ground",
264
+ "service_code": "fedex_ground",
265
+ "currency": "USD",
266
+ "zones": [
267
+ {
268
+ "rate": 15.0,
269
+ "label": "Zone A",
270
+ "postal_codes": ["12345", "67890"],
271
+ }
272
+ ],
273
+ }
274
+ ],
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ UPDATE_RATE_SHEET_DATA = {
281
+ "data": {
282
+ "name": "Updated Rate Sheet",
283
+ "services": [
284
+ {
285
+ "id": ANY, # Will be replaced with actual service ID in test
286
+ "service_name": "Updated Service",
287
+ "zones": [
288
+ {
289
+ "rate": 20.0,
290
+ "label": "Updated Zone",
291
+ "country_codes": ["US", "CA"],
292
+ }
293
+ ],
294
+ }
295
+ ],
296
+ }
297
+ }
298
+
299
+ UPDATE_RATE_SHEET_RESPONSE = {
300
+ "data": {
301
+ "update_rate_sheet": {
302
+ "rate_sheet": {
303
+ "id": ANY,
304
+ "name": "Updated Rate Sheet",
305
+ "services": [
306
+ {
307
+ "id": ANY,
308
+ "service_name": "Updated Service",
309
+ "zones": [
310
+ {
311
+ "country_codes": ["US", "CA"],
312
+ "label": "Updated Zone",
313
+ "rate": 20.0,
314
+ }
315
+ ],
316
+ }
317
+ ],
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ UPDATE_ZONE_DATA = {
324
+ "data": {
325
+ "zone_index": 0,
326
+ "zone": {
327
+ "rate": 25.0,
328
+ "label": "Modified Zone",
329
+ "country_codes": ["MX"],
330
+ },
331
+ }
332
+ }
333
+
334
+ UPDATE_ZONE_RESPONSE = {
335
+ "data": {
336
+ "update_service_zone": {
337
+ "rate_sheet": {
338
+ "id": ANY,
339
+ "services": [
340
+ {
341
+ "id": ANY,
342
+ "zones": [
343
+ {
344
+ "rate": 25.0,
345
+ "label": "Modified Zone",
346
+ "country_codes": ["MX"],
347
+ }
348
+ ],
349
+ }
350
+ ],
351
+ }
352
+ }
353
+ }
354
+ }
@@ -0,0 +1,209 @@
1
+ from unittest.mock import patch, MagicMock
2
+ from karrio.server.graph.tests.base import GraphTestCase
3
+ from django.contrib.auth import get_user_model
4
+
5
+ User = get_user_model()
6
+
7
+
8
+ class TestUserRegistration(GraphTestCase):
9
+
10
+ @patch("karrio.server.conf.settings.ALLOW_SIGNUP", True)
11
+ @patch("karrio.server.conf.settings.EMAIL_ENABLED", False)
12
+ def test_register_user_mutation(self):
13
+ """Test successful user registration"""
14
+ # Ensure user doesn't exist
15
+ User.objects.filter(email="newuser@example.com").delete()
16
+
17
+ response = self.query(
18
+ """
19
+ mutation register_user($data: RegisterUserMutationInput!) {
20
+ register_user(input: $data) {
21
+ user {
22
+ email
23
+ full_name
24
+ is_active
25
+ }
26
+ }
27
+ }
28
+ """,
29
+ operation_name="register_user",
30
+ variables={
31
+ "data": {
32
+ "email": "newuser@example.com",
33
+ "full_name": "New Test User",
34
+ "password1": "TestPassword123!",
35
+ "password2": "TestPassword123!",
36
+ "redirect_url": "http://example.com/email",
37
+ }
38
+ },
39
+ )
40
+
41
+ self.assertResponseNoErrors(response)
42
+ self.assertIsNotNone(response.data["data"]["register_user"]["user"])
43
+ self.assertEqual(
44
+ response.data["data"]["register_user"]["user"]["email"],
45
+ "newuser@example.com",
46
+ )
47
+ self.assertEqual(
48
+ response.data["data"]["register_user"]["user"]["full_name"], "New Test User"
49
+ )
50
+
51
+ # Verify user was created in database
52
+ user = User.objects.get(email="newuser@example.com")
53
+ self.assertEqual(user.full_name, "New Test User")
54
+
55
+ @patch("karrio.server.conf.settings.ALLOW_SIGNUP", True)
56
+ def test_register_user_password_mismatch(self):
57
+ """Test registration fails with mismatched passwords"""
58
+ response = self.query(
59
+ """
60
+ mutation register_user($data: RegisterUserMutationInput!) {
61
+ register_user(input: $data) {
62
+ user {
63
+ email
64
+ }
65
+ }
66
+ }
67
+ """,
68
+ operation_name="register_user",
69
+ variables={
70
+ "data": {
71
+ "email": "mismatch@example.com",
72
+ "full_name": "Mismatch User",
73
+ "password1": "TestPassword123!",
74
+ "password2": "DifferentPassword123!",
75
+ "redirect_url": "http://example.com/email",
76
+ }
77
+ },
78
+ )
79
+
80
+ # Should have errors
81
+ self.assertIsNotNone(response.data.get("errors"))
82
+ self.assertIn("password", str(response.data["errors"][0]))
83
+
84
+ @patch("karrio.server.conf.settings.ALLOW_SIGNUP", True)
85
+ def test_register_user_duplicate_email(self):
86
+ """Test registration fails with duplicate email"""
87
+ # First create a user
88
+ User.objects.create_user(
89
+ email="existing@example.com",
90
+ password="ExistingPass123!",
91
+ full_name="Existing User",
92
+ )
93
+
94
+ response = self.query(
95
+ """
96
+ mutation register_user($data: RegisterUserMutationInput!) {
97
+ register_user(input: $data) {
98
+ user {
99
+ email
100
+ }
101
+ }
102
+ }
103
+ """,
104
+ operation_name="register_user",
105
+ variables={
106
+ "data": {
107
+ "email": "existing@example.com",
108
+ "full_name": "Duplicate User",
109
+ "password1": "TestPassword123!",
110
+ "password2": "TestPassword123!",
111
+ "redirect_url": "http://example.com/email",
112
+ }
113
+ },
114
+ )
115
+
116
+ # Should have errors about duplicate email
117
+ self.assertIsNotNone(response.data.get("errors"))
118
+
119
+ @patch("karrio.server.conf.settings.ALLOW_SIGNUP", False)
120
+ def test_register_user_signup_disabled(self):
121
+ """Test registration fails when signup is disabled"""
122
+ response = self.query(
123
+ """
124
+ mutation register_user($data: RegisterUserMutationInput!) {
125
+ register_user(input: $data) {
126
+ user {
127
+ email
128
+ }
129
+ }
130
+ }
131
+ """,
132
+ operation_name="register_user",
133
+ variables={
134
+ "data": {
135
+ "email": "disabled@example.com",
136
+ "full_name": "Disabled User",
137
+ "password1": "TestPassword123!",
138
+ "password2": "TestPassword123!",
139
+ "redirect_url": "http://example.com/email",
140
+ }
141
+ },
142
+ )
143
+
144
+ # Should have errors about signup not allowed
145
+ self.assertIsNotNone(response.data.get("errors"))
146
+ self.assertIn("Signup is not allowed", str(response.data["errors"][0]))
147
+
148
+
149
+ class TestPasswordReset(GraphTestCase):
150
+
151
+ def setUp(self):
152
+ super().setUp()
153
+ # Create a test user for password reset
154
+ self.reset_user = User.objects.create_user(
155
+ email="resetuser@example.com",
156
+ password="OldPassword123!",
157
+ full_name="Reset User",
158
+ )
159
+
160
+ @patch("django.core.mail.send_mail")
161
+ def test_request_password_reset(self, mock_send_mail):
162
+ """Test requesting a password reset"""
163
+ response = self.query(
164
+ """
165
+ mutation request_password_reset($data: RequestPasswordResetMutationInput!) {
166
+ request_password_reset(input: $data) {
167
+ email
168
+ redirect_url
169
+ }
170
+ }
171
+ """,
172
+ operation_name="request_password_reset",
173
+ variables={
174
+ "data": {
175
+ "email": "resetuser@example.com",
176
+ "redirect_url": "http://example.com/password/reset",
177
+ }
178
+ },
179
+ )
180
+
181
+ self.assertResponseNoErrors(response)
182
+ self.assertEqual(
183
+ response.data["data"]["request_password_reset"]["email"],
184
+ "resetuser@example.com",
185
+ )
186
+
187
+
188
+ class TestEmailConfirmation(GraphTestCase):
189
+
190
+ @patch("karrio.server.graph.schemas.base.mutations.email_verification.verify_token")
191
+ def test_confirm_email(self, mock_verify):
192
+ """Test email confirmation"""
193
+ mock_verify.return_value = (True, None)
194
+
195
+ response = self.query(
196
+ """
197
+ mutation confirm_email($data: ConfirmEmailMutationInput!) {
198
+ confirm_email(input: $data) {
199
+ success
200
+ }
201
+ }
202
+ """,
203
+ operation_name="confirm_email",
204
+ variables={"data": {"token": "test-confirmation-token"}},
205
+ )
206
+
207
+ self.assertResponseNoErrors(response)
208
+ self.assertTrue(response.data["data"]["confirm_email"]["success"])
209
+ mock_verify.assert_called_once_with("test-confirmation-token")