diff --git a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs index ddeedd5ab5..0b457041c6 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs @@ -1025,7 +1025,7 @@ internal static ConnectorType GetCdpTableType(ICdpTableResolver tableResolver, s IList referencedEntities = GetReferenceEntities(connectorName, stringValue); SymbolTable symbolTable = new SymbolTable(); - ConnectorType connectorType = new ConnectorType(jsonElement, tableName, symbolTable, compatibility, referencedEntities, datasetName, name, connectorName, tableResolver, serviceCapabilities, isTableReadOnly); + ConnectorType connectorType = new ConnectorType(jsonElement, tableName, symbolTable, compatibility, referencedEntities, datasetName, name, tableResolver, serviceCapabilities, isTableReadOnly); delegationInfo = ((DataSourceInfo)connectorType.FormulaType._type.AssociatedDataSources.First()).DelegationInfo; optionSets = symbolTable.OptionSets.Select(kvp => kvp.Value); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient.cs b/src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient.cs index ff086f2f58..fed7ac4f0a 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/PowerPlatformConnectorClient.cs @@ -43,6 +43,8 @@ public class PowerPlatformConnectorClient : HttpClient public string EnvironmentId { get; set; } + public string RequestUrlPrefix { get; init; } + /// /// Initializes a new instance of the class. /// @@ -69,6 +71,20 @@ public PowerPlatformConnectorClient(string endpoint, string environmentId, strin { } + /// + /// Initializes a new instance of the class. + /// + /// APIM Endpoint. + /// Environment Id. + /// /// Url Prefix for CDP connectors. + /// Connection/connector Id. + /// Function returning the JWT token. + /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. + public PowerPlatformConnectorClient(string endpoint, string environmentId, string requestUrlPrefix, string connectionId, Func getAuthToken, HttpMessageInvoker httpInvoker = null) + : this(endpoint, environmentId, requestUrlPrefix, connectionId, getAuthToken, null, httpInvoker) + { + } + /// /// Initializes a new instance of the class. /// @@ -82,6 +98,20 @@ public PowerPlatformConnectorClient(string endpoint, string environmentId, strin { } + /// + /// Initializes a new instance of the class. + /// + /// APIM Endpoint. + /// Environment Id. + /// Url Prefix for CDP connectors. + /// Connection/connector Id. + /// Async function returning the JWT token. + /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. + public PowerPlatformConnectorClient(string endpoint, string environmentId, string requestUrlPrefix, string connectionId, Func> getAuthToken, HttpMessageInvoker httpInvoker = null) + : this(endpoint, environmentId, requestUrlPrefix, connectionId, getAuthToken, null, httpInvoker) + { + } + /// /// Initializes a new instance of the class. /// @@ -103,13 +133,28 @@ public PowerPlatformConnectorClient(OpenApiDocument swaggerFile, string environm /// Environment Id. /// Connection/connector Id. /// Function returning the JWT token. - /// /// Product UserAgent to add to Power-Fx one (Power-Fx/version). + /// Product UserAgent to add to Power-Fx one (Power-Fx/version). /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. public PowerPlatformConnectorClient(string endpoint, string environmentId, string connectionId, Func getAuthToken, string userAgent, HttpMessageInvoker httpInvoker = null) : this(endpoint, environmentId, connectionId, async () => getAuthToken(), userAgent, httpInvoker) { } + /// + /// Initializes a new instance of the class. + /// + /// APIM Endpoint. + /// Environment Id. + /// Url Prefix for CDP connectors. + /// Connection/connector Id. + /// Async function returning the JWT token. + /// Product UserAgent to add to Power-Fx one (Power-Fx/version). + /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. + public PowerPlatformConnectorClient(string endpoint, string environmentId, string requestUrlPrefix, string connectionId, Func getAuthToken, string userAgent, HttpMessageInvoker httpInvoker = null) + : this(endpoint, environmentId, requestUrlPrefix, connectionId, async () => getAuthToken(), userAgent, httpInvoker) + { + } + /// /// Initializes a new instance of the class. /// @@ -120,6 +165,21 @@ public PowerPlatformConnectorClient(string endpoint, string environmentId, strin /// Product UserAgent to add to Power-Fx one (Power-Fx/version). /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. public PowerPlatformConnectorClient(string endpoint, string environmentId, string connectionId, Func> getAuthToken, string userAgent, HttpMessageInvoker httpInvoker = null) + : this(endpoint, environmentId, null, connectionId, getAuthToken, userAgent, httpInvoker) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// APIM Endpoint. + /// Environment Id. + /// Url Prefix for CDP connectors. + /// Connection/connector Id. + /// Async function returning the JWT token. + /// Product UserAgent to add to Power-Fx one (Power-Fx/version). + /// Optional HttpMessageInvoker. If not provided a default HttpClient is used. + public PowerPlatformConnectorClient(string endpoint, string environmentId, string requestUrlPrefix, string connectionId, Func> getAuthToken, string userAgent, HttpMessageInvoker httpInvoker = null) { _client = httpInvoker ?? new HttpClient(); @@ -127,6 +187,7 @@ public PowerPlatformConnectorClient(string endpoint, string environmentId, strin ConnectionId = connectionId ?? throw new ArgumentNullException(nameof(connectionId)); EnvironmentId = environmentId ?? throw new ArgumentNullException(nameof(environmentId)); UserAgent = string.IsNullOrWhiteSpace(userAgent) ? $"PowerFx/{Version}" : $"{userAgent} PowerFx/{Version}"; + RequestUrlPrefix = requestUrlPrefix; // Case insensitive comparison per RFC 9110 [4.2.3 http(s) Normalization and Comparison] if (endpoint.StartsWith($"{Uri.UriSchemeHttp}://", StringComparison.OrdinalIgnoreCase)) @@ -171,7 +232,7 @@ public override async Task SendAsync(HttpRequestMessage req public async Task Transform(HttpRequestMessage request) { - var url = request.RequestUri.OriginalString; + var url = $"{RequestUrlPrefix ?? string.Empty}{request.RequestUri.OriginalString}"; if (request.RequestUri.IsAbsoluteUri) { // Client has Basepath set. diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs index e658b59a3b..546d7e4d9d 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs @@ -207,7 +207,7 @@ internal ConnectorType(ISwaggerSchema schema, ConnectorCompatibility compatibili } // Called by ConnectorFunction.GetCdpTableType - internal ConnectorType(JsonElement schema, string tableName, SymbolTable optionSets, ConnectorCompatibility compatibility, IList referencedEntities, string datasetName, string name, string connectorName, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly) + internal ConnectorType(JsonElement schema, string tableName, SymbolTable optionSets, ConnectorCompatibility compatibility, IList referencedEntities, string datasetName, string name, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly) : this(SwaggerJsonSchema.New(schema), null, new SwaggerParameter(null, true, SwaggerJsonSchema.New(schema), null).GetConnectorType(tableName, optionSets, compatibility)) { Name = name; diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs index e8c0499cf1..8636220ff0 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs @@ -24,10 +24,21 @@ internal class CdpTableResolver : ICdpTableResolver private readonly HttpClient _httpClient; - private readonly string _uriPrefix; + [Obsolete] + private readonly string _uriPrefix = null; private readonly bool _doubleEncoding; + public CdpTableResolver(CdpTable tabularTable, HttpClient httpClient, bool doubleEncoding, ConnectorLogger logger = null) + { + _tabularTable = tabularTable; + _httpClient = httpClient; + _doubleEncoding = doubleEncoding; + + Logger = logger; + } + + [Obsolete] public CdpTableResolver(CdpTable tabularTable, HttpClient httpClient, string uriPrefix, bool doubleEncoding, ConnectorLogger logger = null) { _tabularTable = tabularTable; @@ -53,7 +64,12 @@ public async Task ResolveTableAsync(string tableName, Cancellatio } string dataset = _doubleEncoding ? CdpServiceBase.DoubleEncode(_tabularTable.DatasetName) : _tabularTable.DatasetName; - string uri = (_uriPrefix ?? string.Empty) + (UseV2(_uriPrefix) ? "/v2" : string.Empty) + $"/$metadata.json/datasets/{dataset}/tables/{CdpServiceBase.DoubleEncode(tableName)}?api-version=2015-09-01"; + +#pragma warning disable CS0612 // Type or member is obsolete + string prefix = string.IsNullOrEmpty(_uriPrefix) ? string.Empty : (_uriPrefix ?? string.Empty) + (UseV2(_uriPrefix) ? "/v2" : string.Empty); +#pragma warning restore CS0612 // Type or member is obsolete + + string uri = $"{prefix}/$metadata.json/datasets/{dataset}/tables/{CdpServiceBase.DoubleEncode(tableName)}?api-version=2015-09-01"; string text = await CdpServiceBase.GetObject(_httpClient, $"Get table metadata", uri, null, cancellationToken, Logger).ConfigureAwait(false); @@ -92,7 +108,10 @@ public async Task ResolveTableAsync(string tableName, Cancellatio // sqlRelationships = GetSqlRelationships(text2); //} - var parts = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); +#pragma warning disable CS0612 // Type or member is obsolete + var parts = string.IsNullOrEmpty(_uriPrefix) ? Array.Empty() : _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); +#pragma warning restore CS0612 // Type or member is obsolete + string connectorName = (parts.Length > 1) ? parts[1] : string.Empty; ConnectorType connectorType = ConnectorFunction.GetCdpTableType(this, connectorName, _tabularTable.TableName, "Schema/Items", FormulaValue.New(text), ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, @@ -103,8 +122,7 @@ public async Task ResolveTableAsync(string tableName, Cancellatio return connectorType; } - internal static bool IsSql(string uriPrefix) => uriPrefix.Contains("/sql/"); - + [Obsolete] internal static bool UseV2(string uriPrefix) => uriPrefix.Contains("/sql/") || uriPrefix.Contains("/zendesk/"); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs index 6d849e105a..1311b9fde1 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs @@ -23,6 +23,13 @@ public CdpDataSource(string dataset) DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset)); } + public static async Task GetDatasetsMetadataAsync(HttpClient httpClient, CancellationToken cancellationToken, ConnectorLogger logger = null) + { + string uri = $"/$metadata.json/datasets"; + return await GetObject(httpClient, "Get datasets metadata", uri, null, cancellationToken, logger).ConfigureAwait(false); + } + + [Obsolete("Use GetDatasetsMetadataAsync without urlPrefix")] public static async Task GetDatasetsMetadataAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) { string uri = (uriPrefix ?? string.Empty) @@ -32,6 +39,27 @@ public static async Task GetDatasetsMetadataAsync(HttpClient ht return await GetObject(httpClient, "Get datasets metadata", uri, null, cancellationToken, logger).ConfigureAwait(false); } + public virtual async Task> GetTablesAsync(HttpClient httpClient, CancellationToken cancellationToken, ConnectorLogger logger = null) + { + if (DatasetMetadata == null) + { + DatasetMetadata = await GetDatasetsMetadataAsync(httpClient, cancellationToken, logger).ConfigureAwait(false); + } + + string queryName = IsSharepoint(httpClient) ? "/alltables" : "/tables"; + string uri = $"/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}" + queryName; + + GetTables tables = await GetObject(httpClient, "Get tables", uri, null, cancellationToken, logger).ConfigureAwait(false); + return tables?.Value?.Select((RawTable rawTable) => new CdpTable(DatasetName, rawTable.Name, DatasetMetadata, tables?.Value) { DisplayName = rawTable.DisplayName }); + + static bool IsSharepoint(HttpClient httpClient) + { + return httpClient is PowerPlatformConnectorClient ppcc && + ppcc.RequestUrlPrefix.Contains("/sharepointonline/"); + } + } + + [Obsolete("Use GetTablesAsync without urlPrefix")] public virtual async Task> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) { if (DatasetMetadata == null) @@ -50,6 +78,36 @@ public virtual async Task> GetTablesAsync(HttpClient httpC return tables?.Value?.Select((RawTable rawTable) => new CdpTable(DatasetName, rawTable.Name, DatasetMetadata, tables?.Value) { DisplayName = rawTable.DisplayName }); } + /// + /// Retrieves a single CdpTable. + /// + /// HttpClient. + /// Table name to search. + /// bool? value: true = logical only, false = display name only, null = logical or display name. All comparisons are case sensitive. + /// Cancellation token. + /// Logger. + /// CdpTable class. + /// When no or more than one tables are identified. + public virtual async Task GetTableAsync(HttpClient httpClient, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null) + { + cancellationToken.ThrowIfCancellationRequested(); + + IEnumerable tables = await GetTablesAsync(httpClient, cancellationToken, logger).ConfigureAwait(false); + IEnumerable filtered = tables.Where(ct => IsNameMatching(ct.TableName, ct.DisplayName, tableName, logicalOrDisplay)); + + if (!filtered.Any()) + { + throw new InvalidOperationException("Cannot find any table with the specified name"); + } + + if (filtered.Count() > 1) + { + throw new InvalidOperationException($"Too many tables correspond to the specified name - Found {filtered.Count()} tables"); + } + + return filtered.First(); + } + /// /// Retrieves a single CdpTable. /// @@ -61,11 +119,12 @@ public virtual async Task> GetTablesAsync(HttpClient httpC /// Logger. /// CdpTable class. /// When no or more than one tables are identified. + [Obsolete("Use GetTableAsync without urlPrefix")] public virtual async Task GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null) { cancellationToken.ThrowIfCancellationRequested(); - IEnumerable tables = await GetTablesAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false); + IEnumerable tables = await GetTablesAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false); IEnumerable filtered = tables.Where(ct => IsNameMatching(ct.TableName, ct.DisplayName, tableName, logicalOrDisplay)); if (!filtered.Any()) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpServiceBase.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpServiceBase.cs index 4b6c24c733..b29ae26dfc 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpServiceBase.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpServiceBase.cs @@ -37,7 +37,7 @@ protected internal static async Task GetObject(HttpClient httpClient, stri protected internal static async Task GetObject(HttpClient httpClient, string message, string uri, string content, CancellationToken cancellationToken, ConnectorLogger logger = null, [CallerMemberName] string callingMethod = "") { cancellationToken.ThrowIfCancellationRequested(); - string log = $"{callingMethod}.{nameof(GetObject)} for {message}, Uri {uri}"; + string log = $"{callingMethod}.{nameof(GetObject)} for {message}, Uri {(httpClient is PowerPlatformConnectorClient ppcc ? ppcc.RequestUrlPrefix : string.Empty)}{uri}"; try { diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs index f039efa1b5..05abd953ca 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs @@ -39,7 +39,8 @@ public class CdpTable : CdpService internal IReadOnlyCollection Tables; - private string _uriPrefix; + [Obsolete] + private string _uriPrefix = null; private HttpClient _httpClient; @@ -60,6 +61,32 @@ internal CdpTable(string dataset, string table, DatasetMetadata datasetMetadata, //// TABLE METADATA SERVICE // GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01 + public virtual async Task InitAsync(HttpClient httpClient, CancellationToken cancellationToken, ConnectorLogger logger = null) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (IsInitialized) + { + throw new InvalidOperationException("TabularService already initialized"); + } + + _httpClient = httpClient; + + if (DatasetMetadata == null) + { + await InitializeDatasetMetadata(httpClient, logger, cancellationToken).ConfigureAwait(false); + } + + CdpTableResolver tableResolver = new CdpTableResolver(this, httpClient, DatasetMetadata.IsDoubleEncoding, logger); + TabularTableDescriptor = await tableResolver.ResolveTableAsync(TableName, cancellationToken).ConfigureAwait(false); + + _relationships = TabularTableDescriptor.Relationships; + OptionSets = tableResolver.OptionSets; + + RecordType = (RecordType)TabularTableDescriptor.FormulaType; + } + + [Obsolete("Use InitAsync without uriPrefix")] public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) { cancellationToken.ThrowIfCancellationRequested(); @@ -87,10 +114,20 @@ public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, Can RecordType = (RecordType)TabularTableDescriptor.FormulaType; } - private async Task InitializeDatasetMetadata(HttpClient httpClient, string uriPrefix, ConnectorLogger logger, CancellationToken cancellationToken) + private async Task InitializeDatasetMetadata(HttpClient httpClient, ConnectorLogger logger, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(httpClient, cancellationToken, logger).ConfigureAwait(false); + + DatasetMetadata = dm ?? throw new InvalidOperationException("Dataset metadata is not available"); + } + + [Obsolete] + private async Task InitializeDatasetMetadata(HttpClient httpClient, string uriPrefix, ConnectorLogger logger, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false); DatasetMetadata = dm ?? throw new InvalidOperationException("Dataset metadata is not available"); @@ -110,10 +147,11 @@ protected override async Task>> GetItems string queryParams = (odataParameters != null) ? "&" + odataParameters.ToQueryString() : string.Empty; - Uri uri = new Uri( - (_uriPrefix ?? string.Empty) + - (CdpTableResolver.UseV2(_uriPrefix) ? "/v2" : string.Empty) + - $"/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}/tables/{Uri.EscapeDataString(TableName)}/items?api-version=2015-09-01" + queryParams, UriKind.Relative); +#pragma warning disable CS0612 // Type or member is obsolete + string prefix = string.IsNullOrEmpty(_uriPrefix) ? string.Empty : (_uriPrefix ?? string.Empty) + (CdpTableResolver.UseV2(_uriPrefix) ? "/v2" : string.Empty); +#pragma warning restore CS0612 // Type or member is obsolete + + Uri uri = new Uri($"{prefix}/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}/tables/{Uri.EscapeDataString(TableName)}/items?api-version=2015-09-01" + queryParams, UriKind.Relative); string text = await GetObject(_httpClient, $"List items ({nameof(GetItemsInternalAsync)})", uri.ToString(), null, cancellationToken, executionLogger).ConfigureAwait(false); return !string.IsNullOrWhiteSpace(text) ? GetResult(text) : Array.Empty>(); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs index 7655a6d4b4..da011b75bd 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs @@ -29,7 +29,7 @@ public void CompatibilityTest() ConnectorLogger connectorLogger = new ConsoleLogger(_output); CdpTable tabularTable = new CdpTable("dataset", "table", new List() { }); - CdpTableResolver tableResolver = new CdpTableResolver(tabularTable, httpClient, "prefix", true, connectorLogger); + CdpTableResolver tableResolver = new CdpTableResolver(tabularTable, httpClient, true, connectorLogger); string text = (string)LoggingTestServer.GetFileText(@"Responses\Compatibility GetSchema.json"); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs index 0df3bb243d..9db0a48d98 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs @@ -16,6 +16,7 @@ using Xunit.Abstractions; #pragma warning disable SA1116 +#pragma warning disable SA1118 namespace Microsoft.PowerFx.Connectors.Tests { @@ -39,10 +40,10 @@ public async Task SQL_CdpTabular_GetTables() using var httpClient = new HttpClient(testConnector); string connectionId = "c1a4e9f52ec94d55bb82f319b3e33a6a"; string jwt = "eyJ0eXAiOiJKV1QiL..."; - using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", $"/apim/sql/{connectionId}/v2", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; testConnector.SetResponseFromFile(@"Responses\SQL GetDatasetsMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); Assert.NotNull(dm); Assert.Null(dm.Blob); @@ -80,7 +81,7 @@ public async Task SQL_CdpTabular_GetTables() CdpDataSource cds = new CdpDataSource("pfxdev-sql.database.windows.net,connectortest"); testConnector.SetResponseFromFiles(@"Responses\SQL GetDatasetsMetadata.json", @"Responses\SQL GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(tables); Assert.Equal(4, tables.Count()); @@ -93,7 +94,7 @@ public async Task SQL_CdpTabular_GetTables() Assert.Equal("Customers", connectorTable.DisplayName); testConnector.SetResponseFromFiles(@"Responses\SQL Server Load Customers DB.json", @"Responses\SQL GetRelationships SampleDB.json"); - await connectorTable.InitAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue sqlTable = connectorTable.GetTableValue(); @@ -140,6 +141,15 @@ public async Task SQL_CdpTabular_GetTables() result = await engine.EvalAsync("Last(Customers).Phone", CancellationToken.None, runtimeConfig: rc); StringValue phone = Assert.IsType(result); Assert.Equal("+1-425-705-0000", phone.Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sql/c1a4e9f52ec94d55bb82f319b3e33a6a/v2/$metadata.json/datasets", + "/apim/sql/c1a4e9f52ec94d55bb82f319b3e33a6a/v2/datasets/pfxdev-sql.database.windows.net,connectortest/tables", + "/apim/sql/c1a4e9f52ec94d55bb82f319b3e33a6a/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/tables/%255Bdbo%255D.%255BCustomers%255D" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -153,13 +163,13 @@ public async Task SQL_CdpTabular_GetTables2() using var httpClient = new HttpClient(testConnector); string connectionId = "2cc03a388d38465fba53f05cd2c76181"; string jwt = "eyJ0eXAiOiJKSuA..."; - using var client = new PowerPlatformConnectorClient("dac64a92-df6a-ee6e-a6a2-be41a923e371.15.common.tip1002.azure-apihub.net", "dac64a92-df6a-ee6e-a6a2-be41a923e371", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("dac64a92-df6a-ee6e-a6a2-be41a923e371.15.common.tip1002.azure-apihub.net", "dac64a92-df6a-ee6e-a6a2-be41a923e371", $"/apim/sql/{connectionId}/v2", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; string realTableName = "Product"; string fxTableName = "Products"; testConnector.SetResponseFromFile(@"Responses\SQL GetDatasetsMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); Assert.NotNull(dm); Assert.Null(dm.Blob); @@ -167,7 +177,7 @@ public async Task SQL_CdpTabular_GetTables2() CdpDataSource cds = new CdpDataSource("default,default"); testConnector.SetResponseFromFiles(@"Responses\SQL GetDatasetsMetadata.json", @"Responses\SQL GetTables SampleDB.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(tables); Assert.Equal(17, tables.Count()); @@ -184,29 +194,29 @@ public async Task SQL_CdpTabular_GetTables2() Assert.Equal(realTableName, connectorTable.DisplayName); testConnector.SetResponseFromFile(@"Responses\SQL GetTables SampleDB.json"); - CdpTable table2 = await cds.GetTableAsync(client, $"/apim/sql/{connectionId}", realTableName, null /* logical or display name */, CancellationToken.None, logger); + CdpTable table2 = await cds.GetTableAsync(client, realTableName, null /* logical or display name */, CancellationToken.None, logger); Assert.False(table2.IsInitialized); Assert.Equal(realTableName, table2.DisplayName); Assert.Equal("[SalesLT].[Product]", table2.TableName); // Logical Name testConnector.SetResponseFromFile(@"Responses\SQL GetTables SampleDB.json"); - table2 = await cds.GetTableAsync(client, $"/apim/sql/{connectionId}", realTableName, false /* display name only */, CancellationToken.None, logger); + table2 = await cds.GetTableAsync(client, realTableName, false /* display name only */, CancellationToken.None, logger); Assert.False(table2.IsInitialized); Assert.Equal(realTableName, table2.DisplayName); Assert.Equal("[SalesLT].[Product]", table2.TableName); // Logical Name testConnector.SetResponseFromFile(@"Responses\SQL GetTables SampleDB.json"); - table2 = await cds.GetTableAsync(client, $"/apim/sql/{connectionId}", "[SalesLT].[Product]", true /* logical name only */, CancellationToken.None, logger); + table2 = await cds.GetTableAsync(client, "[SalesLT].[Product]", true /* logical name only */, CancellationToken.None, logger); Assert.False(table2.IsInitialized); Assert.Equal(realTableName, table2.DisplayName); Assert.Equal("[SalesLT].[Product]", table2.TableName); // Logical Name testConnector.SetResponseFromFile(@"Responses\SQL GetTables SampleDB.json"); - InvalidOperationException ioe = await Assert.ThrowsAsync(() => cds.GetTableAsync(client, $"/apim/sql/{connectionId}", "[SalesLT].[Product]", false /* display name only */, CancellationToken.None, logger)); + InvalidOperationException ioe = await Assert.ThrowsAsync(() => cds.GetTableAsync(client, "[SalesLT].[Product]", false /* display name only */, CancellationToken.None, logger)); Assert.Equal("Cannot find any table with the specified name", ioe.Message); testConnector.SetResponseFromFiles(@"Responses\SQL GetSchema Products.json", @"Responses\SQL GetRelationships SampleDB.json"); - await connectorTable.InitAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue sqlTable = connectorTable.GetTableValue(); @@ -240,7 +250,7 @@ public async Task SQL_CdpTabular_GetTables2() // For SQL we don't have relationships bool b = sqlTable.RecordType.TryGetFieldExternalTableName("ProductModelID", out string externalTableName, out string foreignKey); Assert.False(b); - + testConnector.SetResponseFromFiles(@"Responses\SQL GetSchema ProductModel.json"); b = sqlTable.RecordType.TryGetFieldType("ProductModelID", out FormulaType productModelID); @@ -249,6 +259,15 @@ public async Task SQL_CdpTabular_GetTables2() Assert.False(productModelId is null); Assert.Equal("ProductID", string.Join("|", GetPrimaryKeyNames(sqlTable.RecordType))); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sql/2cc03a388d38465fba53f05cd2c76181/v2/$metadata.json/datasets", + "/apim/sql/2cc03a388d38465fba53f05cd2c76181/v2/datasets/default,default/tables", + "/apim/sql/2cc03a388d38465fba53f05cd2c76181/v2/$metadata.json/datasets/default,default/tables/%255BSalesLT%255D.%255BProduct%255D" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -262,17 +281,17 @@ public async Task SAP_CDP() using var httpClient = new HttpClient(testConnector); string connectionId = "1e702ce4f10c482684cee1465e686764"; string jwt = "eyJ0eXAi..."; - using var client = new PowerPlatformConnectorClient("066d5714-1ffc-e316-90bd-affc61d8e6fd.18.common.tip2.azure-apihub.net", "066d5714-1ffc-e316-90bd-affc61d8e6fd", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("066d5714-1ffc-e316-90bd-affc61d8e6fd.18.common.tip2.azure-apihub.net", "066d5714-1ffc-e316-90bd-affc61d8e6fd", $"/apim/sapodata/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; testConnector.SetResponseFromFile(@"Responses\SAP GetDataSetMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, $"/apim/sapodata/{connectionId}", CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); CdpDataSource cds = new CdpDataSource("http://sapecckerb.roomsofthehouse.com:8080/sap/opu/odata/sap/HRESS_TEAM_CALENDAR_SERVICE"); testConnector.SetResponseFromFiles(@"Responses\SAP GetDataSetMetadata.json", @"Responses\SAP GetTables.json"); - CdpTable sapTable = await cds.GetTableAsync(client, $"/apim/sapodata/{connectionId}", "TeamCalendarCollection", null, CancellationToken.None, logger); + CdpTable sapTable = await cds.GetTableAsync(client, "TeamCalendarCollection", null, CancellationToken.None, logger); testConnector.SetResponseFromFile(@"Responses\SAP GetTable Schema.json"); - await sapTable.InitAsync(client, $"/apim/sapodata/{connectionId}", CancellationToken.None, logger); + await sapTable.InitAsync(client, CancellationToken.None, logger); Assert.True(sapTable.IsInitialized); @@ -297,6 +316,15 @@ public async Task SAP_CDP() // Not defined for SAP Assert.Equal(string.Empty, string.Join("|", GetPrimaryKeyNames(sapTableValue.RecordType))); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sapodata/1e702ce4f10c482684cee1465e686764/$metadata.json/datasets", + "/apim/sapodata/1e702ce4f10c482684cee1465e686764/datasets/http%253A%252F%252Fsapecckerb.roomsofthehouse.com%253A8080%252Fsap%252Fopu%252Fodata%252Fsap%252FHRESS_TEAM_CALENDAR_SERVICE/tables", + "/apim/sapodata/1e702ce4f10c482684cee1465e686764/$metadata.json/datasets/http%253A%252F%252Fsapecckerb.roomsofthehouse.com%253A8080%252Fsap%252Fopu%252Fodata%252Fsap%252FHRESS_TEAM_CALENDAR_SERVICE/tables/TeamCalendarCollection" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -310,7 +338,7 @@ public async Task SQL_CdpTabular() using var httpClient = new HttpClient(testConnector); string connectionId = "18992e9477684930acd2cc5dc9bb94c2"; string jwt = "eyJ0eXAiOiJK..."; - using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) + using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", $"/apim/sql/{connectionId}/v2", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; @@ -326,7 +354,7 @@ public async Task SQL_CdpTabular() Assert.Equal("Customers", tabularService.TableName); testConnector.SetResponseFromFiles(@"Responses\SQL GetDatasetsMetadata.json", @"Responses\SQL Server Load Customers DB.json", @"Responses\SQL GetRelationships SampleDB.json"); - await tabularService.InitAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger); + await tabularService.InitAsync(client, CancellationToken.None, logger); Assert.True(tabularService.IsInitialized); CdpTableValue sqlTable = tabularService.GetTableValue(); @@ -354,6 +382,14 @@ public async Task SQL_CdpTabular() result = await engine.EvalAsync("Last(Customers).Phone", CancellationToken.None, runtimeConfig: rc); StringValue phone = Assert.IsType(result); Assert.Equal("+1-425-705-0000", phone.Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sql/18992e9477684930acd2cc5dc9bb94c2/v2/$metadata.json/datasets", + "/apim/sql/18992e9477684930acd2cc5dc9bb94c2/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/tables/Customers" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -366,13 +402,13 @@ public async Task SP_CdpTabular_GetTables() using var httpClient = new HttpClient(testConnector); string connectionId = "3738993883dc406d86802d8a6a923d3e"; string jwt = "eyJ0eXAiOiJK..."; - using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", $"/apim/sharepointonline/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; ConsoleLogger logger = new ConsoleLogger(_output); CdpDataSource cds = new CdpDataSource("https://microsofteur.sharepoint.com/teams/pfxtest"); testConnector.SetResponseFromFiles(@"Responses\SP GetDatasetsMetadata.json", @"Responses\SP GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(cds.DatasetMetadata); @@ -402,7 +438,7 @@ public async Task SP_CdpTabular_GetTables() Assert.Equal("4bd37916-0026-4726-94e8-5a0cbc8e476a", connectorTable.TableName); testConnector.SetResponseFromFiles(@"Responses\SP GetTable.json"); - await connectorTable.InitAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue spTable = connectorTable.GetTableValue(); @@ -468,6 +504,15 @@ public async Task SP_CdpTabular_GetTables() Assert.Equal("Document1", docName.Value); Assert.Equal("ID", string.Join("|", GetPrimaryKeyNames(spTable.RecordType))); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sharepointonline/3738993883dc406d86802d8a6a923d3e/$metadata.json/datasets", + "/apim/sharepointonline/3738993883dc406d86802d8a6a923d3e/datasets/https%253A%252F%252Fmicrosofteur.sharepoint.com%252Fteams%252Fpfxtest/alltables", + "/apim/sharepointonline/3738993883dc406d86802d8a6a923d3e/$metadata.json/datasets/https%253A%252F%252Fmicrosofteur.sharepoint.com%252Fteams%252Fpfxtest/tables/4bd37916-0026-4726-94e8-5a0cbc8e476a" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -481,7 +526,7 @@ public async Task SP_CdpTabular() using var httpClient = new HttpClient(testConnector); string connectionId = "0b905132239e463a9d12f816be201da9"; string jwt = "eyJ0eXAiOiJKV...."; - using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) + using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", $"/apim/sharepointonline/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832384" }; @@ -492,7 +537,7 @@ public async Task SP_CdpTabular() Assert.Equal("Documents", tabularService.TableName); testConnector.SetResponseFromFiles(@"Responses\SP GetDatasetsMetadata.json", @"Responses\SP GetTable.json"); - await tabularService.InitAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger); + await tabularService.InitAsync(client, CancellationToken.None, logger); Assert.True(tabularService.IsInitialized); CdpTableValue spTable = tabularService.GetTableValue(); @@ -521,6 +566,14 @@ public async Task SP_CdpTabular() StringValue docName = Assert.IsType(result); Assert.Equal("Document1", docName.Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/sharepointonline/0b905132239e463a9d12f816be201da9/$metadata.json/datasets", + "/apim/sharepointonline/0b905132239e463a9d12f816be201da9/$metadata.json/datasets/https%253A%252F%252Fmicrosofteur.sharepoint.com%252Fteams%252Fpfxtest/tables/Documents" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -534,16 +587,16 @@ public async Task SF_CountRows() using var httpClient = new HttpClient(testConnector); string connectionId = "ba3b1db7bb854aedbad2058b66e36e83"; string jwt = "eyJ0eXAiOiJK..."; - using var client = new PowerPlatformConnectorClient("tip1002-002.azure-apihub.net", "7526ddf1-6e97-eed6-86bb-8fd46790d670", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("tip1002-002.azure-apihub.net", "7526ddf1-6e97-eed6-86bb-8fd46790d670", $"/apim/salesforce/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; CdpDataSource cds = new CdpDataSource("default"); testConnector.SetResponseFromFiles(@"Responses\SF GetDatasetsMetadata.json", @"Responses\SF GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); CdpTable connectorTable = tables.First(t => t.DisplayName == "Accounts"); testConnector.SetResponseFromFile(@"Responses\SF GetSchema.json"); - await connectorTable.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); CdpTableValue sfTable = connectorTable.GetTableValue(); SymbolValues symbolValues = new SymbolValues().Add("Accounts", sfTable); @@ -557,6 +610,15 @@ public async Task SF_CountRows() testConnector.SetResponseFromFile(@"Responses\SF GetData.json"); FormulaValue result = await check.GetEvaluator().EvalAsync(CancellationToken.None, rc); Assert.Equal(6, ((DecimalValue)result).Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/$metadata.json/datasets", + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/datasets/default/tables", + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/$metadata.json/datasets/default/tables/Account" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -570,16 +632,16 @@ public async Task SF_Filter() using var httpClient = new HttpClient(testConnector); string connectionId = "ba3b1db7bb854aedbad2058b66e36e83"; string jwt = "eyJ0eXAiOi..."; - using var client = new PowerPlatformConnectorClient("tip1002-002.azure-apihub.net", "7526ddf1-6e97-eed6-86bb-8fd46790d670", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("tip1002-002.azure-apihub.net", "7526ddf1-6e97-eed6-86bb-8fd46790d670", $"/apim/salesforce/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; CdpDataSource cds = new CdpDataSource("default"); testConnector.SetResponseFromFiles(@"Responses\SF GetDatasetsMetadata.json", @"Responses\SF GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $" / apim/salesforce/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); CdpTable connectorTable = tables.First(t => t.DisplayName == "Accounts"); testConnector.SetResponseFromFile(@"Responses\SF GetSchema.json"); - await connectorTable.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); CdpTableValue sfTable = connectorTable.GetTableValue(); SymbolValues symbolValues = new SymbolValues().Add("Accounts", sfTable); @@ -593,6 +655,15 @@ public async Task SF_Filter() testConnector.SetResponseFromFile(@"Responses\SF GetData.json"); FormulaValue result = await check.GetEvaluator().EvalAsync(CancellationToken.None, rc); Assert.Equal("Kutch and Sons", ((StringValue)result).Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/$metadata.json/datasets", + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/datasets/default/tables", + "/apim/salesforce/ba3b1db7bb854aedbad2058b66e36e83/$metadata.json/datasets/default/tables/Account" + }), + string.Join("|", logger.Uris)); } private static IEnumerable GetPrimaryKeyNames(RecordType rt) @@ -605,7 +676,7 @@ private static IEnumerable GetPrimaryKeyNames(RecordType rt) [Fact] public async Task SF_CdpTabular_GetTables() - { + { using var testConnector = new LoggingTestServer(null /* no swagger */, _output); var config = new PowerFxConfig(Features.PowerFxV1); var engine = new RecalcEngine(config); @@ -614,10 +685,10 @@ public async Task SF_CdpTabular_GetTables() using var httpClient = new HttpClient(testConnector); string connectionId = "3b997639fd9c4d808ecf723eb4b55c64"; string jwt = "eyJ0eXAiOiJKV..."; - using var client = new PowerPlatformConnectorClient("tip1-shared.azure-apim.net", "e48a52f5-3dfe-e2f6-bc0b-155d32baa44c", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("tip1-shared.azure-apim.net", "e48a52f5-3dfe-e2f6-bc0b-155d32baa44c", $"/apim/salesforce/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; testConnector.SetResponseFromFile(@"Responses\SF GetDatasetsMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); Assert.NotNull(dm); Assert.Null(dm.Blob); @@ -635,7 +706,7 @@ public async Task SF_CdpTabular_GetTables() // only one network call as we already read metadata testConnector.SetResponseFromFiles(@"Responses\SF GetDatasetsMetadata.json", @"Responses\SF GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(tables); Assert.Equal(569, tables.Count()); @@ -645,7 +716,7 @@ public async Task SF_CdpTabular_GetTables() Assert.False(connectorTable.IsInitialized); testConnector.SetResponseFromFile(@"Responses\SF GetSchema.json"); - await connectorTable.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue sfTable = connectorTable.GetTableValue(); @@ -796,6 +867,16 @@ public async Task SF_CdpTabular_GetTables() Assert.Equal("Id", string.Join("|", GetPrimaryKeyNames(sfTable.RecordType))); Assert.Equal("Id", string.Join("|", GetPrimaryKeyNames(userTable))); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/salesforce/3b997639fd9c4d808ecf723eb4b55c64/$metadata.json/datasets", + "/apim/salesforce/3b997639fd9c4d808ecf723eb4b55c64/datasets/default/tables", + "/apim/salesforce/3b997639fd9c4d808ecf723eb4b55c64/$metadata.json/datasets/default/tables/Account", + "/apim/salesforce/3b997639fd9c4d808ecf723eb4b55c64/$metadata.json/datasets/default/tables/User" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -809,7 +890,7 @@ public async Task SF_CdpTabular() using var httpClient = new HttpClient(testConnector); string connectionId = "ec5fe6d1cad744a0a716fe4597a74b2e"; string jwt = "eyJ0eXAiOiJ..."; - using var client = new PowerPlatformConnectorClient("tip2-001.azure-apihub.net", "53d7f409-4bce-e458-8245-5fa1346ec433", connectionId, () => jwt, httpClient) + using var client = new PowerPlatformConnectorClient("tip2-001.azure-apihub.net", "53d7f409-4bce-e458-8245-5fa1346ec433", $"/apim/salesforce/{connectionId}", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832384" }; @@ -820,7 +901,7 @@ public async Task SF_CdpTabular() Assert.Equal("Account", tabularService.TableName); testConnector.SetResponseFromFiles(@"Responses\SF GetDatasetsMetadata.json", @"Responses\SF GetSchema.json"); - await tabularService.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger); + await tabularService.InitAsync(client, CancellationToken.None, logger); Assert.True(tabularService.IsInitialized); CdpTableValue sfTable = tabularService.GetTableValue(); @@ -850,6 +931,14 @@ public async Task SF_CdpTabular() StringValue accountId = Assert.IsType(result); Assert.Equal("001DR00001Xj1YmYAJ", accountId.Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/salesforce/ec5fe6d1cad744a0a716fe4597a74b2e/$metadata.json/datasets", + "/apim/salesforce/ec5fe6d1cad744a0a716fe4597a74b2e/$metadata.json/datasets/default/tables/Account" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -862,12 +951,12 @@ public async Task ZD_CdpTabular_GetTables() ConsoleLogger logger = new ConsoleLogger(_output); using var httpClient = new HttpClient(testConnector); string connectionId = "7a82a84f1b454132920a2654b00d45be"; - string uriPrefix = $"/apim/zendesk/{connectionId}"; + string uriPrefix = $"/apim/zendesk/{connectionId}/v2"; string jwt = "eyJ0eXAiOiJK..."; - using var client = new PowerPlatformConnectorClient("tip1-shared.azure-apim.net", "e48a52f5-3dfe-e2f6-bc0b-155d32baa44c", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("tip1-shared.azure-apim.net", "e48a52f5-3dfe-e2f6-bc0b-155d32baa44c", uriPrefix, connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; testConnector.SetResponseFromFile(@"Responses\ZD GetDatasetsMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, uriPrefix, CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); Assert.NotNull(dm); Assert.Null(dm.Blob); @@ -885,7 +974,7 @@ public async Task ZD_CdpTabular_GetTables() // only one network call as we already read metadata testConnector.SetResponseFromFiles(@"Responses\ZD GetDatasetsMetadata.json", @"Responses\ZD GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, uriPrefix, CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(tables); Assert.Equal(18, tables.Count()); @@ -895,14 +984,14 @@ public async Task ZD_CdpTabular_GetTables() Assert.False(connectorTable.IsInitialized); testConnector.SetResponseFromFile(@"Responses\ZD Users GetSchema.json"); - await connectorTable.InitAsync(client, uriPrefix, CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue zdTable = connectorTable.GetTableValue(); Assert.True(zdTable._tabularService.IsInitialized); Assert.True(zdTable.IsDelegable); - Assert.Equal( + Assert.Equal( "r![active:b, alias:s, created_at:d, custom_role_id:w, details:s, email:s, external_id:s, id:w, last_login_at:d, locale:s, locale_id:w, moderator:b, name:s, notes:s, only_private_comments:b, organization_id:w, " + "phone:s, photo:s, restricted_agent:b, role:s, shared:b, shared_agent:b, signature:s, suspended:b, tags:s, ticket_restriction:s, time_zone:s, updated_at:d, url:s, user_fields:s, verified:b]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames()); @@ -920,6 +1009,15 @@ public async Task ZD_CdpTabular_GetTables() StringValue userName = Assert.IsType(result); Assert.Equal("Ram Sitwat", userName.Value); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/zendesk/7a82a84f1b454132920a2654b00d45be/v2/$metadata.json/datasets", + "/apim/zendesk/7a82a84f1b454132920a2654b00d45be/v2/datasets/default/tables", + "/apim/zendesk/7a82a84f1b454132920a2654b00d45be/v2/$metadata.json/datasets/default/tables/users" + }), + string.Join("|", logger.Uris)); } [Fact] @@ -932,12 +1030,12 @@ public async Task ZD_CdpTabular_GetTables2() ConsoleLogger logger = new ConsoleLogger(_output); using var httpClient = new HttpClient(testConnector); string connectionId = "ca06d34f4b684e38b7cf4c0f517a7e99"; - string uriPrefix = $"/apim/zendesk/{connectionId}"; + string uriPrefix = $"/apim/zendesk/{connectionId}/v2"; string jwt = "eyJ0eXA..."; - using var client = new PowerPlatformConnectorClient("4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net", "4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + using var client = new PowerPlatformConnectorClient("4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net", "4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f", uriPrefix, connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; testConnector.SetResponseFromFile(@"Responses\ZD GetDatasetsMetadata.json"); - DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, uriPrefix, CancellationToken.None, logger); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, CancellationToken.None, logger); Assert.NotNull(dm); Assert.Null(dm.Blob); @@ -955,7 +1053,7 @@ public async Task ZD_CdpTabular_GetTables2() // only one network call as we already read metadata testConnector.SetResponseFromFiles(@"Responses\ZD GetDatasetsMetadata.json", @"Responses\ZD GetTables.json"); - IEnumerable tables = await cds.GetTablesAsync(client, uriPrefix, CancellationToken.None, logger); + IEnumerable tables = await cds.GetTablesAsync(client, CancellationToken.None, logger); Assert.NotNull(tables); Assert.Equal(18, tables.Count()); @@ -965,7 +1063,7 @@ public async Task ZD_CdpTabular_GetTables2() Assert.False(connectorTable.IsInitialized); testConnector.SetResponseFromFile(@"Responses\ZD Tickets GetSchema.json"); - await connectorTable.InitAsync(client, uriPrefix, CancellationToken.None, logger); + await connectorTable.InitAsync(client, CancellationToken.None, logger); Assert.True(connectorTable.IsInitialized); CdpTableValue zdTable = connectorTable.GetTableValue(); @@ -997,6 +1095,15 @@ public async Task ZD_CdpTabular_GetTables2() Assert.Equal("priority (tickets), status (tickets), type (tickets)", string.Join(", ", connectorTable.OptionSets.Select(os => os.EntityName.Value).OrderBy(x => x))); Assert.Equal("id", string.Join("|", GetPrimaryKeyNames(zdTable.RecordType))); + + Assert.Equal( + string.Join("|", new string[] + { + "/apim/zendesk/ca06d34f4b684e38b7cf4c0f517a7e99/v2/$metadata.json/datasets", + "/apim/zendesk/ca06d34f4b684e38b7cf4c0f517a7e99/v2/datasets/default/tables", + "/apim/zendesk/ca06d34f4b684e38b7cf4c0f517a7e99/v2/$metadata.json/datasets/default/tables/tickets" + }), + string.Join("|", logger.Uris)); } } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs index e5fdc5c494..0fbdaa4f0b 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs @@ -53,6 +53,10 @@ internal class ConsoleLogger : ConnectorLogger private readonly bool _includeDebug; private readonly List _logs = new (); + internal IEnumerable Uris => _uris.Distinct(); + + private readonly List _uris = new List(); + internal ConsoleLogger(ITestOutputHelper console, bool includeDebug = false) { _console = console; @@ -84,6 +88,13 @@ protected override void Log(ConnectorLog log) if (_includeDebug || log.Category != LogCategory.Debug) { _console.WriteLine(GetMessage(log)); + + string[] parts = log.Message.Split(new string[] { "Uri ", ", ", "?" }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 1) + { + _uris.Add(parts[1]); + } + _logs.Add(log); } }