@@ -249,5 +249,154 @@ public static void ValidateStoredProcedureSchema(
249249 string mismatchedTypeErrorMsg = $ "Failure: Parameter '{ parameterName } ' is type '{ actualGraphQLType } ' but should be type '{ expectedGraphQLType } '";
250250 Assert . AreEqual ( expected : expectedGraphQLType , actual : actualGraphQLType , message : mismatchedTypeErrorMsg ) ;
251251 }
252+
253+ /// <summary>
254+ /// Validates that stored-procedure input arguments are emitted with the correct GraphQL
255+ /// nullability based on the parameter's required flag:
256+ /// <list type="bullet">
257+ /// <item><description>A parameter listed in config with <c>required: true</c> is wrapped in <see cref="NonNullTypeNode"/>.</description></item>
258+ /// <item><description>A parameter listed in config with <c>required: false</c> stays a nullable <see cref="NamedTypeNode"/>.</description></item>
259+ /// <item><description>A parameter discovered from database metadata but not declared in config defaults to required and is wrapped in <see cref="NonNullTypeNode"/>.</description></item>
260+ /// </list>
261+ /// </summary>
262+ [ DataTestMethod ]
263+ [ DataRow ( "requiredParam" , true , false , true , DisplayName = "Config required=true -> NonNull" ) ]
264+ [ DataRow ( "optionalParam" , true , true , false , DisplayName = "Config required=false -> nullable" ) ]
265+ [ DataRow ( "dbOnlyParam" , false , false , true , DisplayName = "Param not in config -> defaults to NonNull (required)" ) ]
266+ public void StoredProcedure_RequiredFlag_ProducesNonNullType (
267+ string parameterName ,
268+ bool listInConfig ,
269+ bool configRequiredFalse ,
270+ bool expectsNonNull )
271+ {
272+ DatabaseObject spDbObj = new DatabaseStoredProcedure ( schemaName : "dbo" , tableName : "spReqTest" )
273+ {
274+ SourceType = EntitySourceType . StoredProcedure ,
275+ StoredProcedureDefinition = new ( )
276+ {
277+ Parameters = new ( ) { { parameterName , new ( ) { SystemType = typeof ( string ) } } }
278+ }
279+ } ;
280+ spDbObj . SourceDefinition . Columns . TryAdd ( "col1" , new ( ) { SystemType = typeof ( string ) } ) ;
281+
282+ List < ParameterMetadata > configParameters = new ( ) ;
283+ if ( listInConfig )
284+ {
285+ configParameters . Add ( new ParameterMetadata
286+ {
287+ Name = parameterName ,
288+ Required = ! configRequiredFalse
289+ } ) ;
290+ }
291+
292+ FieldDefinitionNode field = BuildSchemaAndGetExecuteField (
293+ spDbObj : spDbObj ,
294+ configParameters : configParameters ,
295+ graphQLTypeName : "SpReqTestType" ,
296+ entityName : "SpReqTest" ) ;
297+
298+ InputValueDefinitionNode arg = field . Arguments . First ( a => a . Name . Value == parameterName ) ;
299+
300+ if ( expectsNonNull )
301+ {
302+ Assert . IsInstanceOfType ( arg . Type , typeof ( NonNullTypeNode ) ,
303+ $ "Expected '{ parameterName } ' to be NonNullTypeNode but was '{ arg . Type . GetType ( ) . Name } '.") ;
304+ }
305+ else
306+ {
307+ Assert . IsInstanceOfType ( arg . Type , typeof ( NamedTypeNode ) ,
308+ $ "Expected '{ parameterName } ' to be nullable NamedTypeNode but was '{ arg . Type . GetType ( ) . Name } '.") ;
309+ }
310+
311+ // Underlying named type should remain the same regardless of nullability wrapping.
312+ Assert . AreEqual ( STRING_TYPE , arg . Type . NamedType ( ) . Name . Value ) ;
313+ }
314+
315+ /// <summary>
316+ /// Validates that a required parameter with a config-supplied default value still emits a
317+ /// NON_NULL input argument and preserves the default value on the GraphQL schema.
318+ /// </summary>
319+ [ TestMethod ]
320+ public void StoredProcedure_RequiredWithDefault_KeepsDefaultValue ( )
321+ {
322+ const string parameterName = "title" ;
323+
324+ DatabaseObject spDbObj = new DatabaseStoredProcedure ( schemaName : "dbo" , tableName : "spReqDefault" )
325+ {
326+ SourceType = EntitySourceType . StoredProcedure ,
327+ StoredProcedureDefinition = new ( )
328+ {
329+ Parameters = new ( ) { { parameterName , new ( ) { SystemType = typeof ( string ) } } }
330+ }
331+ } ;
332+ spDbObj . SourceDefinition . Columns . TryAdd ( "col1" , new ( ) { SystemType = typeof ( string ) } ) ;
333+
334+ List < ParameterMetadata > configParameters = new ( )
335+ {
336+ new ParameterMetadata
337+ {
338+ Name = parameterName ,
339+ Required = true ,
340+ Default = "Demo Title"
341+ }
342+ } ;
343+
344+ FieldDefinitionNode field = BuildSchemaAndGetExecuteField (
345+ spDbObj : spDbObj ,
346+ configParameters : configParameters ,
347+ graphQLTypeName : "SpReqDefaultType" ,
348+ entityName : "SpReqDefault" ) ;
349+
350+ InputValueDefinitionNode arg = field . Arguments . First ( a => a . Name . Value == parameterName ) ;
351+
352+ Assert . IsInstanceOfType ( arg . Type , typeof ( NonNullTypeNode ) , "Required parameter should be NonNullTypeNode." ) ;
353+ Assert . IsNotNull ( arg . DefaultValue , "Default value should be preserved on NON_NULL input argument." ) ;
354+ Assert . IsInstanceOfType ( arg . DefaultValue , typeof ( StringValueNode ) ) ;
355+ Assert . AreEqual ( "Demo Title" , ( ( StringValueNode ) arg . DefaultValue ! ) . Value ) ;
356+ }
357+
358+ /// <summary>
359+ /// Helper that builds a query schema for a stored-procedure entity and returns
360+ /// the generated execute* field so individual tests can assert on its argument
361+ /// type nodes and default values.
362+ /// </summary>
363+ private static FieldDefinitionNode BuildSchemaAndGetExecuteField (
364+ DatabaseObject spDbObj ,
365+ List < ParameterMetadata > configParameters ,
366+ string graphQLTypeName ,
367+ string entityName )
368+ {
369+ Entity spEntity = GraphQLTestHelpers . GenerateStoredProcedureEntity (
370+ graphQLTypeName : graphQLTypeName ,
371+ graphQLOperation : GraphQLOperation . Query ,
372+ parameters : configParameters ) ;
373+
374+ ObjectTypeDefinitionNode objectType = CreateGraphQLTypeForEntity ( spEntity , entityName , spDbObj ) ;
375+
376+ DocumentNode root = CreateGraphQLDocument ( new Dictionary < string , ObjectTypeDefinitionNode >
377+ {
378+ { entityName , objectType }
379+ } ) ;
380+
381+ Dictionary < string , EntityMetadata > permissions = GraphQLTestHelpers . CreateStubEntityPermissionsMap (
382+ entityNames : new [ ] { entityName } ,
383+ operations : new [ ] { EntityActionOperation . Execute } ,
384+ roles : SchemaConverterTests . GetRolesAllowedForEntity ( ) ) ;
385+
386+ Dictionary < string , Entity > entities = new ( ) { { entityName , spEntity } } ;
387+ Dictionary < string , DatabaseType > entityToDatabaseName = new ( ) { { entityName , DatabaseType . MSSQL } } ;
388+ Dictionary < string , DatabaseObject > dbObjects = new ( ) { { entityName , spDbObj } } ;
389+
390+ DocumentNode queryRoot = QueryBuilder . Build (
391+ root ,
392+ entityToDatabaseName ,
393+ entities : new ( entities ) ,
394+ inputTypes : null ,
395+ entityPermissionsMap : permissions ,
396+ dbObjects : dbObjects ) ;
397+
398+ ObjectTypeDefinitionNode queryNode = QueryBuilderTests . GetQueryNode ( queryRoot ) ;
399+ return queryNode . Fields . First ( f => f . Name . Value . StartsWith ( $ "execute{ graphQLTypeName } ") ) ;
400+ }
252401 }
253402}
0 commit comments