Skip to content

Commit

Permalink
Merge pull request #476 from WildernessLabs/feature/device-client
Browse files Browse the repository at this point in the history
Add new DeviceClient implementation
  • Loading branch information
stevenkuhn authored Feb 27, 2024
2 parents b0be470 + 8982d88 commit 6407b80
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 9 deletions.
32 changes: 32 additions & 0 deletions Source/v2/Meadow.Cloud.Client/Devices/AddDeviceRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Meadow.Cloud.Client.Devices;

public class AddDeviceRequest(string id, string orgId, string publicKey)
{
public AddDeviceRequest(string id, string name, string orgId, string publicKey)
: this(id, orgId, publicKey)
{
Name = name;
}

public AddDeviceRequest(string id, string name, string orgId, string collectionId, string publicKey)
: this(id, orgId, publicKey)
{
Name = name;
CollectionId = collectionId;
}

[JsonPropertyName("id")]
public string Id { get; set; } = id;

[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("orgId")]
public string OrgId { get; set; } = orgId;

[JsonPropertyName("collectionId")]
public string? CollectionId { get; set; }

[JsonPropertyName("publicKey")]
public string PublicKey { get; set; } = publicKey;
}
16 changes: 16 additions & 0 deletions Source/v2/Meadow.Cloud.Client/Devices/AddDeviceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Meadow.Cloud.Client.Devices;

public class AddDeviceResponse(string id, string name, string orgId, string collectionId)
{
[JsonPropertyName("id")]
public string Id { get; set; } = id;

[JsonPropertyName("name")]
public string? Name { get; set; } = name;

[JsonPropertyName("orgId")]
public string OrgId { get; set; } = orgId;

[JsonPropertyName("collectionId")]
public string? CollectionId { get; set; } = collectionId;
}
23 changes: 18 additions & 5 deletions Source/v2/Meadow.Cloud.Client/Devices/DeviceClient.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
namespace Meadow.Cloud.Client.Devices;

public interface IDeviceClient
public class DeviceClient : MeadowCloudClientBase, IDeviceClient
{
}
public DeviceClient(MeadowCloudContext meadowCloudContext, ILogger logger)
: base(meadowCloudContext, logger)
{
}

public class DeviceClient : IDeviceClient
{
}
public async Task<AddDeviceResponse> AddDevice(AddDeviceRequest request, CancellationToken cancellationToken = default)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}

using var httpRequest = CreateHttpRequestMessage(HttpMethod.Post, "api/v1/devices", request);
using var httpResponse = await HttpClient.SendAsync(httpRequest, cancellationToken);

return await ProcessResponse<AddDeviceResponse>(httpResponse, cancellationToken);
}
}
6 changes: 6 additions & 0 deletions Source/v2/Meadow.Cloud.Client/Devices/IDeviceClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Meadow.Cloud.Client.Devices;

public interface IDeviceClient
{
Task<AddDeviceResponse> AddDevice(AddDeviceRequest request, CancellationToken cancellationToken = default);
}
2 changes: 1 addition & 1 deletion Source/v2/Meadow.Cloud.Client/Firmware/FirmwareClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task<IEnumerable<GetFirmwareVersionsResponse>> GetVersions(string t

if (response.StatusCode == HttpStatusCode.NotFound)
{
return Enumerable.Empty<GetFirmwareVersionsResponse>();
return [];
}

return await ProcessResponse<IEnumerable<GetFirmwareVersionsResponse>>(response, cancellationToken);
Expand Down
6 changes: 4 additions & 2 deletions Source/v2/Meadow.Cloud.Client/MeadowCloudClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class MeadowCloudClient : IMeadowCloudClient
public const string DefaultHost = "https://www.meadowcloud.co";
public static readonly Uri DefaultHostUri = new(DefaultHost);

private readonly Lazy<DeviceClient> _deviceClient;
private readonly Lazy<FirmwareClient> _firmwareClient;
private readonly MeadowCloudContext _meadowCloudContext;
private readonly IdentityManager _identityManager;
Expand All @@ -22,15 +23,16 @@ public MeadowCloudClient(HttpClient httpClient, IdentityManager identityManager,
loggerFactory ??= NullLoggerFactory.Instance;

_meadowCloudContext = new(httpClient, userAgent);


_deviceClient = new Lazy<DeviceClient>(() => new DeviceClient(_meadowCloudContext, loggerFactory.CreateLogger<DeviceClient>()));
_firmwareClient = new Lazy<FirmwareClient>(() => new FirmwareClient(_meadowCloudContext, loggerFactory.CreateLogger<FirmwareClient>()));
_identityManager = identityManager;
}

public IApiTokenClient ApiToken => throw new NotImplementedException("This client is not implemented yet. Please use the 'ApiTokenService' instead.");
public ICollectionClient Collection => throw new NotImplementedException("This client is not implemented yet. Please use the 'CollectionService' instead.");
public ICommandClient Command => throw new NotImplementedException("This client is not implemented yet. Please use the 'CommandService' instead.");
public IDeviceClient Device => throw new NotImplementedException("This client is not implemented yet. Please use the 'DeviceService' instead.");
public IDeviceClient Device => _deviceClient.Value;
public IFirmwareClient Firmware => _firmwareClient.Value;
public IPackageClient Package => throw new NotImplementedException("This client is not implemented yet. Please use the 'PackageService' instead.");
public IUserClient User => throw new NotImplementedException("This client is not implemented yet. Please use the 'UserService' instead.");
Expand Down
19 changes: 18 additions & 1 deletion Source/v2/Meadow.Cloud.Client/MeadowCloudClientBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Meadow.Cloud.Client;
using System.Net.Http;

namespace Meadow.Cloud.Client;

public abstract class MeadowCloudClientBase
{
Expand Down Expand Up @@ -54,6 +56,21 @@ protected HttpRequestMessage CreateHttpRequestMessage(HttpMethod method, string
return CreateHttpRequestMessage(method, new Uri(urlBuilder.ToString(), UriKind.Relative));
}

protected HttpRequestMessage CreateHttpRequestMessage<T>(HttpMethod method, string requestUri, T request)
{
var json = JsonSerializer.SerializeToUtf8Bytes(request);
var content = new ByteArrayContent(json);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");

var httpRequest = new HttpRequestMessage(method, new Uri(MeadowCloudContext.BaseAddress, requestUri))
{
Content = content
};
SetHeaders(httpRequest);

return httpRequest;
}

private static IReadOnlyDictionary<string, IEnumerable<string>> GetHeaders(HttpResponseMessage response)
{
var headers = new Dictionary<string, IEnumerable<string>>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Meadow.Cloud.Client.Unit.Tests.DeviceClientTests;

public class AddDeviceTests
{
private readonly FakeableHttpMessageHandler _handler;
private readonly DeviceClient _deviceClient;

public AddDeviceTests()
{
_handler = A.Fake<FakeableHttpMessageHandler>();
var httpClient = new HttpClient(_handler) { BaseAddress = new Uri("https://example.org") };

A.CallTo(() => _handler
.FakeSendAsync(A<HttpRequestMessage>.Ignored, A<CancellationToken>.Ignored))
.Returns(new HttpResponseMessage(HttpStatusCode.NotFound));

var context = new MeadowCloudContext(httpClient, new Uri("https://example.org"), new MeadowCloudUserAgent("Meadow.Cloud.Client.Unit.Tests"));
_deviceClient = new DeviceClient(context, NullLogger.Instance);
}

[Fact]
public async Task AddDevice_WithNullRequest_ShouldThrowException()
{
// Act/Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => _deviceClient.AddDevice(null!));
}

[Theory]
[InlineData(HttpStatusCode.BadRequest)]
[InlineData(HttpStatusCode.Conflict)]
[InlineData(HttpStatusCode.NotFound)]
[InlineData(HttpStatusCode.Unauthorized)]
[InlineData(HttpStatusCode.InternalServerError)]
public async Task AddDevice_WithUnsuccessfulResponse_ShouldThrowException(HttpStatusCode httpStatusCode)
{
// Arrange
var addDeviceRequest = new AddDeviceRequest("id", "orgId", "publicKey");

A.CallTo(() => _handler
.FakeSendAsync(
A<HttpRequestMessage>.That.Matches(r => r.RequestUri!.AbsolutePath == $"/api/v1/devices"),
A<CancellationToken>.Ignored))
.Returns(new HttpResponseMessage(httpStatusCode));

// Act/Assert
var ex = await Assert.ThrowsAsync<MeadowCloudException>(() => _deviceClient.AddDevice(addDeviceRequest));
Assert.Equal(httpStatusCode, ex.StatusCode);
}

[Fact]
public async Task AddDevice_WithResponse_ShouldReturnResult()
{
// Arrange
var addDeviceRequest = new AddDeviceRequest("device-id", "device-org-id", "device-public-key");

A.CallTo(() => _handler
.FakeSendAsync(
A<HttpRequestMessage>.That.Matches(r => r.RequestUri!.AbsolutePath == $"/api/v1/devices"),
A<CancellationToken>.Ignored))
.Returns(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = JsonContent.Create(new AddDeviceResponse("device-id", "name", "device-org-id", "device-collection-id"))
});

// Act
var response = await _deviceClient.AddDevice(addDeviceRequest);

// Assert
Assert.NotNull(response);
Assert.Equal("device-id", response.Id);
}
}
2 changes: 2 additions & 0 deletions Source/v2/Tests/Meadow.Cloud.Client.Unit.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
global using FakeItEasy;
global using Meadow.Cloud.Client.Devices;
global using Meadow.Cloud.Client.Firmware;
global using Meadow.Cloud.Client.Unit.Tests.Builders;
global using Microsoft.Extensions.Logging.Abstractions;
global using System.Net;
global using System.Net.Http.Headers;
global using System.Net.Http.Json;
Expand Down

0 comments on commit 6407b80

Please sign in to comment.