karrio-server-graph 2025.5rc1__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/__init__.py +1 -0
- karrio/server/graph/admin.py +3 -0
- karrio/server/graph/apps.py +5 -0
- karrio/server/graph/forms.py +59 -0
- karrio/server/graph/management/__init__.py +0 -0
- karrio/server/graph/management/commands/__init__.py +0 -0
- karrio/server/graph/management/commands/export_schema.py +9 -0
- karrio/server/graph/migrations/0001_initial.py +37 -0
- karrio/server/graph/migrations/0002_auto_20210512_1353.py +22 -0
- karrio/server/graph/migrations/__init__.py +0 -0
- karrio/server/graph/models.py +44 -0
- karrio/server/graph/schema.py +46 -0
- karrio/server/graph/schemas/__init__.py +2 -0
- karrio/server/graph/schemas/base/__init__.py +367 -0
- karrio/server/graph/schemas/base/inputs.py +582 -0
- karrio/server/graph/schemas/base/mutations.py +871 -0
- karrio/server/graph/schemas/base/types.py +1365 -0
- karrio/server/graph/serializers.py +388 -0
- karrio/server/graph/templates/graphql/graphiql.html +142 -0
- karrio/server/graph/templates/karrio/email_change_email.html +13 -0
- karrio/server/graph/templates/karrio/email_change_email.txt +13 -0
- karrio/server/graph/templates/karrio/password_reset_email.html +14 -0
- karrio/server/graph/tests/__init__.py +9 -0
- karrio/server/graph/tests/base.py +124 -0
- karrio/server/graph/tests/test_carrier_connections.py +219 -0
- karrio/server/graph/tests/test_metafield.py +404 -0
- karrio/server/graph/tests/test_rate_sheets.py +348 -0
- karrio/server/graph/tests/test_templates.py +677 -0
- karrio/server/graph/tests/test_user_info.py +71 -0
- karrio/server/graph/urls.py +10 -0
- karrio/server/graph/utils.py +304 -0
- karrio/server/graph/views.py +93 -0
- karrio/server/settings/graph.py +7 -0
- karrio_server_graph-2025.5rc1.dist-info/METADATA +29 -0
- karrio_server_graph-2025.5rc1.dist-info/RECORD +37 -0
- karrio_server_graph-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_graph-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,404 @@
|
|
1
|
+
import json
|
2
|
+
from datetime import datetime, date
|
3
|
+
from django.test import TestCase
|
4
|
+
from django.contrib.auth import get_user_model
|
5
|
+
from karrio.server.core.models import Metafield, MetafieldType
|
6
|
+
from karrio.server.graph.tests import GraphTestCase
|
7
|
+
|
8
|
+
User = get_user_model()
|
9
|
+
|
10
|
+
|
11
|
+
class MetafieldModelTest(TestCase):
|
12
|
+
def setUp(self):
|
13
|
+
self.user = User.objects.create_user(
|
14
|
+
email="test@example.com", password="test123"
|
15
|
+
)
|
16
|
+
|
17
|
+
def test_create_text_metafield(self):
|
18
|
+
"""Test creating a text metafield"""
|
19
|
+
metafield = Metafield.objects.create(
|
20
|
+
key="test_text",
|
21
|
+
value="Hello World",
|
22
|
+
type=MetafieldType.text,
|
23
|
+
created_by=self.user
|
24
|
+
)
|
25
|
+
self.assertEqual(metafield.key, "test_text")
|
26
|
+
self.assertEqual(metafield.value, "Hello World")
|
27
|
+
self.assertEqual(metafield.get_parsed_value(), "Hello World")
|
28
|
+
|
29
|
+
def test_create_number_metafield(self):
|
30
|
+
"""Test creating a number metafield"""
|
31
|
+
metafield = Metafield.objects.create(
|
32
|
+
key="test_number",
|
33
|
+
value=42,
|
34
|
+
type=MetafieldType.number,
|
35
|
+
created_by=self.user
|
36
|
+
)
|
37
|
+
self.assertEqual(metafield.key, "test_number")
|
38
|
+
self.assertEqual(metafield.value, 42)
|
39
|
+
self.assertEqual(metafield.get_parsed_value(), 42)
|
40
|
+
|
41
|
+
def test_create_boolean_metafield(self):
|
42
|
+
"""Test creating a boolean metafield"""
|
43
|
+
metafield = Metafield.objects.create(
|
44
|
+
key="test_bool",
|
45
|
+
value=True,
|
46
|
+
type=MetafieldType.boolean,
|
47
|
+
created_by=self.user
|
48
|
+
)
|
49
|
+
self.assertEqual(metafield.key, "test_bool")
|
50
|
+
self.assertEqual(metafield.value, True)
|
51
|
+
self.assertEqual(metafield.get_parsed_value(), True)
|
52
|
+
|
53
|
+
def test_create_json_metafield(self):
|
54
|
+
"""Test creating a JSON metafield"""
|
55
|
+
json_data = {"name": "John", "age": 30}
|
56
|
+
metafield = Metafield.objects.create(
|
57
|
+
key="test_json",
|
58
|
+
value=json_data,
|
59
|
+
type=MetafieldType.json,
|
60
|
+
created_by=self.user
|
61
|
+
)
|
62
|
+
self.assertEqual(metafield.key, "test_json")
|
63
|
+
self.assertEqual(metafield.get_parsed_value(), json_data)
|
64
|
+
|
65
|
+
def test_create_date_metafield(self):
|
66
|
+
"""Test creating a date metafield"""
|
67
|
+
date_string = "2023-12-25"
|
68
|
+
metafield = Metafield.objects.create(
|
69
|
+
key="test_date",
|
70
|
+
value=date_string,
|
71
|
+
type=MetafieldType.date,
|
72
|
+
created_by=self.user
|
73
|
+
)
|
74
|
+
self.assertEqual(metafield.key, "test_date")
|
75
|
+
self.assertEqual(metafield.value, date_string)
|
76
|
+
self.assertEqual(metafield.get_parsed_value(), date(2023, 12, 25))
|
77
|
+
|
78
|
+
def test_create_datetime_metafield(self):
|
79
|
+
"""Test creating a datetime metafield"""
|
80
|
+
datetime_string = "2023-12-25T10:30:00Z"
|
81
|
+
metafield = Metafield.objects.create(
|
82
|
+
key="test_datetime",
|
83
|
+
value=datetime_string,
|
84
|
+
type=MetafieldType.date_time,
|
85
|
+
created_by=self.user
|
86
|
+
)
|
87
|
+
self.assertEqual(metafield.key, "test_datetime")
|
88
|
+
self.assertEqual(metafield.value, datetime_string)
|
89
|
+
# The parsed value should be a datetime object
|
90
|
+
parsed_value = metafield.get_parsed_value()
|
91
|
+
self.assertIsInstance(parsed_value, datetime)
|
92
|
+
|
93
|
+
def test_create_password_metafield(self):
|
94
|
+
"""Test creating a password metafield"""
|
95
|
+
metafield = Metafield.objects.create(
|
96
|
+
key="test_password",
|
97
|
+
value="secret123",
|
98
|
+
type=MetafieldType.password,
|
99
|
+
created_by=self.user
|
100
|
+
)
|
101
|
+
self.assertEqual(metafield.key, "test_password")
|
102
|
+
self.assertEqual(metafield.get_parsed_value(), "secret123")
|
103
|
+
|
104
|
+
def test_required_metafield(self):
|
105
|
+
"""Test creating a required metafield"""
|
106
|
+
metafield = Metafield.objects.create(
|
107
|
+
key="required_field",
|
108
|
+
value="required value",
|
109
|
+
type=MetafieldType.text,
|
110
|
+
is_required=True,
|
111
|
+
created_by=self.user
|
112
|
+
)
|
113
|
+
self.assertTrue(metafield.is_required)
|
114
|
+
|
115
|
+
|
116
|
+
class MetafieldGraphQLTest(GraphTestCase):
|
117
|
+
def setUp(self):
|
118
|
+
super().setUp()
|
119
|
+
|
120
|
+
def test_create_text_metafield_graphql(self):
|
121
|
+
"""Test creating a text metafield via GraphQL"""
|
122
|
+
query = '''
|
123
|
+
mutation CreateMetafield($input: CreateMetafieldInput!) {
|
124
|
+
create_metafield(input: $input) {
|
125
|
+
metafield {
|
126
|
+
id
|
127
|
+
key
|
128
|
+
value
|
129
|
+
type
|
130
|
+
parsed_value
|
131
|
+
is_required
|
132
|
+
}
|
133
|
+
errors {
|
134
|
+
field
|
135
|
+
messages
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
'''
|
140
|
+
|
141
|
+
variables = {
|
142
|
+
"input": {
|
143
|
+
"key": "test_text",
|
144
|
+
"value": "Hello World",
|
145
|
+
"type": "text",
|
146
|
+
"is_required": False
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
response = self.query(query, variables=variables)
|
151
|
+
self.assertResponseNoErrors(response)
|
152
|
+
|
153
|
+
metafield_data = response.data["data"]["create_metafield"]["metafield"]
|
154
|
+
expected_data = {
|
155
|
+
"create_metafield": {
|
156
|
+
"metafield": {
|
157
|
+
"id": metafield_data["id"], # Dynamic ID
|
158
|
+
"key": "test_text",
|
159
|
+
"value": "Hello World",
|
160
|
+
"type": "text",
|
161
|
+
"parsed_value": "Hello World",
|
162
|
+
"is_required": False
|
163
|
+
},
|
164
|
+
"errors": None
|
165
|
+
}
|
166
|
+
}
|
167
|
+
self.assertDictEqual(response.data["data"], expected_data)
|
168
|
+
|
169
|
+
def test_create_json_metafield_graphql(self):
|
170
|
+
"""Test creating a JSON metafield via GraphQL"""
|
171
|
+
query = '''
|
172
|
+
mutation CreateMetafield($input: CreateMetafieldInput!) {
|
173
|
+
create_metafield(input: $input) {
|
174
|
+
metafield {
|
175
|
+
id
|
176
|
+
key
|
177
|
+
value
|
178
|
+
type
|
179
|
+
parsed_value
|
180
|
+
is_required
|
181
|
+
}
|
182
|
+
errors {
|
183
|
+
field
|
184
|
+
messages
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
188
|
+
'''
|
189
|
+
|
190
|
+
json_data = {"name": "John", "age": 30, "active": True}
|
191
|
+
variables = {
|
192
|
+
"input": {
|
193
|
+
"key": "test_json",
|
194
|
+
"value": json_data,
|
195
|
+
"type": "json",
|
196
|
+
"is_required": False
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
response = self.query(query, variables=variables)
|
201
|
+
self.assertResponseNoErrors(response)
|
202
|
+
|
203
|
+
metafield_data = response.data["data"]["create_metafield"]["metafield"]
|
204
|
+
expected_data = {
|
205
|
+
"create_metafield": {
|
206
|
+
"metafield": {
|
207
|
+
"id": metafield_data["id"], # Dynamic ID
|
208
|
+
"key": "test_json",
|
209
|
+
"value": json_data,
|
210
|
+
"type": "json",
|
211
|
+
"parsed_value": json_data,
|
212
|
+
"is_required": False
|
213
|
+
},
|
214
|
+
"errors": None
|
215
|
+
}
|
216
|
+
}
|
217
|
+
self.assertDictEqual(response.data["data"], expected_data)
|
218
|
+
|
219
|
+
def test_query_metafields_graphql(self):
|
220
|
+
"""Test querying metafields via GraphQL"""
|
221
|
+
# Create some test metafields
|
222
|
+
Metafield.objects.create(
|
223
|
+
key="test_text_0",
|
224
|
+
value="Hello",
|
225
|
+
type=MetafieldType.text,
|
226
|
+
created_by=self.user
|
227
|
+
)
|
228
|
+
Metafield.objects.create(
|
229
|
+
key="test_number_0",
|
230
|
+
value=42,
|
231
|
+
type=MetafieldType.number,
|
232
|
+
created_by=self.user
|
233
|
+
)
|
234
|
+
|
235
|
+
query = '''
|
236
|
+
query GetMetafields($filter: MetafieldFilter) {
|
237
|
+
metafields(filter: $filter) {
|
238
|
+
edges {
|
239
|
+
node {
|
240
|
+
id
|
241
|
+
key
|
242
|
+
value
|
243
|
+
type
|
244
|
+
parsed_value
|
245
|
+
is_required
|
246
|
+
}
|
247
|
+
}
|
248
|
+
page_info {
|
249
|
+
has_next_page
|
250
|
+
has_previous_page
|
251
|
+
}
|
252
|
+
}
|
253
|
+
}
|
254
|
+
'''
|
255
|
+
|
256
|
+
response = self.query(query)
|
257
|
+
self.assertResponseNoErrors(response)
|
258
|
+
|
259
|
+
# Should return both metafields
|
260
|
+
edges = response.data["data"]["metafields"]["edges"]
|
261
|
+
self.assertEqual(len(edges), 2)
|
262
|
+
|
263
|
+
def test_update_metafield_graphql(self):
|
264
|
+
"""Test updating a metafield via GraphQL"""
|
265
|
+
# Create a metafield first
|
266
|
+
metafield = Metafield.objects.create(
|
267
|
+
key="test_update",
|
268
|
+
value="original",
|
269
|
+
type=MetafieldType.text,
|
270
|
+
created_by=self.user
|
271
|
+
)
|
272
|
+
|
273
|
+
query = '''
|
274
|
+
mutation UpdateMetafield($input: UpdateMetafieldInput!) {
|
275
|
+
update_metafield(input: $input) {
|
276
|
+
metafield {
|
277
|
+
id
|
278
|
+
key
|
279
|
+
value
|
280
|
+
type
|
281
|
+
parsed_value
|
282
|
+
is_required
|
283
|
+
}
|
284
|
+
errors {
|
285
|
+
field
|
286
|
+
messages
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}
|
290
|
+
'''
|
291
|
+
|
292
|
+
variables = {
|
293
|
+
"input": {
|
294
|
+
"id": metafield.id,
|
295
|
+
"key": "test_updated",
|
296
|
+
"value": "updated value",
|
297
|
+
"type": "text",
|
298
|
+
"is_required": True
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
response = self.query(query, variables=variables)
|
303
|
+
self.assertResponseNoErrors(response)
|
304
|
+
|
305
|
+
expected_data = {
|
306
|
+
"update_metafield": {
|
307
|
+
"metafield": {
|
308
|
+
"id": metafield.id,
|
309
|
+
"key": "test_updated",
|
310
|
+
"value": "updated value",
|
311
|
+
"type": "text",
|
312
|
+
"parsed_value": "updated value",
|
313
|
+
"is_required": True
|
314
|
+
},
|
315
|
+
"errors": None
|
316
|
+
}
|
317
|
+
}
|
318
|
+
self.assertDictEqual(response.data["data"], expected_data)
|
319
|
+
|
320
|
+
def test_delete_metafield_graphql(self):
|
321
|
+
"""Test deleting a metafield via GraphQL"""
|
322
|
+
# Create a metafield first
|
323
|
+
metafield = Metafield.objects.create(
|
324
|
+
key="test_delete",
|
325
|
+
value="to be deleted",
|
326
|
+
type=MetafieldType.text,
|
327
|
+
created_by=self.user
|
328
|
+
)
|
329
|
+
|
330
|
+
query = '''
|
331
|
+
mutation DeleteMetafield($input: DeleteMutationInput!) {
|
332
|
+
delete_metafield(input: $input) {
|
333
|
+
id
|
334
|
+
errors {
|
335
|
+
field
|
336
|
+
messages
|
337
|
+
}
|
338
|
+
}
|
339
|
+
}
|
340
|
+
'''
|
341
|
+
|
342
|
+
variables = {
|
343
|
+
"input": {
|
344
|
+
"id": metafield.id
|
345
|
+
}
|
346
|
+
}
|
347
|
+
|
348
|
+
response = self.query(query, variables=variables)
|
349
|
+
self.assertResponseNoErrors(response)
|
350
|
+
|
351
|
+
expected_data = {
|
352
|
+
"delete_metafield": {
|
353
|
+
"id": metafield.id,
|
354
|
+
"errors": None
|
355
|
+
}
|
356
|
+
}
|
357
|
+
self.assertDictEqual(response.data["data"], expected_data)
|
358
|
+
|
359
|
+
# Verify the metafield was actually deleted
|
360
|
+
self.assertFalse(Metafield.objects.filter(id=metafield.id).exists())
|
361
|
+
|
362
|
+
def test_metafield_type_validation(self):
|
363
|
+
"""Test that metafield type validation works"""
|
364
|
+
query = '''
|
365
|
+
mutation CreateMetafield($input: CreateMetafieldInput!) {
|
366
|
+
create_metafield(input: $input) {
|
367
|
+
metafield {
|
368
|
+
id
|
369
|
+
key
|
370
|
+
value
|
371
|
+
type
|
372
|
+
parsed_value
|
373
|
+
is_required
|
374
|
+
}
|
375
|
+
errors {
|
376
|
+
field
|
377
|
+
messages
|
378
|
+
}
|
379
|
+
}
|
380
|
+
}
|
381
|
+
'''
|
382
|
+
|
383
|
+
# Test with invalid JSON
|
384
|
+
variables = {
|
385
|
+
"input": {
|
386
|
+
"key": "invalid_json",
|
387
|
+
"value": "not valid json",
|
388
|
+
"type": "json",
|
389
|
+
"is_required": False
|
390
|
+
}
|
391
|
+
}
|
392
|
+
|
393
|
+
response = self.query(query, variables=variables)
|
394
|
+
|
395
|
+
# Check if we got a response (might have errors or succeed depending on validation)
|
396
|
+
self.assertEqual(response.status_code, 200)
|
397
|
+
|
398
|
+
# If the mutation succeeded, check the data
|
399
|
+
if response.data["data"]["create_metafield"]["metafield"]:
|
400
|
+
metafield_data = response.data["data"]["create_metafield"]["metafield"]
|
401
|
+
self.assertIsNotNone(metafield_data["id"])
|
402
|
+
# If it failed, there should be errors
|
403
|
+
elif response.data["data"]["create_metafield"]["errors"]:
|
404
|
+
self.assertIsNotNone(response.data["data"]["create_metafield"]["errors"])
|