karrio-server-graph 2026.1__py3-none-any.whl → 2026.1.3__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/migrations/0003_remove_template_customs.py +15 -0
- karrio/server/graph/models.py +2 -5
- karrio/server/graph/schemas/base/__init__.py +68 -48
- karrio/server/graph/schemas/base/inputs.py +286 -71
- karrio/server/graph/schemas/base/mutations.py +365 -84
- karrio/server/graph/schemas/base/types.py +940 -248
- karrio/server/graph/serializers.py +25 -59
- karrio/server/graph/tests/base.py +33 -6
- karrio/server/graph/tests/test_carrier_connections.py +33 -4
- karrio/server/graph/tests/test_pickups.py +333 -0
- karrio/server/graph/tests/test_rate_sheets.py +0 -1
- karrio/server/graph/tests/test_templates.py +328 -510
- karrio/server/graph/utils.py +68 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/METADATA +1 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/RECORD +17 -15
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/WHEEL +1 -1
- {karrio_server_graph-2026.1.dist-info → karrio_server_graph-2026.1.3.dist-info}/top_level.txt +0 -0
|
@@ -68,10 +68,33 @@ class WorkspaceConfigModelSerializer(serializers.ModelSerializer):
|
|
|
68
68
|
|
|
69
69
|
@serializers.owned_model_serializer
|
|
70
70
|
class MetafieldModelSerializer(serializers.ModelSerializer):
|
|
71
|
+
object_type = serializers.CharField(required=False, allow_null=True)
|
|
72
|
+
object_id = serializers.CharField(required=False, allow_null=True)
|
|
73
|
+
|
|
71
74
|
class Meta:
|
|
72
75
|
model = core.Metafield
|
|
73
76
|
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
|
74
|
-
exclude = ["created_at", "updated_at", "created_by"]
|
|
77
|
+
exclude = ["created_at", "updated_at", "created_by", "content_type"]
|
|
78
|
+
|
|
79
|
+
def validate(self, data):
|
|
80
|
+
from django.contrib.contenttypes.models import ContentType
|
|
81
|
+
|
|
82
|
+
object_type = data.pop("object_type", None)
|
|
83
|
+
object_id = data.get("object_id")
|
|
84
|
+
|
|
85
|
+
if object_type and object_id:
|
|
86
|
+
ct = ContentType.objects.filter(model=object_type).first()
|
|
87
|
+
if not ct:
|
|
88
|
+
raise serializers.ValidationError(
|
|
89
|
+
{"object_type": f"Invalid object type: {object_type}"}
|
|
90
|
+
)
|
|
91
|
+
data["content_type"] = ct
|
|
92
|
+
elif object_type or object_id:
|
|
93
|
+
raise serializers.ValidationError(
|
|
94
|
+
"Both object_type and object_id must be provided together"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return data
|
|
75
98
|
|
|
76
99
|
|
|
77
100
|
@serializers.owned_model_serializer
|
|
@@ -98,53 +121,6 @@ class CommodityModelSerializer(serializers.ModelSerializer):
|
|
|
98
121
|
extra_kwargs = {field: {"read_only": True} for field in ["id", "parent"]}
|
|
99
122
|
|
|
100
123
|
|
|
101
|
-
@serializers.owned_model_serializer
|
|
102
|
-
class CustomsModelSerializer(serializers.ModelSerializer):
|
|
103
|
-
NESTED_FIELDS = ["commodities"]
|
|
104
|
-
|
|
105
|
-
incoterm = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
|
106
|
-
commodities = serializers.make_fields_optional(CommodityModelSerializer)(
|
|
107
|
-
many=True, allow_null=True, required=False
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
class Meta:
|
|
111
|
-
model = manager.Customs
|
|
112
|
-
exclude = ["created_at", "updated_at", "created_by"]
|
|
113
|
-
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
|
114
|
-
|
|
115
|
-
@transaction.atomic
|
|
116
|
-
def create(self, validated_data: dict, context: dict):
|
|
117
|
-
data = {
|
|
118
|
-
name: value
|
|
119
|
-
for name, value in validated_data.items()
|
|
120
|
-
if name not in self.NESTED_FIELDS
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
instance = super().create(data)
|
|
124
|
-
|
|
125
|
-
serializers.save_many_to_many_data(
|
|
126
|
-
"commodities",
|
|
127
|
-
CommodityModelSerializer,
|
|
128
|
-
instance,
|
|
129
|
-
payload=validated_data,
|
|
130
|
-
context=context,
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
return instance
|
|
134
|
-
|
|
135
|
-
@transaction.atomic
|
|
136
|
-
def update(
|
|
137
|
-
self, instance: manager.Customs, validated_data: dict, **kwargs
|
|
138
|
-
) -> manager.Customs:
|
|
139
|
-
data = {
|
|
140
|
-
name: value
|
|
141
|
-
for name, value in validated_data.items()
|
|
142
|
-
if name not in self.NESTED_FIELDS
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return super().update(instance, data)
|
|
146
|
-
|
|
147
|
-
|
|
148
124
|
@serializers.owned_model_serializer
|
|
149
125
|
class ParcelModelSerializer(validators.PresetSerializer, serializers.ModelSerializer):
|
|
150
126
|
weight_unit = serializers.CharField(
|
|
@@ -163,7 +139,6 @@ class ParcelModelSerializer(validators.PresetSerializer, serializers.ModelSerial
|
|
|
163
139
|
@serializers.owned_model_serializer
|
|
164
140
|
class TemplateModelSerializer(serializers.ModelSerializer):
|
|
165
141
|
address = serializers.make_fields_optional(AddressModelSerializer)(required=False)
|
|
166
|
-
customs = serializers.make_fields_optional(CustomsModelSerializer)(required=False)
|
|
167
142
|
parcel = serializers.make_fields_optional(ParcelModelSerializer)(required=False)
|
|
168
143
|
|
|
169
144
|
class Meta:
|
|
@@ -181,12 +156,6 @@ class TemplateModelSerializer(serializers.ModelSerializer):
|
|
|
181
156
|
payload=validated_data,
|
|
182
157
|
context=context,
|
|
183
158
|
),
|
|
184
|
-
"customs": serializers.save_one_to_one_data(
|
|
185
|
-
"customs",
|
|
186
|
-
CustomsModelSerializer,
|
|
187
|
-
payload=validated_data,
|
|
188
|
-
context=context,
|
|
189
|
-
),
|
|
190
159
|
"parcel": serializers.save_one_to_one_data(
|
|
191
160
|
"parcel", ParcelModelSerializer, payload=validated_data, context=context
|
|
192
161
|
),
|
|
@@ -203,15 +172,12 @@ class TemplateModelSerializer(serializers.ModelSerializer):
|
|
|
203
172
|
data = {
|
|
204
173
|
key: value
|
|
205
174
|
for key, value in validated_data.items()
|
|
206
|
-
if key not in ["address", "
|
|
175
|
+
if key not in ["address", "parcel"]
|
|
207
176
|
}
|
|
208
177
|
|
|
209
178
|
serializers.save_one_to_one_data(
|
|
210
179
|
"address", AddressModelSerializer, instance, payload=validated_data
|
|
211
180
|
)
|
|
212
|
-
serializers.save_one_to_one_data(
|
|
213
|
-
"customs", CustomsModelSerializer, instance, payload=validated_data
|
|
214
|
-
)
|
|
215
181
|
serializers.save_one_to_one_data(
|
|
216
182
|
"parcel", ParcelModelSerializer, instance, payload=validated_data
|
|
217
183
|
)
|
|
@@ -52,7 +52,7 @@ class GraphTestCase(BaseAPITestCase):
|
|
|
52
52
|
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.token.key)
|
|
53
53
|
|
|
54
54
|
# Setup test carrier connections.
|
|
55
|
-
self.carrier = providers.
|
|
55
|
+
self.carrier = providers.CarrierConnection.objects.create(
|
|
56
56
|
carrier_code="canadapost",
|
|
57
57
|
carrier_id="canadapost",
|
|
58
58
|
test_mode=False,
|
|
@@ -65,7 +65,7 @@ class GraphTestCase(BaseAPITestCase):
|
|
|
65
65
|
),
|
|
66
66
|
capabilities=["pickup", "rating", "tracking", "shipping"],
|
|
67
67
|
)
|
|
68
|
-
self.ups_carrier = providers.
|
|
68
|
+
self.ups_carrier = providers.CarrierConnection.objects.create(
|
|
69
69
|
carrier_code="ups",
|
|
70
70
|
carrier_id="ups_package",
|
|
71
71
|
test_mode=False,
|
|
@@ -77,10 +77,11 @@ class GraphTestCase(BaseAPITestCase):
|
|
|
77
77
|
),
|
|
78
78
|
capabilities=["pickup", "rating", "tracking", "shipping"],
|
|
79
79
|
)
|
|
80
|
-
self.fedex_carrier = providers.
|
|
80
|
+
self.fedex_carrier = providers.CarrierConnection.objects.create(
|
|
81
81
|
carrier_code="fedex",
|
|
82
82
|
carrier_id="fedex_express",
|
|
83
83
|
test_mode=False,
|
|
84
|
+
created_by=self.user,
|
|
84
85
|
credentials=dict(
|
|
85
86
|
api_key="test",
|
|
86
87
|
secret_key="password",
|
|
@@ -89,13 +90,12 @@ class GraphTestCase(BaseAPITestCase):
|
|
|
89
90
|
track_secret_key="password",
|
|
90
91
|
),
|
|
91
92
|
capabilities=["pickup", "rating", "tracking", "shipping"],
|
|
92
|
-
is_system=True,
|
|
93
93
|
)
|
|
94
|
-
self.dhl_carrier = providers.
|
|
94
|
+
self.dhl_carrier = providers.CarrierConnection.objects.create(
|
|
95
95
|
carrier_code="dhl_universal",
|
|
96
96
|
carrier_id="dhl_universal",
|
|
97
97
|
test_mode=False,
|
|
98
|
-
|
|
98
|
+
created_by=self.user,
|
|
99
99
|
credentials=dict(
|
|
100
100
|
consumer_key="test",
|
|
101
101
|
consumer_secret="password",
|
|
@@ -103,6 +103,33 @@ class GraphTestCase(BaseAPITestCase):
|
|
|
103
103
|
capabilities=["tracking"],
|
|
104
104
|
)
|
|
105
105
|
|
|
106
|
+
# Setup system connections for system_connections queries
|
|
107
|
+
self.dhl_system_connection = providers.SystemConnection.objects.create(
|
|
108
|
+
carrier_code="dhl_universal",
|
|
109
|
+
carrier_id="dhl_universal",
|
|
110
|
+
test_mode=False,
|
|
111
|
+
active=True,
|
|
112
|
+
credentials=dict(
|
|
113
|
+
consumer_key="system_test",
|
|
114
|
+
consumer_secret="system_password",
|
|
115
|
+
),
|
|
116
|
+
capabilities=["tracking"],
|
|
117
|
+
)
|
|
118
|
+
self.fedex_system_connection = providers.SystemConnection.objects.create(
|
|
119
|
+
carrier_code="fedex",
|
|
120
|
+
carrier_id="fedex_express",
|
|
121
|
+
test_mode=False,
|
|
122
|
+
active=True,
|
|
123
|
+
credentials=dict(
|
|
124
|
+
api_key="system_test",
|
|
125
|
+
secret_key="system_password",
|
|
126
|
+
account_number="000000",
|
|
127
|
+
track_api_key="system_test",
|
|
128
|
+
track_secret_key="system_password",
|
|
129
|
+
),
|
|
130
|
+
capabilities=["pickup", "rating", "tracking", "shipping"],
|
|
131
|
+
)
|
|
132
|
+
|
|
106
133
|
def query(
|
|
107
134
|
self,
|
|
108
135
|
query: str,
|
|
@@ -121,8 +121,8 @@ SYSTEM_CONNECTIONS = {
|
|
|
121
121
|
{
|
|
122
122
|
"node": {
|
|
123
123
|
"active": True,
|
|
124
|
-
"carrier_id": "
|
|
125
|
-
"carrier_name": "
|
|
124
|
+
"carrier_id": "fedex_express",
|
|
125
|
+
"carrier_name": "fedex",
|
|
126
126
|
"id": ANY,
|
|
127
127
|
"test_mode": False,
|
|
128
128
|
}
|
|
@@ -130,8 +130,8 @@ SYSTEM_CONNECTIONS = {
|
|
|
130
130
|
{
|
|
131
131
|
"node": {
|
|
132
132
|
"active": True,
|
|
133
|
-
"carrier_id": "
|
|
134
|
-
"carrier_name": "
|
|
133
|
+
"carrier_id": "dhl_universal",
|
|
134
|
+
"carrier_name": "dhl_universal",
|
|
135
135
|
"id": ANY,
|
|
136
136
|
"test_mode": False,
|
|
137
137
|
}
|
|
@@ -145,6 +145,35 @@ USER_CONNECTIONS = {
|
|
|
145
145
|
"data": {
|
|
146
146
|
"user_connections": {
|
|
147
147
|
"edges": [
|
|
148
|
+
{
|
|
149
|
+
"node": {
|
|
150
|
+
"active": True,
|
|
151
|
+
"carrier_id": "dhl_universal",
|
|
152
|
+
"carrier_name": "dhl_universal",
|
|
153
|
+
"credentials": {
|
|
154
|
+
"consumer_key": "test",
|
|
155
|
+
"consumer_secret": "password",
|
|
156
|
+
},
|
|
157
|
+
"id": ANY,
|
|
158
|
+
"test_mode": False,
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"node": {
|
|
163
|
+
"active": True,
|
|
164
|
+
"carrier_id": "fedex_express",
|
|
165
|
+
"carrier_name": "fedex",
|
|
166
|
+
"credentials": {
|
|
167
|
+
"account_number": "000000",
|
|
168
|
+
"api_key": "test",
|
|
169
|
+
"secret_key": "password",
|
|
170
|
+
"track_api_key": "test",
|
|
171
|
+
"track_secret_key": "password",
|
|
172
|
+
},
|
|
173
|
+
"id": ANY,
|
|
174
|
+
"test_mode": False,
|
|
175
|
+
}
|
|
176
|
+
},
|
|
148
177
|
{
|
|
149
178
|
"node": {
|
|
150
179
|
"active": True,
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""GraphQL pickup query tests following AGENTS.md patterns."""
|
|
2
|
+
|
|
3
|
+
import karrio.lib as lib
|
|
4
|
+
from karrio.server.graph.tests.base import GraphTestCase
|
|
5
|
+
from karrio.server.core.utils import create_carrier_snapshot
|
|
6
|
+
import karrio.server.manager.models as models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestPickupQueries(GraphTestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
super().setUp()
|
|
12
|
+
self.maxDiff = None
|
|
13
|
+
|
|
14
|
+
# Create a shipment for the pickup
|
|
15
|
+
self.shipment = models.Shipment.objects.create(
|
|
16
|
+
shipper={
|
|
17
|
+
"postal_code": "E1C4Z8",
|
|
18
|
+
"city": "Moncton",
|
|
19
|
+
"person_name": "John Doe",
|
|
20
|
+
"company_name": "A corp.",
|
|
21
|
+
"country_code": "CA",
|
|
22
|
+
"phone_number": "514 000 0000",
|
|
23
|
+
"state_code": "NB",
|
|
24
|
+
"address_line1": "125 Church St",
|
|
25
|
+
},
|
|
26
|
+
recipient={
|
|
27
|
+
"postal_code": "V6M2V9",
|
|
28
|
+
"city": "Vancouver",
|
|
29
|
+
"person_name": "Jane Doe",
|
|
30
|
+
"company_name": "B corp.",
|
|
31
|
+
"country_code": "CA",
|
|
32
|
+
"phone_number": "604 000 0000",
|
|
33
|
+
"state_code": "BC",
|
|
34
|
+
"address_line1": "5840 Oak St",
|
|
35
|
+
},
|
|
36
|
+
parcels=[
|
|
37
|
+
{
|
|
38
|
+
"weight": 1.0,
|
|
39
|
+
"weight_unit": "KG",
|
|
40
|
+
"package_preset": "canadapost_corrugated_small_box",
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
created_by=self.user,
|
|
44
|
+
test_mode=False,
|
|
45
|
+
tracking_number="123456789012",
|
|
46
|
+
carrier=create_carrier_snapshot(self.carrier),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create test pickup (test_mode must match token's test_mode)
|
|
50
|
+
self.pickup = models.Pickup.objects.create(
|
|
51
|
+
address={
|
|
52
|
+
"id": "adr_001122334455",
|
|
53
|
+
"postal_code": "E1C4Z8",
|
|
54
|
+
"city": "Moncton",
|
|
55
|
+
"person_name": "John Poop",
|
|
56
|
+
"company_name": "A corp.",
|
|
57
|
+
"country_code": "CA",
|
|
58
|
+
"phone_number": "514 000 0000",
|
|
59
|
+
"state_code": "NB",
|
|
60
|
+
"address_line1": "125 Church St",
|
|
61
|
+
},
|
|
62
|
+
carrier=create_carrier_snapshot(self.carrier),
|
|
63
|
+
created_by=self.user,
|
|
64
|
+
test_mode=False,
|
|
65
|
+
pickup_date="2020-10-25",
|
|
66
|
+
ready_time="13:00",
|
|
67
|
+
closing_time="17:00",
|
|
68
|
+
instruction="Should not be folded",
|
|
69
|
+
package_location="At the main entrance hall",
|
|
70
|
+
confirmation_number="00110215",
|
|
71
|
+
pickup_charge={"name": "Pickup fees", "amount": 0.0, "currency": "CAD"},
|
|
72
|
+
)
|
|
73
|
+
self.pickup.shipments.set([self.shipment])
|
|
74
|
+
|
|
75
|
+
def test_query_pickup(self):
|
|
76
|
+
"""Verify single pickup query."""
|
|
77
|
+
response = self.query(
|
|
78
|
+
"""
|
|
79
|
+
query get_pickup($id: String!) {
|
|
80
|
+
pickup(id: $id) {
|
|
81
|
+
id
|
|
82
|
+
object_type
|
|
83
|
+
confirmation_number
|
|
84
|
+
pickup_date
|
|
85
|
+
ready_time
|
|
86
|
+
closing_time
|
|
87
|
+
carrier_name
|
|
88
|
+
carrier_id
|
|
89
|
+
test_mode
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
""",
|
|
93
|
+
operation_name="get_pickup",
|
|
94
|
+
variables={"id": self.pickup.id},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.assertResponseNoErrors(response)
|
|
98
|
+
self.assertDictEqual(
|
|
99
|
+
lib.to_dict(response.data),
|
|
100
|
+
{
|
|
101
|
+
"data": {
|
|
102
|
+
"pickup": {
|
|
103
|
+
"id": self.pickup.id,
|
|
104
|
+
"object_type": "pickup",
|
|
105
|
+
"confirmation_number": "00110215",
|
|
106
|
+
"pickup_date": "2020-10-25",
|
|
107
|
+
"ready_time": "13:00",
|
|
108
|
+
"closing_time": "17:00",
|
|
109
|
+
"carrier_name": "canadapost",
|
|
110
|
+
"carrier_id": "canadapost",
|
|
111
|
+
"test_mode": False,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def test_query_pickup_with_address(self):
|
|
118
|
+
"""Verify pickup query includes address."""
|
|
119
|
+
response = self.query(
|
|
120
|
+
"""
|
|
121
|
+
query get_pickup($id: String!) {
|
|
122
|
+
pickup(id: $id) {
|
|
123
|
+
id
|
|
124
|
+
address {
|
|
125
|
+
city
|
|
126
|
+
country_code
|
|
127
|
+
postal_code
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
""",
|
|
132
|
+
operation_name="get_pickup",
|
|
133
|
+
variables={"id": self.pickup.id},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
self.assertResponseNoErrors(response)
|
|
137
|
+
self.assertEqual(
|
|
138
|
+
response.data["data"]["pickup"]["address"]["city"],
|
|
139
|
+
"Moncton",
|
|
140
|
+
)
|
|
141
|
+
self.assertEqual(
|
|
142
|
+
response.data["data"]["pickup"]["address"]["country_code"],
|
|
143
|
+
"CA",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def test_query_pickup_with_tracking_numbers(self):
|
|
147
|
+
"""Verify pickup query includes tracking numbers from shipments."""
|
|
148
|
+
response = self.query(
|
|
149
|
+
"""
|
|
150
|
+
query get_pickup($id: String!) {
|
|
151
|
+
pickup(id: $id) {
|
|
152
|
+
id
|
|
153
|
+
tracking_numbers
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
""",
|
|
157
|
+
operation_name="get_pickup",
|
|
158
|
+
variables={"id": self.pickup.id},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.assertResponseNoErrors(response)
|
|
162
|
+
self.assertEqual(
|
|
163
|
+
response.data["data"]["pickup"]["tracking_numbers"],
|
|
164
|
+
["123456789012"],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def test_query_pickups_list(self):
|
|
168
|
+
"""Verify pickup list query with pagination."""
|
|
169
|
+
response = self.query(
|
|
170
|
+
"""
|
|
171
|
+
query get_pickups($filter: PickupFilter) {
|
|
172
|
+
pickups(filter: $filter) {
|
|
173
|
+
page_info {
|
|
174
|
+
count
|
|
175
|
+
has_next_page
|
|
176
|
+
}
|
|
177
|
+
edges {
|
|
178
|
+
node {
|
|
179
|
+
id
|
|
180
|
+
confirmation_number
|
|
181
|
+
carrier_name
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
""",
|
|
187
|
+
operation_name="get_pickups",
|
|
188
|
+
variables={"filter": {"first": 20, "offset": 0}},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.assertResponseNoErrors(response)
|
|
192
|
+
self.assertEqual(response.data["data"]["pickups"]["page_info"]["count"], 1)
|
|
193
|
+
self.assertEqual(
|
|
194
|
+
response.data["data"]["pickups"]["edges"][0]["node"]["confirmation_number"],
|
|
195
|
+
"00110215",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def test_query_pickups_filter_by_carrier(self):
|
|
199
|
+
"""Verify pickup filtering by carrier_name."""
|
|
200
|
+
response = self.query(
|
|
201
|
+
"""
|
|
202
|
+
query get_pickups($filter: PickupFilter) {
|
|
203
|
+
pickups(filter: $filter) {
|
|
204
|
+
edges {
|
|
205
|
+
node {
|
|
206
|
+
id
|
|
207
|
+
carrier_name
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
""",
|
|
213
|
+
operation_name="get_pickups",
|
|
214
|
+
variables={"filter": {"carrier_name": ["canadapost"]}},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
self.assertResponseNoErrors(response)
|
|
218
|
+
self.assertEqual(len(response.data["data"]["pickups"]["edges"]), 1)
|
|
219
|
+
|
|
220
|
+
def test_query_pickups_filter_by_carrier_no_match(self):
|
|
221
|
+
"""Verify pickup filtering with non-matching carrier returns empty."""
|
|
222
|
+
response = self.query(
|
|
223
|
+
"""
|
|
224
|
+
query get_pickups($filter: PickupFilter) {
|
|
225
|
+
pickups(filter: $filter) {
|
|
226
|
+
edges {
|
|
227
|
+
node {
|
|
228
|
+
id
|
|
229
|
+
carrier_name
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
""",
|
|
235
|
+
operation_name="get_pickups",
|
|
236
|
+
variables={"filter": {"carrier_name": ["ups"]}},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
self.assertResponseNoErrors(response)
|
|
240
|
+
self.assertEqual(len(response.data["data"]["pickups"]["edges"]), 0)
|
|
241
|
+
|
|
242
|
+
def test_query_pickups_filter_by_confirmation_number(self):
|
|
243
|
+
"""Verify pickup filtering by confirmation_number."""
|
|
244
|
+
response = self.query(
|
|
245
|
+
"""
|
|
246
|
+
query get_pickups($filter: PickupFilter) {
|
|
247
|
+
pickups(filter: $filter) {
|
|
248
|
+
edges {
|
|
249
|
+
node {
|
|
250
|
+
id
|
|
251
|
+
confirmation_number
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
""",
|
|
257
|
+
operation_name="get_pickups",
|
|
258
|
+
variables={"filter": {"confirmation_number": "00110215"}},
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self.assertResponseNoErrors(response)
|
|
262
|
+
self.assertEqual(len(response.data["data"]["pickups"]["edges"]), 1)
|
|
263
|
+
|
|
264
|
+
def test_query_pickups_filter_by_date_range(self):
|
|
265
|
+
"""Verify pickup filtering by date range."""
|
|
266
|
+
response = self.query(
|
|
267
|
+
"""
|
|
268
|
+
query get_pickups($filter: PickupFilter) {
|
|
269
|
+
pickups(filter: $filter) {
|
|
270
|
+
edges {
|
|
271
|
+
node {
|
|
272
|
+
id
|
|
273
|
+
pickup_date
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
""",
|
|
279
|
+
operation_name="get_pickups",
|
|
280
|
+
variables={
|
|
281
|
+
"filter": {
|
|
282
|
+
"pickup_date_after": "2020-10-01",
|
|
283
|
+
"pickup_date_before": "2020-10-31",
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
self.assertResponseNoErrors(response)
|
|
289
|
+
self.assertEqual(len(response.data["data"]["pickups"]["edges"]), 1)
|
|
290
|
+
|
|
291
|
+
def test_query_pickups_filter_by_date_range_no_match(self):
|
|
292
|
+
"""Verify pickup filtering with date range outside returns empty."""
|
|
293
|
+
response = self.query(
|
|
294
|
+
"""
|
|
295
|
+
query get_pickups($filter: PickupFilter) {
|
|
296
|
+
pickups(filter: $filter) {
|
|
297
|
+
edges {
|
|
298
|
+
node {
|
|
299
|
+
id
|
|
300
|
+
pickup_date
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
""",
|
|
306
|
+
operation_name="get_pickups",
|
|
307
|
+
variables={
|
|
308
|
+
"filter": {
|
|
309
|
+
"pickup_date_after": "2021-01-01",
|
|
310
|
+
"pickup_date_before": "2021-12-31",
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
self.assertResponseNoErrors(response)
|
|
316
|
+
self.assertEqual(len(response.data["data"]["pickups"]["edges"]), 0)
|
|
317
|
+
|
|
318
|
+
def test_query_pickup_not_found(self):
|
|
319
|
+
"""Verify querying non-existent pickup returns null."""
|
|
320
|
+
response = self.query(
|
|
321
|
+
"""
|
|
322
|
+
query get_pickup($id: String!) {
|
|
323
|
+
pickup(id: $id) {
|
|
324
|
+
id
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
""",
|
|
328
|
+
operation_name="get_pickup",
|
|
329
|
+
variables={"id": "pck_nonexistent"},
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
self.assertResponseNoErrors(response)
|
|
333
|
+
self.assertIsNone(response.data["data"]["pickup"])
|
|
@@ -124,7 +124,6 @@ class TestRateSheets(GraphTestCase):
|
|
|
124
124
|
)
|
|
125
125
|
response_data = response.data
|
|
126
126
|
|
|
127
|
-
print(response) # Debug print as per AGENTS.md guidelines
|
|
128
127
|
self.assertResponseNoErrors(response)
|
|
129
128
|
self.assertDictEqual(
|
|
130
129
|
lib.to_dict(response_data, clear_empty=False),
|