diff --git a/src/DurableTask.ServiceBus/ServiceBusOrchestrationService.cs b/src/DurableTask.ServiceBus/ServiceBusOrchestrationService.cs index b8cd2791..30e7b6ff 100644 --- a/src/DurableTask.ServiceBus/ServiceBusOrchestrationService.cs +++ b/src/DurableTask.ServiceBus/ServiceBusOrchestrationService.cs @@ -431,9 +431,9 @@ internal async Task> GetHubQueueMaxDeliveryCountsAsync() var queues = (await managementClient.GetQueuesAsync()).Where(x => x.Path.StartsWith(this.hubName)).ToList(); - result.Add("TaskOrchestration", queues.Single(q => string.Equals(q.Path, this.orchestratorEntityName))?.MaxDeliveryCount ?? -1); - result.Add("TaskActivity", queues.Single(q => string.Equals(q.Path, this.workerEntityName))?.MaxDeliveryCount ?? -1); - result.Add("Tracking", queues.Single(q => string.Equals(q.Path, this.trackingEntityName))?.MaxDeliveryCount ?? -1); + result.Add("TaskOrchestration", queues.SingleOrDefault(q => string.Equals(q.Path, this.orchestratorEntityName))?.MaxDeliveryCount ?? -1); + result.Add("TaskActivity", queues.SingleOrDefault(q => string.Equals(q.Path, this.workerEntityName))?.MaxDeliveryCount ?? -1); + result.Add("Tracking", queues.SingleOrDefault(q => string.Equals(q.Path, this.trackingEntityName))?.MaxDeliveryCount ?? -1); return result; } diff --git a/src/DurableTask.ServiceBus/Tracking/BlobStorageClient.cs b/src/DurableTask.ServiceBus/Tracking/BlobStorageClient.cs index a1d32d36..dfc268de 100644 --- a/src/DurableTask.ServiceBus/Tracking/BlobStorageClient.cs +++ b/src/DurableTask.ServiceBus/Tracking/BlobStorageClient.cs @@ -59,7 +59,7 @@ public BlobStorageClient(string hubName, string connectionString) // make the hub name lower case since it will be used as part of the prefix of the container name, // which only allows lower case letters - this.containerNamePrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName.ToLower()); + this.containerNamePrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName); } /// @@ -89,7 +89,7 @@ public BlobStorageClient(string hubName, Uri endpoint, TokenCredential credentia // make the hub name lower case since it will be used as part of the prefix of the container name, // which only allows lower case letters - this.containerNamePrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName.ToLower()); + this.containerNamePrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName); } /// @@ -130,19 +130,21 @@ private async Task GetCloudBlobClientAsync(string containerNameSuffi } /// - /// List all containers of the blob storage, whose prefix is containerNamePrefix, i.e., {hubName}-dtfx. + /// List all containers of this hub, i.e. containers named {hubName}-dtfx-{type}-{yyyyMMdd}. + /// Containers of other hubs whose names share this hub's prefix (e.g. hub "alpha-dtfx-backup" for hub "alpha") + /// are not returned. /// /// A list of Azure blob containers public async Task> ListContainersAsync() { List results = new List(); // Use the service client's prefix parameter to only enumerate containers that start with the - // configured containerNamePrefix. Also apply a defensive in-memory filter in case of any - // unexpected differences in casing or service behavior. + // configured containerNamePrefix, then keep only the containers that belong to this hub, since + // hub names may contain dashes and another hub's containers can share the same prefix. var response = this.blobServiceClient.GetBlobContainersAsync(prefix: this.containerNamePrefix); await foreach (var container in response) { - if (container.Name != null && container.Name.StartsWith(this.containerNamePrefix, StringComparison.OrdinalIgnoreCase)) + if (container.Name != null && BlobStorageClientHelper.IsContainerOwnedByHub(container.Name, this.containerNamePrefix)) { results.Add(container); } @@ -152,7 +154,7 @@ public async Task> ListContainersAsync() /// - /// Delete all containers that are older than the input threshold date. + /// Delete all containers of this hub that are older than the input threshold date. /// /// The specified date threshold /// @@ -164,7 +166,7 @@ public async Task DeleteExpiredContainersAsync(DateTime thresholdDateTimeUtc) } /// - /// Delete blob containers with the containerNamePrefix as prefix. + /// Delete all containers of this hub. /// /// public async Task DeleteBlobStoreContainersAsync() diff --git a/src/DurableTask.ServiceBus/Tracking/BlobStorageClientHelper.cs b/src/DurableTask.ServiceBus/Tracking/BlobStorageClientHelper.cs index fd6dc511..e415fd8a 100644 --- a/src/DurableTask.ServiceBus/Tracking/BlobStorageClientHelper.cs +++ b/src/DurableTask.ServiceBus/Tracking/BlobStorageClientHelper.cs @@ -27,6 +27,8 @@ public class BlobStorageClientHelper { static readonly string DateFormat = "yyyyMMdd"; static readonly char ContainerNameDelimiter = '-'; + static readonly string ContainerNameMarker = "dtfx"; + static readonly Regex ContainerNameSuffixRegex = new Regex(@"^[a-z0-9-]+\z", RegexOptions.Compiled | RegexOptions.CultureInvariant); /// /// the blob storage access key is in the format of {DateTime}|{blobName} @@ -60,7 +62,7 @@ public static string BuildMessageBlobKey(string instanceId, string executionId, static string BuildContainerNameSuffix(string containerType, DateTime blobCreationTime) { - return $"{containerType.ToLower()}{ContainerNameDelimiter}{GetDateStringForContainerName(blobCreationTime)}"; + return $"{containerType.ToLowerInvariant()}{ContainerNameDelimiter}{GetDateStringForContainerName(blobCreationTime)}"; } /// @@ -72,7 +74,7 @@ static string BuildContainerNameSuffix(string containerType, DateTime blobCreati /// The container name prefix public static string BuildContainerNamePrefix(string hubName) { - return $"{hubName.ToLower()}{ContainerNameDelimiter}dtfx"; + return $"{hubName.ToLowerInvariant()}{ContainerNameDelimiter}{ContainerNameMarker}"; } /// @@ -91,8 +93,8 @@ public static string BuildSessionBlobKey(string sessionId) static string GetDateStringForContainerName(DateTime messageFireTime) { return messageFireTime.IsSet() ? - messageFireTime.ToString(DateFormat) : - DateTime.UtcNow.ToString(DateFormat); + messageFireTime.ToString(DateFormat, CultureInfo.InvariantCulture) : + DateTime.UtcNow.ToString(DateFormat, CultureInfo.InvariantCulture); } /// @@ -129,8 +131,7 @@ public static void ParseKey(string key, out string containerNameSuffix, out stri /// True if the container name suffix is valid. static bool IsValidContainerNameSuffix(string containerNameSuffix) { - var regex = new Regex(@"^[a-z0-9\\-]+$"); - return regex.Match(containerNameSuffix).Success; + return ContainerNameSuffixRegex.IsMatch(containerNameSuffix); } /// @@ -141,26 +142,21 @@ static bool IsValidContainerNameSuffix(string containerNameSuffix) /// public static bool IsContainerExpired(string containerName, DateTime thresholdDateTimeUtc) { + // Container names are {hubName}-dtfx-{type}-{yyyyMMdd}; the hub name may itself contain dashes, + // so the fixed segments are read from the end. string[] segments = containerName.Split(ContainerNameDelimiter); - if (segments.Length != 4) + if (segments.Length < 4 || !string.Equals(segments[segments.Length - 3], ContainerNameMarker, StringComparison.Ordinal)) { TraceHelper.Trace( TraceEventType.Warning, "BlobStorageClientHelper-IsContainerExpired-ContainerIgnored", - $"Container name {containerName} does not contain required 4 segments. Container {containerName} is ignored."); + $"Container name {containerName} is not in the format {{hubName}}-{ContainerNameMarker}-{{type}}-{{date}}. Container {containerName} is ignored."); return false; } string dateString = segments[segments.Length - 1]; - bool parseSucceeded = DateTime.TryParseExact( - dateString, - DateFormat, - System.Globalization.CultureInfo.InvariantCulture, - DateTimeStyles.None, - out DateTime containerDateTime); - - if (!parseSucceeded) + if (!TryParseContainerDate(dateString, out DateTime containerDateTime)) { TraceHelper.Trace( TraceEventType.Warning, @@ -173,6 +169,39 @@ public static bool IsContainerExpired(string containerName, DateTime thresholdDa return containerDateTime < thresholdDateTimeUtc; } + /// + /// Check if the container belongs to the hub with the given container name prefix. + /// Container names are {hubName}-dtfx-{type}-{yyyyMMdd}; because hub names may contain dashes, a different hub + /// (e.g. "alpha-dtfx-backup") can share the prefix "alpha-dtfx". The container belongs to the hub only if the + /// whole name without its trailing "-{type}-{yyyyMMdd}" segments equals the hub's container name prefix, + /// the type is not empty, and the last segment is a valid yyyyMMdd date. + /// + /// The container name + /// The hub's container name prefix, i.e. {hubName}-dtfx + /// True if the container belongs to the hub. + internal static bool IsContainerOwnedByHub(string containerName, string containerNamePrefix) + { + int dateDelimiterIndex = containerName.LastIndexOf(ContainerNameDelimiter); + if (dateDelimiterIndex <= 0) + { + return false; + } + + int typeDelimiterIndex = containerName.LastIndexOf(ContainerNameDelimiter, dateDelimiterIndex - 1); + if (typeDelimiterIndex <= 0 || typeDelimiterIndex == dateDelimiterIndex - 1) + { + return false; + } + + return string.Equals(containerName.Substring(0, typeDelimiterIndex), containerNamePrefix, StringComparison.OrdinalIgnoreCase) && + TryParseContainerDate(containerName.Substring(dateDelimiterIndex + 1), out _); + } + + static bool TryParseContainerDate(string dateString, out DateTime date) + { + return DateTime.TryParseExact(dateString, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date); + } + /// /// Build a container name using prefix and suffix. /// diff --git a/test/DurableTask.ServiceBus.Tests/BlobStorageClientHelperTest.cs b/test/DurableTask.ServiceBus.Tests/BlobStorageClientHelperTest.cs index 30bf01a9..7da52822 100644 --- a/test/DurableTask.ServiceBus.Tests/BlobStorageClientHelperTest.cs +++ b/test/DurableTask.ServiceBus.Tests/BlobStorageClientHelperTest.cs @@ -15,6 +15,7 @@ namespace DurableTask.ServiceBus.Tests { using System; using System.Diagnostics.CodeAnalysis; + using System.Globalization; using System.Text.RegularExpressions; using Microsoft.VisualStudio.TestTools.UnitTesting; using DurableTask.ServiceBus.Tracking; @@ -42,6 +43,84 @@ public void IsContainerExpiredTest() Assert.IsFalse(BlobStorageClientHelper.IsContainerExpired("hubname-dtfx-message-20146789", DateTime.UtcNow)); } + [TestMethod] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void IsContainerExpiredHubNameWithDashesTest() + { + var dateTime = new DateTime(2015, 05, 17); + Assert.IsTrue(BlobStorageClientHelper.IsContainerExpired("my-hub-dtfx-message-20150516", dateTime)); + Assert.IsTrue(BlobStorageClientHelper.IsContainerExpired("my-test-hub-dtfx-session-20140518", dateTime)); + Assert.IsFalse(BlobStorageClientHelper.IsContainerExpired("my-hub-dtfx-message-20150518", dateTime)); + + // containers not created by the framework are ignored + Assert.IsFalse(BlobStorageClientHelper.IsContainerExpired("my-hub-other-message-20100101", DateTime.UtcNow)); + Assert.IsFalse(BlobStorageClientHelper.IsContainerExpired("dtfx-message-20100101", DateTime.UtcNow)); + } + + [TestMethod] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void ContainerNamesAreCultureInvariantTest() + { + CultureInfo originalCulture = CultureInfo.CurrentCulture; + try + { + // th-TH uses the Buddhist calendar (year 2558 for 2015) + CultureInfo.CurrentCulture = new CultureInfo("th-TH"); + string key = BlobStorageClientHelper.BuildMessageBlobKey("aa", "bb", new DateTime(2015, 05, 17)); + StringAssert.StartsWith(key, "message-20150517|"); + + // tr-TR lower-cases 'I' to a dotless 'ı', which is invalid in a container name + CultureInfo.CurrentCulture = new CultureInfo("tr-TR"); + Assert.AreEqual("myhubid-dtfx", BlobStorageClientHelper.BuildContainerNamePrefix("MYHUBID")); + } + finally + { + CultureInfo.CurrentCulture = originalCulture; + } + } + + [TestMethod] + public void ParseKeyRejectsInvalidSuffixCharactersTest() + { + Assert.ThrowsException( + () => BlobStorageClientHelper.ParseKey(@"message\-20100319|aa/bb/cc", out _, out _)); + Assert.ThrowsException( + () => BlobStorageClientHelper.ParseKey("message-20100319\n|aa/bb/cc", out _, out _)); + } + + [TestMethod] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void IsContainerOwnedByHubTest() + { + Assert.IsTrue(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-20150516", "alpha-dtfx")); + Assert.IsTrue(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-backup-dtfx-session-20150516", "alpha-dtfx-backup-dtfx")); + Assert.IsTrue(BlobStorageClientHelper.IsContainerOwnedByHub("my-hub-dtfx-message-20150516", "my-hub-dtfx")); + + // another hub whose container names share this hub's prefix + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-backup-dtfx-session-20150516", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-20150516", "alpha-dtfx-backup-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-extra-session-20150516", "alpha-dtfx")); + + // malformed names + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-20150516", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("-session-20150516", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("invalidContainerName", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx--20150516", "alpha-dtfx")); + + // non-date suffixes + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-backup", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-2015051", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-201505160", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-", "alpha-dtfx")); + + // invalid calendar dates + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-20150230", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-session-20151301", "alpha-dtfx")); + Assert.IsFalse(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-message-20150001", "alpha-dtfx")); + Assert.IsTrue(BlobStorageClientHelper.IsContainerOwnedByHub("alpha-dtfx-message-20160229", "alpha-dtfx")); + } + [TestMethod] public void BuildMessageBlobKeyTest() { diff --git a/test/DurableTask.ServiceBus.Tests/BlobStorageClientTest.cs b/test/DurableTask.ServiceBus.Tests/BlobStorageClientTest.cs index ebc3c235..5baa2ba2 100644 --- a/test/DurableTask.ServiceBus.Tests/BlobStorageClientTest.cs +++ b/test/DurableTask.ServiceBus.Tests/BlobStorageClientTest.cs @@ -103,6 +103,146 @@ public async Task TestDeleteContainers() Assert.AreEqual(0, containers.Count); } + [TestMethod] + public async Task TestDeleteExpiredContainersPreservesOverlappingHub() + { + // "{hub}-dtfx-backup" shares the container name prefix "{hub}-dtfx" with "{hub}", + // so listing "{hub}" containers also returns the other hub's containers. + string storageConnectionString = TestHelpers.GetTestSetting("StorageConnectionString"); + var serviceClient = new BlobServiceClient(storageConnectionString); + string hubName = "alpha" + Guid.NewGuid().ToString("N").Substring(0, 8); + string overlappingHubName = hubName + "-dtfx-backup"; + var hubClient = new BlobStorageClient(hubName, storageConnectionString); + var overlappingHubClient = new BlobStorageClient(overlappingHubName, storageConnectionString); + + string hubPrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName); + string overlappingPrefix = BlobStorageClientHelper.BuildContainerNamePrefix(overlappingHubName); + string hubExpired = BlobStorageClientHelper.BuildContainerName(hubPrefix, "message-20150516"); + string hubCurrent = BlobStorageClientHelper.BuildContainerName(hubPrefix, "message-20150518"); + string overlappingExpiredMessage = BlobStorageClientHelper.BuildContainerName(overlappingPrefix, "message-20150516"); + string overlappingExpiredSession = BlobStorageClientHelper.BuildContainerName(overlappingPrefix, "session-20150516"); + + try + { + await UploadAsync(hubClient, "message-20150516|a"); + await UploadAsync(hubClient, "message-20150518|b"); + await UploadAsync(overlappingHubClient, "message-20150516|c"); + await UploadAsync(overlappingHubClient, "session-20150516|d"); + + await hubClient.DeleteExpiredContainersAsync(new DateTime(2015, 05, 17)); + + Assert.IsFalse(await serviceClient.GetBlobContainerClient(hubExpired).ExistsAsync(), "The hub's expired container must be deleted."); + Assert.IsTrue(await serviceClient.GetBlobContainerClient(hubCurrent).ExistsAsync(), "The hub's current container must be kept."); + Assert.IsTrue(await serviceClient.GetBlobContainerClient(overlappingExpiredMessage).ExistsAsync(), "The other hub's message container must be kept."); + Assert.IsTrue(await serviceClient.GetBlobContainerClient(overlappingExpiredSession).ExistsAsync(), "The other hub's session container must be kept."); + + Stream session = await overlappingHubClient.DownloadStreamAsync("session-20150516|d"); + Assert.AreEqual("test stream content", new StreamReader(session).ReadToEnd()); + } + finally + { + foreach (string name in new[] { hubExpired, hubCurrent, overlappingExpiredMessage, overlappingExpiredSession }) + { + await serviceClient.GetBlobContainerClient(name).DeleteIfExistsAsync(); + } + } + } + + [TestMethod] + public async Task TestDeleteBlobStoreContainersPreservesOverlappingHub() + { + string storageConnectionString = TestHelpers.GetTestSetting("StorageConnectionString"); + var serviceClient = new BlobServiceClient(storageConnectionString); + string hubName = "alpha" + Guid.NewGuid().ToString("N").Substring(0, 8); + string overlappingHubName = hubName + "-dtfx-backup"; + var hubClient = new BlobStorageClient(hubName, storageConnectionString); + var overlappingHubClient = new BlobStorageClient(overlappingHubName, storageConnectionString); + + string hubPrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName); + string overlappingPrefix = BlobStorageClientHelper.BuildContainerNamePrefix(overlappingHubName); + string hubMessage = BlobStorageClientHelper.BuildContainerName(hubPrefix, "message-20150516"); + string hubSession = BlobStorageClientHelper.BuildContainerName(hubPrefix, "session-20150516"); + string overlappingMessage = BlobStorageClientHelper.BuildContainerName(overlappingPrefix, "message-20150516"); + string overlappingSession = BlobStorageClientHelper.BuildContainerName(overlappingPrefix, "session-20150516"); + + try + { + await UploadAsync(hubClient, "message-20150516|a"); + await UploadAsync(hubClient, "session-20150516|b"); + await UploadAsync(overlappingHubClient, "message-20150516|c"); + await UploadAsync(overlappingHubClient, "session-20150516|d"); + + List listed = (await hubClient.ListContainersAsync()).Select(c => c.Name).OrderBy(n => n).ToList(); + CollectionAssert.AreEqual(new[] { hubMessage, hubSession }, listed, "Only the hub's own containers must be listed."); + + await hubClient.DeleteBlobStoreContainersAsync(); + + Assert.IsFalse(await serviceClient.GetBlobContainerClient(hubMessage).ExistsAsync(), "The hub's message container must be deleted."); + Assert.IsFalse(await serviceClient.GetBlobContainerClient(hubSession).ExistsAsync(), "The hub's session container must be deleted."); + Assert.IsTrue(await serviceClient.GetBlobContainerClient(overlappingMessage).ExistsAsync(), "The other hub's message container must be kept."); + Assert.IsTrue(await serviceClient.GetBlobContainerClient(overlappingSession).ExistsAsync(), "The other hub's session container must be kept."); + } + finally + { + foreach (string name in new[] { hubMessage, hubSession, overlappingMessage, overlappingSession }) + { + await serviceClient.GetBlobContainerClient(name).DeleteIfExistsAsync(); + } + } + } + + [TestMethod] + public async Task TestDeleteBlobStoreContainersIgnoresMalformedContainers() + { + string storageConnectionString = TestHelpers.GetTestSetting("StorageConnectionString"); + var serviceClient = new BlobServiceClient(storageConnectionString); + string hubName = "alpha" + Guid.NewGuid().ToString("N").Substring(0, 8); + var hubClient = new BlobStorageClient(hubName, storageConnectionString); + + string hubPrefix = BlobStorageClientHelper.BuildContainerNamePrefix(hubName); + string hubContainer = BlobStorageClientHelper.BuildContainerName(hubPrefix, "session-20150516"); + string[] malformedContainers = + { + BlobStorageClientHelper.BuildContainerName(hubPrefix, "session-backup"), + BlobStorageClientHelper.BuildContainerName(hubPrefix, "session-20150230"), + }; + + try + { + await UploadAsync(hubClient, "session-20150516|a"); + foreach (string name in malformedContainers) + { + await serviceClient.GetBlobContainerClient(name).CreateIfNotExistsAsync(); + } + + List listed = (await hubClient.ListContainersAsync()).Select(c => c.Name).ToList(); + CollectionAssert.AreEqual(new[] { hubContainer }, listed, "Containers outside the {type}-{yyyyMMdd} format must not be listed."); + + await hubClient.DeleteBlobStoreContainersAsync(); + + Assert.IsFalse(await serviceClient.GetBlobContainerClient(hubContainer).ExistsAsync(), "The hub's container must be deleted."); + foreach (string name in malformedContainers) + { + Assert.IsTrue(await serviceClient.GetBlobContainerClient(name).ExistsAsync(), $"Malformed container {name} must be kept."); + } + } + finally + { + foreach (string name in malformedContainers.Append(hubContainer)) + { + await serviceClient.GetBlobContainerClient(name).DeleteIfExistsAsync(); + } + } + } + + static async Task UploadAsync(BlobStorageClient client, string key) + { + using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes("test stream content"))) + { + await client.UploadStreamBlobAsync(key, stream); + } + } + [TestMethod] public async Task TestListContainersOnlyWithPrefix() { diff --git a/test/DurableTask.ServiceBus.Tests/NoOpTokenCredential.cs b/test/DurableTask.ServiceBus.Tests/NoOpTokenCredential.cs new file mode 100644 index 00000000..3ef5dbdf --- /dev/null +++ b/test/DurableTask.ServiceBus.Tests/NoOpTokenCredential.cs @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace DurableTask.ServiceBus.Tests +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Azure.Core; + + /// + /// Credential stub for offline tests that construct a + /// but never contact Service Bus, so no token is ever requested. + /// + sealed class NoOpTokenCredential : TokenCredential + { + public const string NamespaceHostName = "test.servicebus.windows.net"; + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + => throw new NotSupportedException(); + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + => throw new NotSupportedException(); + } +} diff --git a/test/DurableTask.ServiceBus.Tests/WaitForOrchestrationTests.cs b/test/DurableTask.ServiceBus.Tests/WaitForOrchestrationTests.cs index faee4592..ec85ad39 100644 --- a/test/DurableTask.ServiceBus.Tests/WaitForOrchestrationTests.cs +++ b/test/DurableTask.ServiceBus.Tests/WaitForOrchestrationTests.cs @@ -31,15 +31,14 @@ namespace DurableTask.ServiceBus.Tests public class WaitForOrchestrationTests { const string InstanceId = "instance-1"; - const string FakeConnectionString = - "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=key;SharedAccessKey=dGVzdGtleQ=="; static readonly DateTime BaseTime = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); static ServiceBusOrchestrationService CreateService(FakeInstanceStore instanceStore) { return new ServiceBusOrchestrationService( - FakeConnectionString, + NoOpTokenCredential.NamespaceHostName, + new NoOpTokenCredential(), "testhub", instanceStore, null,