@@ -710,6 +710,126 @@ public void InitializeMetadata_ZeroParamSP_ReturnsEmptyProperties()
710710 Assert . AreEqual ( 0 , props . EnumerateObject ( ) . Count ( ) ) ;
711711 }
712712
713+ /// <summary>
714+ /// DB-discovered parameters with no config default/override are advertised as required.
715+ /// </summary>
716+ [ TestMethod ]
717+ public void InitializeMetadata_IncludesRequiredArray_ForParamsWithoutDefaults ( )
718+ {
719+ // Arrange
720+ Dictionary < string , ParameterDefinition > dbParams = new ( )
721+ {
722+ [ "productId" ] = new ( ) { SystemType = typeof ( int ) }
723+ } ;
724+
725+ // Act
726+ JsonElement schema = InitializeAndGetSchema ( dbParams ) ;
727+
728+ // Assert
729+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ,
730+ "Schema should expose a 'required' array for parameters without defaults." ) ;
731+ CollectionAssert . AreEquivalent (
732+ new [ ] { "productId" } ,
733+ EnumerateStrings ( required ) ,
734+ "'productId' should be listed as required." ) ;
735+ }
736+
737+ /// <summary>
738+ /// Parameters that have a config default or are explicitly marked optional are excluded
739+ /// from the required array, while parameters without defaults remain required.
740+ /// </summary>
741+ [ TestMethod ]
742+ public void InitializeMetadata_ExcludesParamsWithDefaultsOrOptionalFlag_FromRequired ( )
743+ {
744+ // Arrange
745+ Dictionary < string , ParameterDefinition > dbParams = new ( )
746+ {
747+ [ "id" ] = new ( ) { SystemType = typeof ( int ) } ,
748+ [ "title" ] = new ( ) { SystemType = typeof ( string ) , HasConfigDefault = true , ConfigDefaultValue = "randomX" } ,
749+ [ "publisher_id" ] = new ( ) { SystemType = typeof ( int ) , Required = false }
750+ } ;
751+
752+ // Act
753+ JsonElement schema = InitializeAndGetSchema ( dbParams ) ;
754+
755+ // Assert
756+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ) ;
757+ CollectionAssert . AreEquivalent (
758+ new [ ] { "id" } ,
759+ EnumerateStrings ( required ) ,
760+ "Only 'id' (no default, not marked optional) should be required." ) ;
761+ }
762+
763+ /// <summary>
764+ /// A zero-parameter SP must not emit a 'required' array.
765+ /// </summary>
766+ [ TestMethod ]
767+ public void InitializeMetadata_ZeroParamSP_OmitsRequiredArray ( )
768+ {
769+ // Arrange & Act
770+ JsonElement schema = InitializeAndGetSchema ( new Dictionary < string , ParameterDefinition > ( ) ) ;
771+
772+ // Assert
773+ Assert . IsFalse ( schema . TryGetProperty ( "required" , out _ ) ,
774+ "Zero-param SP should not include a 'required' array." ) ;
775+ }
776+
777+ /// <summary>
778+ /// When falling back to config-based schema, parameters without a default are required.
779+ /// </summary>
780+ [ TestMethod ]
781+ public void GetToolMetadata_ConfigFallback_MarksParamsWithoutDefaultsRequired ( )
782+ {
783+ // Arrange - config declares one required param and one with a default
784+ ParameterMetadata [ ] parameters = new [ ]
785+ {
786+ new ParameterMetadata { Name = "userId" } ,
787+ new ParameterMetadata { Name = "tenant" , Default = "contoso" }
788+ } ;
789+ Entity entity = CreateTestStoredProcedureEntity ( parameters : parameters ) ;
790+ DynamicCustomTool tool = new ( "GetUser" , entity ) ;
791+
792+ // Act - no InitializeMetadata call, so the config-based schema is used
793+ JsonElement schema = tool . GetToolMetadata ( ) . InputSchema ;
794+
795+ // Assert
796+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ) ;
797+ CollectionAssert . AreEquivalent (
798+ new [ ] { "userId" } ,
799+ EnumerateStrings ( required ) ,
800+ "Only 'userId' (no default) should be required in the config-based schema." ) ;
801+ }
802+
803+ /// <summary>
804+ /// Helper: Parses the "required" array values into a list of strings.
805+ /// </summary>
806+ private static List < string > EnumerateStrings ( JsonElement array )
807+ {
808+ List < string > values = new ( ) ;
809+ foreach ( JsonElement element in array . EnumerateArray ( ) )
810+ {
811+ values . Add ( element . GetString ( ) ! ) ;
812+ }
813+
814+ return values ;
815+ }
816+
817+ /// <summary>
818+ /// Helper: Creates a DynamicCustomTool, initializes it with mocked DB metadata, and returns
819+ /// the full input schema element.
820+ /// </summary>
821+ private static JsonElement InitializeAndGetSchema (
822+ Dictionary < string , ParameterDefinition > dbParameters ,
823+ string entityName = "TestSP" )
824+ {
825+ Entity entity = CreateTestStoredProcedureEntity ( ) ;
826+ DynamicCustomTool tool = new ( entityName , entity ) ;
827+ IServiceProvider sp = BuildServiceProviderForMetadata ( entityName , dbParameters ) ;
828+
829+ tool . InitializeMetadata ( sp ) ;
830+ return tool . GetToolMetadata ( ) . InputSchema ;
831+ }
832+
713833 /// <summary>
714834 /// Helper: Creates a DynamicCustomTool, initializes it with mocked DB metadata, and returns
715835 /// the "properties" element from the resulting input schema.
0 commit comments