Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions backend/infrahub/graphql/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,25 +787,19 @@ class StatusUpsertInput(InputObjectType):
attr_kind = get_attr_kind(schema, attr)
attr_type = get_attribute_type(kind=attr_kind).get_graphql_update()

# A Field is not required if explicitly indicated or if a default value has been provided
required = not attr.optional if not attr.default_value else False

attrs[attr.name] = graphene.InputField(attr_type, required=required, description=attr.description)
attrs[attr.name] = graphene.InputField(attr_type, description=attr.description)

for rel in schema.relationships:
if rel.internal_peer or rel.read_only:
continue

input_type = self._get_related_input_type(relationship=rel)

required = not rel.optional
if rel.cardinality == RelationshipCardinality.ONE:
attrs[rel.name] = graphene.InputField(input_type, required=required, description=rel.description)
attrs[rel.name] = graphene.InputField(input_type, description=rel.description)

elif rel.cardinality == RelationshipCardinality.MANY:
attrs[rel.name] = graphene.InputField(
graphene.List(input_type), required=required, description=rel.description
)
attrs[rel.name] = graphene.InputField(graphene.List(input_type), description=rel.description)

return type(f"{schema.kind}UpsertInput", (graphene.InputObjectType,), attrs)

Expand Down
1 change: 1 addition & 0 deletions backend/tests/helpers/schema/tshirt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
default_filter="name__value",
display_labels=["name__value"],
uniqueness_constraints=[["name__value"]],
generate_template=True,
attributes=[
AttributeSchema(name="name", kind="Text"),
AttributeSchema(
Expand Down
81 changes: 80 additions & 1 deletion backend/tests/unit/graphql/test_mutation_upsert.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tests.adapters.event import MemoryInfrahubEvent
from tests.constants import TestKind
from tests.helpers.graphql import graphql
from tests.helpers.schema import TICKET
from tests.helpers.schema import COLOR, TICKET, TSHIRT
from tests.node_creation import create_and_save


Expand Down Expand Up @@ -673,3 +673,82 @@ async def test_upsert_node_on_branch_with_hfid_on_default(db: InfrahubDatabase,
in result.errors[0].message
)
assert f"Please rebase this branch to access {person.id} / TestPerson" in result.errors[0].message


async def test_upsert_with_required_relationship_from_template(
db: InfrahubDatabase, default_branch: Branch, register_core_models_schema: None
) -> None:
"""Validate that we can use a template to populate required relationships in upsert mutations.

Steps:
- Create a color node and a Tshirt template node.
- Try to upsert a Tshirt without specifying color or template (should fail).
- Upsert a Tshirt specifying the template (should succeed and apply the color from the template).
"""
registry.schema.register_schema(schema=SchemaRoot(nodes=[TSHIRT, COLOR]), branch=default_branch.name)

# Create a color node
color_node = await Node.init(db=db, schema="TestingColor", branch=default_branch)
await color_node.new(db=db, name="Red", description="Bright Red Color")
await color_node.save(db=db)

# Create a Tshirt template node with the color relationship set
template_node = await Node.init(db=db, schema="TemplateTestingTShirt", branch=default_branch)
await template_node.new(db=db, template_name="Basic Red Tshirt", color=color_node)
await template_node.save(db=db)

# Try to upsert a TShirt without specifying color or template (should fail)
query_missing_required = """
mutation {
TestingTShirtUpsert(data: {name: {value: "My Shirt"} }) {
ok
object {
id
name { value }
color { node { id name { value } } }
}
}
}
"""
gql_params = await prepare_graphql_params(db=db, include_subscription=False, branch=default_branch)
result_missing = await graphql(
schema=gql_params.schema,
source=query_missing_required,
context_value=gql_params.context,
root_value=None,
variable_values={},
)
assert result_missing.errors
assert "color is mandatory for TestingTShirt at color" in str(result_missing.errors)

# Upsert a Tshirt specifying the template (should succeed and apply the color from the template)
query_with_template = """
mutation UpsertTShirt($template_id: String!) {
TestingTShirtUpsert(data: {
name: {value: "My Tshirt"},
object_template: {id: $template_id}
}) {
ok
object {
id
name { value }
color { node { id name { value } } }
}
}
}
"""

result_with_template = await graphql(
schema=gql_params.schema,
source=query_with_template,
context_value=gql_params.context,
root_value=None,
variable_values={"template_id": template_node.id},
)
assert result_with_template.errors is None
assert result_with_template.data
assert result_with_template.data["TestingTShirtUpsert"]["ok"] is True
tshirt_obj = result_with_template.data["TestingTShirtUpsert"]["object"]
assert tshirt_obj["name"]["value"] == "My Tshirt"
assert tshirt_obj["color"]["node"]["id"] == color_node.id
assert tshirt_obj["color"]["node"]["name"]["value"] == "Red"
1 change: 1 addition & 0 deletions changelog/7398.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Loosen requirements for upsert mutations in the GraphQL schema so that required fields can be supplied by a template.