karrio-server-core 2025.5rc12__py3-none-any.whl → 2026.1.1__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.
- karrio/server/core/authentication.py +59 -25
- karrio/server/core/config.py +31 -0
- karrio/server/core/datatypes.py +30 -4
- karrio/server/core/dataunits.py +53 -22
- karrio/server/core/exceptions.py +287 -17
- karrio/server/core/filters.py +14 -0
- karrio/server/core/gateway.py +285 -10
- karrio/server/core/logging.py +403 -0
- karrio/server/core/management/commands/runserver.py +5 -0
- karrio/server/core/middleware.py +104 -2
- karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
- karrio/server/core/models/base.py +34 -1
- karrio/server/core/oauth_validators.py +2 -3
- karrio/server/core/permissions.py +1 -2
- karrio/server/core/serializers.py +183 -10
- karrio/server/core/signals.py +22 -28
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests/__init__.py +27 -0
- karrio/server/core/{tests.py → tests/base.py} +6 -7
- karrio/server/core/tests/test_exception_level.py +159 -0
- karrio/server/core/tests/test_resource_token.py +593 -0
- karrio/server/core/utils.py +688 -38
- karrio/server/core/validators.py +144 -222
- karrio/server/core/views/oauth.py +13 -12
- karrio/server/core/views/references.py +2 -2
- karrio/server/iam/apps.py +1 -4
- karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
- karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
- karrio/server/iam/permissions.py +7 -134
- karrio/server/iam/serializers.py +17 -2
- karrio/server/iam/signals.py +2 -4
- karrio/server/providers/admin.py +1 -1
- karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
- karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
- karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
- karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
- karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
- karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
- karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
- karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
- karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
- karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
- karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
- karrio/server/providers/models/__init__.py +1 -2
- karrio/server/providers/models/carrier.py +103 -18
- karrio/server/providers/models/service.py +188 -1
- karrio/server/providers/models/sheet.py +371 -0
- karrio/server/providers/serializers/base.py +263 -2
- karrio/server/providers/signals.py +2 -4
- karrio/server/providers/templates/providers/oauth_callback.html +105 -0
- karrio/server/providers/tests/__init__.py +5 -0
- karrio/server/providers/tests/test_connections.py +895 -0
- karrio/server/providers/views/carriers.py +1 -3
- karrio/server/providers/views/connections.py +322 -2
- karrio/server/samples.py +1 -1
- karrio/server/serializers/abstract.py +116 -21
- karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
- karrio/server/tracing/models.py +2 -0
- karrio/server/tracing/utils.py +5 -8
- karrio/server/user/migrations/0007_user_metadata.py +25 -0
- karrio/server/user/models.py +38 -23
- karrio/server/user/serializers.py +1 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +1 -1
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +67 -86
- karrio/server/providers/extension/__init__.py +0 -1
- karrio/server/providers/extension/models/__init__.py +0 -1
- karrio/server/providers/extension/models/allied_express.py +0 -22
- karrio/server/providers/extension/models/allied_express_local.py +0 -22
- karrio/server/providers/extension/models/amazon_shipping.py +0 -27
- karrio/server/providers/extension/models/aramex.py +0 -25
- karrio/server/providers/extension/models/asendia_us.py +0 -21
- karrio/server/providers/extension/models/australiapost.py +0 -20
- karrio/server/providers/extension/models/boxknight.py +0 -19
- karrio/server/providers/extension/models/bpost.py +0 -21
- karrio/server/providers/extension/models/canadapost.py +0 -21
- karrio/server/providers/extension/models/canpar.py +0 -19
- karrio/server/providers/extension/models/chronopost.py +0 -22
- karrio/server/providers/extension/models/colissimo.py +0 -22
- karrio/server/providers/extension/models/dhl_express.py +0 -23
- karrio/server/providers/extension/models/dhl_parcel_de.py +0 -25
- karrio/server/providers/extension/models/dhl_poland.py +0 -22
- karrio/server/providers/extension/models/dhl_universal.py +0 -19
- karrio/server/providers/extension/models/dicom.py +0 -20
- karrio/server/providers/extension/models/dpd.py +0 -37
- karrio/server/providers/extension/models/dpdhl.py +0 -26
- karrio/server/providers/extension/models/easypost.py +0 -20
- karrio/server/providers/extension/models/eshipper.py +0 -21
- karrio/server/providers/extension/models/fedex.py +0 -25
- karrio/server/providers/extension/models/fedex_ws.py +0 -24
- karrio/server/providers/extension/models/freightcom.py +0 -21
- karrio/server/providers/extension/models/generic.py +0 -35
- karrio/server/providers/extension/models/geodis.py +0 -22
- karrio/server/providers/extension/models/hay_post.py +0 -22
- karrio/server/providers/extension/models/laposte.py +0 -19
- karrio/server/providers/extension/models/locate2u.py +0 -22
- karrio/server/providers/extension/models/nationex.py +0 -22
- karrio/server/providers/extension/models/purolator.py +0 -21
- karrio/server/providers/extension/models/roadie.py +0 -18
- karrio/server/providers/extension/models/royalmail.py +0 -19
- karrio/server/providers/extension/models/sendle.py +0 -22
- karrio/server/providers/extension/models/tge.py +0 -63
- karrio/server/providers/extension/models/tnt.py +0 -23
- karrio/server/providers/extension/models/ups.py +0 -23
- karrio/server/providers/extension/models/usps.py +0 -23
- karrio/server/providers/extension/models/usps_international.py +0 -23
- karrio/server/providers/extension/models/usps_wt.py +0 -24
- karrio/server/providers/extension/models/usps_wt_international.py +0 -24
- karrio/server/providers/extension/models/zoom2u.py +0 -23
- karrio/server/providers/tests.py +0 -3
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
"""Tests for carrier connection OAuth and Webhook APIs."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, ANY
|
|
5
|
+
from django.urls import reverse
|
|
6
|
+
from rest_framework import status
|
|
7
|
+
from karrio.core.models import (
|
|
8
|
+
OAuthAuthorizeRequest,
|
|
9
|
+
WebhookRegistrationDetails,
|
|
10
|
+
ConfirmationDetails,
|
|
11
|
+
Message,
|
|
12
|
+
)
|
|
13
|
+
from karrio.server.core.tests import APITestCase
|
|
14
|
+
import karrio.server.providers.models as providers
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestConnectionOAuthAuthorize(APITestCase):
|
|
18
|
+
"""Tests for OAuth authorization flow endpoints."""
|
|
19
|
+
|
|
20
|
+
def test_oauth_authorize(self):
|
|
21
|
+
"""Test POST /v1/connections/oauth/{carrier_name}/authorize."""
|
|
22
|
+
url = reverse(
|
|
23
|
+
"karrio.server.providers:connection-oauth-authorize",
|
|
24
|
+
kwargs=dict(carrier_name="teleship"),
|
|
25
|
+
)
|
|
26
|
+
data = OAUTH_AUTHORIZE_DATA
|
|
27
|
+
|
|
28
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
29
|
+
mock.return_value = OAUTH_AUTHORIZE_RETURNED_VALUE
|
|
30
|
+
response = self.client.post(url, data)
|
|
31
|
+
response_data = json.loads(response.content)
|
|
32
|
+
|
|
33
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
34
|
+
self.assertDictEqual(response_data, OAUTH_AUTHORIZE_RESPONSE)
|
|
35
|
+
|
|
36
|
+
def test_oauth_authorize_error(self):
|
|
37
|
+
"""Test OAuth authorize with configuration error."""
|
|
38
|
+
url = reverse(
|
|
39
|
+
"karrio.server.providers:connection-oauth-authorize",
|
|
40
|
+
kwargs=dict(carrier_name="teleship"),
|
|
41
|
+
)
|
|
42
|
+
data = {"state": "test_state", "options": {}}
|
|
43
|
+
|
|
44
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
45
|
+
mock.return_value = OAUTH_AUTHORIZE_ERROR_RETURNED_VALUE
|
|
46
|
+
response = self.client.post(url, data)
|
|
47
|
+
response_data = json.loads(response.content)
|
|
48
|
+
|
|
49
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
50
|
+
self.assertDictEqual(response_data, OAUTH_AUTHORIZE_ERROR_RESPONSE)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestConnectionOAuthCallback(APITestCase):
|
|
54
|
+
"""Tests for OAuth callback handling."""
|
|
55
|
+
|
|
56
|
+
def test_oauth_callback_get(self):
|
|
57
|
+
"""Test GET /v1/connections/oauth/{carrier_name}/callback with valid credentials."""
|
|
58
|
+
url = reverse(
|
|
59
|
+
"karrio.server.providers:connection-oauth-callback",
|
|
60
|
+
kwargs=dict(carrier_name="teleship"),
|
|
61
|
+
)
|
|
62
|
+
query_params = (
|
|
63
|
+
"?code=auth_code_12345"
|
|
64
|
+
"&account_client_id=user_client_abc"
|
|
65
|
+
"&account_client_secret=user_secret_xyz"
|
|
66
|
+
"&state=eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
70
|
+
mock.return_value = OAUTH_CALLBACK_RETURNED_VALUE
|
|
71
|
+
response = self.client.get(f"{url}{query_params}")
|
|
72
|
+
response_data = json.loads(response.content)
|
|
73
|
+
|
|
74
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
75
|
+
self.assertDictEqual(response_data, OAUTH_CALLBACK_RESPONSE)
|
|
76
|
+
|
|
77
|
+
def test_oauth_callback_post(self):
|
|
78
|
+
"""Test POST /v1/connections/oauth/{carrier_name}/callback with valid credentials."""
|
|
79
|
+
url = reverse(
|
|
80
|
+
"karrio.server.providers:connection-oauth-callback",
|
|
81
|
+
kwargs=dict(carrier_name="teleship"),
|
|
82
|
+
)
|
|
83
|
+
query_params = (
|
|
84
|
+
"?code=auth_code_12345"
|
|
85
|
+
"&account_client_id=user_client_abc"
|
|
86
|
+
"&account_client_secret=user_secret_xyz"
|
|
87
|
+
"&state=eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
91
|
+
mock.return_value = OAUTH_CALLBACK_RETURNED_VALUE
|
|
92
|
+
response = self.client.post(f"{url}{query_params}", {}, format="json")
|
|
93
|
+
response_data = json.loads(response.content)
|
|
94
|
+
|
|
95
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
96
|
+
self.assertDictEqual(response_data, OAUTH_CALLBACK_RESPONSE)
|
|
97
|
+
|
|
98
|
+
def test_oauth_callback_error(self):
|
|
99
|
+
"""Test OAuth callback with missing authorization code."""
|
|
100
|
+
url = reverse(
|
|
101
|
+
"karrio.server.providers:connection-oauth-callback",
|
|
102
|
+
kwargs=dict(carrier_name="teleship"),
|
|
103
|
+
)
|
|
104
|
+
query_params = (
|
|
105
|
+
"?account_client_id=user_client_abc"
|
|
106
|
+
"&account_client_secret=user_secret_xyz"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
110
|
+
mock.return_value = OAUTH_CALLBACK_ERROR_RETURNED_VALUE
|
|
111
|
+
response = self.client.get(f"{url}{query_params}")
|
|
112
|
+
response_data = json.loads(response.content)
|
|
113
|
+
|
|
114
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
115
|
+
self.assertDictEqual(response_data, OAUTH_CALLBACK_ERROR_RESPONSE)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestConnectionWebhookRegister(APITestCase):
|
|
119
|
+
"""Tests for webhook registration."""
|
|
120
|
+
|
|
121
|
+
def setUp(self):
|
|
122
|
+
super().setUp()
|
|
123
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
124
|
+
response = self.client.post(url, TELESHIP_CONNECTION_DATA, format="json")
|
|
125
|
+
self.teleship_carrier_pk = json.loads(response.content)["id"]
|
|
126
|
+
|
|
127
|
+
def test_webhook_register(self):
|
|
128
|
+
"""Test POST /v1/connections/webhook/{pk}/register."""
|
|
129
|
+
url = reverse(
|
|
130
|
+
"karrio.server.providers:connection-webhook-register",
|
|
131
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
132
|
+
)
|
|
133
|
+
data = WEBHOOK_REGISTER_DATA
|
|
134
|
+
|
|
135
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
136
|
+
mock.return_value = WEBHOOK_REGISTER_RETURNED_VALUE
|
|
137
|
+
response = self.client.post(url, data)
|
|
138
|
+
response_data = json.loads(response.content)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
141
|
+
self.assertDictEqual(response_data, WEBHOOK_REGISTER_RESPONSE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestConnectionWebhookDeregister(APITestCase):
|
|
145
|
+
"""Tests for webhook deregistration."""
|
|
146
|
+
|
|
147
|
+
def setUp(self):
|
|
148
|
+
super().setUp()
|
|
149
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
150
|
+
response = self.client.post(
|
|
151
|
+
url, TELESHIP_CONNECTION_WITH_WEBHOOK_DATA, format="json"
|
|
152
|
+
)
|
|
153
|
+
self.teleship_carrier_pk = json.loads(response.content)["id"]
|
|
154
|
+
|
|
155
|
+
def test_webhook_deregister(self):
|
|
156
|
+
"""Test POST /v1/connections/webhook/{pk}/deregister."""
|
|
157
|
+
url = reverse(
|
|
158
|
+
"karrio.server.providers:connection-webhook-deregister",
|
|
159
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
163
|
+
mock.return_value = WEBHOOK_DEREGISTER_RETURNED_VALUE
|
|
164
|
+
response = self.client.post(url, {})
|
|
165
|
+
response_data = json.loads(response.content)
|
|
166
|
+
|
|
167
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
168
|
+
self.assertDictEqual(response_data, WEBHOOK_DEREGISTER_RESPONSE)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TestConnectionWebhookDisconnect(APITestCase):
|
|
172
|
+
"""Tests for force disconnecting webhook (local only)."""
|
|
173
|
+
|
|
174
|
+
def setUp(self):
|
|
175
|
+
super().setUp()
|
|
176
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
177
|
+
response = self.client.post(
|
|
178
|
+
url, TELESHIP_CONNECTION_WITH_WEBHOOK_DATA, format="json"
|
|
179
|
+
)
|
|
180
|
+
self.teleship_carrier_pk = json.loads(response.content)["id"]
|
|
181
|
+
|
|
182
|
+
def test_webhook_disconnect(self):
|
|
183
|
+
"""Test POST /v1/connections/webhook/{pk}/disconnect."""
|
|
184
|
+
url = reverse(
|
|
185
|
+
"karrio.server.providers:connection-webhook-disconnect",
|
|
186
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
response = self.client.post(url, {})
|
|
190
|
+
response_data = json.loads(response.content)
|
|
191
|
+
|
|
192
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
193
|
+
self.assertDictEqual(response_data, WEBHOOK_DISCONNECT_RESPONSE)
|
|
194
|
+
|
|
195
|
+
# Verify the webhook config was cleared
|
|
196
|
+
detail_url = reverse(
|
|
197
|
+
"karrio.server.providers:carrier-connection-details",
|
|
198
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
199
|
+
)
|
|
200
|
+
detail_response = self.client.get(detail_url)
|
|
201
|
+
detail_data = json.loads(detail_response.content)
|
|
202
|
+
self.assertIsNone(detail_data["config"].get("webhook_id"))
|
|
203
|
+
self.assertIsNone(detail_data["config"].get("webhook_secret"))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TestConnectionWebhookEvent(APITestCase):
|
|
207
|
+
"""Tests for inbound webhook event processing."""
|
|
208
|
+
|
|
209
|
+
def setUp(self):
|
|
210
|
+
super().setUp()
|
|
211
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
212
|
+
response = self.client.post(
|
|
213
|
+
url, TELESHIP_CONNECTION_WITH_WEBHOOK_DATA, format="json"
|
|
214
|
+
)
|
|
215
|
+
self.teleship_carrier_pk = json.loads(response.content)["id"]
|
|
216
|
+
|
|
217
|
+
def test_webhook_event(self):
|
|
218
|
+
"""Test POST /v1/connections/webhook/{pk}/events with valid event."""
|
|
219
|
+
url = reverse(
|
|
220
|
+
"karrio.server.providers:connection-webhook-event",
|
|
221
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
from karrio.server.core import datatypes
|
|
225
|
+
|
|
226
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
227
|
+
mock.return_value = (
|
|
228
|
+
datatypes.WebhookEventDetails(
|
|
229
|
+
carrier_name="teleship",
|
|
230
|
+
carrier_id="teleship_connection",
|
|
231
|
+
tracking_number="TELESHIP12345678901",
|
|
232
|
+
shipment_identifier="shp-12345",
|
|
233
|
+
tracking=None,
|
|
234
|
+
),
|
|
235
|
+
[],
|
|
236
|
+
)
|
|
237
|
+
client = self.client_class()
|
|
238
|
+
response = client.post(url, WEBHOOK_EVENT_DATA, format="json")
|
|
239
|
+
|
|
240
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
241
|
+
|
|
242
|
+
def test_webhook_event_not_found(self):
|
|
243
|
+
"""Test webhook event with non-existent connection."""
|
|
244
|
+
url = reverse(
|
|
245
|
+
"karrio.server.providers:connection-webhook-event",
|
|
246
|
+
kwargs=dict(pk="non-existent-pk"),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
client = self.client_class()
|
|
250
|
+
response = client.post(url, WEBHOOK_EVENT_DATA, format="json")
|
|
251
|
+
response_data = json.loads(response.content)
|
|
252
|
+
|
|
253
|
+
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
254
|
+
self.assertDictEqual(response_data, WEBHOOK_EVENT_NOT_FOUND_RESPONSE)
|
|
255
|
+
|
|
256
|
+
def test_webhook_event_signature_error(self):
|
|
257
|
+
"""Test webhook event with invalid signature."""
|
|
258
|
+
url = reverse(
|
|
259
|
+
"karrio.server.providers:connection-webhook-event",
|
|
260
|
+
kwargs=dict(pk=self.teleship_carrier_pk),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
from karrio.server.core import datatypes
|
|
264
|
+
|
|
265
|
+
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
266
|
+
mock.return_value = (
|
|
267
|
+
None,
|
|
268
|
+
[
|
|
269
|
+
datatypes.Message(
|
|
270
|
+
carrier_name="teleship",
|
|
271
|
+
carrier_id="teleship_connection",
|
|
272
|
+
code="SIGNATURE_INVALID",
|
|
273
|
+
message="Webhook signature verification failed",
|
|
274
|
+
)
|
|
275
|
+
],
|
|
276
|
+
)
|
|
277
|
+
client = self.client_class()
|
|
278
|
+
response = client.post(
|
|
279
|
+
url,
|
|
280
|
+
WEBHOOK_EVENT_DATA,
|
|
281
|
+
format="json",
|
|
282
|
+
HTTP_X_TELESHIP_SIGNATURE="invalid_signature",
|
|
283
|
+
)
|
|
284
|
+
response_data = json.loads(response.content)
|
|
285
|
+
|
|
286
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
287
|
+
self.assertDictEqual(response_data, WEBHOOK_EVENT_SIGNATURE_ERROR_RESPONSE)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class TestConnectionList(APITestCase):
|
|
291
|
+
"""Tests for listing carrier connections (GET /v1/connections)."""
|
|
292
|
+
|
|
293
|
+
def setUp(self):
|
|
294
|
+
super().setUp()
|
|
295
|
+
self.system_carrier = providers.Carrier.objects.create(
|
|
296
|
+
carrier_code="usps",
|
|
297
|
+
carrier_id="usps_system",
|
|
298
|
+
test_mode=True,
|
|
299
|
+
active=True,
|
|
300
|
+
is_system=True,
|
|
301
|
+
created_by=None,
|
|
302
|
+
credentials=dict(
|
|
303
|
+
client_id="system_client",
|
|
304
|
+
client_secret="system_secret",
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
# Grant the test user access to the system carrier
|
|
308
|
+
self.system_carrier.active_users.add(self.user)
|
|
309
|
+
|
|
310
|
+
def test_list_connections(self):
|
|
311
|
+
"""Test GET /v1/connections returns user and system connections."""
|
|
312
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
313
|
+
|
|
314
|
+
response = self.client.get(url)
|
|
315
|
+
response_data = json.loads(response.content)
|
|
316
|
+
|
|
317
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
318
|
+
self.assertIn("results", response_data)
|
|
319
|
+
carrier_ids = [c["carrier_id"] for c in response_data["results"]]
|
|
320
|
+
self.assertIn("canadapost", carrier_ids)
|
|
321
|
+
self.assertIn("ups_package", carrier_ids)
|
|
322
|
+
self.assertIn("fedex_express", carrier_ids)
|
|
323
|
+
self.assertIn("usps_system", carrier_ids)
|
|
324
|
+
|
|
325
|
+
def test_list_connections_filter_by_carrier_name(self):
|
|
326
|
+
"""Test filtering connections by carrier_name."""
|
|
327
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
328
|
+
|
|
329
|
+
response = self.client.get(f"{url}?carrier_name=canadapost")
|
|
330
|
+
response_data = json.loads(response.content)
|
|
331
|
+
|
|
332
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
333
|
+
self.assertEqual(len(response_data["results"]), 1)
|
|
334
|
+
self.assertEqual(response_data["results"][0]["carrier_name"], "canadapost")
|
|
335
|
+
|
|
336
|
+
def test_list_connections_filter_by_active(self):
|
|
337
|
+
"""Test filtering connections by active status."""
|
|
338
|
+
list_url = reverse("karrio.server.providers:carrier-connection-list")
|
|
339
|
+
self.client.post(list_url, INACTIVE_CONNECTION_DATA, format="json")
|
|
340
|
+
|
|
341
|
+
response = self.client.get(f"{list_url}?active=true")
|
|
342
|
+
response_data = json.loads(response.content)
|
|
343
|
+
|
|
344
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
345
|
+
for connection in response_data["results"]:
|
|
346
|
+
self.assertTrue(connection["active"])
|
|
347
|
+
|
|
348
|
+
def test_system_connections_hide_credentials(self):
|
|
349
|
+
"""Test that system connections don't expose credentials."""
|
|
350
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
351
|
+
|
|
352
|
+
response = self.client.get(url)
|
|
353
|
+
response_data = json.loads(response.content)
|
|
354
|
+
|
|
355
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
356
|
+
for connection in response_data["results"]:
|
|
357
|
+
if connection.get("is_system"):
|
|
358
|
+
self.assertIsNone(connection.get("credentials"))
|
|
359
|
+
|
|
360
|
+
def test_user_connections_show_credentials(self):
|
|
361
|
+
"""Test that user connections expose credentials to their owner."""
|
|
362
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
363
|
+
|
|
364
|
+
response = self.client.get(url)
|
|
365
|
+
response_data = json.loads(response.content)
|
|
366
|
+
|
|
367
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
368
|
+
for connection in response_data["results"]:
|
|
369
|
+
if not connection.get("is_system"):
|
|
370
|
+
self.assertIsNotNone(connection.get("credentials"))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class TestConnectionCreate(APITestCase):
|
|
374
|
+
"""Tests for creating carrier connections (POST /v1/connections)."""
|
|
375
|
+
|
|
376
|
+
def test_create_connection(self):
|
|
377
|
+
"""Test POST /v1/connections creates a new connection."""
|
|
378
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
379
|
+
|
|
380
|
+
response = self.client.post(url, SENDLE_CONNECTION_DATA, format="json")
|
|
381
|
+
response_data = json.loads(response.content)
|
|
382
|
+
|
|
383
|
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
384
|
+
self.assertDictEqual(response_data, SENDLE_CONNECTION_RESPONSE)
|
|
385
|
+
|
|
386
|
+
def test_create_connection_with_config(self):
|
|
387
|
+
"""Test creating connection with additional config."""
|
|
388
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
389
|
+
|
|
390
|
+
response = self.client.post(
|
|
391
|
+
url, SENDLE_CONNECTION_WITH_CONFIG_DATA, format="json"
|
|
392
|
+
)
|
|
393
|
+
response_data = json.loads(response.content)
|
|
394
|
+
|
|
395
|
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
396
|
+
self.assertDictEqual(response_data, SENDLE_CONNECTION_WITH_CONFIG_RESPONSE)
|
|
397
|
+
|
|
398
|
+
def test_create_connection_invalid_carrier(self):
|
|
399
|
+
"""Test creating connection with invalid carrier name."""
|
|
400
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
401
|
+
|
|
402
|
+
response = self.client.post(url, INVALID_CONNECTION_DATA, format="json")
|
|
403
|
+
|
|
404
|
+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class TestConnectionDetail(APITestCase):
|
|
408
|
+
"""Tests for retrieving, updating, and deleting carrier connections."""
|
|
409
|
+
|
|
410
|
+
def test_retrieve_connection(self):
|
|
411
|
+
"""Test GET /v1/connections/{pk} retrieves connection details."""
|
|
412
|
+
url = reverse(
|
|
413
|
+
"karrio.server.providers:carrier-connection-details",
|
|
414
|
+
kwargs=dict(pk=self.carrier.pk),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
response = self.client.get(url)
|
|
418
|
+
response_data = json.loads(response.content)
|
|
419
|
+
|
|
420
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
421
|
+
self.assertEqual(response_data["carrier_name"], "canadapost")
|
|
422
|
+
self.assertEqual(response_data["carrier_id"], "canadapost")
|
|
423
|
+
|
|
424
|
+
def test_update_connection(self):
|
|
425
|
+
"""Test PATCH /v1/connections/{pk} updates connection."""
|
|
426
|
+
url = reverse(
|
|
427
|
+
"karrio.server.providers:carrier-connection-details",
|
|
428
|
+
kwargs=dict(pk=self.carrier.pk),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
response = self.client.patch(url, CONNECTION_UPDATE_DATA, format="json")
|
|
432
|
+
response_data = json.loads(response.content)
|
|
433
|
+
|
|
434
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
435
|
+
self.assertEqual(response_data["carrier_id"], "canadapost_updated")
|
|
436
|
+
self.assertFalse(response_data["active"])
|
|
437
|
+
|
|
438
|
+
def test_update_connection_credentials(self):
|
|
439
|
+
"""Test updating connection credentials."""
|
|
440
|
+
url = reverse(
|
|
441
|
+
"karrio.server.providers:carrier-connection-details",
|
|
442
|
+
kwargs=dict(pk=self.carrier.pk),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
response = self.client.patch(url, CREDENTIALS_UPDATE_DATA, format="json")
|
|
446
|
+
response_data = json.loads(response.content)
|
|
447
|
+
|
|
448
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
449
|
+
self.assertEqual(response_data["credentials"]["username"], "new_username")
|
|
450
|
+
self.assertEqual(response_data["credentials"]["password"], "new_password")
|
|
451
|
+
|
|
452
|
+
def test_delete_connection(self):
|
|
453
|
+
"""Test DELETE /v1/connections/{pk} removes connection."""
|
|
454
|
+
list_url = reverse("karrio.server.providers:carrier-connection-list")
|
|
455
|
+
create_response = self.client.post(
|
|
456
|
+
list_url, SENDLE_TO_DELETE_DATA, format="json"
|
|
457
|
+
)
|
|
458
|
+
connection_pk = json.loads(create_response.content)["id"]
|
|
459
|
+
|
|
460
|
+
url = reverse(
|
|
461
|
+
"karrio.server.providers:carrier-connection-details",
|
|
462
|
+
kwargs=dict(pk=connection_pk),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
response = self.client.delete(url)
|
|
466
|
+
|
|
467
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
468
|
+
get_response = self.client.get(url)
|
|
469
|
+
self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
|
|
470
|
+
|
|
471
|
+
def test_superuser_can_delete_any_connection(self):
|
|
472
|
+
"""Test that superuser can delete any connection."""
|
|
473
|
+
from django.contrib.auth import get_user_model
|
|
474
|
+
|
|
475
|
+
other_user = get_user_model().objects.create_user(
|
|
476
|
+
"other@example.com", "password456"
|
|
477
|
+
)
|
|
478
|
+
other_connection = providers.Carrier.objects.create(
|
|
479
|
+
carrier_code="sendle",
|
|
480
|
+
carrier_id="other_user_sendle",
|
|
481
|
+
test_mode=True,
|
|
482
|
+
active=True,
|
|
483
|
+
created_by=other_user,
|
|
484
|
+
credentials=dict(sendle_id="test", api_key="test"),
|
|
485
|
+
)
|
|
486
|
+
url = reverse(
|
|
487
|
+
"karrio.server.providers:carrier-connection-details",
|
|
488
|
+
kwargs=dict(pk=other_connection.pk),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
response = self.client.delete(url)
|
|
492
|
+
|
|
493
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
494
|
+
self.assertFalse(
|
|
495
|
+
providers.Carrier.objects.filter(pk=other_connection.pk).exists()
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class TestConnectionPagination(APITestCase):
|
|
500
|
+
"""Tests for connection list pagination."""
|
|
501
|
+
|
|
502
|
+
def setUp(self):
|
|
503
|
+
super().setUp()
|
|
504
|
+
list_url = reverse("karrio.server.providers:carrier-connection-list")
|
|
505
|
+
for i in range(25):
|
|
506
|
+
self.client.post(
|
|
507
|
+
list_url,
|
|
508
|
+
{
|
|
509
|
+
"carrier_name": "sendle",
|
|
510
|
+
"carrier_id": f"sendle_paginated_{i}",
|
|
511
|
+
"credentials": {"sendle_id": f"test_{i}", "api_key": f"key_{i}"},
|
|
512
|
+
},
|
|
513
|
+
format="json",
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
def test_default_pagination(self):
|
|
517
|
+
"""Test that connections are paginated by default."""
|
|
518
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
519
|
+
|
|
520
|
+
response = self.client.get(url)
|
|
521
|
+
response_data = json.loads(response.content)
|
|
522
|
+
|
|
523
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
524
|
+
self.assertIn("count", response_data)
|
|
525
|
+
self.assertIn("results", response_data)
|
|
526
|
+
self.assertLessEqual(len(response_data["results"]), 20)
|
|
527
|
+
|
|
528
|
+
def test_custom_limit(self):
|
|
529
|
+
"""Test custom pagination limit."""
|
|
530
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
531
|
+
|
|
532
|
+
response = self.client.get(f"{url}?limit=5")
|
|
533
|
+
response_data = json.loads(response.content)
|
|
534
|
+
|
|
535
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
536
|
+
self.assertEqual(len(response_data["results"]), 5)
|
|
537
|
+
|
|
538
|
+
def test_pagination_offset(self):
|
|
539
|
+
"""Test pagination with offset."""
|
|
540
|
+
url = reverse("karrio.server.providers:carrier-connection-list")
|
|
541
|
+
|
|
542
|
+
response1 = self.client.get(f"{url}?limit=5")
|
|
543
|
+
data1 = json.loads(response1.content)
|
|
544
|
+
|
|
545
|
+
response2 = self.client.get(f"{url}?limit=5&offset=5")
|
|
546
|
+
data2 = json.loads(response2.content)
|
|
547
|
+
|
|
548
|
+
ids1 = {c["id"] for c in data1["results"]}
|
|
549
|
+
ids2 = {c["id"] for c in data2["results"]}
|
|
550
|
+
self.assertEqual(len(ids1.intersection(ids2)), 0)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
if __name__ == "__main__":
|
|
554
|
+
import unittest
|
|
555
|
+
|
|
556
|
+
unittest.main()
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
# =============================================================================
|
|
560
|
+
# TEST FIXTURES
|
|
561
|
+
# =============================================================================
|
|
562
|
+
|
|
563
|
+
# OAuth Authorize
|
|
564
|
+
OAUTH_AUTHORIZE_DATA = {
|
|
565
|
+
"state": "eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9",
|
|
566
|
+
"options": {"scope": "read_accounts"},
|
|
567
|
+
"frontend_url": "https://app.karrio.io/oauth/callback",
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
OAUTH_AUTHORIZE_RETURNED_VALUE = (
|
|
571
|
+
OAuthAuthorizeRequest(
|
|
572
|
+
carrier_name="teleship",
|
|
573
|
+
authorization_url="https://api.teleship.com/oauth/authorize?clientId=test&redirectUri=https%3A%2F%2Fapi.karrio.io%2Fv1%2Fconnections%2Foauth%2Fteleship%2Fcallback&state=eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9",
|
|
574
|
+
meta={"scope": "read_accounts"},
|
|
575
|
+
),
|
|
576
|
+
[],
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
OAUTH_AUTHORIZE_RESPONSE = {
|
|
580
|
+
"operation": "OAuth authorize",
|
|
581
|
+
"request": {
|
|
582
|
+
"carrier_name": "teleship",
|
|
583
|
+
"authorization_url": "https://api.teleship.com/oauth/authorize?clientId=test&redirectUri=https%3A%2F%2Fapi.karrio.io%2Fv1%2Fconnections%2Foauth%2Fteleship%2Fcallback&state=eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9",
|
|
584
|
+
"meta": {"scope": "read_accounts"},
|
|
585
|
+
},
|
|
586
|
+
"frontend_url": "https://app.karrio.io/oauth/callback",
|
|
587
|
+
"messages": [],
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
OAUTH_AUTHORIZE_ERROR_RETURNED_VALUE = (
|
|
591
|
+
OAuthAuthorizeRequest(
|
|
592
|
+
carrier_name="teleship",
|
|
593
|
+
authorization_url="",
|
|
594
|
+
meta={},
|
|
595
|
+
),
|
|
596
|
+
[
|
|
597
|
+
Message(
|
|
598
|
+
carrier_name="teleship",
|
|
599
|
+
carrier_id="teleship",
|
|
600
|
+
code="OAUTH_CONFIG_ERROR",
|
|
601
|
+
message="OAuth not configured. Please set TELESHIP_OAUTH_CLIENT_ID environment variable.",
|
|
602
|
+
)
|
|
603
|
+
],
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
OAUTH_AUTHORIZE_ERROR_RESPONSE = {
|
|
607
|
+
"operation": "OAuth authorize",
|
|
608
|
+
"request": {
|
|
609
|
+
"carrier_name": "teleship",
|
|
610
|
+
"meta": {},
|
|
611
|
+
},
|
|
612
|
+
"frontend_url": None,
|
|
613
|
+
"messages": [
|
|
614
|
+
{
|
|
615
|
+
"carrier_name": "teleship",
|
|
616
|
+
"carrier_id": "teleship",
|
|
617
|
+
"code": "OAUTH_CONFIG_ERROR",
|
|
618
|
+
"message": "OAuth not configured. Please set TELESHIP_OAUTH_CLIENT_ID environment variable.",
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# OAuth Callback
|
|
624
|
+
OAUTH_CALLBACK_RETURNED_VALUE = (
|
|
625
|
+
{"client_id": "user_client_abc", "client_secret": "user_secret_xyz"},
|
|
626
|
+
[],
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
OAUTH_CALLBACK_RESPONSE = {
|
|
630
|
+
"type": "oauth_callback",
|
|
631
|
+
"success": True,
|
|
632
|
+
"carrier_name": "teleship",
|
|
633
|
+
"credentials": {"client_id": "user_client_abc", "client_secret": "user_secret_xyz"},
|
|
634
|
+
"state": "eyJjb25uZWN0aW9uX2lkIjogIjEyMyJ9",
|
|
635
|
+
"messages": [],
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
OAUTH_CALLBACK_ERROR_RETURNED_VALUE = (
|
|
639
|
+
None,
|
|
640
|
+
[
|
|
641
|
+
Message(
|
|
642
|
+
carrier_name="teleship",
|
|
643
|
+
carrier_id="teleship",
|
|
644
|
+
code="OAUTH_CALLBACK_ERROR",
|
|
645
|
+
message="Missing authorization code in callback",
|
|
646
|
+
)
|
|
647
|
+
],
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
OAUTH_CALLBACK_ERROR_RESPONSE = {
|
|
651
|
+
"type": "oauth_callback",
|
|
652
|
+
"success": False,
|
|
653
|
+
"carrier_name": "teleship",
|
|
654
|
+
"credentials": None,
|
|
655
|
+
"state": None,
|
|
656
|
+
"messages": [
|
|
657
|
+
{
|
|
658
|
+
"carrier_name": "teleship",
|
|
659
|
+
"carrier_id": "teleship",
|
|
660
|
+
"code": "OAUTH_CALLBACK_ERROR",
|
|
661
|
+
"message": "Missing authorization code in callback",
|
|
662
|
+
}
|
|
663
|
+
],
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
# Teleship Connection Data
|
|
667
|
+
TELESHIP_CONNECTION_DATA = {
|
|
668
|
+
"carrier_name": "teleship",
|
|
669
|
+
"carrier_id": "teleship_connection",
|
|
670
|
+
"credentials": {
|
|
671
|
+
"client_id": "test_client",
|
|
672
|
+
"client_secret": "test_secret",
|
|
673
|
+
},
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
TELESHIP_CONNECTION_WITH_WEBHOOK_DATA = {
|
|
677
|
+
"carrier_name": "teleship",
|
|
678
|
+
"carrier_id": "teleship_connection",
|
|
679
|
+
"credentials": {
|
|
680
|
+
"client_id": "test_client",
|
|
681
|
+
"client_secret": "test_secret",
|
|
682
|
+
},
|
|
683
|
+
"config": {
|
|
684
|
+
"webhook_id": "WHK-12345",
|
|
685
|
+
"webhook_secret": "whsec_abc123xyz789",
|
|
686
|
+
"webhook_url": "https://api.karrio.io/v1/connections/webhook/test/events",
|
|
687
|
+
},
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
# Webhook Register
|
|
691
|
+
WEBHOOK_REGISTER_DATA = {
|
|
692
|
+
"webhook_url": "https://api.karrio.io/v1/connections/webhook/test/events",
|
|
693
|
+
"description": "Karrio webhook for tracking updates",
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
WEBHOOK_REGISTER_RETURNED_VALUE = (
|
|
697
|
+
WebhookRegistrationDetails(
|
|
698
|
+
carrier_id="teleship_connection",
|
|
699
|
+
carrier_name="teleship",
|
|
700
|
+
webhook_identifier="WHK-12345",
|
|
701
|
+
secret="whsec_abc123xyz789",
|
|
702
|
+
meta={
|
|
703
|
+
"url": "https://api.karrio.io/v1/connections/webhook/test/events",
|
|
704
|
+
"enabledEvents": ["shipment.created", "shipment.updated"],
|
|
705
|
+
},
|
|
706
|
+
),
|
|
707
|
+
[],
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
WEBHOOK_REGISTER_RESPONSE = {
|
|
711
|
+
"operation": "Webhook registration",
|
|
712
|
+
"success": True,
|
|
713
|
+
"carrier_name": "teleship",
|
|
714
|
+
"carrier_id": "teleship_connection",
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# Webhook Deregister
|
|
718
|
+
WEBHOOK_DEREGISTER_RETURNED_VALUE = (
|
|
719
|
+
ConfirmationDetails(
|
|
720
|
+
carrier_id="teleship_connection",
|
|
721
|
+
carrier_name="teleship",
|
|
722
|
+
success=True,
|
|
723
|
+
operation="webhook_deregistration",
|
|
724
|
+
),
|
|
725
|
+
[],
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
WEBHOOK_DEREGISTER_RESPONSE = {
|
|
729
|
+
"operation": "Webhook deregistration",
|
|
730
|
+
"success": True,
|
|
731
|
+
"carrier_name": "teleship",
|
|
732
|
+
"carrier_id": "teleship_connection",
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
# Webhook Disconnect
|
|
736
|
+
WEBHOOK_DISCONNECT_RESPONSE = {
|
|
737
|
+
"operation": "Webhook disconnect",
|
|
738
|
+
"success": True,
|
|
739
|
+
"carrier_name": "teleship",
|
|
740
|
+
"carrier_id": ANY,
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
# Webhook Event
|
|
744
|
+
WEBHOOK_EVENT_DATA = {
|
|
745
|
+
"eventName": "shipment.updated",
|
|
746
|
+
"objectType": "shipment",
|
|
747
|
+
"objectId": "shp-12345",
|
|
748
|
+
"data": {
|
|
749
|
+
"trackingNumber": "TELESHIP12345678901",
|
|
750
|
+
"shipmentId": "shp-12345",
|
|
751
|
+
"events": [
|
|
752
|
+
{
|
|
753
|
+
"timestamp": "2025-01-17T14:30:00.000Z",
|
|
754
|
+
"code": "IN_TRANSIT",
|
|
755
|
+
"description": "Package in transit",
|
|
756
|
+
"location": "Chicago, IL",
|
|
757
|
+
}
|
|
758
|
+
],
|
|
759
|
+
},
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
WEBHOOK_EVENT_NOT_FOUND_RESPONSE = {
|
|
764
|
+
"operation": "Webhook event",
|
|
765
|
+
"success": False,
|
|
766
|
+
"messages": [
|
|
767
|
+
{
|
|
768
|
+
"message": "Connection not found: non-existent-pk",
|
|
769
|
+
}
|
|
770
|
+
],
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
WEBHOOK_EVENT_SIGNATURE_ERROR_RESPONSE = {
|
|
775
|
+
"operation": "Webhook event",
|
|
776
|
+
"success": False,
|
|
777
|
+
"carrier_name": "teleship",
|
|
778
|
+
"carrier_id": "teleship_connection",
|
|
779
|
+
"messages": [
|
|
780
|
+
{
|
|
781
|
+
"carrier_name": "teleship",
|
|
782
|
+
"carrier_id": "teleship_connection",
|
|
783
|
+
"code": "SIGNATURE_INVALID",
|
|
784
|
+
"message": "Webhook signature verification failed",
|
|
785
|
+
}
|
|
786
|
+
],
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
WEBHOOK_EVENT_SUCCESS_RESPONSE = {
|
|
790
|
+
"operation": "Webhook event",
|
|
791
|
+
"success": True,
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
# Connection CRUD
|
|
795
|
+
SENDLE_CONNECTION_DATA = {
|
|
796
|
+
"carrier_name": "sendle",
|
|
797
|
+
"carrier_id": "my_sendle_connection",
|
|
798
|
+
"credentials": {
|
|
799
|
+
"sendle_id": "test_sendle_id",
|
|
800
|
+
"api_key": "test_api_key",
|
|
801
|
+
},
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
SENDLE_CONNECTION_RESPONSE = {
|
|
805
|
+
"id": ANY,
|
|
806
|
+
"object_type": "carrier-connection",
|
|
807
|
+
"carrier_name": "sendle",
|
|
808
|
+
"display_name": "Sendle",
|
|
809
|
+
"carrier_id": "my_sendle_connection",
|
|
810
|
+
"credentials": {
|
|
811
|
+
"sendle_id": "test_sendle_id",
|
|
812
|
+
"api_key": "test_api_key",
|
|
813
|
+
"account_country_code": None,
|
|
814
|
+
},
|
|
815
|
+
"config": {},
|
|
816
|
+
"metadata": {},
|
|
817
|
+
"active": True,
|
|
818
|
+
"test_mode": True,
|
|
819
|
+
"capabilities": ANY,
|
|
820
|
+
"is_system": False,
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
SENDLE_CONNECTION_WITH_CONFIG_DATA = {
|
|
824
|
+
"carrier_name": "sendle",
|
|
825
|
+
"carrier_id": "sendle_with_config",
|
|
826
|
+
"credentials": {
|
|
827
|
+
"sendle_id": "test_sendle_id",
|
|
828
|
+
"api_key": "test_api_key",
|
|
829
|
+
},
|
|
830
|
+
"config": {
|
|
831
|
+
"shipping_options": ["signature_required"],
|
|
832
|
+
},
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
SENDLE_CONNECTION_WITH_CONFIG_RESPONSE = {
|
|
836
|
+
"id": ANY,
|
|
837
|
+
"object_type": "carrier-connection",
|
|
838
|
+
"carrier_name": "sendle",
|
|
839
|
+
"display_name": "Sendle",
|
|
840
|
+
"carrier_id": "sendle_with_config",
|
|
841
|
+
"credentials": {
|
|
842
|
+
"sendle_id": "test_sendle_id",
|
|
843
|
+
"api_key": "test_api_key",
|
|
844
|
+
"account_country_code": None,
|
|
845
|
+
},
|
|
846
|
+
"config": {
|
|
847
|
+
"shipping_options": ["signature_required"],
|
|
848
|
+
},
|
|
849
|
+
"metadata": {},
|
|
850
|
+
"active": True,
|
|
851
|
+
"test_mode": True,
|
|
852
|
+
"capabilities": ANY,
|
|
853
|
+
"is_system": False,
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
INVALID_CONNECTION_DATA = {
|
|
857
|
+
"carrier_name": "invalid_carrier",
|
|
858
|
+
"carrier_id": "test",
|
|
859
|
+
"credentials": {},
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
INACTIVE_CONNECTION_DATA = {
|
|
863
|
+
"carrier_name": "purolator",
|
|
864
|
+
"carrier_id": "purolator_inactive",
|
|
865
|
+
"active": False,
|
|
866
|
+
"credentials": {
|
|
867
|
+
"username": "test",
|
|
868
|
+
"password": "test",
|
|
869
|
+
"account_number": "test",
|
|
870
|
+
"user_token": "test",
|
|
871
|
+
},
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
CONNECTION_UPDATE_DATA = {
|
|
875
|
+
"carrier_id": "canadapost_updated",
|
|
876
|
+
"active": False,
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
CREDENTIALS_UPDATE_DATA = {
|
|
880
|
+
"credentials": {
|
|
881
|
+
"username": "new_username",
|
|
882
|
+
"customer_number": "2004381",
|
|
883
|
+
"contract_id": "42708517",
|
|
884
|
+
"password": "new_password",
|
|
885
|
+
},
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
SENDLE_TO_DELETE_DATA = {
|
|
889
|
+
"carrier_name": "sendle",
|
|
890
|
+
"carrier_id": "sendle_to_delete",
|
|
891
|
+
"credentials": {
|
|
892
|
+
"sendle_id": "test",
|
|
893
|
+
"api_key": "test",
|
|
894
|
+
},
|
|
895
|
+
}
|