|
3 | 3 |
|
4 | 4 | using System; |
5 | 5 | using System.Collections.Generic; |
| 6 | +using System.Data.Common; |
6 | 7 | using System.IO; |
7 | 8 | using System.Net; |
| 9 | +using System.Text.Json.Nodes; |
8 | 10 | using System.Threading.Tasks; |
9 | 11 | using Azure.DataApiBuilder.Config.DatabasePrimitives; |
10 | 12 | using Azure.DataApiBuilder.Config.ObjectModel; |
11 | 13 | using Azure.DataApiBuilder.Core.Authorization; |
12 | 14 | using Azure.DataApiBuilder.Core.Configurations; |
| 15 | +using Azure.DataApiBuilder.Core.Models; |
13 | 16 | using Azure.DataApiBuilder.Core.Resolvers; |
14 | 17 | using Azure.DataApiBuilder.Core.Resolvers.Factories; |
15 | 18 | using Azure.DataApiBuilder.Core.Services; |
16 | 19 | using Azure.DataApiBuilder.Service.Exceptions; |
17 | 20 | using Azure.DataApiBuilder.Service.Tests.Configuration; |
18 | 21 | using Azure.DataApiBuilder.Service.Tests.SqlTests; |
| 22 | +using Microsoft.AspNetCore.Http; |
19 | 23 | using Microsoft.Data.SqlClient; |
20 | 24 | using Microsoft.Extensions.Logging; |
21 | 25 | using Microsoft.VisualStudio.TestTools.UnitTesting; |
@@ -399,6 +403,100 @@ public async Task ValidateInferredRelationshipInfoForPgSql() |
399 | 403 | ValidateInferredRelationshipInfoForTables(); |
400 | 404 | } |
401 | 405 |
|
| 406 | + /// <summary> |
| 407 | + /// Data-driven test to validate that DataApiBuilderException is thrown for various invalid resultFieldName values |
| 408 | + /// during stored procedure result set definition population. |
| 409 | + /// </summary> |
| 410 | + [DataTestMethod, TestCategory(TestCategory.MSSQL)] |
| 411 | + [DataRow(null, DisplayName = "Null result field name")] |
| 412 | + [DataRow("", DisplayName = "Empty result field name")] |
| 413 | + [DataRow(" ", DisplayName = "Multiple spaces result field name")] |
| 414 | + public async Task ValidateExceptionForInvalidResultFieldNames(string invalidFieldName) |
| 415 | + { |
| 416 | + DatabaseEngine = TestCategory.MSSQL; |
| 417 | + TestHelper.SetupDatabaseEnvironment(DatabaseEngine); |
| 418 | + RuntimeConfig baseConfigFromDisk = SqlTestHelper.SetupRuntimeConfig(); |
| 419 | + |
| 420 | + // Create a RuntimeEntities with ONLY our test stored procedure entity |
| 421 | + Dictionary<string, Entity> entitiesDictionary = new() |
| 422 | + { |
| 423 | + { |
| 424 | + "get_book_by_id", new Entity( |
| 425 | + Source: new("dbo.get_book_by_id", EntitySourceType.StoredProcedure, null, null), |
| 426 | + Rest: new(Enabled: true), |
| 427 | + GraphQL: new("get_book_by_id", "get_book_by_ids", Enabled: true), |
| 428 | + Permissions: new EntityPermission[] { |
| 429 | + new( |
| 430 | + Role: "anonymous", |
| 431 | + Actions: new EntityAction[] { |
| 432 | + new(Action: EntityActionOperation.Execute, Fields: null, Policy: null) |
| 433 | + }) |
| 434 | + }, |
| 435 | + Relationships: null, |
| 436 | + Mappings: null |
| 437 | + ) |
| 438 | + } |
| 439 | + }; |
| 440 | + |
| 441 | + RuntimeEntities entities = new(entitiesDictionary); |
| 442 | + RuntimeConfig runtimeConfig = baseConfigFromDisk with { Entities = entities }; |
| 443 | + RuntimeConfigProvider runtimeConfigProvider = TestHelper.GenerateInMemoryRuntimeConfigProvider(runtimeConfig); |
| 444 | + ILogger<ISqlMetadataProvider> sqlMetadataLogger = new Mock<ILogger<ISqlMetadataProvider>>().Object; |
| 445 | + |
| 446 | + // Setup query builder |
| 447 | + _queryBuilder = new MsSqlQueryBuilder(); |
| 448 | + |
| 449 | + try |
| 450 | + { |
| 451 | + string dataSourceName = runtimeConfigProvider.GetConfig().DefaultDataSourceName; |
| 452 | + |
| 453 | + // Create mock query executor that always returns JsonArray with invalid field name |
| 454 | + Mock<IQueryExecutor> mockQueryExecutor = new(); |
| 455 | + |
| 456 | + // Create a JsonArray that simulates the stored procedure result with invalid field name |
| 457 | + JsonArray invalidFieldJsonArray = new(); |
| 458 | + JsonObject jsonObject = new() |
| 459 | + { |
| 460 | + [BaseSqlQueryBuilder.STOREDPROC_COLUMN_NAME] = invalidFieldName, // This will be null, empty, or whitespace |
| 461 | + [BaseSqlQueryBuilder.STOREDPROC_COLUMN_SYSTEMTYPENAME] = "varchar", |
| 462 | + [BaseSqlQueryBuilder.STOREDPROC_COLUMN_ISNULLABLE] = false |
| 463 | + }; |
| 464 | + invalidFieldJsonArray.Add(jsonObject); |
| 465 | + |
| 466 | + // Setup the mock to return our malformed JsonArray for all ExecuteQueryAsync calls |
| 467 | + mockQueryExecutor.Setup(x => x.ExecuteQueryAsync( |
| 468 | + It.IsAny<string>(), |
| 469 | + It.IsAny<IDictionary<string, DbConnectionParam>>(), |
| 470 | + It.IsAny<Func<DbDataReader, List<string>, Task<JsonArray>>>(), |
| 471 | + It.IsAny<string>(), |
| 472 | + It.IsAny<HttpContext>(), |
| 473 | + It.IsAny<List<string>>())) |
| 474 | + .ReturnsAsync(invalidFieldJsonArray); |
| 475 | + |
| 476 | + // Setup Mock query manager Factory |
| 477 | + Mock<IAbstractQueryManagerFactory> queryManagerFactory = new(); |
| 478 | + queryManagerFactory.Setup(x => x.GetQueryBuilder(It.IsAny<DatabaseType>())).Returns(_queryBuilder); |
| 479 | + queryManagerFactory.Setup(x => x.GetQueryExecutor(It.IsAny<DatabaseType>())).Returns(mockQueryExecutor.Object); |
| 480 | + |
| 481 | + ISqlMetadataProvider sqlMetadataProvider = new MsSqlMetadataProvider( |
| 482 | + runtimeConfigProvider, |
| 483 | + queryManagerFactory.Object, |
| 484 | + sqlMetadataLogger, |
| 485 | + dataSourceName); |
| 486 | + |
| 487 | + await sqlMetadataProvider.InitializeAsync(); |
| 488 | + Assert.Fail($"Expected DataApiBuilderException was not thrown for invalid resultFieldName: '{invalidFieldName}'."); |
| 489 | + } |
| 490 | + catch (DataApiBuilderException ex) |
| 491 | + { |
| 492 | + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, ex.StatusCode); |
| 493 | + Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ErrorInInitialization, ex.SubStatusCode); |
| 494 | + Assert.IsTrue(ex.Message.Contains("returns a column without a name")); |
| 495 | + } |
| 496 | + |
| 497 | + TestHelper.UnsetAllDABEnvironmentVariables(); |
| 498 | + } |
| 499 | + |
402 | 500 | /// <summary> |
403 | 501 | /// Helper method for test methods ValidateInferredRelationshipInfoFor{MsSql, MySql, and PgSql}. |
404 | 502 | /// This helper validates that an entity's relationship data is correctly inferred based on config and database supplied relationship metadata. |
|
0 commit comments