• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MrThearMan / undine / 17680768688

12 Sep 2025 04:49PM UTC coverage: 96.866% (-0.5%) from 97.325%
17680768688

push

github

MrThearMan
Update CI templates

1805 of 1907 branches covered (94.65%)

Branch coverage included in aggregate %.

29687 of 30604 relevant lines covered (97.0%)

2.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.02
/tests/test_mutation/test_mutation_type.py
1
from __future__ import annotations
3✔
2

3
from inspect import cleandoc
3✔
4
from typing import Any
3✔
5

6
import pytest
3✔
7
from graphql import DirectiveLocation, GraphQLInt, GraphQLNonNull, GraphQLString
3✔
8

9
from example_project.app.models import Task
3✔
10
from tests.helpers import exact, mock_gql_info
3✔
11
from undine import Input, MutationType, QueryType
3✔
12
from undine.directives import Directive, DirectiveArgument
3✔
13
from undine.exceptions import DirectiveLocationError, MissingModelGenericError
3✔
14
from undine.typing import GQLInfo, MutationKind
3✔
15
from undine.utils.graphql.type_registry import GRAPHQL_REGISTRY
3✔
16

17

18
def test_mutation_type__str() -> None:
3✔
19
    class TaskCreateMutation(MutationType[Task], auto=False):
3✔
20
        name = Input()
3✔
21

22
    assert str(TaskCreateMutation) == cleandoc(
3✔
23
        """
24
        input TaskCreateMutation {
25
          name: String!
26
        }
27
        """
28
    )
29

30

31
def test_mutation_type__registered() -> None:
3✔
32
    assert "MyCreateMutation" not in GRAPHQL_REGISTRY
3✔
33

34
    class MyCreateMutation(MutationType[Task]): ...
3✔
35

36
    assert "MyCreateMutation" not in GRAPHQL_REGISTRY
3✔
37

38
    input_type = MyCreateMutation.__input_type__()
3✔
39

40
    assert "MyCreateMutation" in GRAPHQL_REGISTRY
3✔
41
    assert GRAPHQL_REGISTRY["MyCreateMutation"] == input_type
3✔
42

43

44
def test_mutation_type__attributes() -> None:
3✔
45
    class MyCreateMutation(MutationType[Task]): ...
3✔
46

47
    assert MyCreateMutation.__model__ == Task
3✔
48
    assert sorted(MyCreateMutation.__input_map__) == [
3✔
49
        "acceptancecriteria",
50
        "assignees",
51
        "attachment",
52
        "check_time",
53
        "comments",
54
        "contact_email",
55
        "demo_url",
56
        "done",
57
        "due_by",
58
        "external_uuid",
59
        "extra_data",
60
        "image",
61
        "name",
62
        "objective",
63
        "points",
64
        "progress",
65
        "project",
66
        "related_tasks",
67
        "reports",
68
        "request",
69
        "result",
70
        "steps",
71
        "type",
72
        "worked_hours",
73
    ]
74
    assert MyCreateMutation.__kind__ == MutationKind.create
3✔
75
    assert MyCreateMutation.__schema_name__ == "MyCreateMutation"
3✔
76
    assert MyCreateMutation.__directives__ == []
3✔
77
    assert MyCreateMutation.__extensions__ == {"undine_mutation_type": MyCreateMutation}
3✔
78
    assert MyCreateMutation.__attribute_docstrings__ == {}
3✔
79

80

81
def test_mutation_type__input_type() -> None:
3✔
82
    class MyCreateMutation(MutationType[Task]):
3✔
83
        """Description."""
84

85
    input_type = MyCreateMutation.__input_type__()
3✔
86
    assert input_type.name == "MyCreateMutation"
3✔
87
    assert input_type.description == "Description."
3✔
88
    assert input_type.extensions == {"undine_mutation_type": MyCreateMutation}
3✔
89

90
    assert sorted(input_type.fields) == [
3✔
91
        "acceptancecriteria",
92
        "assignees",
93
        "attachment",
94
        "checkTime",
95
        "comments",
96
        "contactEmail",
97
        "demoUrl",
98
        "done",
99
        "dueBy",
100
        "externalUuid",
101
        "extraData",
102
        "image",
103
        "name",
104
        "objective",
105
        "points",
106
        "progress",
107
        "project",
108
        "relatedTasks",
109
        "reports",
110
        "request",
111
        "result",
112
        "steps",
113
        "type",
114
        "workedHours",
115
    ]
116
    assert input_type.fields["name"].type == GraphQLNonNull(GraphQLString)
3✔
117

118

119
def test_mutation_type__input_type__hidden_input() -> None:
3✔
120
    class MyCreateMutation(MutationType[Task], auto=False):
3✔
121
        pk = Input()
3✔
122
        name = Input()
3✔
123
        foo = Input(str, hidden=True)
3✔
124

125
    input_type = MyCreateMutation.__input_type__()
3✔
126

127
    assert sorted(input_type.fields) == ["name", "pk"]
3✔
128
    assert "foo" in MyCreateMutation.__input_map__
3✔
129

130

131
def test_mutation_type__output_type() -> None:
3✔
132
    class MyQueryType(QueryType[Task]): ...
3✔
133

134
    class MyCreateMutation(MutationType[Task]): ...
3✔
135

136
    assert MyCreateMutation.__output_type__() == MyQueryType.__output_type__()
3✔
137

138

139
def test_mutation_type__no_model() -> None:
3✔
140
    with pytest.raises(MissingModelGenericError):
3✔
141

142
        class MyCreateMutation(MutationType): ...
3✔
143

144

145
def test_mutation_type__kind__create__implicit() -> None:
3✔
146
    class MyCreateMutation(MutationType[Task]): ...
3✔
147

148
    assert MyCreateMutation.__kind__ == MutationKind.create
3✔
149

150

151
def test_mutation_type__kind__create__explicit() -> None:
3✔
152
    class TaskCreateMutation(MutationType[Task], kind="create"): ...
3✔
153

154
    assert TaskCreateMutation.__kind__ == MutationKind.create
3✔
155

156

157
def test_mutation_type__kind__create__primary_key() -> None:
3✔
158
    class MyCreateMutation(MutationType[Task]): ...
3✔
159

160
    assert "pk" not in MyCreateMutation.__input_map__
3✔
161

162

163
def test_mutation_type__kind__create__output_type() -> None:
3✔
164
    class MyQueryType(QueryType[Task]): ...
3✔
165

166
    class MyCreateMutation(MutationType[Task]): ...
3✔
167

168
    assert MyCreateMutation.__output_type__() == MyQueryType.__output_type__()
3✔
169

170

171
def test_mutation_type__kind__update__implicit() -> None:
3✔
172
    class TaskUpdateMutation(MutationType[Task]): ...
3✔
173

174
    assert TaskUpdateMutation.__kind__ == MutationKind.update
3✔
175

176

177
def test_mutation_type__kind__update__explicit() -> None:
3✔
178
    class TaskCreateMutation(MutationType[Task], kind="update"): ...
3✔
179

180
    assert TaskCreateMutation.__kind__ == MutationKind.update
3✔
181

182

183
def test_mutation_type__kind__update__primary_key() -> None:
3✔
184
    class TaskUpdateMutation(MutationType[Task]): ...
3✔
185

186
    assert "pk" in TaskUpdateMutation.__input_map__
3✔
187

188

189
def test_mutation_type__kind__update__output_type() -> None:
3✔
190
    class MyQueryType(QueryType[Task]): ...
3✔
191

192
    class TaskUpdateMutation(MutationType[Task]): ...
3✔
193

194
    assert TaskUpdateMutation.__output_type__() == MyQueryType.__output_type__()
3✔
195

196

197
def test_mutation_type__kind__delete__implicit() -> None:
3✔
198
    class MyDeleteMutation(MutationType[Task]): ...
3✔
199

200
    assert MyDeleteMutation.__kind__ == MutationKind.delete
3✔
201

202

203
def test_mutation_type__kind__delete__explicit() -> None:
3✔
204
    class TaskCreateMutation(MutationType[Task], kind="delete"): ...
3✔
205

206
    assert TaskCreateMutation.__kind__ == MutationKind.delete
3✔
207

208

209
def test_mutation_type__kind__delete__primary_key() -> None:
3✔
210
    class MyDeleteMutation(MutationType[Task]): ...
3✔
211

212
    assert "pk" in MyDeleteMutation.__input_map__
3✔
213

214

215
def test_mutation_type__kind__delete__output_type() -> None:
3✔
216
    class TaskType(QueryType[Task]): ...
3✔
217

218
    class MyDeleteMutation(MutationType[Task]): ...
3✔
219

220
    output_type = MyDeleteMutation.__output_type__()
3✔
221
    assert output_type.name == "MyDeleteMutationOutput"
3✔
222
    assert sorted(output_type.fields) == ["pk"]
3✔
223
    assert output_type.fields["pk"].type == GraphQLNonNull(GraphQLInt)
3✔
224
    assert output_type.fields["pk"].description is None
3✔
225
    assert output_type.fields["pk"].deprecation_reason is None
3✔
226

227

228
def test_mutation_type__kind__custom__single() -> None:
3✔
229
    class MyOtherMutation(MutationType[Task]):
3✔
230
        @classmethod
3✔
231
        def __mutate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> Any:
3✔
232
            return instance
×
233

234
    assert MyOtherMutation.__kind__ == MutationKind.custom
3✔
235

236

237
def test_mutation_type__kind__custom__many() -> None:
3✔
238
    class MyOtherMutation(MutationType[Task]):
3✔
239
        @classmethod
3✔
240
        def __bulk_mutate__(cls, instances: list[Task], info: GQLInfo, input_data: list[dict[str, Any]]) -> Any:
3✔
241
            return instances
×
242

243
    assert MyOtherMutation.__kind__ == MutationKind.custom
3✔
244

245

246
def test_mutation_type__kind__related() -> None:
3✔
247
    class MyRelatedMutation(MutationType[Task], kind="related"): ...
3✔
248

249
    assert MyRelatedMutation.__kind__ == MutationKind.related
3✔
250
    assert "pk" in MyRelatedMutation.__input_map__
3✔
251

252

253
def test_mutation_type__kind__related__output_type() -> None:
3✔
254
    class MyQueryType(QueryType[Task]): ...
3✔
255

256
    class MyRelatedMutation(MutationType[Task], kind="related"): ...
3✔
257

258
    assert MyRelatedMutation.__output_type__() == MyQueryType.__output_type__()
3✔
259

260

261
def test_mutation_type__auto__false() -> None:
3✔
262
    class MyCreateMutation(MutationType[Task], auto=False): ...
3✔
263

264
    assert MyCreateMutation.__input_map__ == {}
3✔
265

266

267
def test_mutation_type__exclude() -> None:
3✔
268
    class TaskUpdateMutation(MutationType[Task], exclude=["name"]): ...
3✔
269

270
    assert sorted(TaskUpdateMutation.__input_map__) == [
3✔
271
        "acceptancecriteria",
272
        "assignees",
273
        "attachment",
274
        "check_time",
275
        "comments",
276
        "contact_email",
277
        "demo_url",
278
        "done",
279
        "due_by",
280
        "external_uuid",
281
        "extra_data",
282
        "image",
283
        "objective",
284
        "pk",
285
        "points",
286
        "progress",
287
        "project",
288
        "related_tasks",
289
        "reports",
290
        "request",
291
        "result",
292
        "steps",
293
        "type",
294
        "worked_hours",
295
    ]
296

297
    input_type = TaskUpdateMutation.__input_type__()
3✔
298
    assert sorted(input_type.fields) == [
3✔
299
        "acceptancecriteria",
300
        "assignees",
301
        "attachment",
302
        "checkTime",
303
        "comments",
304
        "contactEmail",
305
        "demoUrl",
306
        "done",
307
        "dueBy",
308
        "externalUuid",
309
        "extraData",
310
        "image",
311
        "objective",
312
        "pk",
313
        "points",
314
        "progress",
315
        "project",
316
        "relatedTasks",
317
        "reports",
318
        "request",
319
        "result",
320
        "steps",
321
        "type",
322
        "workedHours",
323
    ]
324

325

326
def test_mutation_type__exclude__multiple() -> None:
3✔
327
    class TaskUpdateMutation(MutationType[Task], exclude=["name", "done"]): ...
3✔
328

329
    assert sorted(TaskUpdateMutation.__input_map__) == [
3✔
330
        "acceptancecriteria",
331
        "assignees",
332
        "attachment",
333
        "check_time",
334
        "comments",
335
        "contact_email",
336
        "demo_url",
337
        "due_by",
338
        "external_uuid",
339
        "extra_data",
340
        "image",
341
        "objective",
342
        "pk",
343
        "points",
344
        "progress",
345
        "project",
346
        "related_tasks",
347
        "reports",
348
        "request",
349
        "result",
350
        "steps",
351
        "type",
352
        "worked_hours",
353
    ]
354

355
    input_type = TaskUpdateMutation.__input_type__()
3✔
356
    assert sorted(input_type.fields) == [
3✔
357
        "acceptancecriteria",
358
        "assignees",
359
        "attachment",
360
        "checkTime",
361
        "comments",
362
        "contactEmail",
363
        "demoUrl",
364
        "dueBy",
365
        "externalUuid",
366
        "extraData",
367
        "image",
368
        "objective",
369
        "pk",
370
        "points",
371
        "progress",
372
        "project",
373
        "relatedTasks",
374
        "reports",
375
        "request",
376
        "result",
377
        "steps",
378
        "type",
379
        "workedHours",
380
    ]
381

382

383
def test_mutation_type__schema_name() -> None:
3✔
384
    class TaskCreateMutation(MutationType[Task], schema_name="CustomName"): ...
3✔
385

386
    assert TaskCreateMutation.__schema_name__ == "CustomName"
3✔
387

388
    input_type = TaskCreateMutation.__input_type__()
3✔
389
    assert input_type.name == "CustomName"
3✔
390

391

392
def test_mutation_type__directives() -> None:
3✔
393
    class ValueDirective(Directive, locations=[DirectiveLocation.INPUT_OBJECT], schema_name="value"):
3✔
394
        value = DirectiveArgument(GraphQLNonNull(GraphQLString))
3✔
395

396
    directives: list[Directive] = [ValueDirective(value="foo")]
3✔
397

398
    class TaskCreateMutation(MutationType[Task], directives=directives, auto=False):
3✔
399
        name = Input()
3✔
400

401
    assert TaskCreateMutation.__directives__ == directives
3✔
402

403
    assert str(TaskCreateMutation) == cleandoc(
3✔
404
        """
405
        input TaskCreateMutation @value(value: "foo") {
406
          name: String!
407
        }
408
        """
409
    )
410

411

412
def test_mutation_type__directives__not_applicable() -> None:
3✔
413
    class ValueDirective(Directive, locations=[DirectiveLocation.ENUM], schema_name="value"):
3✔
414
        value = DirectiveArgument(GraphQLNonNull(GraphQLString))
3✔
415

416
    directives: list[Directive] = [ValueDirective(value="foo")]
3✔
417

418
    with pytest.raises(DirectiveLocationError):
3✔
419

420
        class TaskCreateMutation(MutationType[Task], directives=directives): ...
3✔
421

422

423
def test_mutation_type__directives__decorator() -> None:
3✔
424
    class ValueDirective(Directive, locations=[DirectiveLocation.INPUT_OBJECT], schema_name="value"):
3✔
425
        value = DirectiveArgument(GraphQLNonNull(GraphQLString))
3✔
426

427
    @ValueDirective(value="foo")
3✔
428
    class TaskCreateMutation(MutationType[Task], auto=False):
3✔
429
        name = Input()
3✔
430

431
    assert TaskCreateMutation.__directives__ == [ValueDirective(value="foo")]
3✔
432

433
    assert str(TaskCreateMutation) == cleandoc(
3✔
434
        """
435
        input TaskCreateMutation @value(value: "foo") {
436
          name: String!
437
        }
438
        """
439
    )
440

441

442
def test_mutation_type__extensions() -> None:
3✔
443
    class TaskCreateMutation(MutationType[Task], extensions={"foo": "bar"}): ...
3✔
444

445
    assert TaskCreateMutation.__extensions__ == {"foo": "bar", "undine_mutation_type": TaskCreateMutation}
3✔
446

447
    input_type = TaskCreateMutation.__input_type__()
3✔
448
    assert input_type.extensions == {"foo": "bar", "undine_mutation_type": TaskCreateMutation}
3✔
449

450

451
def test_mutation_type__validate() -> None:
3✔
452
    class TaskCreateMutation(MutationType[Task]):
3✔
453
        @classmethod
3✔
454
        def __validate__(cls, instance: Task, info: GQLInfo, input_data: dict[str, Any]) -> None:
3✔
455
            if input_data["foo"] is not True:
3✔
456
                msg = "Foo must be True"
3✔
457
                raise ValueError(msg)
3✔
458

459
    TaskCreateMutation.__validate__(Task(), mock_gql_info(), {"foo": True})
3✔
460

461
    with pytest.raises(ValueError, match=exact("Foo must be True")):
3✔
462
        TaskCreateMutation.__validate__(Task(), mock_gql_info(), {"foo": False})
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc