diff --git a/Directory.Packages.props b/Directory.Packages.props index f14dea27..067dd6fe 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,7 +14,7 @@ - + @@ -40,9 +40,7 @@ - - - + diff --git a/test/LondonTravel.Skill.EndToEndTests/CloudWatchLogsFixture.cs b/test/LondonTravel.Skill.EndToEndTests/CloudWatchLogsFixture.cs index 3ad2a5b3..7d5d70d5 100644 --- a/test/LondonTravel.Skill.EndToEndTests/CloudWatchLogsFixture.cs +++ b/test/LondonTravel.Skill.EndToEndTests/CloudWatchLogsFixture.cs @@ -7,6 +7,7 @@ using Amazon.CloudWatchLogs; using Amazon.CloudWatchLogs.Model; using Xunit.Sdk; +using Xunit.v3; namespace MartinCostello.LondonTravel.Skill.EndToEndTests; @@ -16,7 +17,7 @@ public class CloudWatchLogsFixture(IMessageSink diagnosticMessageSink) : IAsyncL internal Dictionary Requests { get; } = []; - public async Task DisposeAsync() + public async ValueTask DisposeAsync() { if (Requests.Count < 1) { @@ -96,18 +97,20 @@ regionName is not null && .OrderBy((p) => p.Event.Timestamp) .ToList(); - diagnosticMessageSink.OnMessage(new DiagnosticMessage($"Found {events.Count} CloudWatch log events.")); + diagnosticMessageSink.OnMessage(new DiagnosticMessage() { Message = $"Found {events.Count} CloudWatch log events." }); foreach ((_, string message) in events) { - diagnosticMessageSink.OnMessage(new DiagnosticMessage(message)); + diagnosticMessageSink.OnMessage(new DiagnosticMessage() { Message = message }); } await TryPostLogsToPullRequestAsync(events.Select((p) => p.Event)); } + + GC.SuppressFinalize(this); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; private static async Task TryPostLogsToPullRequestAsync(IEnumerable events) { diff --git a/test/LondonTravel.Skill.EndToEndTests/LambdaTests.cs b/test/LondonTravel.Skill.EndToEndTests/LambdaTests.cs index fecedc85..306114f7 100644 --- a/test/LondonTravel.Skill.EndToEndTests/LambdaTests.cs +++ b/test/LondonTravel.Skill.EndToEndTests/LambdaTests.cs @@ -9,7 +9,7 @@ namespace MartinCostello.LondonTravel.Skill.EndToEndTests; -[Collection(CloudWatchLogsFixtureCollection.Name)] +[Collection] public class LambdaTests(CloudWatchLogsFixture fixture, ITestOutputHelper outputHelper) { public static TheoryData Payloads() @@ -24,22 +24,24 @@ public static TheoryData Payloads() return payloads; } - [SkippableTheory] + [Theory] [MemberData(nameof(Payloads))] public async Task Can_Invoke_Intent_Can_Get_Json_Response(string payloadName) { var credentials = TestConfiguration.GetCredentials(); - Skip.If(credentials is null, "No AWS credentials are configured."); + Assert.SkipWhen(credentials is null, "No AWS credentials are configured."); string? functionName = TestConfiguration.FunctionName; string? regionName = TestConfiguration.RegionName; - Skip.If(string.IsNullOrEmpty(functionName), "No Lambda function name is configured."); - Skip.If(string.IsNullOrEmpty(regionName), "No AWS region name is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(functionName), "No Lambda function name is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(regionName), "No AWS region name is configured."); // Arrange - string payload = await File.ReadAllTextAsync(Path.Combine("Payloads", $"{payloadName}.json")); + var cancellationToken = TestContext.Current.CancellationToken; + + string payload = await File.ReadAllTextAsync(Path.Combine("Payloads", $"{payloadName}.json"), cancellationToken); var region = RegionEndpoint.GetBySystemName(regionName); @@ -57,7 +59,7 @@ public async Task Can_Invoke_Intent_Can_Get_Json_Response(string payloadName) outputHelper.WriteLine($"Payload: {request.Payload}"); // Act - var invocation = await client.InvokeAsync(request); + var invocation = await client.InvokeAsync(request, cancellationToken); // Assert invocation.ShouldNotBeNull(); @@ -66,7 +68,7 @@ public async Task Can_Invoke_Intent_Can_Get_Json_Response(string payloadName) fixture.Requests[invocation.ResponseMetadata.RequestId] = payloadName; using var reader = new StreamReader(invocation.Payload); - string responsePayload = await reader.ReadToEndAsync(); + string responsePayload = await reader.ReadToEndAsync(cancellationToken); outputHelper.WriteLine($"ExecutedVersion: {invocation.ExecutedVersion}"); outputHelper.WriteLine($"FunctionError: {invocation.FunctionError}"); diff --git a/test/LondonTravel.Skill.EndToEndTests/LondonTravel.Skill.EndToEndTests.csproj b/test/LondonTravel.Skill.EndToEndTests/LondonTravel.Skill.EndToEndTests.csproj index 2d6d3721..2a8aaa79 100644 --- a/test/LondonTravel.Skill.EndToEndTests/LondonTravel.Skill.EndToEndTests.csproj +++ b/test/LondonTravel.Skill.EndToEndTests/LondonTravel.Skill.EndToEndTests.csproj @@ -11,9 +11,8 @@ - - + @@ -21,6 +20,5 @@ - diff --git a/test/LondonTravel.Skill.EndToEndTests/SkillTests.cs b/test/LondonTravel.Skill.EndToEndTests/SkillTests.cs index 24857d3d..e0f2eddc 100644 --- a/test/LondonTravel.Skill.EndToEndTests/SkillTests.cs +++ b/test/LondonTravel.Skill.EndToEndTests/SkillTests.cs @@ -10,7 +10,7 @@ namespace MartinCostello.LondonTravel.Skill.EndToEndTests; public class SkillTests(ITestOutputHelper outputHelper) { - [SkippableTheory] + [Theory] [InlineData("Alexa, ask London Travel if there is any disruption today.")] [InlineData("Alexa, ask London Travel about the DLR.")] [InlineData("Alexa, ask London Travel about the Elizabeth line.")] @@ -24,10 +24,10 @@ public async Task Can_Invoke_Skill_And_Get_Valid_Response(string content) string? skillId = TestConfiguration.SkillId; string? stage = TestConfiguration.SkillStage; - Skip.If(string.IsNullOrEmpty(functionName), "No Lambda function name is configured."); - Skip.If(string.IsNullOrEmpty(regionName), "No AWS region name is configured."); - Skip.If(string.IsNullOrEmpty(skillId), "No skill ID is configured."); - Skip.If(string.IsNullOrEmpty(stage), "No skill stage is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(functionName), "No Lambda function name is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(regionName), "No AWS region name is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(skillId), "No skill ID is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(stage), "No skill stage is configured."); using var client = await CreateHttpClientAsync(); @@ -83,9 +83,9 @@ private static async Task GenerateAccessTokenAsync() string? clientSecret = TestConfiguration.AlexaClientSecret; string? refreshToken = TestConfiguration.AlexaRefreshToken; - Skip.If(string.IsNullOrEmpty(clientId), "No client ID is configured."); - Skip.If(string.IsNullOrEmpty(clientSecret), "No client secret is configured."); - Skip.If(string.IsNullOrEmpty(refreshToken), "No refresh token is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(clientId), "No client ID is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(clientSecret), "No client secret is configured."); + Assert.SkipWhen(string.IsNullOrEmpty(refreshToken), "No refresh token is configured."); // See https://developer.amazon.com/docs/login-with-amazon/authorization-code-grant.html#using-refresh-tokens var parameters = new Dictionary() diff --git a/test/LondonTravel.Skill.Tests/CommuteTests.cs b/test/LondonTravel.Skill.Tests/CommuteTests.cs index f68c5b92..87cc7d0e 100644 --- a/test/LondonTravel.Skill.Tests/CommuteTests.cs +++ b/test/LondonTravel.Skill.Tests/CommuteTests.cs @@ -36,7 +36,9 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Not_Linked() public async Task Can_Invoke_Function_When_The_Skill_Token_Is_Invalid() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-invalid-token.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "skill-api-invalid-token.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequestWithToken(accessToken: "invalid-access-token"); @@ -84,8 +86,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Api_Fails() public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_No_Favorite_Lines() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-no-favorites.json"); - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json"); + var cancellationToken = TestContext.Current.CancellationToken; + + await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-no-favorites.json", cancellationToken: cancellationToken); + await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json", cancellationToken: cancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequestWithToken(accessToken: "token-for-no-favorites"); @@ -105,8 +109,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_No_Favori public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_One_Favorite_Line() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-one-favorite.json"); - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json"); + var cancellationToken = TestContext.Current.CancellationToken; + + await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-one-favorite.json", cancellationToken: cancellationToken); + await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json", cancellationToken: cancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequestWithToken(accessToken: "token-for-one-favorite"); @@ -126,8 +132,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_One_Favor public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_Two_Favorite_Lines() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-two-favorites.json"); - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json"); + var cancellationToken = TestContext.Current.CancellationToken; + + await Interceptor.RegisterBundleFromResourceStreamAsync("skill-api-two-favorites.json", cancellationToken: cancellationToken); + await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json", cancellationToken: cancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequestWithToken(accessToken: "token-for-two-favorites"); diff --git a/test/LondonTravel.Skill.Tests/DisruptionTests.cs b/test/LondonTravel.Skill.Tests/DisruptionTests.cs index 1fe8e4fd..3797163a 100644 --- a/test/LondonTravel.Skill.Tests/DisruptionTests.cs +++ b/test/LondonTravel.Skill.Tests/DisruptionTests.cs @@ -12,7 +12,9 @@ public class DisruptionTests(ITestOutputHelper outputHelper) : FunctionTests(out public async Task Can_Invoke_Function_When_There_Are_No_Disruptions() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-no-disruptions.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "tfl-no-disruptions.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequest(); @@ -32,7 +34,9 @@ public async Task Can_Invoke_Function_When_There_Are_No_Disruptions() public async Task Can_Invoke_Function_When_There_Is_One_Disruption() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-one-disruption.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "tfl-one-disruption.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequest(); @@ -52,7 +56,9 @@ public async Task Can_Invoke_Function_When_There_Is_One_Disruption() public async Task Can_Invoke_Function_When_There_Are_Multiple_Disruptions() { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-multiple-disruptions.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "tfl-multiple-disruptions.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentRequest(); diff --git a/test/LondonTravel.Skill.Tests/EndToEndTests.cs b/test/LondonTravel.Skill.Tests/EndToEndTests.cs index 0b6f77aa..6c61df85 100644 --- a/test/LondonTravel.Skill.Tests/EndToEndTests.cs +++ b/test/LondonTravel.Skill.Tests/EndToEndTests.cs @@ -15,7 +15,7 @@ public class EndToEndTests(ITestOutputHelper outputHelper) : FunctionTests(outpu { private const int TimeoutMilliseconds = 15_000; - [xRetry.RetryTheory(Timeout = TimeoutMilliseconds)] + [Theory(Timeout = TimeoutMilliseconds)] [InlineData("AMAZON.CancelIntent")] [InlineData("AMAZON.StopIntent")] public async Task Alexa_Function_Can_Process_Intent_Request(string name) @@ -34,7 +34,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request(string name) response.Reprompt.ShouldBeNull(); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_Intent_Request_For_Help() { // Arrange @@ -54,7 +54,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_For_Help() response.OutputSpeech.Ssml.ShouldBe("

This skill allows you to check for the status of a specific line, or for disruption in general. You can ask about any London Underground line, London Overground, the Docklands Light Railway or the Elizabeth line.

Asking about disruption in general provides information about any lines that are currently experiencing issues, such as any delays or planned closures.

Asking for the status for a specific line provides a summary of the current service, such as whether there is a good service or if there are any delays.

If you link your account and setup your preferences in the London Travel website, you can ask about your commute to quickly find out the status of the lines you frequently use.

What would you like to do?

"); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_Intent_Request_With_Unknown_Intent() { // Arrange @@ -74,7 +74,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_With_Unknown_Intent( response.OutputSpeech.Ssml.ShouldBe("Sorry, I don't understand how to do that."); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_Intent_Request_For_Disruption() { // Arrange @@ -95,7 +95,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_For_Disruption() response.OutputSpeech.Ssml.ShouldBe("There is currently no disruption on the tube, London Overground, the D.L.R. or the Elizabeth line."); } - [xRetry.RetryTheory(Timeout = TimeoutMilliseconds)] + [Theory(Timeout = TimeoutMilliseconds)] [InlineData("Northern", "There is a good service on the Northern line.")] [InlineData("Windrush", "There is a good service on the Windrush line.")] public async Task Alexa_Function_Can_Process_Intent_Request_For_Line_Status( @@ -122,7 +122,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_For_Line_Status( response.OutputSpeech.Ssml.ShouldBe($"{expected}"); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_Launch_Request() { // Arrange @@ -142,7 +142,7 @@ public async Task Alexa_Function_Can_Process_Launch_Request() response.OutputSpeech.Ssml.ShouldBe("Welcome to London Travel. You can ask me about disruption or for the status of any tube line, London Overground, the D.L.R. or the Elizabeth line."); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_Session_Ended_Request() { // Arrange @@ -172,7 +172,7 @@ public async Task Alexa_Function_Can_Process_Session_Ended_Request() response.OutputSpeech.Ssml.ShouldBe("Goodbye."); } - [xRetry.RetryFact(Timeout = TimeoutMilliseconds)] + [Fact(Timeout = TimeoutMilliseconds)] public async Task Alexa_Function_Can_Process_System_Exception_Request() { // Arrange diff --git a/test/LondonTravel.Skill.Tests/InteractionModelTests.cs b/test/LondonTravel.Skill.Tests/InteractionModelTests.cs index 01d53b9e..f84a32fe 100644 --- a/test/LondonTravel.Skill.Tests/InteractionModelTests.cs +++ b/test/LondonTravel.Skill.Tests/InteractionModelTests.cs @@ -17,7 +17,7 @@ public static async Task Interaction_Model_Is_Valid_Json() using var model = assembly.GetManifestResourceStream(type.Namespace + ".interaction-model.json")!; using var stream = new MemoryStream(); - await model.CopyToAsync(stream); + await model.CopyToAsync(stream, TestContext.Current.CancellationToken); model.Seek(0, SeekOrigin.Begin); var reader = new Utf8JsonReader(stream.ToArray()); diff --git a/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj b/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj index f7a5068e..3e7454d0 100644 --- a/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj +++ b/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj @@ -3,6 +3,7 @@ true false $(NoWarn);CA1062;CA1707;CA2007;CA2234;SA1600 + Exe MartinCostello.LondonTravel.Skill net9.0 @@ -15,14 +16,13 @@ - + - - +
@@ -31,7 +31,6 @@ - true diff --git a/test/LondonTravel.Skill.Tests/StatusTests.cs b/test/LondonTravel.Skill.Tests/StatusTests.cs index d9aa4311..adf42377 100644 --- a/test/LondonTravel.Skill.Tests/StatusTests.cs +++ b/test/LondonTravel.Skill.Tests/StatusTests.cs @@ -47,7 +47,9 @@ public class StatusTests(ITestOutputHelper outputHelper) : FunctionTests(outputH public async Task Can_Invoke_Function_For_Valid_Lines(string id) { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-statuses.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "tfl-line-statuses.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentForLine(id); @@ -144,7 +146,9 @@ public async Task Can_Invoke_Function_For_Different_Severities( string expected) { // Arrange - await Interceptor.RegisterBundleFromResourceStreamAsync("tfl-line-severities.json"); + await Interceptor.RegisterBundleFromResourceStreamAsync( + "tfl-line-severities.json", + cancellationToken: TestContext.Current.CancellationToken); var function = await CreateFunctionAsync(); var request = CreateIntentForLine(id);