@@ -710,6 +710,154 @@ 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+ [ "category" ] = new ( ) { SystemType = typeof ( string ) , Required = true , HasConfigDefault = true , ConfigDefaultValue = "defaultCategory" } ,
750+ [ "publisher_id" ] = new ( ) { SystemType = typeof ( int ) , Required = false }
751+ } ;
752+
753+ // Act
754+ JsonElement schema = InitializeAndGetSchema ( dbParams ) ;
755+
756+ // Assert
757+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ) ;
758+ CollectionAssert . AreEquivalent (
759+ new [ ] { "id" } ,
760+ EnumerateStrings ( required ) ,
761+ "Only 'id' (no default, not marked optional) should be required." ) ;
762+ }
763+
764+ /// <summary>
765+ /// A zero-parameter SP must not emit a 'required' array.
766+ /// </summary>
767+ [ TestMethod ]
768+ public void InitializeMetadata_ZeroParamSP_OmitsRequiredArray ( )
769+ {
770+ // Arrange & Act
771+ JsonElement schema = InitializeAndGetSchema ( new Dictionary < string , ParameterDefinition > ( ) ) ;
772+
773+ // Assert
774+ Assert . IsFalse ( schema . TryGetProperty ( "required" , out _ ) ,
775+ "Zero-param SP should not include a 'required' array." ) ;
776+ }
777+
778+ /// <summary>
779+ /// When falling back to config-based schema, parameters without a default are required.
780+ /// </summary>
781+ [ TestMethod ]
782+ public void GetToolMetadata_ConfigFallback_MarksParamsWithoutDefaultsRequired ( )
783+ {
784+ // Arrange - config declares one required param and one with a default
785+ ParameterMetadata [ ] parameters = new [ ]
786+ {
787+ new ParameterMetadata { Name = "userId" } ,
788+ new ParameterMetadata { Name = "tenant" , Default = "contoso" }
789+ } ;
790+ Entity entity = CreateTestStoredProcedureEntity ( parameters : parameters ) ;
791+ DynamicCustomTool tool = new ( "GetUser" , entity ) ;
792+
793+ // Act - no InitializeMetadata call, so the config-based schema is used
794+ JsonElement schema = tool . GetToolMetadata ( ) . InputSchema ;
795+
796+ // Assert
797+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ) ;
798+ CollectionAssert . AreEquivalent (
799+ new [ ] { "userId" } ,
800+ EnumerateStrings ( required ) ,
801+ "Only 'userId' (no default) should be required in the config-based schema." ) ;
802+ }
803+
804+ /// <summary>
805+ /// Config fallback should exclude parameters with defaults from the required array even when
806+ /// they are explicitly marked required.
807+ /// </summary>
808+ [ TestMethod ]
809+ public void GetToolMetadata_ConfigFallback_ExcludesRequiredParamWhenDefaultExists ( )
810+ {
811+ // Arrange
812+ ParameterMetadata [ ] parameters = new [ ]
813+ {
814+ new ParameterMetadata { Name = "userId" , Required = true } ,
815+ new ParameterMetadata { Name = "tenant" , Required = true , Default = "contoso" }
816+ } ;
817+ Entity entity = CreateTestStoredProcedureEntity ( parameters : parameters ) ;
818+ DynamicCustomTool tool = new ( "GetUser" , entity ) ;
819+
820+ // Act
821+ JsonElement schema = tool . GetToolMetadata ( ) . InputSchema ;
822+
823+ // Assert
824+ Assert . IsTrue ( schema . TryGetProperty ( "required" , out JsonElement required ) ) ;
825+ CollectionAssert . AreEquivalent (
826+ new [ ] { "userId" } ,
827+ EnumerateStrings ( required ) ,
828+ "Parameters with config defaults should not be required even when marked required." ) ;
829+ }
830+
831+ /// <summary>
832+ /// Helper: Parses the "required" array values into a list of strings.
833+ /// </summary>
834+ private static List < string > EnumerateStrings ( JsonElement array )
835+ {
836+ List < string > values = new ( ) ;
837+ foreach ( JsonElement element in array . EnumerateArray ( ) )
838+ {
839+ values . Add ( element . GetString ( ) ! ) ;
840+ }
841+
842+ return values ;
843+ }
844+
845+ /// <summary>
846+ /// Helper: Creates a DynamicCustomTool, initializes it with mocked DB metadata, and returns
847+ /// the full input schema element.
848+ /// </summary>
849+ private static JsonElement InitializeAndGetSchema (
850+ Dictionary < string , ParameterDefinition > dbParameters ,
851+ string entityName = "TestSP" )
852+ {
853+ Entity entity = CreateTestStoredProcedureEntity ( ) ;
854+ DynamicCustomTool tool = new ( entityName , entity ) ;
855+ IServiceProvider sp = BuildServiceProviderForMetadata ( entityName , dbParameters ) ;
856+
857+ tool . InitializeMetadata ( sp ) ;
858+ return tool . GetToolMetadata ( ) . InputSchema ;
859+ }
860+
713861 /// <summary>
714862 /// Helper: Creates a DynamicCustomTool, initializes it with mocked DB metadata, and returns
715863 /// the "properties" element from the resulting input schema.
0 commit comments