From 1d5d1eb78bc6cffac775fc2f79c8799c924b272c Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Mon, 14 Sep 2026 14:11:54 +0200 Subject: [PATCH] Keep isFromGlobalInput out of the payload it is not part of Jackson reads an is-prefixed no-arg boolean as a property, so the helper added with the field put a "fromGlobalInput" into every block payload and every persisted flow - a key the generated schema never declares, sitting next to the one it is derived from. ModelParameters.isEmpty carries the same guard for the same reason. Also cover the two steps between this field and the code that reads it: the generated schema has to carry it, and a configuration posted back by the editor has to survive deserialization into a block whose ports reflect the choice. Co-Authored-By: Claude Opus 5 (1M context) --- .../configurations/MCPAgentUploadInput.java | 11 +++- .../MCPAgentUploadGlobalSchemaTest.java | 55 +++++++++++++++++++ .../MCPAgentUploadInputJsonTest.java | 46 ++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadGlobalSchemaTest.java create mode 100644 src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInputJsonTest.java diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInput.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInput.java index 4fa9852..da27aa0 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInput.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInput.java @@ -1,5 +1,6 @@ package it.cnr.isti.workflow.manager.blocks.configurations; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import it.cnr.isti.workflow.manager.configurations.annotations.FieldRetriever; @@ -40,7 +41,15 @@ public record MCPAgentUploadInput( this(name, kind, multiple, null); } - /** Whether this attachment comes from a global input rather than a port of its own. */ + /** + * Whether this attachment comes from a global input rather than a port of its own. + * + *

{@code @JsonIgnore} because Jackson reads an is-prefixed no-arg boolean as a property: + * without it a "fromGlobalInput" field appeared in every API payload and every persisted flow, + * in a shape the schema does not declare - the same trap {@code ModelParameters.isEmpty} carries + * the same guard for. + */ + @JsonIgnore public boolean isFromGlobalInput() { return globalInput != null && !globalInput.isBlank(); } diff --git a/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadGlobalSchemaTest.java b/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadGlobalSchemaTest.java new file mode 100644 index 0000000..88e44ba --- /dev/null +++ b/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadGlobalSchemaTest.java @@ -0,0 +1,55 @@ +package it.cnr.isti.workflow.manager.blocks.configurations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +import tools.jackson.databind.JsonNode; + +import it.cnr.isti.workflow.manager.app.ObjectMapperHolder; +import it.cnr.isti.workflow.manager.blocks.Block; +import it.cnr.isti.workflow.manager.blocks.types.MCPAgentBlockType; +import it.cnr.isti.workflow.manager.controllers.BlocksController; + +@SpringBootTest +@TestPropertySource(locations = "classpath:test.properties") +public class MCPAgentUploadGlobalSchemaTest { + + @Autowired + private JsonSchemaProducer schemaProducer; + + @Autowired + private BlocksController blocksController; + + @Test + public void theEditorIsToldAnUploadCanNameAGlobal() { + JsonNode schema = schemaProducer.generateSchemaNode(MCPAgentBlockConfiguration.class); + assertNotNull(schema); + String asText = schema.toString(); + assertTrue(asText.contains("globalInput"), "the upload input schema must carry the new field: " + asText); + } + + @Test + public void aBlockWhoseUploadNamesAGlobalSurvivesTheRoundTripTheEditorMakes() { + // Exactly what the editor posts back when the field is filled from the dropdown. + String payload = """ + {"type":"MCPAgentBlockConfiguration","name":"Read the plan","model":"llama3.1:8b", + "prompt":"Summarize the attached plan","mcpServers":[], + "uploadInputs":[{"name":"planDoc","kind":"DOCUMENT","multiple":false,"globalInput":"document"}], + "shareSession":false,"useSharedSession":false} + """; + MCPAgentBlockConfiguration configuration = + ObjectMapperHolder.mapper.readValue(payload, MCPAgentBlockConfiguration.class); + assertEquals("document", configuration.getUploadInputs().getFirst().globalInput()); + + Block block = blocksController.create(configuration, null, null); + + assertNotNull(block); + assertTrue(block.getInputs().stream().noneMatch(input -> input.getName().equals("planDoc"))); + } +} diff --git a/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInputJsonTest.java b/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInputJsonTest.java new file mode 100644 index 0000000..4f08df8 --- /dev/null +++ b/src/test/java/it/cnr/isti/workflow/manager/blocks/configurations/MCPAgentUploadInputJsonTest.java @@ -0,0 +1,46 @@ +package it.cnr.isti.workflow.manager.blocks.configurations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class MCPAgentUploadInputJsonTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + public void readsAnUploadInputThatNamesAGlobal() { + MCPAgentUploadInput upload = mapper.readValue( + "{\"name\":\"planDoc\",\"kind\":\"DOCUMENT\",\"multiple\":false,\"globalInput\":\"document\"}", + MCPAgentUploadInput.class); + + assertEquals("planDoc", upload.name()); + assertEquals("document", upload.globalInput()); + } + + @Test + public void readsAnUploadInputSavedBeforeGlobalsWereAnOption() { + MCPAgentUploadInput upload = mapper.readValue( + "{\"name\":\"planDoc\",\"kind\":\"DOCUMENT\",\"multiple\":false}", + MCPAgentUploadInput.class); + + assertEquals("planDoc", upload.name()); + assertNull(upload.globalInput()); + } + + @Test + public void doesNotInventAFieldTheSchemaNeverDeclared() { + // Jackson reads an is-prefixed no-arg boolean as a property, so isFromGlobalInput() would + // otherwise put a "fromGlobalInput" into every payload and every persisted flow. + String written = mapper.writeValueAsString( + new MCPAgentUploadInput("planDoc", MCPAgentUploadInput.MCPAgentUploadKind.DOCUMENT, false, "document")); + + assertFalse(written.contains("fromGlobalInput"), written); + assertTrue(written.contains("globalInput"), written); + } +}