Skip to content

Commit

Permalink
E2E Test Coverage Part 1 (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
pglombardo authored Nov 18, 2024
1 parent e11ca74 commit e935d2a
Show file tree
Hide file tree
Showing 18 changed files with 536 additions and 17 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ name: Release Drafter

on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- main

permissions:
contents: read

jobs:
update_release_draft:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
165 changes: 165 additions & 0 deletions Source/HiveMQtt/Client/DisconnectOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright 2024-present HiveMQ and the HiveMQ Community
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace HiveMQtt.Client;

using HiveMQtt.Client.Options;
using HiveMQtt.MQTT5.ReasonCodes;

public class DisconnectOptionsBuilder
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

private readonly DisconnectOptions options;

public DisconnectOptionsBuilder() => this.options = new DisconnectOptions();

/// <summary>
/// Sets the session expiry interval for the disconnect.
/// </summary>
/// <param name="sessionExpiryInterval">The session expiry interval in seconds.</param>
/// <returns>The builder instance.</returns>
public DisconnectOptionsBuilder WithSessionExpiryInterval(int sessionExpiryInterval)
{
this.options.SessionExpiryInterval = sessionExpiryInterval;
return this;
}

/// <summary>
/// Sets the reason code for the disconnect.
/// </summary>
/// <param name="reasonCode">The reason code for the disconnect.</param>
/// <returns>The builder instance.</returns>
public DisconnectOptionsBuilder WithReasonCode(DisconnectReasonCode reasonCode)
{
this.options.ReasonCode = reasonCode;
return this;
}

/// <summary>
/// Sets the reason string for the disconnect.
/// </summary>
/// <param name="reasonString">The reason string for the disconnect.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if the reason string is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the reason string is null.</exception>
public DisconnectOptionsBuilder WithReasonString(string reasonString)
{
if (reasonString is null)
{
Logger.Error("Reason string cannot be null.");
throw new ArgumentNullException(nameof(reasonString));
}

if (reasonString.Length is < 1 or > 65535)
{
Logger.Error("Reason string must be between 1 and 65535 characters.");
throw new ArgumentException("Reason string must be between 1 and 65535 characters.");
}

this.options.ReasonString = reasonString;
return this;
}

/// <summary>
/// Adds a user property to the disconnect.
/// </summary>
/// <param name="key">The key for the user property.</param>
/// <param name="value">The value for the user property.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if the key or value is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the key or value is null.</exception>
public DisconnectOptionsBuilder WithUserProperty(string key, string value)
{
if (key is null)
{
Logger.Error("User property key cannot be null.");
throw new ArgumentNullException(nameof(key));
}

if (value is null)
{
Logger.Error("User property value cannot be null.");
throw new ArgumentNullException(nameof(value));
}

if (key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(key, value);
return this;
}

/// <summary>
/// Adds a dictionary of user properties to the disconnect.
/// </summary>
/// <param name="properties">The dictionary of user properties to add.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown if a key or value is not between 1 and 65535 characters.</exception>
/// <exception cref="ArgumentNullException">Thrown if the key or value is null.</exception>
public DisconnectOptionsBuilder WithUserProperties(Dictionary<string, string> properties)
{
foreach (var property in properties)
{
if (property.Key is null)
{
Logger.Error("User property key cannot be null.");
throw new ArgumentNullException(nameof(properties));
}

if (property.Value is null)
{
Logger.Error("User property value cannot be null.");
throw new ArgumentNullException(nameof(properties));
}

if (property.Key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (property.Value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(property.Key, property.Value);
}

return this;
}

/// <summary>
/// Builds the SubscribeOptions based on the previous calls. This
/// step will also run validation on the final SubscribeOptions.
/// </summary>
/// <returns>The constructed SubscribeOptions instance.</returns>
public DisconnectOptions Build()
{
this.options.Validate();
return this.options;
}
}
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/HiveMQClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public async Task<bool> DisconnectAsync(DisconnectOptions? options = null)
// Fire the corresponding event
this.BeforeDisconnectEventLauncher();

var disconnectPacket = new DisconnectPacket
var disconnectPacket = new DisconnectPacket(options)
{
DisconnectReasonCode = options.ReasonCode,
};
Expand Down
37 changes: 37 additions & 0 deletions Source/HiveMQtt/Client/HiveMQClientOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ public HiveMQClientOptionsBuilder WithPort(int port)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithClientId(string clientId)
{
if (clientId.Length is < 0 or > 65535)
{
Logger.Error("Client Id must be between 0 and 65535 characters.");
throw new ArgumentException("Client Id must be between 0 and 65535 characters.");
}

this.options.ClientId = clientId;
return this;
}
Expand Down Expand Up @@ -275,8 +281,15 @@ public HiveMQClientOptionsBuilder WithKeepAlive(int keepAlive)
/// </summary>
/// <param name="method">The authentication method.</param>
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
/// <exception cref="ArgumentException">Thrown when the authentication method is not between 1 and 65535 characters.</exception>
public HiveMQClientOptionsBuilder WithAuthenticationMethod(string method)
{
if (method.Length is < 1 or > 65535)
{
Logger.Error("Authentication method must be between 1 and 65535 characters.");
throw new ArgumentException("Authentication method must be between 1 and 65535 characters.");
}

this.options.AuthenticationMethod = method;
return this;
}
Expand Down Expand Up @@ -309,6 +322,18 @@ public HiveMQClientOptionsBuilder WithAuthenticationData(byte[] data)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithUserProperty(string key, string value)
{
if (key.Length is < 1 or > 65535)
{
Logger.Error("User property key must be between 1 and 65535 characters.");
throw new ArgumentException("User property key must be between 1 and 65535 characters.");
}

if (value.Length is < 1 or > 65535)
{
Logger.Error("User property value must be between 1 and 65535 characters.");
throw new ArgumentException("User property value must be between 1 and 65535 characters.");
}

this.options.UserProperties.Add(key, value);
return this;
}
Expand Down Expand Up @@ -432,6 +457,12 @@ public HiveMQClientOptionsBuilder WithSessionExpiryInterval(int sessionExpiryInt
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithUserName(string username)
{
if (username.Length is < 0 or > 65535)
{
Logger.Error("Username must be between 0 and 65535 characters.");
throw new ArgumentException("Username must be between 0 and 65535 characters.");
}

this.options.UserName = username;
return this;
}
Expand Down Expand Up @@ -462,6 +493,12 @@ public HiveMQClientOptionsBuilder WithUserName(string username)
/// <returns>The HiveMQClientOptionsBuilder instance.</returns>
public HiveMQClientOptionsBuilder WithPassword(string password)
{
if (password.Length is < 0 or > 65535)
{
Logger.Error("Password must be between 0 and 65535 characters.");
throw new ArgumentException("Password must be between 0 and 65535 characters.");
}

this.options.Password = password;
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/HiveMQClientUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ protected virtual void Dispose(bool disposing)
Logger.Trace("HiveMQClient Dispose: Disconnecting connected client.");
_ = Task.Run(async () => await this.DisconnectAsync().ConfigureAwait(false));
}
}
}

// Call the appropriate methods to clean up
// unmanaged resources here.
Expand Down
31 changes: 30 additions & 1 deletion Source/HiveMQtt/Client/Options/DisconnectOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
namespace HiveMQtt.Client.Options;

using HiveMQtt.Client.Exceptions;
using HiveMQtt.MQTT5.ReasonCodes;

/// <summary>
Expand All @@ -41,7 +42,7 @@ public DisconnectOptions()
/// to the indicated value. The value represents the session expiration time
/// in seconds.
/// </summary>
public int? SessionExpiry { get; set; }
public int? SessionExpiryInterval { get; set; }

/// <summary>
/// Gets or sets the reason string for the disconnection. This is a human readable
Expand All @@ -53,4 +54,32 @@ public DisconnectOptions()
/// Gets or sets the user properties for the disconnection.
/// </summary>
public Dictionary<string, string> UserProperties { get; set; }

/// <summary>
/// Validate that the options in this instance are valid.
/// </summary>
/// <exception cref="HiveMQttClientException">The exception raised if some value is out of range or invalid.</exception>
public void Validate()
{
// Validate SessionExpiry is non-negative if provided
if (this.SessionExpiryInterval.HasValue && this.SessionExpiryInterval < 0)
{
throw new HiveMQttClientException("Session expiry must be a non-negative value.");
}

// Validate ReasonString length (assuming max length of 65535 characters)
if (this.ReasonString != null && this.ReasonString.Length > 65535)
{
throw new HiveMQttClientException("Reason string must not exceed 65535 characters.");
}

// Validate UserProperties for null keys or values
foreach (var kvp in this.UserProperties)
{
if (kvp.Key == null || kvp.Value == null)
{
throw new HiveMQttClientException("User properties must not have null keys or values.");
}
}
}
}
1 change: 1 addition & 0 deletions Source/HiveMQtt/Client/Options/HiveMQClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public HiveMQClientOptions()
/// Examples:
/// ws://localhost:8000/mqtt
/// wss://localhost:8884/mqtt
/// .
/// </para>
/// </summary>
public string WebSocketServer { get; set; }
Expand Down
19 changes: 19 additions & 0 deletions Source/HiveMQtt/Client/PublishMessageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,27 @@ public PublishMessageBuilder WithPayload(string payload)
/// </summary>
/// <param name="topic">The topic.</param>
/// <returns>The builder instance.</returns>
/// <exception cref="ArgumentException">Thrown when the topic is null or empty.</exception>
/// <exception cref="ArgumentException">Thrown when the topic length is greater than 65535 characters.</exception>
/// <exception cref="ArgumentException">Thrown when the topic contains wildcard characters.</exception>
public PublishMessageBuilder WithTopic(string topic)
{
if (string.IsNullOrEmpty(topic))
{
throw new ArgumentException("Topic must not be null or empty.");
}

if (topic.Length is < 1 or > 65535)
{
throw new ArgumentException("Topic must be between 1 and 65535 characters.");
}

// The topic string should not contain any wildcard characters.
if (topic.Contains('+') || topic.Contains('#'))
{
throw new ArgumentException("Topic must not contain wildcard characters. Use TopicFilter instead.");
}

this.message.Topic = topic;
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/HiveMQtt/Client/Transport/WebSocketTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ public override async Task<TransportReadResult> ReadAsync(CancellationToken canc

Logger.Trace($"Received {ms.Length} bytes");

// Development
// ms.Seek(0, SeekOrigin.Begin);

if (result.MessageType == WebSocketMessageType.Binary)
{
// Prepare the result and return
Expand Down
Loading

0 comments on commit e935d2a

Please sign in to comment.