From 9a90d26cdf7a37cc56a705b32c6be37ca06f6cb0 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 12 Apr 2024 11:05:59 -0400 Subject: [PATCH 1/8] Standardize null handling and error handling in custom Json converters --- Source/ZoomNet/Json/BooleanConverter.cs | 6 ++++ Source/ZoomNet/Json/DateTimeConverter.cs | 8 ++++- Source/ZoomNet/Json/KeyValuePairConverter.cs | 2 +- Source/ZoomNet/Json/MeetingConverter.cs | 2 +- .../ZoomNet/Json/NullableDateTimeConverter.cs | 11 +++--- .../ZoomNet/Json/NullableHexColorConverter.cs | 34 ++++++++++--------- .../Json/ParticipantDeviceConverter.cs | 2 +- Source/ZoomNet/Json/WebhookEventConverter.cs | 4 +-- Source/ZoomNet/Json/WebinarConverter.cs | 2 +- 9 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Source/ZoomNet/Json/BooleanConverter.cs b/Source/ZoomNet/Json/BooleanConverter.cs index 5b0b5944..e63fb15b 100644 --- a/Source/ZoomNet/Json/BooleanConverter.cs +++ b/Source/ZoomNet/Json/BooleanConverter.cs @@ -14,11 +14,17 @@ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer { switch (reader.TokenType) { + case JsonTokenType.None: + case JsonTokenType.Null: + throw new JsonException($"Unable to convert a null value into a boolean value"); + case JsonTokenType.True: case JsonTokenType.False: return reader.GetBoolean(); + case JsonTokenType.Number: return reader.GetByte() == 1; + default: throw new JsonException($"Unable to convert the content of {reader.TokenType.ToEnumString()} JSON node into a boolean value"); } diff --git a/Source/ZoomNet/Json/DateTimeConverter.cs b/Source/ZoomNet/Json/DateTimeConverter.cs index 593963fa..9eebc008 100644 --- a/Source/ZoomNet/Json/DateTimeConverter.cs +++ b/Source/ZoomNet/Json/DateTimeConverter.cs @@ -13,10 +13,16 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso { switch (reader.TokenType) { + case JsonTokenType.None: + case JsonTokenType.Null: + case JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()): + throw new JsonException($"Unable to convert a null value to DateTime"); + case JsonTokenType.String: return reader.GetDateTime(); + default: - throw new Exception($"Unable to convert {reader.TokenType.ToEnumString()} to DateTime"); + throw new JsonException($"Unable to convert {reader.TokenType.ToEnumString()} to DateTime"); } } diff --git a/Source/ZoomNet/Json/KeyValuePairConverter.cs b/Source/ZoomNet/Json/KeyValuePairConverter.cs index b055092a..bc50af19 100644 --- a/Source/ZoomNet/Json/KeyValuePairConverter.cs +++ b/Source/ZoomNet/Json/KeyValuePairConverter.cs @@ -59,7 +59,7 @@ public override KeyValuePair[] Read(ref Utf8JsonReader reader, T return values.ToArray(); } - throw new Exception("Unable to read Key/Value pair"); + throw new JsonException("Unable to read Key/Value pair"); } public override void Write(Utf8JsonWriter writer, KeyValuePair[] value, JsonSerializerOptions options) diff --git a/Source/ZoomNet/Json/MeetingConverter.cs b/Source/ZoomNet/Json/MeetingConverter.cs index aba36e7f..01e31066 100644 --- a/Source/ZoomNet/Json/MeetingConverter.cs +++ b/Source/ZoomNet/Json/MeetingConverter.cs @@ -28,7 +28,7 @@ public override Meeting Read(ref Utf8JsonReader reader, Type typeToConvert, Json case MeetingType.RecurringNoFixedTime: return rootElement.ToObject(options); default: - throw new Exception($"{meetingType} is an unknown meeting type"); + throw new JsonException($"{meetingType} is an unknown meeting type"); } } } diff --git a/Source/ZoomNet/Json/NullableDateTimeConverter.cs b/Source/ZoomNet/Json/NullableDateTimeConverter.cs index 53410a9b..d3faf33a 100644 --- a/Source/ZoomNet/Json/NullableDateTimeConverter.cs +++ b/Source/ZoomNet/Json/NullableDateTimeConverter.cs @@ -9,6 +9,8 @@ namespace ZoomNet.Json /// internal class NullableDateTimeConverter : ZoomNetJsonConverter { + private readonly DateTimeConverter _dateTimeConverter = new DateTimeConverter(); + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) @@ -17,17 +19,16 @@ internal class NullableDateTimeConverter : ZoomNetJsonConverter case JsonTokenType.Null: case JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()): return null; - case JsonTokenType.String: - return reader.GetDateTime(); + default: - throw new Exception($"Unable to convert {reader.TokenType.ToEnumString()} to nullable DateTime"); + return _dateTimeConverter.Read(ref reader, typeToConvert, options); } } public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) { - if (value.HasValue) writer.WriteStringValue(value.Value.ToZoomFormat()); - else writer.WriteNullValue(); + if (!value.HasValue) writer.WriteNullValue(); + else _dateTimeConverter.Write(writer, value.Value, options); } } } diff --git a/Source/ZoomNet/Json/NullableHexColorConverter.cs b/Source/ZoomNet/Json/NullableHexColorConverter.cs index 8c2f3c02..7d4f596e 100644 --- a/Source/ZoomNet/Json/NullableHexColorConverter.cs +++ b/Source/ZoomNet/Json/NullableHexColorConverter.cs @@ -20,26 +20,28 @@ public override bool CanConvert(Type typeToConvert) /// public override Color? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var str = reader.GetString(); - if (string.IsNullOrEmpty(str)) - return null; - str = str.Replace("#", string.Empty); - if (str.Length == 6) - str = $"FF{str}"; - return int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argB) - ? Color.FromArgb(argB) - : null; + switch (reader.TokenType) + { + case JsonTokenType.None: + case JsonTokenType.Null: + case JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()): + return null; + + default: + { + var str = reader.GetString().Replace("#", string.Empty); + if (str.Length == 6) str = $"FF{str}"; + return int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argB) + ? Color.FromArgb(argB) + : null; + } + } } /// public override void Write(Utf8JsonWriter writer, Color? value, JsonSerializerOptions options) { - if (value == null) - { - writer.WriteNullValue(); - return; - } - - writer.WriteStringValue($"#{value.Value.R:X2}{value.Value.G:X2}{value.Value.B:X2}"); + if (!value.HasValue) writer.WriteNullValue(); + else writer.WriteStringValue($"#{value.Value.R:X2}{value.Value.G:X2}{value.Value.B:X2}"); } } diff --git a/Source/ZoomNet/Json/ParticipantDeviceConverter.cs b/Source/ZoomNet/Json/ParticipantDeviceConverter.cs index 869f1cc3..ff26e552 100644 --- a/Source/ZoomNet/Json/ParticipantDeviceConverter.cs +++ b/Source/ZoomNet/Json/ParticipantDeviceConverter.cs @@ -28,7 +28,7 @@ public override ParticipantDevice[] Read(ref Utf8JsonReader reader, Type typeToC return items; default: - throw new Exception("Unable to convert to ParticipantDevice"); + throw new JsonException("Unable to convert to ParticipantDevice"); } } diff --git a/Source/ZoomNet/Json/WebhookEventConverter.cs b/Source/ZoomNet/Json/WebhookEventConverter.cs index 9a1f5dff..a881c475 100644 --- a/Source/ZoomNet/Json/WebhookEventConverter.cs +++ b/Source/ZoomNet/Json/WebhookEventConverter.cs @@ -280,7 +280,7 @@ public override Event Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe webHookEvent = endpointUrlValidationEvent; break; default: - throw new Exception($"{eventType} is an unknown event type"); + throw new JsonException($"{eventType} is an unknown event type"); } webHookEvent.EventType = eventType; @@ -306,7 +306,7 @@ private static KeyValuePair ConvertJsonPropertyToKeyValuePair(Js if (value.TryGetInt64(out var longValue)) return new KeyValuePair(key, longValue); if (value.TryGetInt32(out var intValue)) return new KeyValuePair(key, intValue); if (value.TryGetInt16(out var shortValue)) return new KeyValuePair(key, shortValue); - throw new Exception($"Property {key} appears to contain a numerical value but we are unable to determine to exact type"); + throw new JsonException($"Property {key} appears to contain a numerical value but we are unable to determine to exact type"); default: return new KeyValuePair(key, value.GetRawText()); } } diff --git a/Source/ZoomNet/Json/WebinarConverter.cs b/Source/ZoomNet/Json/WebinarConverter.cs index bf84d616..5ae880e2 100644 --- a/Source/ZoomNet/Json/WebinarConverter.cs +++ b/Source/ZoomNet/Json/WebinarConverter.cs @@ -25,7 +25,7 @@ public override Webinar Read(ref Utf8JsonReader reader, Type typeToConvert, Json case WebinarType.RecurringNoFixedTime: return rootElement.ToObject(options); default: - throw new Exception($"{webinarType} is an unknown webinar type"); + throw new JsonException($"{webinarType} is an unknown webinar type"); } } } From 9389bc50afd5ae38de5c40309ec1a86c9167defe Mon Sep 17 00:00:00 2001 From: pvgritsenko-ansible <165666478+pvgritsenko-ansible@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:57:40 +0300 Subject: [PATCH 2/8] Issue: The DownloadFileAsync method will throw an OutOfMemoryException if the file is too large (#342) * DownloadFileWithouBufferingAsync method added to avoid buffering of the recording file stream * cancellation token added * Apply PR comments --------- Co-authored-by: P. Gritsenko --- Source/ZoomNet/Resources/CloudRecordings.cs | 46 ++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/Source/ZoomNet/Resources/CloudRecordings.cs b/Source/ZoomNet/Resources/CloudRecordings.cs index 5fbe2e5e..66204e7e 100644 --- a/Source/ZoomNet/Resources/CloudRecordings.cs +++ b/Source/ZoomNet/Resources/CloudRecordings.cs @@ -4,10 +4,12 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using ZoomNet.Models; +using ZoomNet.Utilities; namespace ZoomNet.Resources { @@ -356,20 +358,44 @@ public Task RejectRegistrantsAsync(long meetingId, IEnumerable registran return UpdateRegistrantsStatusAsync(meetingId, registrantIds, "deny", cancellationToken); } - /// - /// Download the recording file. - /// - /// The URL of the recording file to download. - /// Cancellation token. - /// - /// The containing the file. - /// - public Task DownloadFileAsync(string downloadUrl, CancellationToken cancellationToken = default) + /// + public async Task DownloadFileAsync(string downloadUrl, CancellationToken cancellationToken = default) { - return _client + /* + * PLEASE NOTE: + * + * The HttpRequestMessage in this method is dispatched with its completion option set to "ResponseHeadersRead". + * This ensures the content of the response is streamed rather than buffered in memory. + * This is important in cases where the downloaded file is quite large. + * In this scenario, we don't want the entirety of the file to be buffered in a MemoryStream because + * it could lead to "out of memory" exceptions if the file is large enough. + * See https://github.com/Jericho/ZoomNet/pull/342 for a discussion on this topic. + * + * Forthermore, as of this writing, the FluentHttp library does not allow us to stream the content of responses + * which means that the code in this method cannot be simplified like so: + return _client .GetAsync(downloadUrl) .WithCancellationToken(cancellationToken) .AsStream(); + * + * The downside of not using the FluentHttp library to dispatch the request is that we lose automatic retries, + * error handling, logging, etc. + */ + + using (var request = new HttpRequestMessage(HttpMethod.Get, downloadUrl)) + { + var tokenHandler = _client.Filters.OfType().SingleOrDefault(); + if (tokenHandler != null) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenHandler.Token); + } + + var response = await _client.BaseClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStreamAsync(); + } } private Task UpdateRegistrantsStatusAsync(long meetingId, IEnumerable registrantIds, string status, CancellationToken cancellationToken = default) From 4d586b5820b38f883c96cb2c4902daa4a6f6fd0f Mon Sep 17 00:00:00 2001 From: Snezhanna Roshchupkina Date: Fri, 26 Apr 2024 22:47:20 +0300 Subject: [PATCH 3/8] Added endpoint to get list of Zoom Phone users (#344) * Get list of Phone users endpoint is added * Fix: set missing default value for pageSize and added a check for the value to be in available range; added unit test * Changed type of get phone users endpoint response * Added new model 'PhoneUser' and reused it in Phone.ListPhoneUsers resource instead of 'PhoneCallUserProfile' model --------- Co-authored-by: Snezhanna Roshchupkina --- .../Models/PhoneUserTests.cs | 84 +++++++++++++ .../Resources/PhoneUserTests.cs | 114 ++++++++++++++++++ .../Json/ZoomNetJsonSerializerContext.cs | 4 + Source/ZoomNet/Models/PhoneCallUserProfile.cs | 2 +- Source/ZoomNet/Models/PhoneUser.cs | 81 +++++++++++++ Source/ZoomNet/Models/Site.cs | 21 ++++ Source/ZoomNet/Resources/IPhone.cs | 30 +++++ Source/ZoomNet/Resources/Phone.cs | 32 +++++ 8 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs create mode 100644 Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs create mode 100644 Source/ZoomNet/Models/PhoneUser.cs create mode 100644 Source/ZoomNet/Models/Site.cs diff --git a/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs b/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs new file mode 100644 index 00000000..4eac4468 --- /dev/null +++ b/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using Shouldly; +using Xunit; +using ZoomNet.Json; +using ZoomNet.Models; + +namespace ZoomNet.UnitTests.Models +{ + public class PhoneUserTests + { + #region constants + + internal const string PHONE_USER = @"{ + ""id"": ""NL3cEpSdRc-c2t8aLoZqiw"", + ""phone_user_id"": ""u7pnC468TaS46OuNoEw6GA"", + ""email"": ""test_phone_user@testapi.com"", + ""name"": ""test phone user"", + ""extension_id"": ""CcrEGgmeQem1uyJsuIRKwA"", + ""extension_number"": 123, + ""status"": ""activate"", + ""calling_plans"": [ + { + ""type"": 600, + ""name"": ""Delhi billing"", + ""billing_account_id"": ""3WWAEiEjTj2IQuyDiKMd_A"", + ""billing_account_name"": ""Delhi billing"" + } + ], + ""phone_numbers"": [ + { + ""id"": ""---M1padRvSUtw7YihN7sA"", + ""number"": ""14232058798"" + } + ], + ""site"": { + ""id"": ""8f71O6rWT8KFUGQmJIFAdQ"", + ""name"": ""Test Site"" + }, + ""department"": ""Test"", + ""cost_center"": ""Cost Test Center"" + }"; + + #endregion + + #region tests + + [Fact] + public void Parse_Json_PhoneUserTests() + { + // Arrange + + // Act + var result = JsonSerializer.Deserialize( + PHONE_USER, JsonFormatter.SerializerOptions); + + // Assert + result.Id.ShouldBe("NL3cEpSdRc-c2t8aLoZqiw"); + result.PhoneUserId.ShouldBe("u7pnC468TaS46OuNoEw6GA"); + result.Email.ShouldBe("test_phone_user@testapi.com"); + result.Name.ShouldBe("test phone user"); + result.ExtensionId.ShouldBe("CcrEGgmeQem1uyJsuIRKwA"); + result.ExtensionNumber.ShouldBe(123); + result.Status.ShouldBe(PhoneCallUserStatus.Active); + result.CallingPlans.ShouldNotBeNull(); + result.CallingPlans.Length.ShouldBe(1); + result.PhoneNumbers.ShouldNotBeNull(); + result.PhoneNumbers.Length.ShouldBe(1); + result.Department.ShouldBe("Test"); + result.CostCenter.ShouldBe("Cost Test Center"); + result.Site.Id.ShouldBe("8f71O6rWT8KFUGQmJIFAdQ"); + result.Site.Name.ShouldBe("Test Site"); + + var callingPlan = result.CallingPlans[0]; + callingPlan.BillingAccountId.ShouldBe("3WWAEiEjTj2IQuyDiKMd_A"); + callingPlan.BillingAccountName.ShouldBe("Delhi billing"); + + var phoneNumber = result.PhoneNumbers[0]; + phoneNumber.PhoneNumberId.ShouldBe("---M1padRvSUtw7YihN7sA"); + phoneNumber.PhoneNumber.ShouldBe("14232058798"); + } + + #endregion + } +} diff --git a/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs new file mode 100644 index 00000000..6cdddff0 --- /dev/null +++ b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using RichardSzalay.MockHttp; +using Shouldly; +using Xunit; +using ZoomNet.Resources; + +namespace ZoomNet.UnitTests.Resources +{ + public class PhoneUserTests + { + #region constants + + internal const string PHONE_USERS_PAGINATED_OBJECT = @"{ + ""next_page_token"": ""F2qwertyg5eIqRRgC2YMauur8ZHUaJqtS3i"", + ""page_size"": 1, + ""total_records"": 10, + ""users"": [ + { + ""id"": ""NL3cEpSdRc-c2t8aLoZqiw"", + ""phone_user_id"": ""u7pnC468TaS46OuNoEw6GA"", + ""email"": ""test_phone_user@testapi.com"", + ""name"": ""test phone user"", + ""extension_id"": ""CcrEGgmeQem1uyJsuIRKwA"", + ""extension_number"": 123, + ""status"": ""activate"", + ""calling_plans"": [ + { + ""type"": 600, + ""name"": ""Delhi billing"", + ""billing_account_id"": ""3WWAEiEjTj2IQuyDiKMd_A"", + ""billing_account_name"": ""Delhi billing"" + } + ], + ""phone_numbers"": [ + { + ""id"": ""---M1padRvSUtw7YihN7sA"", + ""number"": ""14232058798"" + } + ], + ""site"": { + ""id"": ""8f71O6rWT8KFUGQmJIFAdQ"", + ""name"": ""Test Site"" + }, + ""department"": ""Test"", + ""cost_center"": ""Cost Test Center"" + } + ] + }"; + + #endregion + + #region tests + + [Fact] + public async Task GetPhoneUsersPaginatedResponseTestsAsync() + { + // Arrange + var pageSize = 1; + + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect( + HttpMethod.Get, + Utils.GetZoomApiUri("phone/users")) + .Respond( + "application/json", + PHONE_USERS_PAGINATED_OBJECT); + + var client = Utils.GetFluentClient(mockHttp); + var phone = new Phone(client); + + // Act + var result = await phone + .ListPhoneUsersAsync(pageSize: pageSize) + .ConfigureAwait(true); + + // Assert + mockHttp.VerifyNoOutstandingExpectation(); + mockHttp.VerifyNoOutstandingRequest(); + result.NextPageToken.ShouldNotBeNullOrEmpty(); + result.PageSize.ShouldBe(1); + result.TotalRecords.ShouldBe(10); + result.Records.ShouldNotBeNull(); + result.Records.Length.ShouldBe(1); + result.Records[0].Id.ShouldBe("NL3cEpSdRc-c2t8aLoZqiw"); + result.Records[0].PhoneNumbers[0].PhoneNumberId.ShouldBe("---M1padRvSUtw7YihN7sA"); + result.Records[0].PhoneNumbers[0].PhoneNumber.ShouldBe("14232058798"); + } + + [Theory] + [InlineData(0)] + [InlineData(101)] + public void InvalidPageSize_GetPhoneUsersPaginatedResponseTests(int pageSize) + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + + var client = Utils.GetFluentClient(mockHttp); + var phone = new Phone(client); + + // Act and Assert + var exception = Assert.Throws(() => phone + .ListPhoneUsersAsync(pageSize: pageSize) + .ConfigureAwait(true)); + + exception.ParamName.ShouldBe(nameof(pageSize)); + exception.Message.ShouldBe($"Records per page must be between 1 and 100 (Parameter '{nameof(pageSize)}')"); + } + + #endregion + } +} diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 418aaf6d..9b572362 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -152,6 +152,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PhoneCallUserStatus))] [JsonSerializable(typeof(ZoomNet.Models.PhoneNumber))] [JsonSerializable(typeof(ZoomNet.Models.PhoneType))] + [JsonSerializable(typeof(ZoomNet.Models.PhoneUser))] [JsonSerializable(typeof(ZoomNet.Models.PmiMeetingPasswordRequirementType))] [JsonSerializable(typeof(ZoomNet.Models.Poll))] [JsonSerializable(typeof(ZoomNet.Models.PollAnswer))] @@ -208,6 +209,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.ScreenshareDetails))] [JsonSerializable(typeof(ZoomNet.Models.SecuritySettings))] [JsonSerializable(typeof(ZoomNet.Models.SharingAndRecordingDetail))] + [JsonSerializable(typeof(ZoomNet.Models.Site))] [JsonSerializable(typeof(ZoomNet.Models.SmsAttachment))] [JsonSerializable(typeof(ZoomNet.Models.SmsAttachmentType))] [JsonSerializable(typeof(ZoomNet.Models.SmsDirection))] @@ -448,6 +450,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PhoneCallUserStatus[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneNumber[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneType[]))] + [JsonSerializable(typeof(ZoomNet.Models.PhoneUser[]))] [JsonSerializable(typeof(ZoomNet.Models.PmiMeetingPasswordRequirementType[]))] [JsonSerializable(typeof(ZoomNet.Models.Poll[]))] [JsonSerializable(typeof(ZoomNet.Models.PollAnswer[]))] @@ -504,6 +507,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.ScreenshareDetails[]))] [JsonSerializable(typeof(ZoomNet.Models.SecuritySettings[]))] [JsonSerializable(typeof(ZoomNet.Models.SharingAndRecordingDetail[]))] + [JsonSerializable(typeof(ZoomNet.Models.Site[]))] [JsonSerializable(typeof(ZoomNet.Models.SmsAttachment[]))] [JsonSerializable(typeof(ZoomNet.Models.SmsAttachmentType[]))] [JsonSerializable(typeof(ZoomNet.Models.SmsDirection[]))] diff --git a/Source/ZoomNet/Models/PhoneCallUserProfile.cs b/Source/ZoomNet/Models/PhoneCallUserProfile.cs index de9ffd70..095fe729 100644 --- a/Source/ZoomNet/Models/PhoneCallUserProfile.cs +++ b/Source/ZoomNet/Models/PhoneCallUserProfile.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace ZoomNet.Models; diff --git a/Source/ZoomNet/Models/PhoneUser.cs b/Source/ZoomNet/Models/PhoneUser.cs new file mode 100644 index 00000000..58c5b313 --- /dev/null +++ b/Source/ZoomNet/Models/PhoneUser.cs @@ -0,0 +1,81 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models; + +/// +/// Zoom Phone user information. +/// +public class PhoneUser +{ + /// + /// Gets or sets the calling plans. + /// + [JsonPropertyName("calling_plans")] + public CallingPlan[] CallingPlans { get; set; } + + /// + /// Gets or sets the cost center. + /// + [JsonPropertyName("cost_center")] + public string CostCenter { get; set; } + + /// + /// Gets or sets the department of the object. + /// + [JsonPropertyName("department")] + public string Department { get; set; } + + /// + /// Gets or sets the email address. + /// + [JsonPropertyName("email")] + public string Email { get; set; } + + /// + /// Gets or sets the extension ID. + /// + [JsonPropertyName("extension_id")] + public string ExtensionId { get; set; } + + /// + /// Gets or sets the extension number. + /// + [JsonPropertyName("extension_number")] + public int ExtensionNumber { get; set; } + + /// + /// Gets or sets the Zoom user ID. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Gets or sets the Zoom user name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Gets or sets the phone numbers. + /// + [JsonPropertyName("phone_numbers")] + public PhoneCallPhoneNumber[] PhoneNumbers { get; set; } + + /// + /// Gets or sets the Zoom phone user id. + /// + [JsonPropertyName("phone_user_id")] + public string PhoneUserId { get; set; } + + /// + /// Gets or sets a site. + /// + [JsonPropertyName("site")] + public Site Site { get; set; } + + /// + /// Gets or sets the status of the user. + /// + [JsonPropertyName("status")] + public PhoneCallUserStatus Status { get; set; } +} diff --git a/Source/ZoomNet/Models/Site.cs b/Source/ZoomNet/Models/Site.cs new file mode 100644 index 00000000..1375b493 --- /dev/null +++ b/Source/ZoomNet/Models/Site.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models; + +/// +/// Site information to which a user belongs to. +/// +public class Site +{ + /// + /// Gets or sets the Id. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Gets or sets the name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } +} diff --git a/Source/ZoomNet/Resources/IPhone.cs b/Source/ZoomNet/Resources/IPhone.cs index fac8a3f8..14702d94 100644 --- a/Source/ZoomNet/Resources/IPhone.cs +++ b/Source/ZoomNet/Resources/IPhone.cs @@ -58,6 +58,36 @@ Task GetRecordingTranscriptAsync( Task GetPhoneCallUserProfileAsync( string userId, CancellationToken cancellationToken = default); + /// + /// Retrieves a list of all of an account's users who are assigned a Zoom Phone license. + /// + /// The number of records returned from a single API call. Default is 30. + /// + /// The next page token paginates through a large set of results. + /// A next page token is returned whenever the set of available results exceeds the current page size. + /// The expiration period for this token is 15 minutes. + /// + /// The unique identifier of the site. + /// The type of calling plan. + /// The status of the Zoom Phone user. + /// The department where the user belongs. + /// The cost center where the user belongs. + /// The partial string of user's name, extension number, or phone number. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// + /// A task representing the asynchronous operation. The task result contains an array of Zoom Phone users in type of . + /// + Task> ListPhoneUsersAsync( + int pageSize = 30, + string nextPageToken = null, + string siteId = null, + int? callingType = null, + PhoneCallUserStatus? status = null, + string department = null, + string costCenter = null, + string keyword = null, + CancellationToken cancellationToken = default); + #endregion } } diff --git a/Source/ZoomNet/Resources/Phone.cs b/Source/ZoomNet/Resources/Phone.cs index fa9e3ea8..b6382545 100644 --- a/Source/ZoomNet/Resources/Phone.cs +++ b/Source/ZoomNet/Resources/Phone.cs @@ -1,4 +1,5 @@ using Pathoschild.Http.Client; +using System; using System.Threading; using System.Threading.Tasks; using ZoomNet.Models; @@ -62,6 +63,37 @@ public Task GetPhoneCallUserProfileAsync(string userId, Ca .AsObject(); } + /// + public Task> ListPhoneUsersAsync( + int pageSize = 30, + string nextPageToken = null, + string siteId = null, + int? callingType = null, + PhoneCallUserStatus? status = null, + string department = null, + string costCenter = null, + string keyword = null, + CancellationToken cancellationToken = default) + { + if (pageSize < 1 || pageSize > 100) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "Records per page must be between 1 and 100"); + } + + return _client + .GetAsync($"phone/users") + .WithArgument("page_size", pageSize) + .WithArgument("next_page_token", nextPageToken) + .WithArgument("site_id", siteId) + .WithArgument("calling_type", callingType) + .WithArgument("status", status) + .WithArgument("department", department) + .WithArgument("cost_center", costCenter) + .WithArgument("keyword", keyword) + .WithCancellationToken(cancellationToken) + .AsPaginatedResponseWithToken("users"); + } + #endregion } } From 7118327da44ce4fd912235fd9f784839c2592aa8 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 26 Apr 2024 16:00:02 -0400 Subject: [PATCH 4/8] inheritdoc does not require 'cref'. Also 'select' is no longer used in inheritdoc tag. --- Source/ZoomNet.IntegrationTests/Tests/Chatbot.cs | 2 +- Source/ZoomNet/Json/NullableHexColorConverter.cs | 6 +++--- .../ZoomNet/Models/ChatbotMessage/ChatbotFormFields.cs | 2 +- .../Models/ChatbotMessage/ChatbotMessageLine.cs | 2 +- Source/ZoomNet/Models/ChatbotMessage/ChatbotSection.cs | 2 +- Source/ZoomNet/Resources/Phone.cs | 10 +++++----- Source/ZoomNet/Resources/Sms.cs | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/ZoomNet.IntegrationTests/Tests/Chatbot.cs b/Source/ZoomNet.IntegrationTests/Tests/Chatbot.cs index f851048e..fcd9b8c5 100644 --- a/Source/ZoomNet.IntegrationTests/Tests/Chatbot.cs +++ b/Source/ZoomNet.IntegrationTests/Tests/Chatbot.cs @@ -11,7 +11,7 @@ namespace ZoomNet.IntegrationTests.Tests; public class Chatbot : IIntegrationTest { - /// + /// public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient client, TextWriter log, CancellationToken cancellationToken) { var accountId = Environment.GetEnvironmentVariable("ZOOM_OAUTH_ACCOUNTID", EnvironmentVariableTarget.User); diff --git a/Source/ZoomNet/Json/NullableHexColorConverter.cs b/Source/ZoomNet/Json/NullableHexColorConverter.cs index 7d4f596e..73308e09 100644 --- a/Source/ZoomNet/Json/NullableHexColorConverter.cs +++ b/Source/ZoomNet/Json/NullableHexColorConverter.cs @@ -11,13 +11,13 @@ namespace ZoomNet.Json; /// public class NullableHexColorConverter : JsonConverter { - /// + /// public override bool CanConvert(Type typeToConvert) { return typeToConvert == typeof(Color?); } - /// + /// public override Color? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) @@ -38,7 +38,7 @@ public override bool CanConvert(Type typeToConvert) } } - /// + /// public override void Write(Utf8JsonWriter writer, Color? value, JsonSerializerOptions options) { if (!value.HasValue) writer.WriteNullValue(); diff --git a/Source/ZoomNet/Models/ChatbotMessage/ChatbotFormFields.cs b/Source/ZoomNet/Models/ChatbotMessage/ChatbotFormFields.cs index 209358d5..ab2a7b32 100644 --- a/Source/ZoomNet/Models/ChatbotMessage/ChatbotFormFields.cs +++ b/Source/ZoomNet/Models/ChatbotMessage/ChatbotFormFields.cs @@ -14,7 +14,7 @@ public class ChatbotFormFields : IChatbotBody, IChatbotSection, IChatbotValidate [JsonPropertyName("items")] public ICollection Items { get; set; } - /// + /// public void Validate(bool enableMarkdownSupport) { foreach (var item in Items) diff --git a/Source/ZoomNet/Models/ChatbotMessage/ChatbotMessageLine.cs b/Source/ZoomNet/Models/ChatbotMessage/ChatbotMessageLine.cs index 50c7150d..676dbe32 100644 --- a/Source/ZoomNet/Models/ChatbotMessage/ChatbotMessageLine.cs +++ b/Source/ZoomNet/Models/ChatbotMessage/ChatbotMessageLine.cs @@ -54,7 +54,7 @@ public ChatbotMessageLine(string text) [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string Link { get; set; } - /// + /// public void Validate(bool enableMarkdownSupport) { if (enableMarkdownSupport && Link != null) diff --git a/Source/ZoomNet/Models/ChatbotMessage/ChatbotSection.cs b/Source/ZoomNet/Models/ChatbotMessage/ChatbotSection.cs index c80badc2..2230c100 100644 --- a/Source/ZoomNet/Models/ChatbotMessage/ChatbotSection.cs +++ b/Source/ZoomNet/Models/ChatbotMessage/ChatbotSection.cs @@ -64,7 +64,7 @@ public DateTime TimeStampFromDateTime set => TimeStamp = value.ToUnixTime(Internal.UnixTimePrecision.Milliseconds); } - /// + /// public void Validate(bool enableMarkdownSupport) { foreach (var section in Sections.OfType()) diff --git a/Source/ZoomNet/Resources/Phone.cs b/Source/ZoomNet/Resources/Phone.cs index b6382545..f5fdade5 100644 --- a/Source/ZoomNet/Resources/Phone.cs +++ b/Source/ZoomNet/Resources/Phone.cs @@ -6,7 +6,7 @@ namespace ZoomNet.Resources { - /// + /// public class Phone : IPhone { #region private fields @@ -30,7 +30,7 @@ internal Phone(IClient client) #region Recordings endpoints - /// + /// public Task GetRecordingAsync( string callId, CancellationToken cancellationToken = default) { @@ -40,7 +40,7 @@ public Task GetRecordingAsync( .AsObject(); } - /// + /// public Task GetRecordingTranscriptAsync( string recordingId, CancellationToken cancellationToken = default) { @@ -54,7 +54,7 @@ public Task GetRecordingTranscriptAsync( #region Users Endpoints - /// + /// public Task GetPhoneCallUserProfileAsync(string userId, CancellationToken cancellationToken = default) { return _client @@ -63,7 +63,7 @@ public Task GetPhoneCallUserProfileAsync(string userId, Ca .AsObject(); } - /// + /// public Task> ListPhoneUsersAsync( int pageSize = 30, string nextPageToken = null, diff --git a/Source/ZoomNet/Resources/Sms.cs b/Source/ZoomNet/Resources/Sms.cs index d3600ed8..19e4a18c 100644 --- a/Source/ZoomNet/Resources/Sms.cs +++ b/Source/ZoomNet/Resources/Sms.cs @@ -6,7 +6,7 @@ namespace ZoomNet.Resources { - /// + /// public class Sms : ISms { private readonly IClient _client; @@ -20,7 +20,7 @@ internal Sms(IClient client) _client = client; } - /// + /// public Task> GetSmsSessionDetailsAsync( string sessionId, DateTime? from, DateTime? to, bool? orderAscending = true, int recordsPerPage = 30, string pagingToken = null, CancellationToken cancellationToken = default) { From df145436e1a5bdadcbbc3db8692601e9aad60380 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 26 Apr 2024 16:06:20 -0400 Subject: [PATCH 5/8] Formatting --- Source/ZoomNet/Extensions/Internal.cs | 30 +++++++++++++-------------- Source/ZoomNet/Resources/CallLogs.cs | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 79ee3779..61b62734 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -500,22 +500,22 @@ internal static string EnsureEndsWith(this string value, string suffix) internal static T GetPropertyValue(this JsonElement element, string name, T defaultValue) { - return GetPropertyValue(element, new[] { name }, defaultValue, false); + return element.GetPropertyValue(new[] { name }, defaultValue, false); } internal static T GetPropertyValue(this JsonElement element, string[] names, T defaultValue) { - return GetPropertyValue(element, names, defaultValue, false); + return element.GetPropertyValue(names, defaultValue, false); } internal static T GetPropertyValue(this JsonElement element, string name) { - return GetPropertyValue(element, new[] { name }, default, true); + return element.GetPropertyValue(new[] { name }, default, true); } internal static T GetPropertyValue(this JsonElement element, string[] names) { - return GetPropertyValue(element, names, default, true); + return element.GetPropertyValue(names, default, true); } internal static async Task ForEachAsync(this IEnumerable items, Func> action, int maxDegreeOfParalellism) @@ -644,8 +644,8 @@ internal static IEnumerable> ParseQuerystring(this internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response) { - var diagnosticId = response.Message.RequestMessage.Headers.GetValue(DiagnosticHandler.DIAGNOSTIC_ID_HEADER_NAME); - DiagnosticHandler.DiagnosticsInfo.TryGetValue(diagnosticId, out DiagnosticInfo diagnosticInfo); + var diagnosticId = response.Message.RequestMessage.Headers.GetValue(DIAGNOSTIC_ID_HEADER_NAME); + DiagnosticsInfo.TryGetValue(diagnosticId, out DiagnosticInfo diagnosticInfo); return diagnosticInfo; } @@ -687,8 +687,8 @@ internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response) if (rootJsonElement.ValueKind == JsonValueKind.Object) { - errorCode = rootJsonElement.TryGetProperty("code", out JsonElement jsonErrorCode) ? (int?)jsonErrorCode.GetInt32() : (int?)null; - errorMessage = rootJsonElement.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : (errorCode.HasValue ? $"Error code: {errorCode}" : errorMessage); + errorCode = rootJsonElement.TryGetProperty("code", out JsonElement jsonErrorCode) ? jsonErrorCode.GetInt32() : null; + errorMessage = rootJsonElement.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : errorCode.HasValue ? $"Error code: {errorCode}" : errorMessage; if (rootJsonElement.TryGetProperty("errors", out JsonElement jsonErrorDetails)) { var errorDetails = string.Join( @@ -749,7 +749,7 @@ internal static async Task DecompressAsync(this Stream source) internal static string ToEnumString(this T enumValue) where T : Enum { - if (TryToEnumString(enumValue, out string stringValue)) return stringValue; + if (enumValue.TryToEnumString(out string stringValue)) return stringValue; return enumValue.ToString(); } @@ -796,7 +796,7 @@ internal static bool TryToEnumString(this T enumValue, out string stringValue internal static T ToEnum(this string str) where T : Enum { - if (TryToEnum(str, out T enumValue)) return enumValue; + if (str.TryToEnum(out T enumValue)) return enumValue; throw new ArgumentException($"There is no value in the {typeof(T).Name} enum that corresponds to '{str}'."); } @@ -907,7 +907,7 @@ private static async Task AsObject(this HttpContent httpContent, string pr return JsonSerializer.Deserialize(responseContent, options ?? JsonFormatter.DeserializerOptions); } - var jsonDoc = JsonDocument.Parse(responseContent, (JsonDocumentOptions)default); + var jsonDoc = JsonDocument.Parse(responseContent, default); if (jsonDoc.RootElement.TryGetProperty(propertyName, out JsonElement property)) { return property.ToObject(options); @@ -933,7 +933,7 @@ private static async Task AsRawJsonDocument(this HttpContent httpC { var responseContent = await httpContent.ReadAsStringAsync(null, cancellationToken).ConfigureAwait(false); - var jsonDoc = JsonDocument.Parse(responseContent, (JsonDocumentOptions)default); + var jsonDoc = JsonDocument.Parse(responseContent, default); if (string.IsNullOrEmpty(propertyName)) { @@ -943,7 +943,7 @@ private static async Task AsRawJsonDocument(this HttpContent httpC if (jsonDoc.RootElement.TryGetProperty(propertyName, out JsonElement property)) { var propertyContent = property.GetRawText(); - return JsonDocument.Parse(propertyContent, (JsonDocumentOptions)default); + return JsonDocument.Parse(propertyContent, default); } else if (throwIfPropertyIsMissing) { @@ -1106,7 +1106,7 @@ private static T GetPropertyValue(this JsonElement element, string[] names, T { var underlyingType = Nullable.GetUnderlyingType(typeOfT); var getElementValue = typeof(Internal) - .GetMethod(nameof(Internal.GetElementValue), BindingFlags.Static | BindingFlags.NonPublic) + .GetMethod(nameof(GetElementValue), BindingFlags.Static | BindingFlags.NonPublic) .MakeGenericMethod(underlyingType); return (T)getElementValue.Invoke(null, new object[] { property.Value }); @@ -1116,7 +1116,7 @@ private static T GetPropertyValue(this JsonElement element, string[] names, T { var elementType = typeOfT.GetElementType(); var getElementValue = typeof(Internal) - .GetMethod(nameof(Internal.GetElementValue), BindingFlags.Static | BindingFlags.NonPublic) + .GetMethod(nameof(GetElementValue), BindingFlags.Static | BindingFlags.NonPublic) .MakeGenericMethod(elementType); var arrayList = new ArrayList(property.Value.GetArrayLength()); diff --git a/Source/ZoomNet/Resources/CallLogs.cs b/Source/ZoomNet/Resources/CallLogs.cs index 9f2f4441..e9857153 100644 --- a/Source/ZoomNet/Resources/CallLogs.cs +++ b/Source/ZoomNet/Resources/CallLogs.cs @@ -76,7 +76,7 @@ public Task> GetAsync(string userId, Dat /// An array of . /// public Task> GetAsync(DateTime? from = null, DateTime? to = null, CallLogType type = CallLogType.All, CallLogPathType? pathType = null, CallLogTimeType? timeType = CallLogTimeType.StartTime, string siteId = null, bool chargedCallLogs = false, int recordsPerPage = 30, string pagingToken = null, CancellationToken cancellationToken = default) - { + { if (recordsPerPage < 1 || recordsPerPage > 300) { throw new ArgumentOutOfRangeException(nameof(recordsPerPage), "Records per page must be between 1 and 300"); From d04f3cd170eaefe0a97f028d3fbe7557b571a710 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 26 Apr 2024 16:09:42 -0400 Subject: [PATCH 6/8] Code cleanup --- .../ZoomNet.UnitTests/Models/PhoneCallUserProfileTests.cs | 4 ++-- Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs | 2 +- .../Resources/PhoneCallUserProfileTests.cs | 6 +++--- Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs | 4 ++-- Source/ZoomNet.UnitTests/Resources/SmsSessionTests.cs | 4 ++-- Source/ZoomNet/Models/ContinuousMeetingChatSettings.cs | 7 +------ 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Source/ZoomNet.UnitTests/Models/PhoneCallUserProfileTests.cs b/Source/ZoomNet.UnitTests/Models/PhoneCallUserProfileTests.cs index c64ea1a9..77fdfb4b 100644 --- a/Source/ZoomNet.UnitTests/Models/PhoneCallUserProfileTests.cs +++ b/Source/ZoomNet.UnitTests/Models/PhoneCallUserProfileTests.cs @@ -1,5 +1,5 @@ -using System.Text.Json; -using Shouldly; +using Shouldly; +using System.Text.Json; using Xunit; using ZoomNet.Json; using ZoomNet.Models; diff --git a/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs b/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs index 4eac4468..35eceacf 100644 --- a/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs +++ b/Source/ZoomNet.UnitTests/Models/PhoneUserTests.cs @@ -1,5 +1,5 @@ -using System.Text.Json; using Shouldly; +using System.Text.Json; using Xunit; using ZoomNet.Json; using ZoomNet.Models; diff --git a/Source/ZoomNet.UnitTests/Resources/PhoneCallUserProfileTests.cs b/Source/ZoomNet.UnitTests/Resources/PhoneCallUserProfileTests.cs index c1e44131..5beae3c9 100644 --- a/Source/ZoomNet.UnitTests/Resources/PhoneCallUserProfileTests.cs +++ b/Source/ZoomNet.UnitTests/Resources/PhoneCallUserProfileTests.cs @@ -1,8 +1,8 @@ -using System.Net.Http; +using RichardSzalay.MockHttp; +using Shouldly; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using RichardSzalay.MockHttp; -using Shouldly; using Xunit; using ZoomNet.Resources; diff --git a/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs index 6cdddff0..d112b2ad 100644 --- a/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs +++ b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs @@ -1,8 +1,8 @@ +using RichardSzalay.MockHttp; +using Shouldly; using System; using System.Net.Http; using System.Threading.Tasks; -using RichardSzalay.MockHttp; -using Shouldly; using Xunit; using ZoomNet.Resources; diff --git a/Source/ZoomNet.UnitTests/Resources/SmsSessionTests.cs b/Source/ZoomNet.UnitTests/Resources/SmsSessionTests.cs index b7f1ebb5..12dc1853 100644 --- a/Source/ZoomNet.UnitTests/Resources/SmsSessionTests.cs +++ b/Source/ZoomNet.UnitTests/Resources/SmsSessionTests.cs @@ -1,7 +1,7 @@ -using System.Net.Http; -using System.Threading.Tasks; using RichardSzalay.MockHttp; using Shouldly; +using System.Net.Http; +using System.Threading.Tasks; using Xunit; using ZoomNet.Resources; diff --git a/Source/ZoomNet/Models/ContinuousMeetingChatSettings.cs b/Source/ZoomNet/Models/ContinuousMeetingChatSettings.cs index 02b9278d..bd931fd6 100644 --- a/Source/ZoomNet/Models/ContinuousMeetingChatSettings.cs +++ b/Source/ZoomNet/Models/ContinuousMeetingChatSettings.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace ZoomNet.Models { /// /// Information about the Enable continuous meeting chat feature. - /// + /// public class ContinuousMeetingChatSettings { /// From 541e98e8955534ac9ca7cc2a0292007c72b30679 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 26 Apr 2024 16:26:18 -0400 Subject: [PATCH 7/8] Fix unit test. Today I learned that the error message of an ArgumentOutOfRange exception was slightly different under .NET 4.8 which was causing the unit test to fail under ,NET 4.8 but it was succeeding under .NET 6, 7 and 8 --- Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs index d112b2ad..20906c0b 100644 --- a/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs +++ b/Source/ZoomNet.UnitTests/Resources/PhoneUserTests.cs @@ -106,7 +106,7 @@ public void InvalidPageSize_GetPhoneUsersPaginatedResponseTests(int pageSize) .ConfigureAwait(true)); exception.ParamName.ShouldBe(nameof(pageSize)); - exception.Message.ShouldBe($"Records per page must be between 1 and 100 (Parameter '{nameof(pageSize)}')"); + exception.Message.ShouldStartWith("Records per page must be between 1 and 100"); } #endregion From 7ed13a6b94e9f01c66172c51261c1ad252aca6d2 Mon Sep 17 00:00:00 2001 From: jericho Date: Fri, 26 Apr 2024 16:26:49 -0400 Subject: [PATCH 8/8] Sync our Json serializer context with our model classes --- Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 9b572362..42f4c6df 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -58,7 +58,10 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessage.ChatbotSection), TypeInfoPropertyName = "ChatbotMessageChatbotSection")] [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessageInformation))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannel))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelAddMemberPermissions))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelMember))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelMentionAllPermissions))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelPostingPermissions))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelRole))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelSettings))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelType))] @@ -356,7 +359,10 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessage.ChatbotSection[]), TypeInfoPropertyName = "ChatbotMessageChatbotSectionArray")] [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessageInformation[]))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannel[]))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelAddMemberPermissions[]))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelMember[]))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelMentionAllPermissions[]))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelPostingPermissions[]))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelRole[]))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelSettings[]))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelType[]))] @@ -623,6 +629,9 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.CallLogType?))] [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessage.ChatbotActionStyle?), TypeInfoPropertyName = "ChatbotMessageChatbotActionStyleNullable")] [JsonSerializable(typeof(ZoomNet.Models.ChatbotMessage.ChatbotDropdownStaticSource?), TypeInfoPropertyName = "ChatbotMessageChatbotDropdownStaticSourceNullable")] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelAddMemberPermissions?))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelMentionAllPermissions?))] + [JsonSerializable(typeof(ZoomNet.Models.ChatChannelPostingPermissions?))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelRole?))] [JsonSerializable(typeof(ZoomNet.Models.ChatChannelType?))] [JsonSerializable(typeof(ZoomNet.Models.ChatMentionType?))]