karrio-server-core 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.
- karrio/server/conf.py +54 -0
- karrio/server/core/__init__.py +3 -0
- karrio/server/core/admin.py +1 -0
- karrio/server/core/apps.py +10 -0
- karrio/server/core/authentication.py +347 -0
- karrio/server/core/config.py +31 -0
- karrio/server/core/context_processors.py +12 -0
- karrio/server/core/datatypes.py +394 -0
- karrio/server/core/dataunits.py +187 -0
- karrio/server/core/exceptions.py +404 -0
- karrio/server/core/fields.py +12 -0
- karrio/server/core/filters.py +837 -0
- karrio/server/core/gateway.py +1011 -0
- karrio/server/core/logging.py +403 -0
- karrio/server/core/management/commands/cli.py +19 -0
- karrio/server/core/management/commands/create_oauth_client.py +41 -0
- karrio/server/core/management/commands/runserver.py +5 -0
- karrio/server/core/middleware.py +197 -0
- karrio/server/core/migrations/0001_initial.py +28 -0
- karrio/server/core/migrations/0002_apilogindex.py +69 -0
- karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
- karrio/server/core/migrations/0004_metafield.py +74 -0
- karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
- karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
- karrio/server/core/migrations/__init__.py +0 -0
- karrio/server/core/models/__init__.py +48 -0
- karrio/server/core/models/base.py +103 -0
- karrio/server/core/models/entity.py +24 -0
- karrio/server/core/models/metafield.py +144 -0
- karrio/server/core/models/third_party.py +21 -0
- karrio/server/core/oauth_validators.py +170 -0
- karrio/server/core/permissions.py +36 -0
- karrio/server/core/renderers.py +11 -0
- karrio/server/core/router.py +3 -0
- karrio/server/core/serializers.py +1971 -0
- karrio/server/core/signals.py +55 -0
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests.py +99 -0
- karrio/server/core/tests_resource_token.py +411 -0
- karrio/server/core/urls.py +12 -0
- karrio/server/core/utils.py +1025 -0
- karrio/server/core/validators.py +264 -0
- karrio/server/core/views/__init__.py +2 -0
- karrio/server/core/views/api.py +133 -0
- karrio/server/core/views/metadata.py +44 -0
- karrio/server/core/views/oauth.py +75 -0
- karrio/server/core/views/references.py +82 -0
- karrio/server/core/views/schema.py +310 -0
- karrio/server/filters/__init__.py +2 -0
- karrio/server/filters/abstract.py +26 -0
- karrio/server/iam/__init__.py +0 -0
- karrio/server/iam/admin.py +3 -0
- karrio/server/iam/apps.py +21 -0
- karrio/server/iam/migrations/0001_initial.py +33 -0
- karrio/server/iam/migrations/__init__.py +0 -0
- karrio/server/iam/models.py +48 -0
- karrio/server/iam/permissions.py +155 -0
- karrio/server/iam/serializers.py +54 -0
- karrio/server/iam/signals.py +18 -0
- karrio/server/iam/tests.py +3 -0
- karrio/server/iam/views.py +3 -0
- karrio/server/openapi.py +75 -0
- karrio/server/providers/__init__.py +1 -0
- karrio/server/providers/admin.py +364 -0
- karrio/server/providers/apps.py +10 -0
- karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
- karrio/server/providers/migrations/0001_initial.py +140 -0
- karrio/server/providers/migrations/0002_carrier_active.py +18 -0
- karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
- karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
- karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
- karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
- karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
- karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
- karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
- karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
- karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
- karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
- karrio/server/providers/migrations/0013_tntsettings.py +30 -0
- karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
- karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
- karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
- karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
- karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
- karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
- karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
- karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
- karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
- karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
- karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
- karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
- karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
- karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
- karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
- karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
- karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
- karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
- karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
- karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
- karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
- karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
- karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
- karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
- karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
- karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
- karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
- karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
- karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
- karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
- karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
- karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
- karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
- karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
- karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
- karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
- karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
- karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
- karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
- karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
- karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
- karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
- karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
- karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
- karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
- karrio/server/providers/migrations/0059_ratesheet.py +81 -0
- karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
- karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
- karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
- karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
- karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
- karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
- karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
- karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
- karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
- karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
- karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
- karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
- karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
- karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
- karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
- karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
- karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
- karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
- karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
- karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
- karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
- karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -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/__init__.py +0 -0
- karrio/server/providers/models/__init__.py +16 -0
- karrio/server/providers/models/carrier.py +387 -0
- karrio/server/providers/models/config.py +30 -0
- karrio/server/providers/models/service.py +192 -0
- karrio/server/providers/models/sheet.py +287 -0
- karrio/server/providers/models/template.py +39 -0
- karrio/server/providers/models/utils.py +58 -0
- karrio/server/providers/router.py +3 -0
- karrio/server/providers/serializers/__init__.py +3 -0
- karrio/server/providers/serializers/base.py +538 -0
- karrio/server/providers/signals.py +25 -0
- 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/urls.py +11 -0
- karrio/server/providers/views/__init__.py +0 -0
- karrio/server/providers/views/carriers.py +267 -0
- karrio/server/providers/views/connections.py +496 -0
- karrio/server/samples.py +352 -0
- karrio/server/serializers/__init__.py +2 -0
- karrio/server/serializers/abstract.py +602 -0
- karrio/server/tracing/__init__.py +0 -0
- karrio/server/tracing/admin.py +63 -0
- karrio/server/tracing/apps.py +8 -0
- karrio/server/tracing/migrations/0001_initial.py +41 -0
- karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
- karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
- karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
- karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
- karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
- karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
- karrio/server/tracing/migrations/__init__.py +0 -0
- karrio/server/tracing/models.py +82 -0
- karrio/server/tracing/tests.py +3 -0
- karrio/server/tracing/utils.py +109 -0
- karrio/server/user/__init__.py +0 -0
- karrio/server/user/admin.py +96 -0
- karrio/server/user/apps.py +7 -0
- karrio/server/user/forms.py +35 -0
- karrio/server/user/migrations/0001_initial.py +41 -0
- karrio/server/user/migrations/0002_token.py +29 -0
- karrio/server/user/migrations/0003_token_test_mode.py +20 -0
- karrio/server/user/migrations/0004_group.py +26 -0
- karrio/server/user/migrations/0005_token_label.py +21 -0
- karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
- karrio/server/user/migrations/0007_user_metadata.py +25 -0
- karrio/server/user/migrations/__init__.py +0 -0
- karrio/server/user/models.py +218 -0
- karrio/server/user/serializers.py +47 -0
- karrio/server/user/templates/registration/login.html +108 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
- karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
- karrio/server/user/tests.py +3 -0
- karrio/server/user/urls.py +10 -0
- karrio/server/user/utils.py +60 -0
- karrio/server/user/views.py +9 -0
- karrio_server_core-2025.5.dist-info/METADATA +32 -0
- karrio_server_core-2025.5.dist-info/RECORD +213 -0
- karrio_server_core-2025.5.dist-info/WHEEL +5 -0
- karrio_server_core-2025.5.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from rest_framework.test import APITestCase as BaseAPITestCase, APIClient
|
|
3
|
+
from karrio.server.core.logging import logger
|
|
4
|
+
|
|
5
|
+
from karrio.server.user.models import Token
|
|
6
|
+
import karrio.server.iam.permissions as iam
|
|
7
|
+
import karrio.server.providers.models as providers
|
|
8
|
+
|
|
9
|
+
iam.setup_groups()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class APITestCase(BaseAPITestCase):
|
|
13
|
+
def setUp(self) -> None:
|
|
14
|
+
self.maxDiff = None
|
|
15
|
+
# Loguru is already configured globally in settings
|
|
16
|
+
|
|
17
|
+
# Setup user and API Token.
|
|
18
|
+
self.user = get_user_model().objects.create_superuser(
|
|
19
|
+
"admin@example.com", "test"
|
|
20
|
+
)
|
|
21
|
+
self.token = Token.objects.create(user=self.user, test_mode=True)
|
|
22
|
+
|
|
23
|
+
# Setup API client.
|
|
24
|
+
self.client = APIClient()
|
|
25
|
+
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.token.key)
|
|
26
|
+
|
|
27
|
+
# Setup test carrier connections.
|
|
28
|
+
self.carrier = providers.Carrier.objects.create(
|
|
29
|
+
carrier_code="canadapost",
|
|
30
|
+
carrier_id="canadapost",
|
|
31
|
+
test_mode=True,
|
|
32
|
+
active=True,
|
|
33
|
+
created_by=self.user,
|
|
34
|
+
credentials=dict(
|
|
35
|
+
username="6e93d53968881714",
|
|
36
|
+
customer_number="2004381",
|
|
37
|
+
contract_id="42708517",
|
|
38
|
+
password="0bfa9fcb9853d1f51ee57a",
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
self.ups_carrier = providers.Carrier.objects.create(
|
|
42
|
+
carrier_code="ups",
|
|
43
|
+
carrier_id="ups_package",
|
|
44
|
+
test_mode=True,
|
|
45
|
+
active=True,
|
|
46
|
+
created_by=self.user,
|
|
47
|
+
credentials=dict(
|
|
48
|
+
client_id="test",
|
|
49
|
+
client_secret="test",
|
|
50
|
+
account_number="000000",
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
self.fedex_carrier = providers.Carrier.objects.create(
|
|
54
|
+
carrier_code="fedex",
|
|
55
|
+
carrier_id="fedex_express",
|
|
56
|
+
test_mode=True,
|
|
57
|
+
active=True,
|
|
58
|
+
created_by=self.user,
|
|
59
|
+
credentials=dict(
|
|
60
|
+
api_key="test",
|
|
61
|
+
secret_key="password",
|
|
62
|
+
account_number="000000",
|
|
63
|
+
track_api_key="test",
|
|
64
|
+
track_secret_key="password",
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
self.dhl_carrier = providers.Carrier.objects.create(
|
|
68
|
+
carrier_code="dhl_express",
|
|
69
|
+
carrier_id="dhl_express",
|
|
70
|
+
test_mode=True,
|
|
71
|
+
active=True,
|
|
72
|
+
created_by=self.user,
|
|
73
|
+
credentials=dict(
|
|
74
|
+
site_id="test",
|
|
75
|
+
password="password",
|
|
76
|
+
account_number="000000",
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def getJWTToken(self, email: str, password: str) -> str:
|
|
81
|
+
url = reverse("jwt-obtain-pair")
|
|
82
|
+
data = dict(
|
|
83
|
+
email=email,
|
|
84
|
+
password=password,
|
|
85
|
+
)
|
|
86
|
+
response = self.client.post(url, data)
|
|
87
|
+
|
|
88
|
+
return response.data.get("access")
|
|
89
|
+
|
|
90
|
+
def assertResponseNoErrors(self, response):
|
|
91
|
+
is_ok = f"{response.status_code}".startswith("2")
|
|
92
|
+
|
|
93
|
+
if is_ok is False or response.data.get("errors") is not None:
|
|
94
|
+
logger.error("Response has errors",
|
|
95
|
+
status_code=response.status_code,
|
|
96
|
+
response_data=response.data)
|
|
97
|
+
|
|
98
|
+
self.assertTrue(is_ok)
|
|
99
|
+
assert response.data.get("errors") is None
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""Tests for ResourceAccessToken and /api/tokens endpoint."""
|
|
2
|
+
|
|
3
|
+
from unittest import mock
|
|
4
|
+
from django.test import TestCase
|
|
5
|
+
from django.contrib.auth import get_user_model
|
|
6
|
+
from rest_framework.test import APITestCase, APIClient
|
|
7
|
+
|
|
8
|
+
from karrio.server.user.models import Token
|
|
9
|
+
from karrio.server.core.utils import ResourceAccessToken
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestResourceAccessTokenUnit(TestCase):
|
|
13
|
+
"""Unit tests for ResourceAccessToken class."""
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.user = get_user_model().objects.create_user(
|
|
17
|
+
email="test@example.com", password="testpass123"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def test_create_token_for_single_resource(self):
|
|
21
|
+
"""Test creating a token for a single resource."""
|
|
22
|
+
token = ResourceAccessToken.for_resource(
|
|
23
|
+
user=self.user,
|
|
24
|
+
resource_type="shipment",
|
|
25
|
+
resource_ids=["shp_123"],
|
|
26
|
+
access=["label"],
|
|
27
|
+
format="pdf",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.assertDictEqual(
|
|
31
|
+
{
|
|
32
|
+
"resource_type": token["resource_type"],
|
|
33
|
+
"resource_ids": token["resource_ids"],
|
|
34
|
+
"access": token["access"],
|
|
35
|
+
"format": token["format"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"resource_type": "shipment",
|
|
39
|
+
"resource_ids": ["shp_123"],
|
|
40
|
+
"access": ["label"],
|
|
41
|
+
"format": "pdf",
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_create_token_for_multiple_resources(self):
|
|
46
|
+
"""Test creating a token for multiple resources."""
|
|
47
|
+
token = ResourceAccessToken.for_resource(
|
|
48
|
+
user=self.user,
|
|
49
|
+
resource_type="document",
|
|
50
|
+
resource_ids=["shp_1", "shp_2", "shp_3"],
|
|
51
|
+
access=["batch_labels"],
|
|
52
|
+
format="pdf",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.assertDictEqual(
|
|
56
|
+
{
|
|
57
|
+
"resource_type": token["resource_type"],
|
|
58
|
+
"resource_ids": sorted(token["resource_ids"]),
|
|
59
|
+
"access": token["access"],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"resource_type": "document",
|
|
63
|
+
"resource_ids": ["shp_1", "shp_2", "shp_3"],
|
|
64
|
+
"access": ["batch_labels"],
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def test_decode_valid_token(self):
|
|
69
|
+
"""Test decoding a valid token."""
|
|
70
|
+
token = ResourceAccessToken.for_resource(
|
|
71
|
+
user=self.user,
|
|
72
|
+
resource_type="manifest",
|
|
73
|
+
resource_ids=["mnf_456"],
|
|
74
|
+
access=["manifest"],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
claims = ResourceAccessToken.decode(str(token))
|
|
78
|
+
|
|
79
|
+
self.assertDictEqual(
|
|
80
|
+
{
|
|
81
|
+
"resource_type": claims["resource_type"],
|
|
82
|
+
"resource_ids": claims["resource_ids"],
|
|
83
|
+
"access": claims["access"],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"resource_type": "manifest",
|
|
87
|
+
"resource_ids": ["mnf_456"],
|
|
88
|
+
"access": ["manifest"],
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def test_validate_access_success(self):
|
|
93
|
+
"""Test successful access validation."""
|
|
94
|
+
token = ResourceAccessToken.for_resource(
|
|
95
|
+
user=self.user,
|
|
96
|
+
resource_type="shipment",
|
|
97
|
+
resource_ids=["shp_123"],
|
|
98
|
+
access=["label", "invoice"],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
claims = ResourceAccessToken.validate_access(
|
|
102
|
+
token_string=str(token),
|
|
103
|
+
resource_type="shipment",
|
|
104
|
+
resource_id="shp_123",
|
|
105
|
+
access="label",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.assertIsNotNone(claims)
|
|
109
|
+
self.assertEqual(claims["resource_type"], "shipment")
|
|
110
|
+
|
|
111
|
+
def test_validate_access_wrong_resource_type(self):
|
|
112
|
+
"""Test validation fails for wrong resource type."""
|
|
113
|
+
token = ResourceAccessToken.for_resource(
|
|
114
|
+
user=self.user,
|
|
115
|
+
resource_type="shipment",
|
|
116
|
+
resource_ids=["shp_123"],
|
|
117
|
+
access=["label"],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
with self.assertRaises(PermissionError) as context:
|
|
121
|
+
ResourceAccessToken.validate_access(
|
|
122
|
+
token_string=str(token),
|
|
123
|
+
resource_type="manifest",
|
|
124
|
+
resource_id="shp_123",
|
|
125
|
+
access="label",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.assertIn("resource type", str(context.exception).lower())
|
|
129
|
+
|
|
130
|
+
def test_validate_access_wrong_resource_id(self):
|
|
131
|
+
"""Test validation fails for wrong resource ID."""
|
|
132
|
+
token = ResourceAccessToken.for_resource(
|
|
133
|
+
user=self.user,
|
|
134
|
+
resource_type="shipment",
|
|
135
|
+
resource_ids=["shp_123"],
|
|
136
|
+
access=["label"],
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
with self.assertRaises(PermissionError) as context:
|
|
140
|
+
ResourceAccessToken.validate_access(
|
|
141
|
+
token_string=str(token),
|
|
142
|
+
resource_type="shipment",
|
|
143
|
+
resource_id="shp_999",
|
|
144
|
+
access="label",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
self.assertIn("resource", str(context.exception).lower())
|
|
148
|
+
|
|
149
|
+
def test_validate_access_wrong_permission(self):
|
|
150
|
+
"""Test validation fails for wrong access permission."""
|
|
151
|
+
token = ResourceAccessToken.for_resource(
|
|
152
|
+
user=self.user,
|
|
153
|
+
resource_type="shipment",
|
|
154
|
+
resource_ids=["shp_123"],
|
|
155
|
+
access=["label"],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
with self.assertRaises(PermissionError) as context:
|
|
159
|
+
ResourceAccessToken.validate_access(
|
|
160
|
+
token_string=str(token),
|
|
161
|
+
resource_type="shipment",
|
|
162
|
+
resource_id="shp_123",
|
|
163
|
+
access="invoice",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.assertIn("access", str(context.exception).lower())
|
|
167
|
+
|
|
168
|
+
def test_validate_batch_access_success(self):
|
|
169
|
+
"""Test successful batch access validation."""
|
|
170
|
+
token = ResourceAccessToken.for_resource(
|
|
171
|
+
user=self.user,
|
|
172
|
+
resource_type="document",
|
|
173
|
+
resource_ids=["shp_1", "shp_2", "shp_3"],
|
|
174
|
+
access=["batch_labels"],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
claims = ResourceAccessToken.validate_batch_access(
|
|
178
|
+
token_string=str(token),
|
|
179
|
+
resource_type="document",
|
|
180
|
+
resource_ids=["shp_1", "shp_2"],
|
|
181
|
+
access="batch_labels",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self.assertIsNotNone(claims)
|
|
185
|
+
self.assertEqual(claims["resource_type"], "document")
|
|
186
|
+
|
|
187
|
+
def test_validate_batch_access_missing_id(self):
|
|
188
|
+
"""Test batch validation fails when requesting ID not in token."""
|
|
189
|
+
token = ResourceAccessToken.for_resource(
|
|
190
|
+
user=self.user,
|
|
191
|
+
resource_type="document",
|
|
192
|
+
resource_ids=["shp_1", "shp_2"],
|
|
193
|
+
access=["batch_labels"],
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
with self.assertRaises(PermissionError) as context:
|
|
197
|
+
ResourceAccessToken.validate_batch_access(
|
|
198
|
+
token_string=str(token),
|
|
199
|
+
resource_type="document",
|
|
200
|
+
resource_ids=["shp_1", "shp_2", "shp_3"],
|
|
201
|
+
access="batch_labels",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.assertIn("shp_3", str(context.exception))
|
|
205
|
+
|
|
206
|
+
def test_token_with_org_id_and_test_mode(self):
|
|
207
|
+
"""Test token includes org_id and test_mode when provided."""
|
|
208
|
+
token = ResourceAccessToken.for_resource(
|
|
209
|
+
user=self.user,
|
|
210
|
+
resource_type="shipment",
|
|
211
|
+
resource_ids=["shp_123"],
|
|
212
|
+
access=["label"],
|
|
213
|
+
org_id="org_abc",
|
|
214
|
+
test_mode=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
claims = ResourceAccessToken.decode(str(token))
|
|
218
|
+
|
|
219
|
+
self.assertDictEqual(
|
|
220
|
+
{
|
|
221
|
+
"org_id": claims["org_id"],
|
|
222
|
+
"test_mode": claims["test_mode"],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"org_id": "org_abc",
|
|
226
|
+
"test_mode": True,
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestResourceTokenAPI(APITestCase):
|
|
232
|
+
"""API tests for /api/tokens endpoint."""
|
|
233
|
+
|
|
234
|
+
def setUp(self):
|
|
235
|
+
self.user = get_user_model().objects.create_user(
|
|
236
|
+
email="api_test@example.com", password="testpass123"
|
|
237
|
+
)
|
|
238
|
+
self.token = Token.objects.create(user=self.user, test_mode=True)
|
|
239
|
+
self.client = APIClient()
|
|
240
|
+
self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}")
|
|
241
|
+
|
|
242
|
+
def test_generate_shipment_label_token(self):
|
|
243
|
+
"""Test generating a token for shipment label access."""
|
|
244
|
+
response = self.client.post(
|
|
245
|
+
"/api/tokens",
|
|
246
|
+
{
|
|
247
|
+
"resource_type": "shipment",
|
|
248
|
+
"resource_ids": ["shp_123"],
|
|
249
|
+
"access": ["label"],
|
|
250
|
+
"format": "pdf",
|
|
251
|
+
},
|
|
252
|
+
format="json",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
self.assertEqual(response.status_code, 201)
|
|
256
|
+
self.assertDictEqual(
|
|
257
|
+
{
|
|
258
|
+
"has_token": "token" in response.data,
|
|
259
|
+
"has_expires_at": "expires_at" in response.data,
|
|
260
|
+
"has_resource_urls": "resource_urls" in response.data,
|
|
261
|
+
"has_shp_123_url": "shp_123" in response.data.get("resource_urls", {}),
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"has_token": True,
|
|
265
|
+
"has_expires_at": True,
|
|
266
|
+
"has_resource_urls": True,
|
|
267
|
+
"has_shp_123_url": True,
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def test_generate_batch_labels_token(self):
|
|
272
|
+
"""Test generating a token for batch labels."""
|
|
273
|
+
response = self.client.post(
|
|
274
|
+
"/api/tokens",
|
|
275
|
+
{
|
|
276
|
+
"resource_type": "document",
|
|
277
|
+
"resource_ids": ["shp_1", "shp_2", "shp_3"],
|
|
278
|
+
"access": ["batch_labels"],
|
|
279
|
+
"format": "pdf",
|
|
280
|
+
},
|
|
281
|
+
format="json",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
self.assertEqual(response.status_code, 201)
|
|
285
|
+
self.assertIn("batch", response.data["resource_urls"])
|
|
286
|
+
|
|
287
|
+
def test_generate_manifest_token(self):
|
|
288
|
+
"""Test generating a token for manifest access."""
|
|
289
|
+
response = self.client.post(
|
|
290
|
+
"/api/tokens",
|
|
291
|
+
{
|
|
292
|
+
"resource_type": "manifest",
|
|
293
|
+
"resource_ids": ["mnf_456"],
|
|
294
|
+
"access": ["manifest"],
|
|
295
|
+
},
|
|
296
|
+
format="json",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
self.assertEqual(response.status_code, 201)
|
|
300
|
+
self.assertIn("mnf_456", response.data["resource_urls"])
|
|
301
|
+
|
|
302
|
+
def test_generate_template_token(self):
|
|
303
|
+
"""Test generating a token for template access."""
|
|
304
|
+
response = self.client.post(
|
|
305
|
+
"/api/tokens",
|
|
306
|
+
{
|
|
307
|
+
"resource_type": "template",
|
|
308
|
+
"resource_ids": ["tpl_789"],
|
|
309
|
+
"access": ["render"],
|
|
310
|
+
},
|
|
311
|
+
format="json",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
self.assertEqual(response.status_code, 201)
|
|
315
|
+
self.assertIn("tpl_789", response.data["resource_urls"])
|
|
316
|
+
|
|
317
|
+
def test_unauthenticated_request_fails(self):
|
|
318
|
+
"""Test that unauthenticated requests are rejected."""
|
|
319
|
+
self.client.credentials()
|
|
320
|
+
response = self.client.post(
|
|
321
|
+
"/api/tokens",
|
|
322
|
+
{
|
|
323
|
+
"resource_type": "shipment",
|
|
324
|
+
"resource_ids": ["shp_123"],
|
|
325
|
+
"access": ["label"],
|
|
326
|
+
},
|
|
327
|
+
format="json",
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
self.assertEqual(response.status_code, 401)
|
|
331
|
+
|
|
332
|
+
def test_invalid_resource_type_fails(self):
|
|
333
|
+
"""Test that invalid resource type is rejected."""
|
|
334
|
+
response = self.client.post(
|
|
335
|
+
"/api/tokens",
|
|
336
|
+
{
|
|
337
|
+
"resource_type": "invalid_type",
|
|
338
|
+
"resource_ids": ["shp_123"],
|
|
339
|
+
"access": ["label"],
|
|
340
|
+
},
|
|
341
|
+
format="json",
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
self.assertEqual(response.status_code, 400)
|
|
345
|
+
|
|
346
|
+
def test_invalid_access_type_fails(self):
|
|
347
|
+
"""Test that invalid access type is rejected."""
|
|
348
|
+
response = self.client.post(
|
|
349
|
+
"/api/tokens",
|
|
350
|
+
{
|
|
351
|
+
"resource_type": "shipment",
|
|
352
|
+
"resource_ids": ["shp_123"],
|
|
353
|
+
"access": ["invalid_access"],
|
|
354
|
+
},
|
|
355
|
+
format="json",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
self.assertEqual(response.status_code, 400)
|
|
359
|
+
|
|
360
|
+
def test_empty_resource_ids_fails(self):
|
|
361
|
+
"""Test that empty resource_ids is rejected."""
|
|
362
|
+
response = self.client.post(
|
|
363
|
+
"/api/tokens",
|
|
364
|
+
{
|
|
365
|
+
"resource_type": "shipment",
|
|
366
|
+
"resource_ids": [],
|
|
367
|
+
"access": ["label"],
|
|
368
|
+
},
|
|
369
|
+
format="json",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
self.assertEqual(response.status_code, 400)
|
|
373
|
+
|
|
374
|
+
def test_custom_expiration_success(self):
|
|
375
|
+
"""Test custom token expiration."""
|
|
376
|
+
response = self.client.post(
|
|
377
|
+
"/api/tokens",
|
|
378
|
+
{
|
|
379
|
+
"resource_type": "shipment",
|
|
380
|
+
"resource_ids": ["shp_123"],
|
|
381
|
+
"access": ["label"],
|
|
382
|
+
"expires_in": 600,
|
|
383
|
+
},
|
|
384
|
+
format="json",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
self.assertEqual(response.status_code, 201)
|
|
388
|
+
|
|
389
|
+
def test_response_has_no_cache_headers(self):
|
|
390
|
+
"""Test that token response includes cache prevention headers."""
|
|
391
|
+
response = self.client.post(
|
|
392
|
+
"/api/tokens",
|
|
393
|
+
{
|
|
394
|
+
"resource_type": "shipment",
|
|
395
|
+
"resource_ids": ["shp_123"],
|
|
396
|
+
"access": ["label"],
|
|
397
|
+
},
|
|
398
|
+
format="json",
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
self.assertEqual(response.status_code, 201)
|
|
402
|
+
self.assertDictEqual(
|
|
403
|
+
{
|
|
404
|
+
"cache_control": response.get("Cache-Control"),
|
|
405
|
+
"cdn_cache_control": response.get("CDN-Cache-Control"),
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"cache_control": "no-store",
|
|
409
|
+
"cdn_cache_control": "no-store",
|
|
410
|
+
},
|
|
411
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
karrio server core module urls
|
|
3
|
+
"""
|
|
4
|
+
from django.urls import include, path
|
|
5
|
+
from karrio.server.core.views import metadata, router
|
|
6
|
+
|
|
7
|
+
app_name = "karrio.server.core"
|
|
8
|
+
urlpatterns = [
|
|
9
|
+
path("", metadata.view, name="metadata"),
|
|
10
|
+
path("v1/", include(router.urls), name="references"),
|
|
11
|
+
path("status/", include("health_check.urls")),
|
|
12
|
+
]
|