karrio-server-graph 2025.5rc14__py3-none-any.whl → 2025.5rc15__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/graph/schemas/base/__init__.py +3 -3
- karrio/server/graph/schemas/base/inputs.py +4 -4
- karrio/server/graph/schemas/base/mutations.py +33 -8
- karrio/server/graph/schemas/base/types.py +27 -14
- karrio/server/graph/tests/test_carrier_connections.py +68 -48
- karrio/server/graph/tests/test_registration.py +205 -0
- karrio/server/graph/utils.py +9 -4
- {karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/METADATA +1 -1
- {karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/RECORD +11 -10
- {karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/WHEEL +0 -0
- {karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/top_level.txt +0 -0
|
@@ -30,10 +30,10 @@ class Query:
|
|
|
30
30
|
resolver=types.SystemUsageType.resolve
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
user_connections:
|
|
34
|
-
resolver=types.CarrierConnectionType.
|
|
33
|
+
user_connections: utils.Connection[types.CarrierConnectionType] = strawberry.field(
|
|
34
|
+
resolver=types.CarrierConnectionType.resolve_list
|
|
35
35
|
)
|
|
36
|
-
system_connections:
|
|
36
|
+
system_connections: utils.Connection[types.SystemConnectionType] = strawberry.field(
|
|
37
37
|
resolver=types.SystemConnectionType.resolve_list
|
|
38
38
|
)
|
|
39
39
|
|
|
@@ -31,8 +31,8 @@ class TracingRecordFilter(utils.Paginated):
|
|
|
31
31
|
@strawberry.input
|
|
32
32
|
class TrackerFilter(utils.Paginated):
|
|
33
33
|
tracking_number: typing.Optional[str] = strawberry.UNSET
|
|
34
|
-
created_after: typing.Optional[
|
|
35
|
-
created_before: typing.Optional[
|
|
34
|
+
created_after: typing.Optional[str] = strawberry.UNSET
|
|
35
|
+
created_before: typing.Optional[str] = strawberry.UNSET
|
|
36
36
|
carrier_name: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
37
37
|
status: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
38
38
|
|
|
@@ -42,8 +42,8 @@ class ShipmentFilter(utils.Paginated):
|
|
|
42
42
|
keyword: typing.Optional[str] = strawberry.UNSET
|
|
43
43
|
address: typing.Optional[str] = strawberry.UNSET
|
|
44
44
|
id: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
45
|
-
created_after: typing.Optional[
|
|
46
|
-
created_before: typing.Optional[
|
|
45
|
+
created_after: typing.Optional[str] = strawberry.UNSET
|
|
46
|
+
created_before: typing.Optional[str] = strawberry.UNSET
|
|
47
47
|
carrier_name: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
48
48
|
reference: typing.Optional[str] = strawberry.UNSET
|
|
49
49
|
service: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
@@ -260,7 +260,14 @@ class RegisterUserMutation(utils.BaseMutation):
|
|
|
260
260
|
)
|
|
261
261
|
|
|
262
262
|
try:
|
|
263
|
-
form = user_forms.SignUpForm(input)
|
|
263
|
+
form = user_forms.SignUpForm(data=input)
|
|
264
|
+
if not form.is_valid():
|
|
265
|
+
errors = []
|
|
266
|
+
for field, messages in form.errors.items():
|
|
267
|
+
for message in messages:
|
|
268
|
+
errors.append(f"{field}: {message}")
|
|
269
|
+
raise Exception(". ".join(errors))
|
|
270
|
+
|
|
264
271
|
user = form.save()
|
|
265
272
|
|
|
266
273
|
return RegisterUserMutation(user=user) # type:ignore
|
|
@@ -533,13 +540,12 @@ class CreateRateSheetMutation(utils.BaseMutation):
|
|
|
533
540
|
)
|
|
534
541
|
|
|
535
542
|
if any(carriers):
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
_.save(update_fields=["rate_sheet"])
|
|
543
|
+
(
|
|
544
|
+
providers.Carrier.access_by(info.context.request).filter(
|
|
545
|
+
carrier_code=rate_sheet.carrier_name,
|
|
546
|
+
id__in=carriers,
|
|
547
|
+
).update(rate_sheet=rate_sheet)
|
|
548
|
+
)
|
|
543
549
|
|
|
544
550
|
return CreateRateSheetMutation(rate_sheet=rate_sheet)
|
|
545
551
|
|
|
@@ -559,6 +565,7 @@ class UpdateRateSheetMutation(utils.BaseMutation):
|
|
|
559
565
|
id=input["id"]
|
|
560
566
|
)
|
|
561
567
|
data = input.copy()
|
|
568
|
+
carriers = data.pop("carriers", [])
|
|
562
569
|
serializer = serializers.RateSheetModelSerializer(
|
|
563
570
|
instance,
|
|
564
571
|
data=data,
|
|
@@ -579,6 +586,24 @@ class UpdateRateSheetMutation(utils.BaseMutation):
|
|
|
579
586
|
context=info.context.request,
|
|
580
587
|
)
|
|
581
588
|
|
|
589
|
+
if any(carriers):
|
|
590
|
+
# Link listed carriers to rate sheet
|
|
591
|
+
(
|
|
592
|
+
providers.Carrier.access_by(info.context.request).filter(
|
|
593
|
+
carrier_code=rate_sheet.carrier_name,
|
|
594
|
+
id__in=carriers,
|
|
595
|
+
).update(rate_sheet=rate_sheet)
|
|
596
|
+
)
|
|
597
|
+
# Unlink missing carriers from rate sheet
|
|
598
|
+
(
|
|
599
|
+
providers.Carrier.access_by(info.context.request).filter(
|
|
600
|
+
carrier_code=rate_sheet.carrier_name,
|
|
601
|
+
rate_sheet=rate_sheet,
|
|
602
|
+
)
|
|
603
|
+
.exclude(id__in=carriers)
|
|
604
|
+
.update(rate_sheet=None)
|
|
605
|
+
)
|
|
606
|
+
|
|
582
607
|
return UpdateRateSheetMutation(
|
|
583
608
|
rate_sheet=providers.RateSheet.objects.get(id=input["id"])
|
|
584
609
|
)
|
|
@@ -177,7 +177,16 @@ class WorkspaceConfigType:
|
|
|
177
177
|
@staticmethod
|
|
178
178
|
@utils.authentication_required
|
|
179
179
|
def resolve(info) -> typing.Optional["WorkspaceConfigType"]:
|
|
180
|
-
|
|
180
|
+
workspace_config = auth.WorkspaceConfig.access_by(info.context.request).first()
|
|
181
|
+
|
|
182
|
+
# Create a default workspace config if none exists
|
|
183
|
+
if workspace_config is None:
|
|
184
|
+
workspace_config = auth.WorkspaceConfig.objects.create(
|
|
185
|
+
created_by=info.context.request.user,
|
|
186
|
+
config={}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return workspace_config
|
|
181
190
|
|
|
182
191
|
|
|
183
192
|
@strawberry.type
|
|
@@ -277,7 +286,8 @@ class SystemUsageType:
|
|
|
277
286
|
.qs.annotate(date=functions.TruncDay("created_at"))
|
|
278
287
|
.values("date")
|
|
279
288
|
.annotate(
|
|
280
|
-
count=models.
|
|
289
|
+
count=models.Count("id"),
|
|
290
|
+
amount=models.Sum(
|
|
281
291
|
functions.Cast("selected_rate__total_charge", models.FloatField())
|
|
282
292
|
)
|
|
283
293
|
)
|
|
@@ -301,9 +311,11 @@ class SystemUsageType:
|
|
|
301
311
|
total_requests = sum([item["count"] for item in api_requests], 0)
|
|
302
312
|
total_trackers = sum([item["count"] for item in tracker_count], 0)
|
|
303
313
|
total_shipments = sum([item["count"] for item in shipment_count], 0)
|
|
304
|
-
order_volume = lib.
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
order_volume = lib.to_decimal(
|
|
315
|
+
sum([item["count"] for item in order_volumes if item["count"] is not None], 0.0)
|
|
316
|
+
)
|
|
317
|
+
total_shipping_spend = lib.to_decimal(
|
|
318
|
+
sum([item["amount"] for item in shipping_spend if item["amount"] is not None], 0.0)
|
|
307
319
|
)
|
|
308
320
|
user_count = User.objects.count()
|
|
309
321
|
organization_count = 1
|
|
@@ -314,20 +326,20 @@ class SystemUsageType:
|
|
|
314
326
|
organization_count = orgs.Organization.objects.count()
|
|
315
327
|
|
|
316
328
|
return SystemUsageType(
|
|
329
|
+
user_count=user_count,
|
|
317
330
|
order_volume=order_volume,
|
|
318
331
|
total_errors=total_errors,
|
|
319
332
|
total_requests=total_requests,
|
|
320
333
|
total_trackers=total_trackers,
|
|
321
334
|
total_shipments=total_shipments,
|
|
322
335
|
organization_count=organization_count,
|
|
323
|
-
user_count=user_count,
|
|
324
336
|
total_shipping_spend=total_shipping_spend,
|
|
325
|
-
api_errors=[utils.UsageStatType.parse(item) for item in api_errors],
|
|
326
|
-
api_requests=[utils.UsageStatType.parse(item) for item in api_requests],
|
|
327
|
-
order_volumes=[utils.UsageStatType.parse(item) for item in order_volumes],
|
|
328
|
-
shipment_count=[utils.UsageStatType.parse(item) for item in shipment_count],
|
|
329
|
-
shipping_spend=[utils.UsageStatType.parse(item) for item in shipping_spend],
|
|
330
|
-
tracker_count=[utils.UsageStatType.parse(item) for item in tracker_count],
|
|
337
|
+
api_errors=[utils.UsageStatType.parse(item, label="api_errors") for item in api_errors],
|
|
338
|
+
api_requests=[utils.UsageStatType.parse(item, label="api_requests") for item in api_requests],
|
|
339
|
+
order_volumes=[utils.UsageStatType.parse(item, label="order_volumes") for item in order_volumes],
|
|
340
|
+
shipment_count=[utils.UsageStatType.parse(item, label="shipment_count") for item in shipment_count],
|
|
341
|
+
shipping_spend=[utils.UsageStatType.parse(item, label="shipping_spend") for item in shipping_spend],
|
|
342
|
+
tracker_count=[utils.UsageStatType.parse(item, label="tracker_count") for item in tracker_count],
|
|
331
343
|
)
|
|
332
344
|
|
|
333
345
|
|
|
@@ -566,6 +578,7 @@ class ChargeType:
|
|
|
566
578
|
name: typing.Optional[str] = None
|
|
567
579
|
amount: typing.Optional[float] = None
|
|
568
580
|
currency: utils.CurrencyCodeEnum = None
|
|
581
|
+
id: typing.Optional[str] = None
|
|
569
582
|
|
|
570
583
|
@staticmethod
|
|
571
584
|
def parse(charge: dict):
|
|
@@ -1273,7 +1286,7 @@ class SystemConnectionType:
|
|
|
1273
1286
|
def resolve_list(
|
|
1274
1287
|
info,
|
|
1275
1288
|
filter: typing.Optional[inputs.CarrierFilter] = strawberry.UNSET,
|
|
1276
|
-
) ->
|
|
1289
|
+
) -> utils.Connection["SystemConnectionType"]:
|
|
1277
1290
|
_filter = filter if not utils.is_unset(filter) else inputs.CarrierFilter()
|
|
1278
1291
|
connections = filters.CarrierFilters(
|
|
1279
1292
|
_filter.to_dict(),
|
|
@@ -1282,7 +1295,7 @@ class SystemConnectionType:
|
|
|
1282
1295
|
test_mode=getattr(info.context.request, "test_mode", False),
|
|
1283
1296
|
),
|
|
1284
1297
|
).qs
|
|
1285
|
-
return connections
|
|
1298
|
+
return utils.paginated_connection(connections, **_filter.pagination())
|
|
1286
1299
|
|
|
1287
1300
|
|
|
1288
1301
|
@strawberry.type
|
|
@@ -9,11 +9,15 @@ class TestSystemConnections(GraphTestCase):
|
|
|
9
9
|
"""
|
|
10
10
|
query get_system_connections {
|
|
11
11
|
system_connections {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
edges {
|
|
13
|
+
node {
|
|
14
|
+
id
|
|
15
|
+
carrier_id
|
|
16
|
+
carrier_name
|
|
17
|
+
active
|
|
18
|
+
test_mode
|
|
19
|
+
}
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
""",
|
|
@@ -34,14 +38,18 @@ class TestUserConnections(GraphTestCase):
|
|
|
34
38
|
"""
|
|
35
39
|
query get_user_connections {
|
|
36
40
|
user_connections {
|
|
41
|
+
edges {
|
|
42
|
+
node {
|
|
37
43
|
id
|
|
38
44
|
carrier_id
|
|
39
45
|
carrier_name
|
|
40
|
-
test_mode
|
|
41
46
|
active
|
|
47
|
+
test_mode
|
|
42
48
|
credentials
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
}
|
|
52
|
+
}
|
|
45
53
|
""",
|
|
46
54
|
operation_name="get_user_connections",
|
|
47
55
|
)
|
|
@@ -62,8 +70,8 @@ class TestUserConnections(GraphTestCase):
|
|
|
62
70
|
id
|
|
63
71
|
carrier_id
|
|
64
72
|
carrier_name
|
|
65
|
-
test_mode
|
|
66
73
|
active
|
|
74
|
+
test_mode
|
|
67
75
|
credentials
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -108,54 +116,66 @@ class TestUserConnections(GraphTestCase):
|
|
|
108
116
|
|
|
109
117
|
SYSTEM_CONNECTIONS = {
|
|
110
118
|
"data": {
|
|
111
|
-
"system_connections":
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
"system_connections": {
|
|
120
|
+
"edges": [
|
|
121
|
+
{
|
|
122
|
+
"node": {
|
|
123
|
+
"active": True,
|
|
124
|
+
"carrier_id": "dhl_universal",
|
|
125
|
+
"carrier_name": "dhl_universal",
|
|
126
|
+
"id": ANY,
|
|
127
|
+
"test_mode": False,
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"node": {
|
|
132
|
+
"active": True,
|
|
133
|
+
"carrier_id": "fedex_express",
|
|
134
|
+
"carrier_name": "fedex",
|
|
135
|
+
"id": ANY,
|
|
136
|
+
"test_mode": False,
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
}
|
|
127
141
|
}
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
USER_CONNECTIONS = {
|
|
131
145
|
"data": {
|
|
132
|
-
"user_connections":
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
"user_connections": {
|
|
147
|
+
"edges": [
|
|
148
|
+
{
|
|
149
|
+
"node": {
|
|
150
|
+
"active": True,
|
|
151
|
+
"carrier_id": "ups_package",
|
|
152
|
+
"carrier_name": "ups",
|
|
153
|
+
"credentials": {
|
|
154
|
+
"account_number": "000000",
|
|
155
|
+
"client_id": "test",
|
|
156
|
+
"client_secret": "test",
|
|
157
|
+
},
|
|
158
|
+
"id": ANY,
|
|
159
|
+
"test_mode": False,
|
|
160
|
+
}
|
|
141
161
|
},
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
{
|
|
163
|
+
"node": {
|
|
164
|
+
"active": True,
|
|
165
|
+
"carrier_id": "canadapost",
|
|
166
|
+
"carrier_name": "canadapost",
|
|
167
|
+
"credentials": {
|
|
168
|
+
"contract_id": "42708517",
|
|
169
|
+
"customer_number": "2004381",
|
|
170
|
+
"password": "0bfa9fcb9853d1f51ee57a",
|
|
171
|
+
"username": "6e93d53968881714",
|
|
172
|
+
},
|
|
173
|
+
"id": ANY,
|
|
174
|
+
"test_mode": False,
|
|
175
|
+
}
|
|
154
176
|
},
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
]
|
|
177
|
+
]
|
|
178
|
+
}
|
|
159
179
|
}
|
|
160
180
|
}
|
|
161
181
|
|
|
@@ -0,0 +1,205 @@
|
|
|
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://localhost:3000/email"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.assertResponseNoErrors(response)
|
|
42
|
+
self.assertIsNotNone(response.data['data']['register_user']['user'])
|
|
43
|
+
self.assertEqual(response.data['data']['register_user']['user']['email'], "newuser@example.com")
|
|
44
|
+
self.assertEqual(response.data['data']['register_user']['user']['full_name'], "New Test User")
|
|
45
|
+
|
|
46
|
+
# Verify user was created in database
|
|
47
|
+
user = User.objects.get(email="newuser@example.com")
|
|
48
|
+
self.assertEqual(user.full_name, "New Test User")
|
|
49
|
+
|
|
50
|
+
@patch('karrio.server.conf.settings.ALLOW_SIGNUP', True)
|
|
51
|
+
def test_register_user_password_mismatch(self):
|
|
52
|
+
"""Test registration fails with mismatched passwords"""
|
|
53
|
+
response = self.query(
|
|
54
|
+
"""
|
|
55
|
+
mutation register_user($data: RegisterUserMutationInput!) {
|
|
56
|
+
register_user(input: $data) {
|
|
57
|
+
user {
|
|
58
|
+
email
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
""",
|
|
63
|
+
operation_name="register_user",
|
|
64
|
+
variables={
|
|
65
|
+
"data": {
|
|
66
|
+
"email": "mismatch@example.com",
|
|
67
|
+
"full_name": "Mismatch User",
|
|
68
|
+
"password1": "TestPassword123!",
|
|
69
|
+
"password2": "DifferentPassword123!",
|
|
70
|
+
"redirect_url": "http://localhost:3000/email"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Should have errors
|
|
76
|
+
self.assertIsNotNone(response.data.get('errors'))
|
|
77
|
+
self.assertIn("password", str(response.data['errors'][0]))
|
|
78
|
+
|
|
79
|
+
@patch('karrio.server.conf.settings.ALLOW_SIGNUP', True)
|
|
80
|
+
def test_register_user_duplicate_email(self):
|
|
81
|
+
"""Test registration fails with duplicate email"""
|
|
82
|
+
# First create a user
|
|
83
|
+
User.objects.create_user(
|
|
84
|
+
email="existing@example.com",
|
|
85
|
+
password="ExistingPass123!",
|
|
86
|
+
full_name="Existing User"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
response = self.query(
|
|
90
|
+
"""
|
|
91
|
+
mutation register_user($data: RegisterUserMutationInput!) {
|
|
92
|
+
register_user(input: $data) {
|
|
93
|
+
user {
|
|
94
|
+
email
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
""",
|
|
99
|
+
operation_name="register_user",
|
|
100
|
+
variables={
|
|
101
|
+
"data": {
|
|
102
|
+
"email": "existing@example.com",
|
|
103
|
+
"full_name": "Duplicate User",
|
|
104
|
+
"password1": "TestPassword123!",
|
|
105
|
+
"password2": "TestPassword123!",
|
|
106
|
+
"redirect_url": "http://localhost:3000/email"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Should have errors about duplicate email
|
|
112
|
+
self.assertIsNotNone(response.data.get('errors'))
|
|
113
|
+
|
|
114
|
+
@patch('karrio.server.conf.settings.ALLOW_SIGNUP', False)
|
|
115
|
+
def test_register_user_signup_disabled(self):
|
|
116
|
+
"""Test registration fails when signup is disabled"""
|
|
117
|
+
response = self.query(
|
|
118
|
+
"""
|
|
119
|
+
mutation register_user($data: RegisterUserMutationInput!) {
|
|
120
|
+
register_user(input: $data) {
|
|
121
|
+
user {
|
|
122
|
+
email
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
""",
|
|
127
|
+
operation_name="register_user",
|
|
128
|
+
variables={
|
|
129
|
+
"data": {
|
|
130
|
+
"email": "disabled@example.com",
|
|
131
|
+
"full_name": "Disabled User",
|
|
132
|
+
"password1": "TestPassword123!",
|
|
133
|
+
"password2": "TestPassword123!",
|
|
134
|
+
"redirect_url": "http://localhost:3000/email"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Should have errors about signup not allowed
|
|
140
|
+
self.assertIsNotNone(response.data.get('errors'))
|
|
141
|
+
self.assertIn("Signup is not allowed", str(response.data['errors'][0]))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestPasswordReset(GraphTestCase):
|
|
145
|
+
|
|
146
|
+
def setUp(self):
|
|
147
|
+
super().setUp()
|
|
148
|
+
# Create a test user for password reset
|
|
149
|
+
self.reset_user = User.objects.create_user(
|
|
150
|
+
email="resetuser@example.com",
|
|
151
|
+
password="OldPassword123!",
|
|
152
|
+
full_name="Reset User"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@patch('django.core.mail.send_mail')
|
|
156
|
+
def test_request_password_reset(self, mock_send_mail):
|
|
157
|
+
"""Test requesting a password reset"""
|
|
158
|
+
response = self.query(
|
|
159
|
+
"""
|
|
160
|
+
mutation request_password_reset($data: RequestPasswordResetMutationInput!) {
|
|
161
|
+
request_password_reset(input: $data) {
|
|
162
|
+
email
|
|
163
|
+
redirect_url
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
""",
|
|
167
|
+
operation_name="request_password_reset",
|
|
168
|
+
variables={
|
|
169
|
+
"data": {
|
|
170
|
+
"email": "resetuser@example.com",
|
|
171
|
+
"redirect_url": "http://localhost:3000/password/reset"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
self.assertResponseNoErrors(response)
|
|
177
|
+
self.assertEqual(response.data['data']['request_password_reset']['email'], "resetuser@example.com")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestEmailConfirmation(GraphTestCase):
|
|
181
|
+
|
|
182
|
+
@patch('karrio.server.graph.schemas.base.mutations.email_verification.verify_token')
|
|
183
|
+
def test_confirm_email(self, mock_verify):
|
|
184
|
+
"""Test email confirmation"""
|
|
185
|
+
mock_verify.return_value = (True, None)
|
|
186
|
+
|
|
187
|
+
response = self.query(
|
|
188
|
+
"""
|
|
189
|
+
mutation confirm_email($data: ConfirmEmailMutationInput!) {
|
|
190
|
+
confirm_email(input: $data) {
|
|
191
|
+
success
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
""",
|
|
195
|
+
operation_name="confirm_email",
|
|
196
|
+
variables={
|
|
197
|
+
"data": {
|
|
198
|
+
"token": "test-confirmation-token"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
self.assertResponseNoErrors(response)
|
|
204
|
+
self.assertTrue(response.data['data']['confirm_email']['success'])
|
|
205
|
+
mock_verify.assert_called_once_with("test-confirmation-token")
|
karrio/server/graph/utils.py
CHANGED
|
@@ -179,12 +179,16 @@ class UsageStatType:
|
|
|
179
179
|
date: typing.Optional[str] = None
|
|
180
180
|
label: typing.Optional[str] = None
|
|
181
181
|
count: typing.Optional[float] = None
|
|
182
|
+
amount: typing.Optional[float] = None
|
|
183
|
+
currency: typing.Optional[str] = None
|
|
182
184
|
|
|
183
185
|
@staticmethod
|
|
184
|
-
def parse(value: dict) -> "UsageStatType":
|
|
185
|
-
return UsageStatType(
|
|
186
|
-
**
|
|
187
|
-
|
|
186
|
+
def parse(value: dict, label: str = None) -> "UsageStatType":
|
|
187
|
+
return UsageStatType(**{
|
|
188
|
+
**(dict(label=label) if label else {}),
|
|
189
|
+
**{k: v for k, v in value.items() if k in UsageStatType.__annotations__},
|
|
190
|
+
"amount": lib.to_decimal(value.get("amount")),
|
|
191
|
+
})
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
@strawberry.input
|
|
@@ -192,6 +196,7 @@ class UsageFilter(BaseInput):
|
|
|
192
196
|
date_after: typing.Optional[str] = strawberry.UNSET
|
|
193
197
|
date_before: typing.Optional[str] = strawberry.UNSET
|
|
194
198
|
omit: typing.Optional[typing.List[str]] = strawberry.UNSET
|
|
199
|
+
surcharge_id: typing.Optional[str] = strawberry.UNSET
|
|
195
200
|
|
|
196
201
|
|
|
197
202
|
@dataclasses.dataclass
|
{karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/RECORD
RENAMED
|
@@ -6,7 +6,7 @@ karrio/server/graph/models.py,sha256=CEnE4AsVyjBufyK6ebWmUH3s8DwA0HvZg0fUoZb5Pn4
|
|
|
6
6
|
karrio/server/graph/schema.py,sha256=2dXM8nD1usOc1S6QSalajoFmgwYuXxsrwj20AJ5HtT4,1151
|
|
7
7
|
karrio/server/graph/serializers.py,sha256=kAVpyoN74gkA20ZnmDycvr_yrwFOb_A9di2-vrkdL7U,13760
|
|
8
8
|
karrio/server/graph/urls.py,sha256=HKo0gkx5TgoWDV0ap2QCtueNTmaAqvX6qVDe3c2UT1E,183
|
|
9
|
-
karrio/server/graph/utils.py,sha256=
|
|
9
|
+
karrio/server/graph/utils.py,sha256=kfg8y8IBOilHobb1cf4tB59VwZr3vPgB0SeSelLim_M,9308
|
|
10
10
|
karrio/server/graph/views.py,sha256=qWfa-wteB-Mb7glAYz6SVlZSwHPw_ImMm7XVmewdmTQ,2889
|
|
11
11
|
karrio/server/graph/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
karrio/server/graph/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -15,24 +15,25 @@ karrio/server/graph/migrations/0001_initial.py,sha256=oeaS5JSkQP6w9ulYar3mdn695y
|
|
|
15
15
|
karrio/server/graph/migrations/0002_auto_20210512_1353.py,sha256=TnUwR9EP0qp3gJ38f9w0PYawK2VheDtqXEgyRhYZS2M,538
|
|
16
16
|
karrio/server/graph/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
karrio/server/graph/schemas/__init__.py,sha256=Kn-I1j3HP3jwZccpz6FL9r1k6b3UEAMVh2kFPCKNS0w,80
|
|
18
|
-
karrio/server/graph/schemas/base/__init__.py,sha256=
|
|
19
|
-
karrio/server/graph/schemas/base/inputs.py,sha256=
|
|
20
|
-
karrio/server/graph/schemas/base/mutations.py,sha256=
|
|
21
|
-
karrio/server/graph/schemas/base/types.py,sha256=
|
|
18
|
+
karrio/server/graph/schemas/base/__init__.py,sha256=nnOfynQW842qOA0qD6bYz8GSHQCQeBW36vbq0sqRkJs,15121
|
|
19
|
+
karrio/server/graph/schemas/base/inputs.py,sha256=i6_PN-WD1mu_qH-2oIgfDUbgMRyUZmdr6gyySwyEShg,21307
|
|
20
|
+
karrio/server/graph/schemas/base/mutations.py,sha256=IUe7rvV4bI95iJ-pEg3YK9Qb0PVM9xgd0IvjOwDPvsM,34538
|
|
21
|
+
karrio/server/graph/schemas/base/types.py,sha256=TGgvJdvtrsUDIoXZuab3-_XWqBzBWxZq-xm-j9DfPzo,47120
|
|
22
22
|
karrio/server/graph/templates/graphql/graphiql.html,sha256=MQjQbBqoRE0QLsOUck8SaXo6B2oJO8dT6YZzUqbDan0,3786
|
|
23
23
|
karrio/server/graph/templates/karrio/email_change_email.html,sha256=YHqTy9VGV_s7kio57Tg3v7TCIN3QlnPHi2ioTOcHJLE,467
|
|
24
24
|
karrio/server/graph/templates/karrio/email_change_email.txt,sha256=NXXuzLR63hn2F1fVAjzmuguptuuTvujwqI7YLSrQoio,431
|
|
25
25
|
karrio/server/graph/templates/karrio/password_reset_email.html,sha256=dttqYVL73cQNuTFsVdn2GV4Ckee8PTY8oEF53GbDRcg,553
|
|
26
26
|
karrio/server/graph/tests/__init__.py,sha256=dPzsYY5hoO5gmY6fhL8tiz7Bfst8RB3JzsBVHZazHRE,338
|
|
27
27
|
karrio/server/graph/tests/base.py,sha256=m3k1CE-d9JHDsqfyXX0Fwksmkel33S7sh9-zkypp8wc,4004
|
|
28
|
-
karrio/server/graph/tests/test_carrier_connections.py,sha256=
|
|
28
|
+
karrio/server/graph/tests/test_carrier_connections.py,sha256=qZ1OL8CgZrHuluKJd7cjFXaRZ0VpogN5Srjk2EApLWU,6892
|
|
29
29
|
karrio/server/graph/tests/test_metafield.py,sha256=K7Oon0CLEm_MUMbmcu0t2iAZvFN8Wl7Kp4QAWeUXo_Y,12783
|
|
30
30
|
karrio/server/graph/tests/test_partial_shipments.py,sha256=dPIdIq4wiyovOaIIzbIX69eZnBqCA4ZvBSiGKYADM2g,19031
|
|
31
31
|
karrio/server/graph/tests/test_rate_sheets.py,sha256=cUzPV8dXQFPFh1r7W8bY6Lou3fjh8f9VGpyZrfbMXec,10300
|
|
32
|
+
karrio/server/graph/tests/test_registration.py,sha256=jK8ZCmhtvKZ9L3yqSIK1-JXzYT2PWwxQ3Md1_ff9818,7192
|
|
32
33
|
karrio/server/graph/tests/test_templates.py,sha256=WVU6vcfr6tEk917uSn1dECU8bkQtgD7FNuE-GJuFido,21626
|
|
33
34
|
karrio/server/graph/tests/test_user_info.py,sha256=K91BL7SgxLWflCZriSVI8Vt5M5RIqmSCHKrgN2w8GmM,1928
|
|
34
35
|
karrio/server/settings/graph.py,sha256=cz2yQHbp3xCfyFKuUkPEFfkI2fFVggExIY49wGz7mt0,106
|
|
35
|
-
karrio_server_graph-2025.
|
|
36
|
-
karrio_server_graph-2025.
|
|
37
|
-
karrio_server_graph-2025.
|
|
38
|
-
karrio_server_graph-2025.
|
|
36
|
+
karrio_server_graph-2025.5rc15.dist-info/METADATA,sha256=YT3yl8qMK24AoSodvtliaYbRcesRKHpelootMSaTI8I,744
|
|
37
|
+
karrio_server_graph-2025.5rc15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
karrio_server_graph-2025.5rc15.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
|
|
39
|
+
karrio_server_graph-2025.5rc15.dist-info/RECORD,,
|
|
File without changes
|
{karrio_server_graph-2025.5rc14.dist-info → karrio_server_graph-2025.5rc15.dist-info}/top_level.txt
RENAMED
|
File without changes
|