Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON cleanup of IoT Hub service direct method + unit tests #3011

Merged
merged 2 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SDK v2 migration guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ What was a loose affiliation of separate clients is now a consolidated client wi

- The library now includes `IIotHubServiceRetryPolicy` implementations: `IotHubServiceExponentialBackoffRetryPolicy`, `IotHubServiceFixedDelayRetryPolicy`, `IotHubServiceIncrementalDelayRetryPolicy` and `IotHubServiceNoRetry`,
which can be set via `IotHubServiceClientOptions.RetryPolicy`.
- `DirectMethodClientResponse` now has a method `TryGetValue<T>` to deserialize the payload to a type of your choice.

#### API mapping

Expand All @@ -291,6 +292,7 @@ What was a loose affiliation of separate clients is now a consolidated client wi
| `ServiceClient.InvokeDeviceMethodAsync(...)` | `IotHubServiceClient.DirectMethods.InvokeAsync(...)` | |
| `CloudToDeviceMethod` | `DirectMethodServiceRequest` | Disambiguate from types in the device client.² |
| `CloudToDeviceMethodResult` | `DirectMethodClientResponse` | See² |
| `CloudToDeviceMethodResult.GetPayloadAsJson()` | `DirectMethodClientResponse.PayloadAsString` | |
| `ServiceClient.GetFeedbackReceiver(...)` | `IotHubServiceClient.MessageFeedback.MessageFeedbackProcessor` | |
| `ServiceClient.GetFileNotificationReceiver()` | `IotHubServiceClient.FileUploadNotifications.FileUploadNotificationProcessor` | |
| `IotHubException` | `IotHubServiceException` | Specify the exception is for Hub service client only. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ async Task TestOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDe

// D2C Operation
VerboseTestLogger.WriteLine($"{nameof(CombinedClientOperationsPoolAmqpTests)}: Operation 1: Send D2C for device={testDevice.Id}");
var message = TelemetryE2ETests.ComposeD2cTestMessage(out string _, out string _);
TelemetryMessage message = TelemetryE2ETests.ComposeD2cTestMessage(out string _, out string _);
Task sendD2cMessage = deviceClient.SendTelemetryAsync(message);
clientOperations.Add(sendD2cMessage);

Expand Down
10 changes: 5 additions & 5 deletions e2e/test/iothub/device/MethodE2ECustomPayloadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,20 @@ private async Task SendMethodAndRespondAsync(
await Task.WhenAll(serviceSendTask, methodReceivedTask).ConfigureAwait(false);
}

public static async Task ServiceSendMethodAndVerifyResponseAsync(
public static async Task ServiceSendMethodAndVerifyResponseAsync<T>(
string deviceId,
string methodName,
object response,
object request,
T request,
TimeSpan responseTimeout = default,
IotHubServiceClientOptions serviceClientTransportSettings = default)
{
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);
TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout;
VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}.");

var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved
{
MethodName = methodName,
ResponseTimeout = methodTimeout,
Payload = request,
};
Expand All @@ -153,7 +152,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync(

VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Method status: {methodResponse.Status}.");
methodResponse.Status.Should().Be(200);
JsonConvert.SerializeObject(methodResponse.Payload).Should().BeEquivalentTo(JsonConvert.SerializeObject(response));
methodResponse.TryGetPayload(out T actual).Should().BeTrue();
JsonConvert.SerializeObject(actual).Should().BeEquivalentTo(JsonConvert.SerializeObject(response));
abhipsaMisra marked this conversation as resolved.
Show resolved Hide resolved
}

public static async Task<Task> SetDeviceReceiveMethod_booleanPayloadAsync(IotHubDeviceClient deviceClient, string methodName)
Expand Down
36 changes: 16 additions & 20 deletions e2e/test/iothub/device/MethodE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,8 @@ public async Task Method_ServiceInvokeDeviceMethodWithUnknownDeviceThrows()
{
// setup
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);
var methodInvocation = new DirectMethodServiceRequest
var methodInvocation = new DirectMethodServiceRequest("SetTelemetryInterval")
{
MethodName = "SetTelemetryInterval",
Payload = "10"
};

Expand Down Expand Up @@ -175,9 +174,8 @@ public async Task Method_ServiceInvokeDeviceMethodWithUnknownModuleThrows()
// setup
using TestDevice testDevice = await TestDevice.GetTestDeviceAsync("ModuleNotFoundTest").ConfigureAwait(false);
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);
var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest("SetTelemetryInterval")
{
MethodName = "SetTelemetryInterval",
Payload = "10",
};

Expand All @@ -199,7 +197,7 @@ public async Task Method_ServiceInvokeDeviceMethodWithNullPayload_DoesNotThrow()
{
// arrange

const string commandName = "Reboot";
const string methodName = "Reboot";
bool deviceMethodCalledSuccessfully = false;
TestDevice testDevice = await TestDevice.GetTestDeviceAsync("NullMethodPayloadTest").ConfigureAwait(false);
await using IotHubDeviceClient deviceClient = testDevice.CreateDeviceClient(new IotHubClientOptions(new IotHubClientMqttSettings()));
Expand All @@ -210,7 +208,7 @@ await deviceClient
.SetDirectMethodCallbackAsync(
(methodRequest) =>
{
methodRequest.MethodName.Should().Be(commandName);
methodRequest.MethodName.Should().Be(methodName);
deviceMethodCalledSuccessfully = true;
var response = new Client.DirectMethodResponse(200);

Expand All @@ -219,9 +217,8 @@ await deviceClient
.ConfigureAwait(false);

using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);
var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
{
MethodName = commandName,
ConnectionTimeout = TimeSpan.FromMinutes(1),
ResponseTimeout = TimeSpan.FromMinutes(1),
};
Expand Down Expand Up @@ -270,9 +267,8 @@ public static async Task ServiceSendMethodAndVerifyNotReceivedAsync(
TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout;
VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}.");

var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
{
MethodName = methodName,
ResponseTimeout = methodTimeout,
};

Expand All @@ -289,10 +285,10 @@ public static async Task ServiceSendMethodAndVerifyNotReceivedAsync(
error.And.IsTransient.Should().BeTrue();
}

public static async Task ServiceSendMethodAndVerifyResponseAsync(
public static async Task ServiceSendMethodAndVerifyResponseAsync<T>(
string deviceId,
string methodName,
object respJson,
T respJson,
object reqJson,
TimeSpan responseTimeout = default,
IotHubServiceClientOptions serviceClientTransportSettings = default)
Expand All @@ -301,9 +297,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync(
TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout;
VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}.");

var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
{
MethodName = methodName,
ResponseTimeout = methodTimeout,
Payload = reqJson,
};
Expand All @@ -314,14 +309,15 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync(

VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Method status: {response.Status}.");
response.Status.Should().Be(200);
JsonConvert.SerializeObject(response.Payload).Should().Be(JsonConvert.SerializeObject(respJson));
response.TryGetPayload(out T actual).Should().BeTrue();
JsonConvert.SerializeObject(actual).Should().Be(JsonConvert.SerializeObject(respJson));
}

public static async Task ServiceSendMethodAndVerifyResponseAsync(
public static async Task ServiceSendMethodAndVerifyResponseAsync<T>(
string deviceId,
string moduleId,
string methodName,
object respJson,
T respJson,
object reqJson,
TimeSpan responseTimeout = default,
IotHubServiceClientOptions serviceClientTransportSettings = default)
Expand All @@ -330,9 +326,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync(

TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout;

var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
{
MethodName = methodName,
ResponseTimeout = methodTimeout,
Payload = reqJson,
};
Expand All @@ -344,7 +339,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync(

VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Method status: {response.Status}.");
response.Status.Should().Be(200);
JsonConvert.SerializeObject(response.Payload).Should().Be(JsonConvert.SerializeObject(respJson));
response.TryGetPayload(out T actual).Should().BeTrue();
JsonConvert.SerializeObject(actual).Should().Be(JsonConvert.SerializeObject(respJson));
}

public static async Task<Task> SubscribeAndUnsubscribeMethodAsync(IotHubDeviceClient deviceClient, string methodName)
Expand Down
8 changes: 4 additions & 4 deletions e2e/test/iothub/device/MethodFaultInjectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ await SendMethodAndRespondRecoveryAsync(
.ConfigureAwait(false);
}

private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, string methodName, object deviceResponsePayload, object serviceRequestPayload)
private async Task ServiceSendMethodAndVerifyResponseAsync<T>(string deviceName, string methodName, T deviceResponsePayload, object serviceRequestPayload)
{
var sw = Stopwatch.StartNew();
bool done = false;
Expand All @@ -224,9 +224,8 @@ private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, st
{
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);

var directMethodRequest = new DirectMethodServiceRequest
var directMethodRequest = new DirectMethodServiceRequest(methodName)
{
MethodName = methodName,
Payload = serviceRequestPayload,
ResponseTimeout = TimeSpan.FromMinutes(5),
};
Expand All @@ -239,7 +238,8 @@ private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, st
VerboseTestLogger.WriteLine($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Method status: {response.Status}.");

response.Status.Should().Be(200);
JsonConvert.SerializeObject(response.Payload).Should().Be(JsonConvert.SerializeObject(deviceResponsePayload));
response.TryGetPayload<T>(out T actual).Should().BeTrue();
JsonConvert.SerializeObject(actual).Should().Be(JsonConvert.SerializeObject(deviceResponsePayload));

done = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,18 @@ private static async Task Main(string[] args)
// Invoke the direct method on the device, passing the payload.
private static async Task InvokeMethodAsync(IotHubServiceClient serviceClient, string deviceId)
{
var methodInvocation = new DirectMethodServiceRequest
var methodInvocation = new DirectMethodServiceRequest("SetTelemetryInterval")
{
MethodName = "SetTelemetryInterval",
ResponseTimeout = TimeSpan.FromSeconds(30),
Payload = "10",
ResponseTimeout = TimeSpan.FromSeconds(30),
};

Console.WriteLine($"Invoking direct method for device: {deviceId}");

// Invoke the direct method asynchronously and get the response from the simulated device.
DirectMethodClientResponse response = await serviceClient.DirectMethods.InvokeAsync(deviceId, methodInvocation);

Console.WriteLine($"Response status: {response.Status}, payload:\n\t{JsonConvert.SerializeObject(response.Payload)}");
Console.WriteLine($"Response status: {response.Status}, payload:\n\t{JsonConvert.SerializeObject(response.PayloadAsString)}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,9 @@ private async Task InvokeGetMaxMinReportCommandAsync()
const string getMaxMinReportCommandName = "getMaxMinReport";

// Create command name to invoke for component. The command is formatted as <component name>*<command name>
string commandToInvoke = $"{Thermostat1Component}*{getMaxMinReportCommandName}";
var commandInvocation = new DirectMethodServiceRequest
string commandName = $"{Thermostat1Component}*{getMaxMinReportCommandName}";
var commandInvocation = new DirectMethodServiceRequest(commandName)
{
MethodName = commandToInvoke,
ResponseTimeout = TimeSpan.FromSeconds(30),
Payload = DateTimeOffset.Now.Subtract(TimeSpan.FromMinutes(2)),
};
Expand All @@ -73,7 +72,7 @@ private async Task InvokeGetMaxMinReportCommandAsync()
{
DirectMethodClientResponse result = await _serviceClient.DirectMethods.InvokeAsync(_deviceId, commandInvocation);
_logger.LogDebug($"Command {getMaxMinReportCommandName} was invoked on component {Thermostat1Component}." +
$"\nDevice returned status: {result.Status}. \nReport: {result.Payload}");
$"\nDevice returned status: {result.Status}. \nReport: {result.PayloadAsString}");
}
catch (IotHubServiceException ex) when (ex.ErrorCode == IotHubServiceErrorCode.DeviceNotFound)
{
Expand All @@ -85,26 +84,25 @@ private async Task InvokeGetMaxMinReportCommandAsync()
private async Task InvokeRebootCommandAsync()
{
// Create command name to invoke for component
const string commandToInvoke = "reboot";
var commandInvocation = new DirectMethodServiceRequest
const string commandName = "reboot";
var commandInvocation = new DirectMethodServiceRequest(commandName)
{
MethodName = commandToInvoke,
ResponseTimeout = TimeSpan.FromSeconds(30),
Payload = JsonConvert.SerializeObject(3),
};

_logger.LogDebug($"Invoke the {commandToInvoke} command on the {_deviceId} device twin." +
_logger.LogDebug($"Invoke the {commandName} command on the {_deviceId} device twin." +
$"\nThis will set the \"targetTemperature\" on \"Thermostat\" component to 0.");

try
{
DirectMethodClientResponse result = await _serviceClient.DirectMethods.InvokeAsync(_deviceId, commandInvocation);
_logger.LogDebug($"Command {commandToInvoke} was invoked on the {_deviceId} device twin." +
_logger.LogDebug($"Command {commandName} was invoked on the {_deviceId} device twin." +
$"\nDevice returned status: {result.Status}.");
}
catch (IotHubServiceException ex) when (ex.ErrorCode == IotHubServiceErrorCode.DeviceNotFound)
{
_logger.LogWarning($"Unable to execute command {commandToInvoke} on component {Thermostat1Component}." +
_logger.LogWarning($"Unable to execute command {commandName} on component {Thermostat1Component}." +
$"\nMake sure that the device sample TemperatureController located in {DeviceSampleLink} is also running.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ private async Task InvokeGetMaxMinReportCommandAsync()
const string getMaxMinReportCommandName = "getMaxMinReport";

// Create command name to invoke for component
var commandInvocation = new DirectMethodServiceRequest
var commandInvocation = new DirectMethodServiceRequest(getMaxMinReportCommandName)
{
MethodName = getMaxMinReportCommandName,
ResponseTimeout = TimeSpan.FromSeconds(30),
Payload = DateTimeOffset.Now.Subtract(TimeSpan.FromMinutes(2)),
};
Expand All @@ -87,7 +86,7 @@ private async Task InvokeGetMaxMinReportCommandAsync()
DirectMethodClientResponse result = await _serviceClient.DirectMethods.InvokeAsync(_deviceId, commandInvocation);

_logger.LogDebug($"Command {getMaxMinReportCommandName} was invoked on device twin {_deviceId}." +
$"\nDevice returned status: {result.Status}. \nReport: {result.Payload}");
$"\nDevice returned status: {result.Status}. \nReport: {result.PayloadAsString}");
}
catch (IotHubServiceException ex) when (ex.ErrorCode == IotHubServiceErrorCode.DeviceNotFound)
{
Expand Down
3 changes: 1 addition & 2 deletions iothub/service/src/DirectMethod/DirectMethodsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public class DirectMethodsClient
/// Creates an instance of this class. Provided for unit testing purposes only.
/// </summary>
protected DirectMethodsClient()
{
}
{ }

internal DirectMethodsClient(
string hostName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ protected internal DirectMethodClientResponse()
public int Status { get; protected internal set; }

/// <summary>
/// Get the payload object. May be null or empty.
/// Get the payload as a JSON string.
/// </summary>
/// <remarks>
/// The payload can be null or primitive type (e.g., string, int/array/list/dictionary/custom type)
/// To get the payload as a specified type, use <see cref="TryGetPayload{T}(out T)"/>.
/// </remarks>
[JsonIgnore]
public object Payload => JsonConvert.DeserializeObject((string)JsonPayload.Value);
public string PayloadAsString => JsonPayload.Value<string>();
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved

[JsonProperty("payload")]
internal JRaw JsonPayload { get; set; }
Expand All @@ -56,7 +56,7 @@ public bool TryGetPayload<T>(out T value)

try
{
value = JsonPayload.Value<T>();
value = JsonConvert.DeserializeObject<T>(JsonPayload.Value<string>());
return true;
}
catch (JsonSerializationException)
Expand Down
Loading