diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fe28ac82..ac77ee62 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -77,7 +77,7 @@ jobs:
- name: Create Lambda ZIP file
if: runner.os == 'Linux'
run: |
- cd "./artifacts/publish/LondonTravel.Skill/release_linux-arm64" || exit
+ cd "./artifacts/publish/LondonTravel.Skill/release_linux-x64" || exit
if [ -f "./bootstrap" ]
then
chmod +x ./bootstrap
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 867074bb..bd900015 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -30,4 +30,16 @@
+
+
+ $(NoWarn);IL2026
+ false
+
+
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3c38dcbd..46156436 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,7 +6,7 @@
-
+
@@ -14,7 +14,6 @@
-
@@ -23,7 +22,6 @@
-
diff --git a/build.ps1 b/build.ps1
index bbb58d80..98b42978 100755
--- a/build.ps1
+++ b/build.ps1
@@ -91,10 +91,11 @@ function DotNetPublish {
if ($IsLinux -And (-Not $UseManagedRuntime)) {
$additionalArgs += "--runtime"
- $additionalArgs += "linux-arm64"
+ $additionalArgs += "linux-x64"
$additionalArgs += "--self-contained"
$additionalArgs += "true"
$additionalArgs += "/p:AssemblyName=bootstrap"
+ $additionalArgs += "/p:PublishAot=true"
$additionalArgs += "/p:PublishReadyToRun=true"
}
diff --git a/src/LondonTravel.Skill/AlexaFunction.cs b/src/LondonTravel.Skill/AlexaFunction.cs
index 153edfa6..6d0dd412 100644
--- a/src/LondonTravel.Skill/AlexaFunction.cs
+++ b/src/LondonTravel.Skill/AlexaFunction.cs
@@ -1,12 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
using MartinCostello.LondonTravel.Skill.Extensions;
using MartinCostello.LondonTravel.Skill.Intents;
-using Microsoft.ApplicationInsights;
-using Microsoft.ApplicationInsights.Extensibility;
+using MartinCostello.LondonTravel.Skill.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -81,7 +78,11 @@ public async Task HandlerAsync(SkillRequest request)
Log.InvokingSkillRequest(logger, request.Request.Type);
- return await handler.HandleAsync(request);
+ var converted = request.FromModel();
+
+ var response = await handler.HandleAsync(converted);
+
+ return response.ToModel();
}
///
@@ -133,8 +134,6 @@ protected virtual void ConfigureServices(IServiceCollection services)
services.AddSingleton();
services.AddSingleton();
services.AddSingleton, ValidateSkillConfiguration>();
- services.AddSingleton((_) => TelemetryConfiguration.CreateDefault());
- services.AddSingleton(CreateTelemetryClient);
services.AddSingleton((p) => p.GetRequiredService>().Value);
services.AddSingleton(configuration);
@@ -160,27 +159,6 @@ protected virtual void Dispose(bool disposing)
}
}
- ///
- /// Creates an .
- ///
- /// The to use.
- ///
- /// The created instance of .
- ///
- private static TelemetryClient CreateTelemetryClient(IServiceProvider serviceProvider)
- {
- var config = serviceProvider.GetRequiredService();
-
- var configuration = serviceProvider.GetRequiredService();
-
- if (!string.IsNullOrEmpty(config.ApplicationInsightsConnectionString))
- {
- configuration.ConnectionString = config.ApplicationInsightsConnectionString;
- }
-
- return new TelemetryClient(configuration);
- }
-
///
/// Creates the to use.
///
diff --git a/src/LondonTravel.Skill/AlexaSkill.cs b/src/LondonTravel.Skill/AlexaSkill.cs
index eb18de91..aa9f48ba 100644
--- a/src/LondonTravel.Skill/AlexaSkill.cs
+++ b/src/LondonTravel.Skill/AlexaSkill.cs
@@ -4,7 +4,6 @@
using Alexa.NET.Request;
using Alexa.NET.Request.Type;
using Alexa.NET.Response;
-using Microsoft.ApplicationInsights;
using Microsoft.Extensions.Logging;
namespace MartinCostello.LondonTravel.Skill;
@@ -16,11 +15,9 @@ namespace MartinCostello.LondonTravel.Skill;
/// Initializes a new instance of the class.
///
/// The factory to use for the skill intents.
-/// The telemetry client to use.
/// The logger to use.
internal sealed class AlexaSkill(
IntentFactory intentFactory,
- TelemetryClient telemetry,
ILogger logger)
{
///
@@ -57,8 +54,6 @@ public SkillResponse OnError(Exception exception, Session session)
{
Log.HandlerException(logger, exception, session.SessionId);
- TrackException(exception, session);
-
return SkillResponseBuilder
.Tell(Strings.InternalError)
.Build();
@@ -75,8 +70,6 @@ public SkillResponse OnError(Exception exception, Session session)
///
public async Task OnIntentAsync(Intent intent, Session session)
{
- TrackEvent(intent.Name, session, intent);
-
IIntent userIntent = intentFactory.Create(intent);
return await userIntent.RespondAsync(intent, session);
@@ -91,7 +84,7 @@ public async Task OnIntentAsync(Intent intent, Session session)
///
public SkillResponse OnLaunch(Session session)
{
- TrackEvent("LaunchRequest", session);
+ Log.SessionLaunched(logger, session.User.UserId, session.SessionId);
return SkillResponseBuilder
.Tell(Strings.LaunchResponse)
@@ -108,45 +101,10 @@ public SkillResponse OnLaunch(Session session)
///
public SkillResponse OnSessionEnded(Session session)
{
- TrackEvent("SessionEndedRequest", session);
+ Log.SessionEnded(logger, session.User.UserId, session.SessionId);
return SkillResponseBuilder
.Tell(Strings.SessionEndResponse)
.Build();
}
-
- private Dictionary ToTelemetryProperties(Session session)
- {
- bool hasAccessToken = !string.IsNullOrEmpty(session.User?.AccessToken);
-
-#pragma warning disable CA1308
- return new Dictionary()
- {
- ["hasAccessToken"] = hasAccessToken.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
- ["sessionId"] = session.SessionId,
- ["userId"] = session.User?.UserId,
- };
-#pragma warning restore CA1308
- }
-
- private void TrackEvent(string eventName, Session session, Intent intent = null)
- {
- Dictionary properties = ToTelemetryProperties(session);
-
- if (intent?.Slots?.Count > 0)
- {
- foreach (var slot in intent.Slots.Values)
- {
- properties[$"slot:{slot.Name}"] = slot.Value;
- }
- }
-
- telemetry.TrackEvent(eventName, properties);
- }
-
- private void TrackException(Exception exception, Session session)
- {
- Dictionary properties = ToTelemetryProperties(session);
- telemetry.TrackException(exception, properties);
- }
}
diff --git a/src/LondonTravel.Skill/AppJsonSerializerContext.cs b/src/LondonTravel.Skill/AppJsonSerializerContext.cs
index a752feaa..f871c202 100644
--- a/src/LondonTravel.Skill/AppJsonSerializerContext.cs
+++ b/src/LondonTravel.Skill/AppJsonSerializerContext.cs
@@ -1,18 +1,25 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using MartinCostello.LondonTravel.Skill.Clients;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
///
/// A class representing the to use for the application.
///
+[ExcludeFromCodeCoverage]
[JsonSerializable(typeof(IList))]
[JsonSerializable(typeof(IList))]
+[JsonSerializable(typeof(LinkAccountCard))]
+[JsonSerializable(typeof(SkillRequest))]
+[JsonSerializable(typeof(SkillResponse))]
[JsonSerializable(typeof(SkillUserPreferences))]
+[JsonSerializable(typeof(StandardCard))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
-internal sealed partial class AppJsonSerializerContext : JsonSerializerContext
+public sealed partial class AppJsonSerializerContext : JsonSerializerContext
{
}
diff --git a/src/LondonTravel.Skill/Clients/Line.cs b/src/LondonTravel.Skill/Clients/Line.cs
index c9e230fc..f8ce3f85 100644
--- a/src/LondonTravel.Skill/Clients/Line.cs
+++ b/src/LondonTravel.Skill/Clients/Line.cs
@@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
///
/// A class representing a line. This class cannot be inherited.
///
-internal sealed class Line
+public sealed class Line
{
///
/// Gets or sets the line's name.
diff --git a/src/LondonTravel.Skill/Clients/LineStatus.cs b/src/LondonTravel.Skill/Clients/LineStatus.cs
index abf4b3ce..38336b7a 100644
--- a/src/LondonTravel.Skill/Clients/LineStatus.cs
+++ b/src/LondonTravel.Skill/Clients/LineStatus.cs
@@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
///
/// A class representing a line's status. This class cannot be inherited.
///
-internal sealed class LineStatus
+public sealed class LineStatus
{
///
/// Gets or sets the status severity.
diff --git a/src/LondonTravel.Skill/Clients/LineStatusSeverity.cs b/src/LondonTravel.Skill/Clients/LineStatusSeverity.cs
index 148b4089..4db5ced5 100644
--- a/src/LondonTravel.Skill/Clients/LineStatusSeverity.cs
+++ b/src/LondonTravel.Skill/Clients/LineStatusSeverity.cs
@@ -6,7 +6,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
///
/// An enumeration of line status severities.
///
-internal enum LineStatusSeverity
+public enum LineStatusSeverity
{
SpecialService = 0,
diff --git a/src/LondonTravel.Skill/Clients/ServiceDisruption.cs b/src/LondonTravel.Skill/Clients/ServiceDisruption.cs
index 0c4470a6..f437b3ae 100644
--- a/src/LondonTravel.Skill/Clients/ServiceDisruption.cs
+++ b/src/LondonTravel.Skill/Clients/ServiceDisruption.cs
@@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
///
/// A class representing a service disruption. This class cannot be inherited.
///
-internal sealed class ServiceDisruption
+public sealed class ServiceDisruption
{
///
/// Gets or sets the description of the disruption.
diff --git a/src/LondonTravel.Skill/Clients/SkillUserPreferences.cs b/src/LondonTravel.Skill/Clients/SkillUserPreferences.cs
index 2a2b38a8..ef127d8b 100644
--- a/src/LondonTravel.Skill/Clients/SkillUserPreferences.cs
+++ b/src/LondonTravel.Skill/Clients/SkillUserPreferences.cs
@@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
///
/// A class representing a user's preferences for the skill. This class cannot be inherited.
///
-internal sealed class SkillUserPreferences
+public sealed class SkillUserPreferences
{
///
/// Gets or sets the user's favorite lines.
diff --git a/src/LondonTravel.Skill/Extensions/ApplicationJsonSerializerContext.cs b/src/LondonTravel.Skill/Extensions/ApplicationJsonSerializerContext.cs
deleted file mode 100644
index 330ee5ec..00000000
--- a/src/LondonTravel.Skill/Extensions/ApplicationJsonSerializerContext.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Martin Costello, 2017. All rights reserved.
-// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Serialization;
-using MartinCostello.LondonTravel.Skill.Clients;
-
-namespace MartinCostello.LondonTravel.Skill.Extensions;
-
-#pragma warning disable SA1601
-
-[ExcludeFromCodeCoverage]
-[JsonSerializable(typeof(IList))]
-[JsonSerializable(typeof(IList))]
-[JsonSerializable(typeof(SkillUserPreferences))]
-internal sealed partial class ApplicationJsonSerializerContext : JsonSerializerContext
-{
-}
diff --git a/src/LondonTravel.Skill/Extensions/IHttpClientBuilderExtensions.cs b/src/LondonTravel.Skill/Extensions/IHttpClientBuilderExtensions.cs
index 7f81feb7..bc965780 100644
--- a/src/LondonTravel.Skill/Extensions/IHttpClientBuilderExtensions.cs
+++ b/src/LondonTravel.Skill/Extensions/IHttpClientBuilderExtensions.cs
@@ -39,7 +39,7 @@ public static IHttpClientBuilder ApplyDefaultConfiguration(this IHttpClientBuild
private static void ApplyDefaultConfiguration(HttpClient client)
{
client.DefaultRequestHeaders.UserAgent.Add(_userAgent.Value);
- client.Timeout = TimeSpan.FromSeconds(5);
+ client.Timeout = TimeSpan.FromSeconds(7.5);
}
///
diff --git a/src/LondonTravel.Skill/FunctionEntrypoint.cs b/src/LondonTravel.Skill/FunctionEntrypoint.cs
index 8311194c..64c55b97 100644
--- a/src/LondonTravel.Skill/FunctionEntrypoint.cs
+++ b/src/LondonTravel.Skill/FunctionEntrypoint.cs
@@ -1,10 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
using Amazon.Lambda.RuntimeSupport;
-using Amazon.Lambda.Serialization.Json;
+using Amazon.Lambda.Serialization.SystemTextJson;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -30,7 +29,7 @@ public static async Task RunAsync(
CancellationToken cancellationToken = default)
where T : AlexaFunction, new()
{
- var serializer = new JsonSerializer();
+ var serializer = new SourceGeneratorLambdaJsonSerializer();
await using var function = new T();
using var bootstrap = LambdaBootstrapBuilder
diff --git a/src/LondonTravel.Skill/Log.cs b/src/LondonTravel.Skill/Log.cs
index ac879f2b..465c85fd 100644
--- a/src/LondonTravel.Skill/Log.cs
+++ b/src/LondonTravel.Skill/Log.cs
@@ -57,4 +57,16 @@ public static partial void SystemError(
Level = LogLevel.Information,
Message = "Invoking skill request of type {RequestType}.")]
public static partial void InvokingSkillRequest(ILogger logger, string requestType);
+
+ [LoggerMessage(
+ EventId = 8,
+ Level = LogLevel.Debug,
+ Message = "Launched session for user Id {UserId} and session Id {SessionId}.")]
+ public static partial void SessionLaunched(ILogger logger, string userId, string sessionId);
+
+ [LoggerMessage(
+ EventId = 9,
+ Level = LogLevel.Debug,
+ Message = "Ended session for user Id {UserId} and session Id {SessionId}.")]
+ public static partial void SessionEnded(ILogger logger, string userId, string sessionId);
}
diff --git a/src/LondonTravel.Skill/LondonTravel.Skill.csproj b/src/LondonTravel.Skill/LondonTravel.Skill.csproj
index 899c371a..419f7307 100644
--- a/src/LondonTravel.Skill/LondonTravel.Skill.csproj
+++ b/src/LondonTravel.Skill/LondonTravel.Skill.csproj
@@ -3,8 +3,10 @@
Lambda
true
true
+ true
$(NoWarn);CA1822
Exe
+ true
MartinCostello.LondonTravel.Skill
net8.0
false
@@ -13,8 +15,7 @@
-
-
+
@@ -23,7 +24,6 @@
-
@@ -35,12 +35,4 @@
-
-
-
-
-
-
-
-
diff --git a/src/LondonTravel.Skill/Models/AlexaError.cs b/src/LondonTravel.Skill/Models/AlexaError.cs
new file mode 100644
index 00000000..38c304d6
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/AlexaError.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class AlexaError
+{
+ [JsonConverter(typeof(CustomStringEnumConverter))]
+ [JsonPropertyName("type")]
+ public AlexaErrorType Type { get; set; }
+
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/AlexaErrorCause.cs b/src/LondonTravel.Skill/Models/AlexaErrorCause.cs
new file mode 100644
index 00000000..efaaecd4
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/AlexaErrorCause.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class AlexaErrorCause
+{
+ [JsonPropertyName("requestId")]
+ public string RequestId { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/AlexaErrorType.cs b/src/LondonTravel.Skill/Models/AlexaErrorType.cs
new file mode 100644
index 00000000..e7b6f253
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/AlexaErrorType.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public enum AlexaErrorType
+{
+ [EnumMember(Value = "INVALID_RESPONSE")]
+ InvalidResponse = 0,
+
+ [EnumMember(Value = "DEVICE_COMMUNICATION_ERROR")]
+ DeviceCommunicationError,
+
+ [EnumMember(Value = "INTERNAL_ERROR")]
+ InternalError,
+
+ [EnumMember(Value = "MEDIA_ERROR_UNKNOWN")]
+ MediaErrorUnknown,
+
+ [EnumMember(Value = "MEDIA_ERROR_INVALID_REQUEST")]
+ InvalidMediaRequest,
+
+ [EnumMember(Value = "MEDIA_ERROR_SERVICE_UNAVAILABLE")]
+ MediaServiceUnavailable,
+
+ [EnumMember(Value = "MEDIA_ERROR_INTERNAL_SERVER_ERROR")]
+ InternalServerError,
+
+ [EnumMember(Value = "MEDIA_ERROR_INTERNAL_DEVICE_ERROR")]
+ InternalDeviceError,
+
+ [EnumMember(Value = "ALREADY_IN_OPERATION")]
+ AlreadyInOperation,
+
+ [EnumMember(Value = "BRIDGE_UNREACHABLE")]
+ BridgeUnreachable,
+
+ [EnumMember(Value = "ENDPOINT_BUSY")]
+ EndpointBusy,
+
+ [EnumMember(Value = "ENDPOINT_LOW_POWER")]
+ EndpointLowPower,
+
+ [EnumMember(Value = "ENDPOINT_UNREACHABLE")]
+ EndpointUnreachable,
+
+ [EnumMember(Value = "ENDPOINT_CONTROL_UNAVAILABLE")]
+ EndpointControlUnavailable,
+
+ [EnumMember(Value = "EXPIRED_AUTHORIZATION_CREDENTIAL")]
+ ExpiredAuthorizationCredential,
+
+ [EnumMember(Value = "FIRMWARE_OUT_OF_DATE")]
+ FirmwareOutOfDate,
+
+ [EnumMember(Value = "HARDWARE_MALFUNCTION")]
+ HardwareMalfunction,
+
+ [EnumMember(Value = "INSUFFICIENT_PERMISSIONS")]
+ InsufficientPermissions,
+
+ [EnumMember(Value = "INTERNAL_SERVICE_ERROR")]
+ InternalServiceError,
+
+ [EnumMember(Value = "INVALID_AUTHORIZATION_CREDENTIAL")]
+ InvalidAuthorizationCredential,
+
+ [EnumMember(Value = "INVALID_DIRECTIVE")]
+ InvalidDirective,
+
+ [EnumMember(Value = "INVALID_VALUE")]
+ InvalidValue,
+
+ [EnumMember(Value = "NO_SUCH_ENDPOINT")]
+ NoSuchEndpoint,
+
+ [EnumMember(Value = "NOT_CALIBRATED")]
+ NotCalibrated,
+
+ [EnumMember(Value = "NOT_IN_OPERATION")]
+ NotInOperation,
+
+ [EnumMember(Value = "NOT_SUPPORTED_IN_CURRENT_MODE")]
+ NotSupportedInCurrentMode,
+
+ [EnumMember(Value = "NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE")]
+ NotSupportedWithCurrentBatteryChargeState,
+
+ [EnumMember(Value = "PARTNER_APPLICATION_REDIRECTION")]
+ PartnerApplicationRedirection,
+
+ [EnumMember(Value = "POWER_LEVEL_NOT_SUPPORTED")]
+ PowerLevelNotSupported,
+
+ [EnumMember(Value = "RATE_LIMIT_EXCEEDED")]
+ RateLimitExceeded,
+
+ [EnumMember(Value = "TEMPERATURE_VALUE_OUT_OF_RANGE")]
+ TemperatureValueOutOfRange,
+
+ [EnumMember(Value = "TOO_MANY_FAILED_ATTEMPTS")]
+ TooManyFailedAttempts,
+
+ [EnumMember(Value = "VALUE_OUT_OF_RANGE")]
+ ValueOutOfRange,
+}
diff --git a/src/LondonTravel.Skill/Models/AlexaSystem.cs b/src/LondonTravel.Skill/Models/AlexaSystem.cs
new file mode 100644
index 00000000..9acd801a
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/AlexaSystem.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class AlexaSystem
+{
+ [JsonPropertyName("apiAccessToken")]
+ public string ApiAccessToken { get; set; }
+
+ [JsonPropertyName("apiEndpoint")]
+ public string ApiEndpoint { get; set; }
+
+ [JsonPropertyName("application")]
+ public Application Application { get; set; }
+
+ [JsonPropertyName("user")]
+ public User User { get; set; }
+
+ [JsonPropertyName("device")]
+ public Device Device { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Application.cs b/src/LondonTravel.Skill/Models/Application.cs
new file mode 100644
index 00000000..0244c67b
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Application.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Application
+{
+ [JsonPropertyName("applicationId")]
+ public string ApplicationId { get; set; }
+}
diff --git a/src/LondonTravel.Skill/LambdaAttributes.cs b/src/LondonTravel.Skill/Models/Card.cs
similarity index 57%
rename from src/LondonTravel.Skill/LambdaAttributes.cs
rename to src/LondonTravel.Skill/Models/Card.cs
index cd6c5a58..9892c5ad 100644
--- a/src/LondonTravel.Skill/LambdaAttributes.cs
+++ b/src/LondonTravel.Skill/Models/Card.cs
@@ -1,7 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Amazon.Lambda.Core;
-using Amazon.Lambda.Serialization.Json;
+namespace MartinCostello.LondonTravel.Skill.Models;
-[assembly: LambdaSerializer(typeof(JsonSerializer))]
+public abstract class Card
+{
+ public abstract string Type { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Context.cs b/src/LondonTravel.Skill/Models/Context.cs
new file mode 100644
index 00000000..824d0f70
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Context.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Context
+{
+ [JsonPropertyName("System")]
+ public AlexaSystem System { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/CustomStringEnumConverter`1.cs b/src/LondonTravel.Skill/Models/CustomStringEnumConverter`1.cs
new file mode 100644
index 00000000..2b52b1e1
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/CustomStringEnumConverter`1.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+internal sealed class CustomStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>()
+ : JsonStringEnumConverter(namingPolicy: ResolveNamingPolicy())
+ where TEnum : struct, Enum
+{
+ private static EnumMemberNamingPolicy ResolveNamingPolicy()
+ {
+ var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
+ .Select((p) => (p.Name, AttributeName: p.GetCustomAttribute()?.Value))
+ .Where((p) => p.AttributeName is not null)
+ .ToDictionary();
+
+ return map.Count > 0 ? new EnumMemberNamingPolicy(map) : null;
+ }
+
+ private sealed class EnumMemberNamingPolicy(Dictionary map) : JsonNamingPolicy
+ {
+ public override string ConvertName(string name)
+ => map.TryGetValue(name, out string overriden) ? overriden : name;
+ }
+}
diff --git a/src/LondonTravel.Skill/Models/Device.cs b/src/LondonTravel.Skill/Models/Device.cs
new file mode 100644
index 00000000..3f1095fc
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Device.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Device
+{
+ [JsonPropertyName("deviceId")]
+ public string DeviceId { get; set; }
+
+ [JsonPropertyName("supportedInterfaces")]
+ public Dictionary SupportedInterfaces { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("persistentEndpointId")]
+ public string PersistentEndpointId { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Intent.cs b/src/LondonTravel.Skill/Models/Intent.cs
new file mode 100644
index 00000000..0525bbe7
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Intent.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Intent
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("slots")]
+ public Dictionary Slots { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/LinkAccountCard.cs b/src/LondonTravel.Skill/Models/LinkAccountCard.cs
new file mode 100644
index 00000000..9e59ca8e
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/LinkAccountCard.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class LinkAccountCard : Card
+{
+ [JsonPropertyName("type")]
+ [JsonRequired]
+ public override string Type { get; set; } = "LinkAccount";
+}
diff --git a/src/LondonTravel.Skill/Models/MixedDateTimeConverter.cs b/src/LondonTravel.Skill/Models/MixedDateTimeConverter.cs
new file mode 100644
index 00000000..53c109be
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/MixedDateTimeConverter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class MixedDateTimeConverter : JsonConverter
+{
+ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.TokenType switch
+ {
+ JsonTokenType.Number => DateTime.UnixEpoch.AddMilliseconds(reader.GetInt64()),
+ JsonTokenType.String => DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture),
+ _ => throw new JsonException($"Unable to deserialize token of type {reader.TokenType} to a {nameof(DateTime)} value."),
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.ToString("O", CultureInfo.InvariantCulture));
+}
diff --git a/src/LondonTravel.Skill/Models/Reason.cs b/src/LondonTravel.Skill/Models/Reason.cs
new file mode 100644
index 00000000..c4a74ada
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Reason.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public enum Reason
+{
+ [EnumMember(Value = "USER_INITIATED")]
+ UserInitiated = 0,
+
+ [EnumMember(Value = "ERROR")]
+ Error,
+
+ [EnumMember(Value = "EXCEEDED_MAX_REPROMPTS")]
+ ExceededMaxReprompts,
+}
diff --git a/src/LondonTravel.Skill/Models/Reprompt.cs b/src/LondonTravel.Skill/Models/Reprompt.cs
new file mode 100644
index 00000000..d13eb641
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Reprompt.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Reprompt
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("outputSpeech")]
+ public SsmlOutputSpeech OutputSpeech { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Request.cs b/src/LondonTravel.Skill/Models/Request.cs
new file mode 100644
index 00000000..0d7b05e9
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Request.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+#pragma warning disable CA1724
+
+public sealed class Request
+{
+ [JsonPropertyName("type")]
+ [JsonRequired]
+ public string Type { get; set; }
+
+ [JsonPropertyName("requestId")]
+ public string RequestId { get; set; }
+
+ [JsonPropertyName("locale")]
+ public string Locale { get; set; }
+
+ [JsonConverter(typeof(MixedDateTimeConverter))]
+ [JsonPropertyName("timestamp")]
+ public DateTime Timestamp { get; set; }
+
+ //// Properties for "IntentRequest"
+
+ [JsonPropertyName("dialogState")]
+ public string DialogState { get; set; }
+
+ [JsonPropertyName("intent")]
+ public Intent Intent { get; set; }
+
+ //// Properties for "SessionEndedRequest"
+
+ [JsonConverter(typeof(CustomStringEnumConverter))]
+ [JsonPropertyName("reason")]
+ public Reason Reason { get; set; }
+
+ //// Properties for "System.ExceptionEncountered"
+
+ [JsonPropertyName("cause")]
+ public AlexaErrorCause ErrorCause { get; set; }
+
+ //// Properties for "SessionEndedRequest" and "System.ExceptionEncountered"
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("error")]
+ public AlexaError Error { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/ResponseBody.cs b/src/LondonTravel.Skill/Models/ResponseBody.cs
new file mode 100644
index 00000000..12c16a4b
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/ResponseBody.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class ResponseBody
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("outputSpeech")]
+ public SsmlOutputSpeech OutputSpeech { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("card")]
+ public Card Card { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("reprompt")]
+ public Reprompt Reprompt { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("shouldEndSession")]
+ public bool? ShouldEndSession { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Session.cs b/src/LondonTravel.Skill/Models/Session.cs
new file mode 100644
index 00000000..4ba4a7b3
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Session.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Session
+{
+ [JsonPropertyName("new")]
+ public bool New { get; set; }
+
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; }
+
+ [JsonPropertyName("attributes")]
+ public Dictionary Attributes { get; set; }
+
+ [JsonPropertyName("application")]
+ public Application Application { get; set; }
+
+ [JsonPropertyName("user")]
+ public User User { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/SkillRequest.cs b/src/LondonTravel.Skill/Models/SkillRequest.cs
new file mode 100644
index 00000000..0e5e95ba
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/SkillRequest.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class SkillRequest
+{
+ [JsonPropertyName("version")]
+ public string Version { get; set; }
+
+ [JsonPropertyName("session")]
+ public Session Session { get; set; }
+
+ [JsonPropertyName("context")]
+ public Context Context { get; set; }
+
+ [JsonPropertyName("request")]
+ public Request Request { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/SkillResponse.cs b/src/LondonTravel.Skill/Models/SkillResponse.cs
new file mode 100644
index 00000000..0b8e0a54
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/SkillResponse.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class SkillResponse
+{
+ [JsonPropertyName("version")]
+ [JsonRequired]
+ public string Version { get; set; }
+
+ [JsonPropertyName("response")]
+ [JsonRequired]
+ public ResponseBody Response { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/Slot.cs b/src/LondonTravel.Skill/Models/Slot.cs
new file mode 100644
index 00000000..9bd24eea
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/Slot.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class Slot
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("value")]
+ public string Value { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/SsmlOutputSpeech.cs b/src/LondonTravel.Skill/Models/SsmlOutputSpeech.cs
new file mode 100644
index 00000000..fa097c51
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/SsmlOutputSpeech.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class SsmlOutputSpeech
+{
+ [JsonPropertyName("type")]
+ [JsonRequired]
+ public string Type { get; set; } = "SSML";
+
+ [JsonPropertyName("ssml")]
+ [JsonRequired]
+ public string Ssml { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/StandardCard.cs b/src/LondonTravel.Skill/Models/StandardCard.cs
new file mode 100644
index 00000000..d76b3122
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/StandardCard.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class StandardCard : Card
+{
+ [JsonPropertyName("type")]
+ [JsonRequired]
+ public override string Type { get; set; } = "Standard";
+
+ [JsonPropertyName("title")]
+ [JsonRequired]
+ public string Title { get; set; }
+
+ [JsonPropertyName("text")]
+ [JsonRequired]
+ public string Content { get; set; }
+}
diff --git a/src/LondonTravel.Skill/Models/User.cs b/src/LondonTravel.Skill/Models/User.cs
new file mode 100644
index 00000000..804976ee
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/User.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
+
+namespace MartinCostello.LondonTravel.Skill.Models;
+
+public sealed class User
+{
+ [JsonPropertyName("userId")]
+ public string UserId { get; set; }
+
+ [JsonPropertyName("accessToken")]
+ public string AccessToken { get; set; }
+}
diff --git a/src/LondonTravel.Skill/SkillConfiguration.cs b/src/LondonTravel.Skill/SkillConfiguration.cs
index a357f39f..6b21e64e 100644
--- a/src/LondonTravel.Skill/SkillConfiguration.cs
+++ b/src/LondonTravel.Skill/SkillConfiguration.cs
@@ -10,11 +10,6 @@ namespace MartinCostello.LondonTravel.Skill;
///
public sealed class SkillConfiguration
{
- ///
- /// Gets or sets the ApplicationInsights connection string to use.
- ///
- public string ApplicationInsightsConnectionString { get; set; } = string.Empty;
-
///
/// Gets or sets the URL for the skill API.
///
diff --git a/src/LondonTravel.Skill/SkillRequestExtensions.cs b/src/LondonTravel.Skill/SkillRequestExtensions.cs
new file mode 100644
index 00000000..387c18f9
--- /dev/null
+++ b/src/LondonTravel.Skill/SkillRequestExtensions.cs
@@ -0,0 +1,176 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Alexa.NET.Request.Type;
+using SkillRequest = Alexa.NET.Request.SkillRequest;
+
+namespace MartinCostello.LondonTravel.Skill;
+
+public static class SkillRequestExtensions
+{
+ public static SkillRequest FromModel(this Models.SkillRequest request)
+ {
+ var result = MapRequest(request);
+
+ result.Request = request.Request.Type switch
+ {
+ "IntentRequest" => MapIntent(request.Request),
+ "LaunchRequest" => new LaunchRequest(),
+ "SessionEndedRequest" => MapSessionEnd(request.Request),
+ "System.ExceptionEncountered" => MapException(request.Request),
+ _ => new UnknownRequest(),
+ };
+
+ result.Request.Locale = request.Request.Locale;
+ result.Request.RequestId = request.Request.RequestId;
+ result.Request.Timestamp = request.Request.Timestamp;
+ result.Request.Type = request.Request.Type;
+
+ return result;
+ }
+
+ private static SkillRequest MapRequest(Models.SkillRequest request)
+ {
+ var result = new SkillRequest() { Version = request.Version };
+
+ if (request.Context is { } context)
+ {
+ result.Context = new();
+
+ if (context.System is { } system)
+ {
+ result.Context.System = new()
+ {
+ ApiAccessToken = system.ApiAccessToken,
+ ApiEndpoint = system.ApiEndpoint,
+ };
+
+ if (system.Application is { } application)
+ {
+ result.Context.System.Application = new()
+ {
+ ApplicationId = application.ApplicationId,
+ };
+ }
+
+ if (system.Device is { } device)
+ {
+ result.Context.System.Device = new()
+ {
+ DeviceID = device.DeviceId,
+ PersistentEndpointID = device.PersistentEndpointId,
+ SupportedInterfaces = device.SupportedInterfaces,
+ };
+ }
+
+ if (system.User is { } user)
+ {
+ result.Context.System.User = new()
+ {
+ AccessToken = user.AccessToken,
+ Permissions = new(),
+ UserId = user.UserId,
+ };
+ }
+ }
+ }
+
+ if (request.Session is { } session)
+ {
+ result.Session = new()
+ {
+ Attributes = session.Attributes,
+ New = session.New,
+ SessionId = session.SessionId,
+ };
+
+ if (session.Application is { } application)
+ {
+ result.Session.Application = new()
+ {
+ ApplicationId = application.ApplicationId,
+ };
+ }
+
+ if (session.User is { } user)
+ {
+ result.Session.User = new()
+ {
+ AccessToken = user.AccessToken,
+ Permissions = new(),
+ UserId = user.UserId,
+ };
+ }
+ }
+
+ return result;
+ }
+
+ private static SystemExceptionRequest MapException(Models.Request exception)
+ {
+ var result = new SystemExceptionRequest();
+
+ if (exception.ErrorCause is { } cause)
+ {
+ result.ErrorCause = new() { requestId = cause.RequestId };
+ }
+
+ if (exception.Error is { } error)
+ {
+ result.Error = new()
+ {
+ Message = error.Message,
+ Type = Enum.Parse(error.Type.ToString()),
+ };
+ }
+
+ return result;
+ }
+
+ private static IntentRequest MapIntent(Models.Request request)
+ {
+ var result = new IntentRequest()
+ {
+ DialogState = request.DialogState,
+ };
+
+ if (request.Intent is { } intent)
+ {
+ result.Intent = new() { Name = intent.Name };
+
+ if (intent.Slots is { } slots)
+ {
+ result.Intent.Slots = slots.Where((p) => p.Value is not null).ToDictionary((p) => p.Key, (p) => new Alexa.NET.Request.Slot()
+ {
+ Name = p.Value.Name,
+ Value = p.Value.Value,
+ });
+ }
+ }
+
+ return result;
+ }
+
+ private static SessionEndedRequest MapSessionEnd(Models.Request session)
+ {
+ var result = new SessionEndedRequest()
+ {
+ Reason = Enum.Parse(session.Reason.ToString()),
+ };
+
+ if (session.Error is { } error)
+ {
+ result.Error = new()
+ {
+ Message = error.Message,
+ Type = Enum.Parse(error.Type.ToString()),
+ };
+ }
+
+ return result;
+ }
+
+ private sealed class UnknownRequest : Request
+ {
+ }
+}
diff --git a/src/LondonTravel.Skill/SkillResponseExtensions.cs b/src/LondonTravel.Skill/SkillResponseExtensions.cs
new file mode 100644
index 00000000..a66b604d
--- /dev/null
+++ b/src/LondonTravel.Skill/SkillResponseExtensions.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using Alexa.NET.Response;
+
+namespace MartinCostello.LondonTravel.Skill;
+
+internal static class SkillResponseExtensions
+{
+ public static Models.SkillResponse ToModel(this SkillResponse value)
+ {
+ var model = new Models.SkillResponse()
+ {
+ Response = new(),
+ Version = value.Version,
+ };
+
+ if (value.Response.Card is { } card)
+ {
+ model.Response.Card = card switch
+ {
+ LinkAccountCard _ => new Models.LinkAccountCard(),
+ StandardCard standard => new Models.StandardCard()
+ {
+ Content = standard.Content,
+ Title = standard.Title,
+ },
+ _ => throw new UnreachableException($"Unexpected card type {card.GetType()}."),
+ };
+ }
+
+ Debug.Assert(
+ value.Response.OutputSpeech is null || value.Response.OutputSpeech is SsmlOutputSpeech,
+ $"Unexpected output speech type {value.Response.OutputSpeech?.GetType()}");
+
+ if (value.Response.OutputSpeech is SsmlOutputSpeech ssml)
+ {
+ model.Response.OutputSpeech = new() { Ssml = ssml.Ssml };
+ }
+
+ Debug.Assert(
+ value.Response.Reprompt is null || value.Response.Reprompt.OutputSpeech is SsmlOutputSpeech,
+ $"Unexpected output speech type {value.Response.Reprompt?.OutputSpeech.GetType()}");
+
+ if (value.Response.Reprompt is { } reprompt &&
+ reprompt.OutputSpeech is SsmlOutputSpeech speech)
+ {
+ model.Response.Reprompt = new()
+ {
+ OutputSpeech = new() { Ssml = speech.Ssml },
+ };
+ }
+
+ model.Response.ShouldEndSession = value.Response.ShouldEndSession;
+
+ return model;
+ }
+}
diff --git a/src/LondonTravel.Skill/appsettings.json b/src/LondonTravel.Skill/appsettings.json
index 80a9e532..7574f5cb 100644
--- a/src/LondonTravel.Skill/appsettings.json
+++ b/src/LondonTravel.Skill/appsettings.json
@@ -8,7 +8,6 @@
}
},
"Skill": {
- "ApplicationInsightsConnectionString": "",
"SkillApiUrl": "",
"SkillId": "",
"TflApplicationId": "",
diff --git a/src/LondonTravel.Skill/aws-lambda-tools-defaults.json b/src/LondonTravel.Skill/aws-lambda-tools-defaults.json
index 1dfbe0a8..591770fa 100644
--- a/src/LondonTravel.Skill/aws-lambda-tools-defaults.json
+++ b/src/LondonTravel.Skill/aws-lambda-tools-defaults.json
@@ -3,7 +3,7 @@
"region": "eu-west-1",
"configuration": "Release",
"framework": "net8.0",
- "function-architecture": "arm64",
+ "function-architecture": "x86_64",
"function-handler": "LondonTravel.Skill",
"function-memory-size": 192,
"function-runtime": "provided.al2023",
diff --git a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
index 8b27499f..aee9a42c 100644
--- a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
+++ b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -56,20 +54,20 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
// Arrange
AlexaFunction function = await CreateFunctionAsync();
- var error = new SystemExceptionRequest()
+ var error = new Request()
{
- Error = new Error()
+ Error = new()
{
Message = "Internal error.",
- Type = ErrorType.InternalError,
+ Type = AlexaErrorType.InternalError,
},
- ErrorCause = new ErrorCause()
+ ErrorCause = new()
{
- requestId = "my-request-id",
+ RequestId = "my-request-id",
},
};
- var request = CreateRequest(error);
+ var request = CreateRequest("System.ExceptionEncountered", error);
// Act
SkillResponse actual = await function.HandlerAsync(request);
diff --git a/test/LondonTravel.Skill.Tests/CancelTests.cs b/test/LondonTravel.Skill.Tests/CancelTests.cs
index 7350f9d4..27a3f85b 100644
--- a/test/LondonTravel.Skill.Tests/CancelTests.cs
+++ b/test/LondonTravel.Skill.Tests/CancelTests.cs
@@ -1,8 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
diff --git a/test/LondonTravel.Skill.Tests/CommuteTests.cs b/test/LondonTravel.Skill.Tests/CommuteTests.cs
index ef034b79..89634d38 100644
--- a/test/LondonTravel.Skill.Tests/CommuteTests.cs
+++ b/test/LondonTravel.Skill.Tests/CommuteTests.cs
@@ -1,9 +1,8 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
using JustEat.HttpClientInterception;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
diff --git a/test/LondonTravel.Skill.Tests/DisruptionTests.cs b/test/LondonTravel.Skill.Tests/DisruptionTests.cs
index 3966886d..98c9d62d 100644
--- a/test/LondonTravel.Skill.Tests/DisruptionTests.cs
+++ b/test/LondonTravel.Skill.Tests/DisruptionTests.cs
@@ -1,9 +1,8 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
using JustEat.HttpClientInterception;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
diff --git a/test/LondonTravel.Skill.Tests/EndToEndTests.cs b/test/LondonTravel.Skill.Tests/EndToEndTests.cs
index f63a02c9..160dddf8 100644
--- a/test/LondonTravel.Skill.Tests/EndToEndTests.cs
+++ b/test/LondonTravel.Skill.Tests/EndToEndTests.cs
@@ -1,26 +1,126 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using System.Text.Json;
+using MartinCostello.LondonTravel.Skill.Models;
using MartinCostello.Testing.AwsLambdaTestServer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
namespace MartinCostello.LondonTravel.Skill;
public class EndToEndTests(ITestOutputHelper outputHelper) : FunctionTests(outputHelper)
{
[xRetry.RetryFact]
- public async Task Alexa_Function_Can_Process_Request()
+ public async Task Alexa_Function_Can_Process_Intent_Request()
{
// Arrange
- SkillRequest request = CreateRequest();
- request.Request.Type = "LaunchRequest";
+ SkillRequest request = CreateIntentRequest("AMAZON.CancelIntent");
- string json = JsonConvert.SerializeObject(request);
+ // Act
+ var actual = await ProcessRequestAsync(request);
+
+ // Assert
+ ResponseBody response = AssertResponse(actual);
+
+ response.Card.ShouldBeNull();
+ response.OutputSpeech.ShouldBeNull();
+ response.Reprompt.ShouldBeNull();
+ }
+
+ [xRetry.RetryFact]
+ public async Task Alexa_Function_Can_Process_Launch_Request()
+ {
+ // Arrange
+ SkillRequest request = CreateRequest("LaunchRequest");
+
+ // Act
+ var actual = await ProcessRequestAsync(request);
+
+ // Assert
+ ResponseBody response = AssertResponse(actual, shouldEndSession: false);
+
+ response.Card.ShouldBeNull();
+ response.Reprompt.ShouldBeNull();
+
+ response.OutputSpeech.ShouldNotBeNull();
+ response.OutputSpeech.Type.ShouldBe("SSML");
+
+ var ssml = response.OutputSpeech.ShouldBeOfType();
+ ssml.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]
+ public async Task Alexa_Function_Can_Process_Session_Ended_Request()
+ {
+ // Arrange
+ var session = new Request()
+ {
+ Reason = Reason.ExceededMaxReprompts,
+ Error = new()
+ {
+ Message = "Too many requests.",
+ Type = AlexaErrorType.RateLimitExceeded,
+ },
+ };
+
+ SkillRequest request = CreateRequest("SessionEndedRequest", session);
+
+ // Act
+ var actual = await ProcessRequestAsync(request);
+
+ ResponseBody response = AssertResponse(actual);
+
+ // Assert
+ response.Card.ShouldBeNull();
+ response.Reprompt.ShouldBeNull();
+
+ response.OutputSpeech.ShouldNotBeNull();
+ response.OutputSpeech.Type.ShouldBe("SSML");
+
+ var ssml = response.OutputSpeech.ShouldBeOfType();
+ ssml.Ssml.ShouldBe("Goodbye.");
+ }
+
+ [xRetry.RetryFact]
+ public async Task Alexa_Function_Can_Process_System_Exception_Request()
+ {
+ // Arrange
+ var exception = new Request()
+ {
+ Error = new()
+ {
+ Message = "An unknown error occurred.",
+ Type = AlexaErrorType.InternalServerError,
+ },
+ ErrorCause = new()
+ {
+ RequestId = "amzn1.echo-api.request.1234",
+ },
+ };
+
+ SkillRequest request = CreateRequest("System.ExceptionEncountered", exception);
+
+ // Act
+ var actual = await ProcessRequestAsync(request);
+
+ ResponseBody response = AssertResponse(actual);
+
+ // Assert
+ response.Card.ShouldBeNull();
+ response.Reprompt.ShouldBeNull();
+
+ response.OutputSpeech.ShouldNotBeNull();
+ response.OutputSpeech.Type.ShouldBe("SSML");
+
+ var ssml = response.OutputSpeech.ShouldBeOfType();
+ ssml.Ssml.ShouldBe("Sorry, something went wrong.");
+ }
+
+ private async Task ProcessRequestAsync(SkillRequest request)
+ {
+ // Arrange
+ string json = JsonSerializer.Serialize(request, AppJsonSerializerContext.Default.SkillRequest);
void Configure(IServiceCollection services)
{
@@ -59,19 +159,10 @@ void Configure(IServiceCollection services)
result.Content.ShouldNotBeEmpty();
json = await result.ReadAsStringAsync();
- var actual = JsonConvert.DeserializeObject(json);
+ var actual = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.SkillResponse);
actual.ShouldNotBeNull();
- ResponseBody response = AssertResponse(actual, shouldEndSession: false);
-
- response.Card.ShouldBeNull();
- response.Reprompt.ShouldBeNull();
-
- response.OutputSpeech.ShouldNotBeNull();
- response.OutputSpeech.Type.ShouldBe("SSML");
-
- var ssml = response.OutputSpeech.ShouldBeOfType();
- ssml.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.");
+ return actual;
}
}
diff --git a/test/LondonTravel.Skill.Tests/FunctionTests.cs b/test/LondonTravel.Skill.Tests/FunctionTests.cs
index d695c130..e89caa8b 100644
--- a/test/LondonTravel.Skill.Tests/FunctionTests.cs
+++ b/test/LondonTravel.Skill.Tests/FunctionTests.cs
@@ -1,11 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
using JustEat.HttpClientInterception;
using MartinCostello.Logging.XUnit;
+using MartinCostello.LondonTravel.Skill.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
@@ -48,7 +46,7 @@ protected virtual async Task CreateFunctionAsync()
protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] slots)
{
- var request = new IntentRequest()
+ var request = new Request()
{
Intent = new Intent()
{
@@ -66,11 +64,10 @@ protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] sl
}
}
- return CreateRequest(request);
+ return CreateRequest("IntentRequest", request);
}
- protected virtual SkillRequest CreateRequest(T request = null)
- where T : Request, new()
+ protected virtual SkillRequest CreateRequest(string type, Request request = null)
{
var application = new Application()
{
@@ -86,10 +83,6 @@ protected virtual SkillRequest CreateRequest(T request = null)
{
Context = new()
{
- AudioPlayer = new()
- {
- PlayerActivity = "IDLE",
- },
System = new()
{
Application = application,
@@ -103,7 +96,7 @@ protected virtual SkillRequest CreateRequest(T request = null)
User = user,
},
},
- Request = request ?? new T(),
+ Request = request ?? new(),
Session = new()
{
Application = application,
@@ -114,6 +107,7 @@ protected virtual SkillRequest CreateRequest(T request = null)
Version = "1.0",
};
+ result.Request.Type = type;
result.Request.Locale = "en-GB";
return result;
diff --git a/test/LondonTravel.Skill.Tests/HelpTests.cs b/test/LondonTravel.Skill.Tests/HelpTests.cs
index 354cfd20..316b353b 100644
--- a/test/LondonTravel.Skill.Tests/HelpTests.cs
+++ b/test/LondonTravel.Skill.Tests/HelpTests.cs
@@ -1,8 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
diff --git a/test/LondonTravel.Skill.Tests/LaunchTests.cs b/test/LondonTravel.Skill.Tests/LaunchTests.cs
index 3f51b034..3df41c5b 100644
--- a/test/LondonTravel.Skill.Tests/LaunchTests.cs
+++ b/test/LondonTravel.Skill.Tests/LaunchTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -15,7 +13,7 @@ public async Task Can_Invoke_Function()
// Arrange
AlexaFunction function = await CreateFunctionAsync();
- SkillRequest request = CreateRequest();
+ SkillRequest request = CreateRequest("LaunchRequest");
// Act
SkillResponse actual = await function.HandlerAsync(request);
diff --git a/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj b/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj
index dd42baf8..e70b6f28 100644
--- a/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj
+++ b/test/LondonTravel.Skill.Tests/LondonTravel.Skill.Tests.csproj
@@ -21,7 +21,7 @@
-
+
true
diff --git a/test/LondonTravel.Skill.Tests/Samples/IntentRequest.json b/test/LondonTravel.Skill.Tests/Samples/IntentRequest.json
new file mode 100644
index 00000000..583d9e86
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/Samples/IntentRequest.json
@@ -0,0 +1,45 @@
+{
+ "version": "1.0",
+ "session": {
+ "new": false,
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
+ "application": {
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
+ },
+ "attributes": {
+ "supportedHoroscopePeriods": {
+ "daily": true,
+ "weekly": false,
+ "monthly": false
+ }
+ },
+ "user": {
+ "userId": "amzn1.account.AM3B00000000000000000000000",
+ "accessToken": null,
+ "permissions": null
+ }
+ },
+ "context": null,
+ "request": {
+ "type": "IntentRequest",
+ "dialogState": "IN_PROGRESS",
+ "requestId": " amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
+ "timestamp": "2015-05-13T12:34:56Z",
+ "locale": null,
+ "intent": {
+ "name": "GetZodiacHoroscopeIntent",
+ "confirmationStatus": "DENIED",
+ "slots": {
+ "ZodiacSign": {
+ "name": "ZodiacSign",
+ "value": "virgo"
+ },
+ "Date": {
+ "name": "Date",
+ "value": "2015-11-25",
+ "confirmationStatus": "CONFIRMED"
+ }
+ }
+ }
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/Samples/LaunchRequest.json b/test/LondonTravel.Skill.Tests/Samples/LaunchRequest.json
new file mode 100644
index 00000000..f3790aef
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/Samples/LaunchRequest.json
@@ -0,0 +1,19 @@
+{
+ "version": "1.0",
+ "session": {
+ "new": true,
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
+ "application": {
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
+ },
+ "attributes": {},
+ "user": {
+ "userId": "amzn1.account.AM3B00000000000000000000000"
+ }
+ },
+ "request": {
+ "type": "LaunchRequest",
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
+ "timestamp": "2015-05-13T12:34:56Z"
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/Samples/LaunchRequestWithEpochTimestamp.json b/test/LondonTravel.Skill.Tests/Samples/LaunchRequestWithEpochTimestamp.json
new file mode 100644
index 00000000..84088268
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/Samples/LaunchRequestWithEpochTimestamp.json
@@ -0,0 +1,19 @@
+{
+ "version": "1.0",
+ "session": {
+ "new": true,
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
+ "application": {
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
+ },
+ "attributes": {},
+ "user": {
+ "userId": "amzn1.account.AM3B00000000000000000000000"
+ }
+ },
+ "request": {
+ "type": "LaunchRequest",
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
+ "timestamp": 1431520496000
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/Samples/SessionEndedRequest.json b/test/LondonTravel.Skill.Tests/Samples/SessionEndedRequest.json
new file mode 100644
index 00000000..1c9e44b1
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/Samples/SessionEndedRequest.json
@@ -0,0 +1,30 @@
+{
+ "version": "1.0",
+ "session": {
+ "new": false,
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
+ "application": {
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
+ },
+ "attributes": {
+ "supportedHoroscopePeriods": {
+ "daily": true,
+ "weekly": false,
+ "monthly": false
+ }
+ },
+ "user": {
+ "userId": "amzn1.account.AM3B00000000000000000000000"
+ }
+ },
+ "request": {
+ "type": "SessionEndedRequest",
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
+ "timestamp": "2015-05-13T12:34:56Z",
+ "reason": "USER_INITIATED",
+ "error": {
+ "type": "INVALID_RESPONSE",
+ "message": "test message"
+ }
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/Samples/SkillResponse.json b/test/LondonTravel.Skill.Tests/Samples/SkillResponse.json
new file mode 100644
index 00000000..3e785a01
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/Samples/SkillResponse.json
@@ -0,0 +1,22 @@
+{
+ "version": "1.0",
+ "sessionAttributes": {
+ "supportedHoriscopePeriods": {
+ "daily": true,
+ "weekly": false,
+ "monthly": false
+ }
+ },
+ "response": {
+ "outputSpeech": {
+ "type": "PlainText",
+ "text": "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless. Can I help you with anything else?"
+ },
+ "card": {
+ "type": "Simple",
+ "title": "Horoscope",
+ "content": "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless."
+ },
+ "shouldEndSession": false
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/SerializationTests.cs b/test/LondonTravel.Skill.Tests/SerializationTests.cs
new file mode 100644
index 00000000..9731bb36
--- /dev/null
+++ b/test/LondonTravel.Skill.Tests/SerializationTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Martin Costello, 2017. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json;
+
+namespace MartinCostello.LondonTravel.Skill;
+
+public static class SerializationTests
+{
+ [Theory]
+ [InlineData("IntentRequest")]
+ [InlineData("LaunchRequest")]
+ [InlineData("LaunchRequestWithEpochTimestamp")]
+ [InlineData("SessionEndedRequest")]
+ public static async Task Can_Deserialize_Request(string name)
+ {
+ // Arrange
+ string json = await File.ReadAllTextAsync(Path.Combine("Samples", $"{name}.json"));
+
+ // Act
+ var actual = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.SkillRequest);
+
+ // Assert
+ actual.ShouldNotBeNull();
+ actual.Request.ShouldNotBeNull();
+ }
+
+ [Theory]
+ [InlineData("SkillResponse")]
+ public static async Task Can_Deserialize_Response(string name)
+ {
+ // Arrange
+ string json = await File.ReadAllTextAsync(Path.Combine("Samples", $"{name}.json"));
+
+ // Act
+ var actual = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.ResponseBody);
+
+ // Assert
+ actual.ShouldNotBeNull();
+ }
+}
diff --git a/test/LondonTravel.Skill.Tests/SessionEndedTests.cs b/test/LondonTravel.Skill.Tests/SessionEndedTests.cs
index 0686d547..eaf52993 100644
--- a/test/LondonTravel.Skill.Tests/SessionEndedTests.cs
+++ b/test/LondonTravel.Skill.Tests/SessionEndedTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -15,7 +13,7 @@ public async Task Can_Invoke_Function()
// Arrange
AlexaFunction function = await CreateFunctionAsync();
- SkillRequest request = CreateRequest();
+ SkillRequest request = CreateRequest("SessionEndedRequest");
// Act
SkillResponse actual = await function.HandlerAsync(request);
diff --git a/test/LondonTravel.Skill.Tests/StatusTests.cs b/test/LondonTravel.Skill.Tests/StatusTests.cs
index 8fc86489..d4adbc31 100644
--- a/test/LondonTravel.Skill.Tests/StatusTests.cs
+++ b/test/LondonTravel.Skill.Tests/StatusTests.cs
@@ -1,9 +1,8 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
using JustEat.HttpClientInterception;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -42,7 +41,7 @@ public class StatusTests(ITestOutputHelper outputHelper) : FunctionTests(outputH
[InlineData("Waterloo")]
[InlineData("Waterloo & City")]
[InlineData("Waterloo and City")]
- public async Task Can_Invoke_Function_For_Valid_Line(string id)
+ public async Task Can_Invoke_Function_For_Valid_Lines(string id)
{
// Arrange
await Interceptor.RegisterBundleAsync(Path.Combine("Bundles", "tfl-line-statuses.json"));
diff --git a/test/LondonTravel.Skill.Tests/StopTests.cs b/test/LondonTravel.Skill.Tests/StopTests.cs
index e3e9ac86..dadd2b4c 100644
--- a/test/LondonTravel.Skill.Tests/StopTests.cs
+++ b/test/LondonTravel.Skill.Tests/StopTests.cs
@@ -1,8 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
diff --git a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
index 1295a6bc..696ee317 100644
--- a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
+++ b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -32,8 +30,4 @@ public async Task Can_Invoke_Function()
var ssml = response.OutputSpeech.ShouldBeOfType();
ssml.Ssml.ShouldBe("Sorry, I don't understand how to do that.");
}
-
- private sealed class UnknownRequest : Request
- {
- }
}
diff --git a/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs b/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs
index 6075b35b..4bab1edd 100644
--- a/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs
+++ b/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs
@@ -1,9 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
+using MartinCostello.LondonTravel.Skill.Models;
namespace MartinCostello.LondonTravel.Skill;
@@ -15,7 +13,7 @@ public async Task Can_Invoke_Function()
// Arrange
AlexaFunction function = await CreateFunctionAsync();
- SkillRequest request = CreateRequest();
+ SkillRequest request = CreateRequest("Unknown");
// Act
SkillResponse actual = await function.HandlerAsync(request);
@@ -23,8 +21,4 @@ public async Task Can_Invoke_Function()
// Assert
AssertResponse(actual);
}
-
- private sealed class UnknownRequest : Request
- {
- }
}
diff --git a/test/LondonTravel.Skill.Tests/testsettings.json b/test/LondonTravel.Skill.Tests/testsettings.json
index 60d2dfeb..708e9e9e 100644
--- a/test/LondonTravel.Skill.Tests/testsettings.json
+++ b/test/LondonTravel.Skill.Tests/testsettings.json
@@ -8,7 +8,6 @@
}
},
"Skill": {
- "ApplicationInsightsConnectionString": "InstrumentationKey=my-application-insights-key;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/",
"SkillApiUrl": "https://londontravel.martincostello.local/",
"SkillId": "my-skill-id",
"TflApplicationId": "my-tfl-app-id",