From 998af17619bb30c606f6c662f838a7ecc8266d76 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Mon, 13 Oct 2025 17:13:14 +1000 Subject: [PATCH] test: add failing tests for polymorphic many-to-many list input mutations Refs: #793 --- .../__init__.py | 0 .../models.py | 33 ++++ .../schema.py | 76 ++++++++ .../test_optimizer.py | 169 ++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 tests/node_polymorphism_inheritancemanager/__init__.py create mode 100644 tests/node_polymorphism_inheritancemanager/models.py create mode 100644 tests/node_polymorphism_inheritancemanager/schema.py create mode 100644 tests/node_polymorphism_inheritancemanager/test_optimizer.py diff --git a/tests/node_polymorphism_inheritancemanager/__init__.py b/tests/node_polymorphism_inheritancemanager/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/node_polymorphism_inheritancemanager/models.py b/tests/node_polymorphism_inheritancemanager/models.py new file mode 100644 index 000000000..9893df61c --- /dev/null +++ b/tests/node_polymorphism_inheritancemanager/models.py @@ -0,0 +1,33 @@ +from django.db import models +from model_utils.managers import InheritanceManager + + +class Project(models.Model): + topic = models.CharField(max_length=30) + dependencies = models.ManyToManyField( + "self", symmetrical=False, related_name="dependants", blank=True + ) + + base_objects = InheritanceManager() + objects = InheritanceManager() + + class Meta: + base_manager_name = "base_objects" + + +class ArtProject(Project): + artist = models.CharField(max_length=30) + art_style = models.CharField(max_length=30) + + +class ResearchProject(Project): + supervisor = models.CharField(max_length=30) + research_notes = models.TextField() + + +class SoftwareProject(Project): + repository = models.CharField(max_length=255) + + +class EngineeringProject(Project): + lead_engineer = models.CharField(max_length=255) diff --git a/tests/node_polymorphism_inheritancemanager/schema.py b/tests/node_polymorphism_inheritancemanager/schema.py new file mode 100644 index 000000000..ab6df6e84 --- /dev/null +++ b/tests/node_polymorphism_inheritancemanager/schema.py @@ -0,0 +1,76 @@ +import strawberry +from strawberry.relay import Node + +import strawberry_django +from strawberry_django import ListInput, NodeInput +from strawberry_django.optimizer import DjangoOptimizerExtension +from strawberry_django.relay import DjangoListConnection + +from .models import ( + ArtProject, + EngineeringProject, + Project, + ResearchProject, + SoftwareProject, +) + + +@strawberry_django.interface(Project) +class ProjectType(Node): + topic: strawberry.auto + dependencies: DjangoListConnection["ProjectType"] = strawberry_django.connection() + dependants: DjangoListConnection["ProjectType"] = strawberry_django.connection() + + +@strawberry_django.partial(Project) +class ProjectInputPartial(NodeInput): + topic: strawberry.auto = strawberry_django.field() + dependencies: ListInput[NodeInput] | None = strawberry_django.field() + dependants: ListInput[NodeInput] | None = strawberry_django.field() + + +@strawberry_django.type(ArtProject) +class ArtProjectType(ProjectType): + artist: strawberry.auto + art_style: strawberry.auto + + +@strawberry_django.type(ResearchProject) +class ResearchProjectType(ProjectType): + supervisor: strawberry.auto + + +@strawberry_django.type(SoftwareProject) +class SoftwareProjectType(ProjectType): + repository: strawberry.auto + + +@strawberry_django.type(EngineeringProject) +class EngineeringProjectType(ProjectType): + lead_engineer: strawberry.auto + + +@strawberry.type +class Query: + node: Node = strawberry.relay.node() + projects: DjangoListConnection[ProjectType] = strawberry_django.field() + + +@strawberry.type +class Mutation: + update_art_project: ArtProjectType = strawberry_django.mutations.update( + ProjectInputPartial + ) + + +schema = strawberry.Schema( + query=Query, + mutation=Mutation, + types=[ + ArtProjectType, + ResearchProjectType, + EngineeringProjectType, + SoftwareProjectType, + ], + extensions=[DjangoOptimizerExtension], +) diff --git a/tests/node_polymorphism_inheritancemanager/test_optimizer.py b/tests/node_polymorphism_inheritancemanager/test_optimizer.py new file mode 100644 index 000000000..60f24f16e --- /dev/null +++ b/tests/node_polymorphism_inheritancemanager/test_optimizer.py @@ -0,0 +1,169 @@ +import pytest +from strawberry.relay import GlobalID + +from .models import ( + ArtProject, + EngineeringProject, + ResearchProject, + SoftwareProject, +) +from .schema import schema + + +@pytest.mark.django_db(transaction=True) +def test_polymorphic_mutation_abstract_model_many_to_many_rel(): + ap = ArtProject.objects.create(topic="Art", artist="Artist", art_style="Modern") + sp = SoftwareProject.objects.create( + topic="Software", repository="https://example.com" + ) + ep = EngineeringProject.objects.create( + topic="Engineering", lead_engineer="Elara Voss" + ) + rp = ResearchProject.objects.create( + topic="Research", supervisor="J. Hiles", research_notes="Important notes" + ) + + ap_node = GlobalID(type_name="ArtProjectType", node_id=str(ap.pk)) + sp_node = GlobalID(type_name="SoftwareProjectType", node_id=str(sp.pk)) + ep_node = GlobalID(type_name="EngineeringProjectType", node_id=str(ep.pk)) + rp_node = GlobalID(type_name="ResearchProjectType", node_id=str(rp.pk)) + + query = f""" + query {{ + node(id: "{ap_node}") {{ + __typename + ... on ArtProjectType {{ + id + artist + artStyle + dependencies {{ + totalCount + edges {{ + node {{ + __typename + id + }} + }} + }} + dependants {{ + totalCount + edges {{ + node {{ + __typename + id + }} + }} + }} + }} + }} + }} + """ + + mutation = f""" + mutation {{ + updateArtProject(data: {{ + id: "{ap_node}" + dependencies: {{ set: [ {{ id: "{sp_node}" }}, {{ id: "{ep_node}" }} ] }} + dependants: {{ set: [ {{ id: "{rp_node}" }} ] }} + }}) {{ + __typename + ... on ArtProjectType {{ + id + artist + artStyle + dependencies {{ + totalCount + edges {{ + node {{ + __typename + id + }} + }} + }} + dependants {{ + totalCount + edges {{ + node {{ + __typename + id + }} + }} + }} + }} + }} + }} + """ + + # Fetch the current project + result = schema.execute_sync(query) + assert not result.errors + assert result.data == { + "node": { + "__typename": "ArtProjectType", + "id": str(ap_node), + "artist": ap.artist, + "artStyle": ap.art_style, + "dependencies": {"totalCount": 0, "edges": []}, + "dependants": {"totalCount": 0, "edges": []}, + } + } + + # Update with _new_ project dependencies + result = schema.execute_sync(mutation) + assert not result.errors + assert result.data == { + "updateArtProject": { + "__typename": "ArtProjectType", + "id": str(ap_node), + "artist": ap.artist, + "artStyle": ap.art_style, + "dependencies": { + "totalCount": 2, + "edges": [ + {"node": {"__typename": "SoftwareProjectType", "id": str(sp_node)}}, + { + "node": { + "__typename": "EngineeringProjectType", + "id": str(ep_node), + } + }, + ], + }, + "dependants": { + "totalCount": 1, + "edges": [ + {"node": {"__typename": "ResearchProjectType", "id": str(rp_node)}} + ], + }, + } + } + + # Update _existing_ project dependencies (no change) + result = schema.execute_sync(mutation) + assert not result.errors + assert result.data == { + "updateArtProject": { + "__typename": "ArtProjectType", + "id": str(ap_node), + "artist": ap.artist, + "artStyle": ap.art_style, + "dependencies": { + "totalCount": 2, # The existing dependencies remain on the connection + "edges": [ + {"node": {"__typename": "SoftwareProjectType", "id": str(sp_node)}}, + { + "node": { + "__typename": "EngineeringProjectType", + "id": str(ep_node), + } + }, + ], + }, + "dependants": { + "totalCount": 1, # The existing dependants remain on the connection + "edges": [ + {"node": {"__typename": "ResearchProjectType", "id": str(rp_node)}} + ], + }, + } + }