This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new e2fcc6728 refactor(csharp): change expiry handling to use TimeSpan
(#2690)
e2fcc6728 is described below
commit e2fcc6728ab39c29fdba5ecf592bdc52e6892fcc
Author: Ćukasz Zborek <[email protected]>
AuthorDate: Sat Feb 7 10:14:33 2026 +0100
refactor(csharp): change expiry handling to use TimeSpan (#2690)
- change expiery argument from ulong to TimeSpan
- internal change from TimeSpan to ulong value via `DurationHelpers`
BREAKING
---------
Co-authored-by: Piotr Gankiewicz <[email protected]>
---
foreign/csharp/Benchmarks/Program.cs | 6 +-
.../Fixtures/FetchMessagesFixture.cs | 6 +-
.../Fixtures/FlushMessageFixture.cs | 6 +-
.../Fixtures/IggyServerFixture.cs | 3 +-
.../Fixtures/OffsetFixtures.cs | 8 +-
.../Fixtures/PartitionsFixture.cs | 4 +-
.../Fixtures/SendMessageFixture.cs | 4 +-
.../Helpers/TopicFactory.cs | 7 +-
.../Models/CreateTestTopic.cs | 51 +++++
.../Models/UpdateTestTopic.cs | 27 +++
.../PersonalAccessTokenTests.cs | 7 +-
.../Iggy_SDK.Tests.Integration/StreamsTests.cs | 9 +-
.../Iggy_SDK.Tests.Integration/TopicsTests.cs | 24 ++-
foreign/csharp/Iggy_SDK/Contracts/TopicResponse.cs | 3 +-
.../IggyClient/IIggyPersonalAccessToken.cs | 4 +-
foreign/csharp/Iggy_SDK/IggyClient/IIggyTopic.cs | 8 +-
.../Implementations/HttpMessageStream.cs | 20 +-
.../IggyClient/Implementations/TcpMessageStream.cs | 14 +-
foreign/csharp/Iggy_SDK/Iggy_SDK.csproj | 2 +-
.../Iggy_SDK/JsonConverters/TimeSpanConverter.cs | 42 ++++
foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs | 3 +-
.../Iggy_SDK/Publishers/IggyPublisherBuilder.cs | 3 +-
.../Iggy_SDK/Publishers/IggyPublisherConfig.cs | 4 +-
foreign/csharp/Iggy_SDK/Utils/DurationHelpers.cs | 60 ++++++
.../UtilityTests/DurationHelperTests.cs | 231 +++++++++++++++++++++
25 files changed, 483 insertions(+), 73 deletions(-)
diff --git a/foreign/csharp/Benchmarks/Program.cs
b/foreign/csharp/Benchmarks/Program.cs
index bf721fb82..9934d15fe 100644
--- a/foreign/csharp/Benchmarks/Program.cs
+++ b/foreign/csharp/Benchmarks/Program.cs
@@ -39,7 +39,7 @@ var loggerFactory = LoggerFactory.Create(builder =>
for (var i = 0; i < producerCount; i++)
{
- var bus = IggyClientFactory.CreateClient(new IggyClientConfigurator()
+ var bus = IggyClientFactory.CreateClient(new IggyClientConfigurator
{
BaseAddress = "127.0.0.1:8090",
Protocol = Protocol.Tcp,
@@ -67,9 +67,9 @@ try
await clients[0].CreateStreamAsync($"Test bench stream_{i}");
await clients[0].CreateTopicAsync(Identifier.Numeric(startingStreamId
+ i),
- name: $"Test bench topic_{i}",
+ $"Test bench topic_{i}",
compressionAlgorithm: CompressionAlgorithm.None,
- messageExpiry: 0,
+ messageExpiry: TimeSpan.Zero,
maxTopicSize: 2_000_000_000,
replicationFactor: 3,
partitionsCount: 1);
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FetchMessagesFixture.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FetchMessagesFixture.cs
index e58e2742f..5b2dd71a0 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FetchMessagesFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FetchMessagesFixture.cs
@@ -16,12 +16,12 @@
// // under the License.
using System.Text;
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.Headers;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Messages;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using TUnit.Core.Interfaces;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
@@ -31,8 +31,8 @@ public class FetchMessagesFixture : IAsyncInitializer
{
internal readonly int MessageCount = 20;
internal readonly string StreamId = "FetchMessagesStream";
- internal readonly CreateTopicRequest TopicHeadersRequest =
TopicFactory.CreateTopic("HeadersTopic");
- internal readonly CreateTopicRequest TopicRequest =
TopicFactory.CreateTopic("Topic");
+ internal readonly CreateTestTopic TopicHeadersRequest =
TopicFactory.CreateTopic("HeadersTopic");
+ internal readonly CreateTestTopic TopicRequest =
TopicFactory.CreateTopic("Topic");
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture IggyServerFixture { get; init; }
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FlushMessageFixture.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FlushMessageFixture.cs
index 482301036..511a4bcd5 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FlushMessageFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/FlushMessageFixture.cs
@@ -15,12 +15,11 @@
// // specific language governing permissions and limitations
// // under the License.
-using Apache.Iggy.Contracts;
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Messages;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using TUnit.Core.Interfaces;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
@@ -29,7 +28,7 @@ namespace Apache.Iggy.Tests.Integrations.Fixtures;
public class FlushMessageFixture : IAsyncInitializer
{
internal readonly string StreamId = "FlushMessageStream";
- internal readonly CreateTopicRequest TopicRequest =
TopicFactory.CreateTopic("Topic");
+ internal readonly CreateTestTopic TopicRequest =
TopicFactory.CreateTopic("Topic");
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture IggyServerFixture { get; init; }
@@ -53,7 +52,6 @@ public class FlushMessageFixture : IAsyncInitializer
};
await
client.Value.SendMessagesAsync(Identifier.String(StreamId.GetWithProtocol(client.Key)),
Identifier.String(TopicRequest.Name), Partitioning.None(),
messages);
-
}
}
}
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyServerFixture.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyServerFixture.cs
index 39922d2a2..1c2fc9b47 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyServerFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyServerFixture.cs
@@ -46,7 +46,8 @@ public class IggyServerFixture : IAsyncInitializer,
IAsyncDisposable
{ "IGGY_ROOT_USERNAME", "iggy" },
{ "IGGY_ROOT_PASSWORD", "iggy" },
{ "IGGY_TCP_ADDRESS", "0.0.0.0:8090" },
- { "IGGY_HTTP_ADDRESS", "0.0.0.0:3000" }
+ { "IGGY_HTTP_ADDRESS", "0.0.0.0:3000" },
+ { "IGGY_SYSTEM_TOPIC_MESSAGE_EXPIRY", "10m" }
};
/// <summary>
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/OffsetFixtures.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/OffsetFixtures.cs
index 0867f97ea..6e33f8ced 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/OffsetFixtures.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/OffsetFixtures.cs
@@ -15,12 +15,11 @@
// // specific language governing permissions and limitations
// // under the License.
-using Apache.Iggy.Contracts;
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Messages;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using TUnit.Core.Interfaces;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
@@ -29,7 +28,7 @@ namespace Apache.Iggy.Tests.Integrations.Fixtures;
public class OffsetFixtures : IAsyncInitializer
{
internal readonly string StreamId = "OffsetStream";
- internal readonly CreateTopicRequest TopicRequest =
TopicFactory.CreateTopic("Topic");
+ internal readonly CreateTestTopic TopicRequest =
TopicFactory.CreateTopic("Topic");
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture IggyServerFixture { get; init; }
@@ -49,8 +48,7 @@ public class OffsetFixtures : IAsyncInitializer
{
new(Guid.NewGuid(), "Test message 1"u8.ToArray()),
new(Guid.NewGuid(), "Test message 2"u8.ToArray()),
- new(Guid.NewGuid(), "Test message 3"u8.ToArray()),
- new(Guid.NewGuid(), "Test message 4"u8.ToArray())
+ new(Guid.NewGuid(), "Test message 3"u8.ToArray()),
new(Guid.NewGuid(), "Test message 4"u8.ToArray())
};
await
client.Value.SendMessagesAsync(Identifier.String(StreamId.GetWithProtocol(client.Key)),
Identifier.String(TopicRequest.Name), Partitioning.None(),
messages);
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/PartitionsFixture.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/PartitionsFixture.cs
index 873a7451d..cdd5fb9a8 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/PartitionsFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/PartitionsFixture.cs
@@ -15,10 +15,10 @@
// // specific language governing permissions and limitations
// // under the License.
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using TUnit.Core.Interfaces;
namespace Apache.Iggy.Tests.Integrations.Fixtures;
@@ -26,7 +26,7 @@ namespace Apache.Iggy.Tests.Integrations.Fixtures;
public class PartitionsFixture : IAsyncInitializer
{
internal readonly string StreamId = "PartitionsStream";
- internal readonly CreateTopicRequest TopicRequest =
TopicFactory.CreateTopic("Topic");
+ internal readonly CreateTestTopic TopicRequest =
TopicFactory.CreateTopic("Topic");
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture IggyServerFixture { get; init; }
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/SendMessageFixture.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/SendMessageFixture.cs
index f1347854a..ec7b98499 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/SendMessageFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/SendMessageFixture.cs
@@ -15,10 +15,10 @@
// // specific language governing permissions and limitations
// // under the License.
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using TUnit.Core.Interfaces;
namespace Apache.Iggy.Tests.Integrations.Fixtures;
@@ -26,7 +26,7 @@ namespace Apache.Iggy.Tests.Integrations.Fixtures;
public class SendMessageFixture : IAsyncInitializer
{
internal readonly string StreamId = "SendMessageStream";
- internal readonly CreateTopicRequest TopicRequest =
TopicFactory.CreateTopic("Topic");
+ internal readonly CreateTestTopic TopicRequest =
TopicFactory.CreateTopic("Topic");
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture IggyServerFixture { get; init; }
diff --git a/foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/TopicFactory.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/TopicFactory.cs
index 5959b7ba6..52576c80b 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/TopicFactory.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/TopicFactory.cs
@@ -15,15 +15,16 @@
// // specific language governing permissions and limitations
// // under the License.
-using Apache.Iggy.Contracts.Http;
+using Apache.Iggy.Tests.Integrations.Models;
namespace Apache.Iggy.Tests.Integrations.Helpers;
public static class TopicFactory
{
- internal static CreateTopicRequest CreateTopic(string topicId, uint
partitionsCount = 1, ulong messageExpiry = 0)
+ internal static CreateTestTopic CreateTopic(string topicId, uint
partitionsCount = 1,
+ TimeSpan messageExpiry = default)
{
- return new CreateTopicRequest
+ return new CreateTestTopic
{
Name = topicId,
PartitionsCount = partitionsCount,
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Models/CreateTestTopic.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Models/CreateTestTopic.cs
new file mode 100644
index 000000000..d58f123e7
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Models/CreateTestTopic.cs
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+using System.Diagnostics.CodeAnalysis;
+using Apache.Iggy.Enums;
+
+namespace Apache.Iggy.Tests.Integrations.Models;
+
+internal class CreateTestTopic
+{
+ public required string Name { get; set; }
+ public CompressionAlgorithm CompressionAlgorithm { get; set; } =
CompressionAlgorithm.None;
+ public TimeSpan MessageExpiry { get; set; } = TimeSpan.Zero;
+ public uint PartitionsCount { get; set; } = 1;
+ public byte? ReplicationFactor { get; set; } = 1;
+ public ulong MaxTopicSize { get; set; }
+
+ public CreateTestTopic()
+ {
+ }
+
+ [SetsRequiredMembers]
+ public CreateTestTopic(string name,
+ CompressionAlgorithm compressionAlgorithm,
+ TimeSpan messageExpiry,
+ uint partitionsCount,
+ byte? replicationFactor,
+ ulong maxTopicSize)
+ {
+ Name = name;
+ CompressionAlgorithm = compressionAlgorithm;
+ MessageExpiry = messageExpiry;
+ PartitionsCount = partitionsCount;
+ ReplicationFactor = replicationFactor;
+ MaxTopicSize = maxTopicSize;
+ }
+}
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/Models/UpdateTestTopic.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/Models/UpdateTestTopic.cs
new file mode 100644
index 000000000..fbdcb5bb8
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Models/UpdateTestTopic.cs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+using Apache.Iggy.Enums;
+
+namespace Apache.Iggy.Tests.Integrations.Models;
+
+internal record UpdateTestTopic(
+ string Name,
+ CompressionAlgorithm CompressionAlgorithm,
+ ulong MaxTopicSize,
+ TimeSpan MessageExpiry,
+ byte? ReplicationFactor);
diff --git
a/foreign/csharp/Iggy_SDK.Tests.Integration/PersonalAccessTokenTests.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/PersonalAccessTokenTests.cs
index ec7d90f74..b2a284c71 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/PersonalAccessTokenTests.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/PersonalAccessTokenTests.cs
@@ -18,7 +18,6 @@
using Apache.Iggy.Contracts.Auth;
using Apache.Iggy.Enums;
using Apache.Iggy.Exceptions;
-using Apache.Iggy.Tests.Integrations.Attributes;
using Apache.Iggy.Tests.Integrations.Fixtures;
using Shouldly;
@@ -27,7 +26,7 @@ namespace Apache.Iggy.Tests.Integrations;
public class PersonalAccessTokenTests
{
private const string Name = "test-pat";
- private const ulong Expiry = 100_000_000;
+ private static readonly TimeSpan Expiry = TimeSpan.FromHours(1);
[ClassDataSource<PersonalAccessTokenFixture>(Shared = SharedType.PerClass)]
public required PersonalAccessTokenFixture Fixture { get; init; }
@@ -64,7 +63,7 @@ public class PersonalAccessTokenTests
response.ShouldNotBeNull();
response.Count.ShouldBe(1);
response[0].Name.ShouldBe(Name);
- var tokenExpiryDateTimeOffset =
DateTimeOffset.UtcNow.AddMicroseconds(Expiry);
+ var tokenExpiryDateTimeOffset = DateTimeOffset.UtcNow.Add(Expiry);
response[0].ExpiryAt!.Value.ToUniversalTime().ShouldBe(tokenExpiryDateTimeOffset,
TimeSpan.FromMinutes(1));
}
@@ -73,7 +72,7 @@ public class PersonalAccessTokenTests
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task
LoginWithPersonalAccessToken_Should_Be_Successfully(Protocol protocol)
{
- var response = await
Fixture.Clients[protocol].CreatePersonalAccessTokenAsync("test-pat-login",
100_000_000);
+ var response = await
Fixture.Clients[protocol].CreatePersonalAccessTokenAsync("test-pat-login",
Expiry);
var client = await Fixture.IggyServerFixture.CreateClient(protocol);
diff --git a/foreign/csharp/Iggy_SDK.Tests.Integration/StreamsTests.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/StreamsTests.cs
index 32c9b26ed..6caa0b530 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/StreamsTests.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/StreamsTests.cs
@@ -19,11 +19,9 @@ using Apache.Iggy.Contracts;
using Apache.Iggy.Enums;
using Apache.Iggy.Exceptions;
using Apache.Iggy.Messages;
-using Apache.Iggy.Tests.Integrations.Attributes;
using Apache.Iggy.Tests.Integrations.Fixtures;
using Apache.Iggy.Tests.Integrations.Helpers;
using Shouldly;
-using TUnit.Core.Logging;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
namespace Apache.Iggy.Tests.Integrations;
@@ -121,8 +119,8 @@ public class StreamsTests
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task
GetStreamById_WithTopics_Should_ReturnValidResponse(Protocol protocol)
{
- var topicRequest1 = TopicFactory.CreateTopic("Topic1", messageExpiry:
100_000);
- var topicRequest2 = TopicFactory.CreateTopic("Topic2", messageExpiry:
100_000);
+ var topicRequest1 = TopicFactory.CreateTopic("Topic1", messageExpiry:
TimeSpan.FromHours(1));
+ var topicRequest2 = TopicFactory.CreateTopic("Topic2", messageExpiry:
TimeSpan.FromHours(1));
await
Fixture.Clients[protocol].CreateTopicAsync(Identifier.String(Name.GetWithProtocol(protocol)),
topicRequest1.Name, topicRequest1.PartitionsCount, messageExpiry:
topicRequest1.MessageExpiry);
@@ -230,7 +228,8 @@ public class StreamsTests
public async Task
DeleteStream_NotExists_Should_Throw_InvalidResponse(Protocol protocol)
{
await Should.ThrowAsync<IggyInvalidStatusCodeException>(() =>
-
Fixture.Clients[protocol].DeleteStreamAsync(Identifier.String("stream-to-delete".GetWithProtocol(protocol))));
+ Fixture.Clients[protocol]
+
.DeleteStreamAsync(Identifier.String("stream-to-delete".GetWithProtocol(protocol))));
}
[Test]
diff --git a/foreign/csharp/Iggy_SDK.Tests.Integration/TopicsTests.cs
b/foreign/csharp/Iggy_SDK.Tests.Integration/TopicsTests.cs
index 4bb55b42d..d516e790e 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/TopicsTests.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/TopicsTests.cs
@@ -17,13 +17,12 @@
using System.Text;
using Apache.Iggy.Contracts;
-using Apache.Iggy.Contracts.Http;
using Apache.Iggy.Enums;
using Apache.Iggy.Exceptions;
using Apache.Iggy.Messages;
-using Apache.Iggy.Tests.Integrations.Attributes;
using Apache.Iggy.Tests.Integrations.Fixtures;
using Apache.Iggy.Tests.Integrations.Helpers;
+using Apache.Iggy.Tests.Integrations.Models;
using Shouldly;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
@@ -31,14 +30,14 @@ namespace Apache.Iggy.Tests.Integrations;
public class TopicsTests
{
- private static readonly CreateTopicRequest TopicRequest = new("Test
Topic", CompressionAlgorithm.Gzip, 1000, 1,
- 2, 2_000_000_000);
+ private static readonly CreateTestTopic TopicRequest = new("Test Topic",
CompressionAlgorithm.Gzip,
+ TimeSpan.FromMinutes(10), 1, 2, 2_000_000_000);
- private static readonly CreateTopicRequest TopicRequestSecond
- = new("Test Topic 2", CompressionAlgorithm.Gzip, 1000, 1, 2,
2_000_000_000);
+ private static readonly CreateTestTopic TopicRequestSecond
+ = new("Test Topic 2", CompressionAlgorithm.Gzip,
TimeSpan.FromMinutes(10), 1, 2, 2_000_000_000);
- private static readonly UpdateTopicRequest UpdateTopicRequest
- = new("Updated Topic", CompressionAlgorithm.Gzip, 3_000_000_000, 2000,
3);
+ private static readonly UpdateTestTopic UpdateTopicRequest
+ = new("Updated Topic", CompressionAlgorithm.Gzip, 3_000_000_000,
TimeSpan.FromMinutes(10), 3);
[ClassDataSource<TopicsFixture>(Shared = SharedType.PerClass)]
public required TopicsFixture Fixture { get; init; }
@@ -173,18 +172,21 @@ public class TopicsTests
public async Task
Get_Topic_WithPartitions_Should_ReturnValidResponse(Protocol protocol)
{
await Fixture.Clients[protocol]
-
.CreatePartitionsAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
Identifier.String(TopicRequest.Name),
+
.CreatePartitionsAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
+ Identifier.String(TopicRequest.Name),
2);
for (var i = 0; i < 3; i++)
{
await Fixture.Clients[protocol]
-
.SendMessagesAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
Identifier.String(TopicRequest.Name),
+
.SendMessagesAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
+ Identifier.String(TopicRequest.Name),
Partitioning.None(), GetMessages(i + 2));
}
var response = await Fixture.Clients[protocol]
-
.GetTopicByIdAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
Identifier.String(TopicRequest.Name));
+
.GetTopicByIdAsync(Identifier.String(Fixture.StreamId.GetWithProtocol(protocol)),
+ Identifier.String(TopicRequest.Name));
response.ShouldNotBeNull();
response.Id.ShouldBeGreaterThanOrEqualTo(0u);
diff --git a/foreign/csharp/Iggy_SDK/Contracts/TopicResponse.cs
b/foreign/csharp/Iggy_SDK/Contracts/TopicResponse.cs
index ebec03c6b..68297de8a 100644
--- a/foreign/csharp/Iggy_SDK/Contracts/TopicResponse.cs
+++ b/foreign/csharp/Iggy_SDK/Contracts/TopicResponse.cs
@@ -57,7 +57,8 @@ public sealed class TopicResponse
/// <summary>
/// Message expiry in milliseconds.
/// </summary>
- public ulong MessageExpiry { get; init; }
+ [JsonConverter(typeof(TimeSpanConverter))]
+ public TimeSpan MessageExpiry { get; init; }
/// <summary>
/// Maximum topic size in bytes.
diff --git a/foreign/csharp/Iggy_SDK/IggyClient/IIggyPersonalAccessToken.cs
b/foreign/csharp/Iggy_SDK/IggyClient/IIggyPersonalAccessToken.cs
index 023b02d7d..661c294d9 100644
--- a/foreign/csharp/Iggy_SDK/IggyClient/IIggyPersonalAccessToken.cs
+++ b/foreign/csharp/Iggy_SDK/IggyClient/IIggyPersonalAccessToken.cs
@@ -39,13 +39,13 @@ public interface IIggyPersonalAccessToken
/// Creates a new personal access token for the current user.
/// </summary>
/// <param name="name">The name to identify this token.</param>
- /// <param name="expiry">The expiration time in milliseconds from now
(optional, null means no expiration).</param>
+ /// <param name="expiry">The expiration time from now (optional, null
means server default).</param>
/// <param name="token">The cancellation token to cancel the
operation.</param>
/// <returns>
/// A task that represents the asynchronous operation and returns the
created personal access token with its
/// secret value, or null if creation failed.
/// </returns>
- Task<RawPersonalAccessToken?> CreatePersonalAccessTokenAsync(string name,
ulong? expiry = null,
+ Task<RawPersonalAccessToken?> CreatePersonalAccessTokenAsync(string name,
TimeSpan? expiry = null,
CancellationToken token = default);
/// <summary>
diff --git a/foreign/csharp/Iggy_SDK/IggyClient/IIggyTopic.cs
b/foreign/csharp/Iggy_SDK/IggyClient/IIggyTopic.cs
index db8884af3..5298d25bd 100644
--- a/foreign/csharp/Iggy_SDK/IggyClient/IIggyTopic.cs
+++ b/foreign/csharp/Iggy_SDK/IggyClient/IIggyTopic.cs
@@ -58,7 +58,7 @@ public interface IIggyTopic
/// <param name="partitionsCount">The number of partitions for the topic
(max 1000).</param>
/// <param name="compressionAlgorithm">The compression algorithm to use
for messages (default: None).</param>
/// <param name="replicationFactor">The replication factor for the topic
(optional).</param>
- /// <param name="messageExpiry">The message expiry period in milliseconds
(0 = never expire).</param>
+ /// <param name="messageExpiry">The message expiry period (0 for server
default, MaxValue for never expire).</param>
/// <param name="maxTopicSize">The maximum size of the topic in bytes (0 =
unlimited).</param>
/// <param name="token">The cancellation token to cancel the
operation.</param>
/// <returns>
@@ -67,7 +67,7 @@ public interface IIggyTopic
/// </returns>
Task<TopicResponse?> CreateTopicAsync(Identifier streamId, string name,
uint partitionsCount,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
byte? replicationFactor = null,
- ulong messageExpiry = 0, ulong maxTopicSize = 0, CancellationToken
token = default);
+ TimeSpan? messageExpiry = null, ulong maxTopicSize = 0,
CancellationToken token = default);
/// <summary>
/// Updates the configuration of an existing topic.
@@ -81,13 +81,13 @@ public interface IIggyTopic
/// <param name="name">The new name for the topic (max 255
characters).</param>
/// <param name="compressionAlgorithm">The new compression algorithm to
use (default: None).</param>
/// <param name="maxTopicSize">The new maximum size of the topic in bytes
(0 = unlimited).</param>
- /// <param name="messageExpiry">The new message expiry period in
milliseconds (0 = never expire).</param>
+ /// <param name="messageExpiry">The new message expiry period (0 for
server default, MaxValue for never expire).</param>
/// <param name="replicationFactor">The new replication factor
(optional).</param>
/// <param name="token">The cancellation token to cancel the
operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task UpdateTopicAsync(Identifier streamId, Identifier topicId, string name,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
ulong maxTopicSize = 0,
- ulong messageExpiry = 0, byte? replicationFactor = null,
CancellationToken token = default);
+ TimeSpan? messageExpiry = null, byte? replicationFactor = null,
CancellationToken token = default);
/// <summary>
/// Deletes an existing topic and all its associated messages and
partitions.
diff --git
a/foreign/csharp/Iggy_SDK/IggyClient/Implementations/HttpMessageStream.cs
b/foreign/csharp/Iggy_SDK/IggyClient/Implementations/HttpMessageStream.cs
index 4e46b47de..73db25c1d 100644
--- a/foreign/csharp/Iggy_SDK/IggyClient/Implementations/HttpMessageStream.cs
+++ b/foreign/csharp/Iggy_SDK/IggyClient/Implementations/HttpMessageStream.cs
@@ -30,6 +30,7 @@ using Apache.Iggy.Exceptions;
using Apache.Iggy.Kinds;
using Apache.Iggy.Messages;
using Apache.Iggy.StringHandlers;
+using Apache.Iggy.Utils;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
namespace Apache.Iggy.IggyClient.Implementations;
@@ -55,10 +56,7 @@ public class HttpMessageStream : IIggyClient
_jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- Converters =
- {
- new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower)
- }
+ Converters = { new
JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) }
};
}
@@ -147,15 +145,14 @@ public class HttpMessageStream : IIggyClient
/// <inheritdoc />
public async Task<TopicResponse?> CreateTopicAsync(Identifier streamId,
string name, uint partitionsCount,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
byte? replicationFactor = null,
- ulong messageExpiry = 0, ulong maxTopicSize = 0,
- CancellationToken token = default)
+ TimeSpan? messageExpiry = null, ulong maxTopicSize = 0,
CancellationToken token = default)
{
var json = JsonSerializer.Serialize(new CreateTopicRequest
{
Name = name,
CompressionAlgorithm = compressionAlgorithm,
MaxTopicSize = maxTopicSize,
- MessageExpiry = messageExpiry,
+ MessageExpiry = DurationHelpers.ToDuration(messageExpiry),
PartitionsCount = partitionsCount,
ReplicationFactor = replicationFactor
}, _jsonSerializerOptions);
@@ -176,11 +173,11 @@ public class HttpMessageStream : IIggyClient
/// <inheritdoc />
public async Task UpdateTopicAsync(Identifier streamId, Identifier
topicId, string name,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
- ulong maxTopicSize = 0, ulong messageExpiry = 0, byte?
replicationFactor = null,
+ ulong maxTopicSize = 0, TimeSpan? messageExpiry = null, byte?
replicationFactor = null,
CancellationToken token = default)
{
var json = JsonSerializer.Serialize(
- new UpdateTopicRequest(name, compressionAlgorithm, maxTopicSize,
messageExpiry, replicationFactor),
+ new UpdateTopicRequest(name, compressionAlgorithm, maxTopicSize,
DurationHelpers.ToDuration(messageExpiry), replicationFactor),
_jsonSerializerOptions);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = await
_httpClient.PutAsync($"/streams/{streamId}/topics/{topicId}", data, token);
@@ -708,10 +705,11 @@ public class HttpMessageStream : IIggyClient
}
/// <inheritdoc />
- public async Task<RawPersonalAccessToken?>
CreatePersonalAccessTokenAsync(string name, ulong? expiry = null,
+ public async Task<RawPersonalAccessToken?>
CreatePersonalAccessTokenAsync(string name, TimeSpan? expiry = null,
CancellationToken token = default)
{
- var json = JsonSerializer.Serialize(new
CreatePersonalAccessTokenRequest(name, expiry), _jsonSerializerOptions);
+ var json = JsonSerializer.Serialize(
+ new CreatePersonalAccessTokenRequest(name,
DurationHelpers.ToDuration(expiry)), _jsonSerializerOptions);
var content = new StringContent(json, Encoding.UTF8,
"application/json");
var response = await _httpClient.PostAsync("/personal-access-tokens",
content, token);
diff --git
a/foreign/csharp/Iggy_SDK/IggyClient/Implementations/TcpMessageStream.cs
b/foreign/csharp/Iggy_SDK/IggyClient/Implementations/TcpMessageStream.cs
index 3425b1fa0..1d7c665a6 100644
--- a/foreign/csharp/Iggy_SDK/IggyClient/Implementations/TcpMessageStream.cs
+++ b/foreign/csharp/Iggy_SDK/IggyClient/Implementations/TcpMessageStream.cs
@@ -209,10 +209,11 @@ public sealed class TcpMessageStream : IIggyClient
/// <inheritdoc />
public async Task<TopicResponse?> CreateTopicAsync(Identifier streamId,
string name, uint partitionsCount,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
byte? replicationFactor = null,
- ulong messageExpiry = 0, ulong maxTopicSize = 0, CancellationToken
token = default)
+ TimeSpan? messageExpiry = null, ulong maxTopicSize = 0,
CancellationToken token = default)
{
+ var messageExpiryValue = DurationHelpers.ToDuration(messageExpiry);
var message = TcpContracts.CreateTopic(streamId, name,
partitionsCount, compressionAlgorithm,
- replicationFactor, messageExpiry, maxTopicSize);
+ replicationFactor, messageExpiryValue, maxTopicSize);
var payload = new byte[4 + BufferSizes.INITIAL_BYTES_LENGTH +
message.Length];
TcpMessageStreamHelpers.CreatePayload(payload, message,
CommandCodes.CREATE_TOPIC_CODE);
@@ -229,11 +230,12 @@ public sealed class TcpMessageStream : IIggyClient
/// <inheritdoc />
public async Task UpdateTopicAsync(Identifier streamId, Identifier
topicId, string name,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
- ulong maxTopicSize = 0, ulong messageExpiry = 0, byte?
replicationFactor = null,
+ ulong maxTopicSize = 0, TimeSpan? messageExpiry = null, byte?
replicationFactor = null,
CancellationToken token = default)
{
+ var messageExpiryValue = DurationHelpers.ToDuration(messageExpiry);
var message = TcpContracts.UpdateTopic(streamId, topicId, name,
compressionAlgorithm, maxTopicSize,
- messageExpiry, replicationFactor);
+ messageExpiryValue, replicationFactor);
var payload = new byte[4 + BufferSizes.INITIAL_BYTES_LENGTH +
message.Length];
TcpMessageStreamHelpers.CreatePayload(payload, message,
CommandCodes.UPDATE_TOPIC_CODE);
@@ -745,10 +747,10 @@ public sealed class TcpMessageStream : IIggyClient
}
/// <inheritdoc />
- public async Task<RawPersonalAccessToken?>
CreatePersonalAccessTokenAsync(string name, ulong? expiry = 0,
+ public async Task<RawPersonalAccessToken?>
CreatePersonalAccessTokenAsync(string name, TimeSpan? expiry = null,
CancellationToken token = default)
{
- var message = TcpContracts.CreatePersonalAccessToken(name, expiry);
+ var message = TcpContracts.CreatePersonalAccessToken(name,
DurationHelpers.ToDuration(expiry));
var payload = new byte[4 + BufferSizes.INITIAL_BYTES_LENGTH +
message.Length];
TcpMessageStreamHelpers.CreatePayload(payload, message,
CommandCodes.CREATE_PERSONAL_ACCESS_TOKEN_CODE);
diff --git a/foreign/csharp/Iggy_SDK/Iggy_SDK.csproj
b/foreign/csharp/Iggy_SDK/Iggy_SDK.csproj
index 634855ae1..6d794d291 100644
--- a/foreign/csharp/Iggy_SDK/Iggy_SDK.csproj
+++ b/foreign/csharp/Iggy_SDK/Iggy_SDK.csproj
@@ -7,7 +7,7 @@
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<AssemblyName>Apache.Iggy</AssemblyName>
<RootNamespace>Apache.Iggy</RootNamespace>
- <PackageVersion>0.6.3-edge.1</PackageVersion>
+ <PackageVersion>0.6.3-edge.2</PackageVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/foreign/csharp/Iggy_SDK/JsonConverters/TimeSpanConverter.cs
b/foreign/csharp/Iggy_SDK/JsonConverters/TimeSpanConverter.cs
new file mode 100644
index 000000000..842ab63cf
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK/JsonConverters/TimeSpanConverter.cs
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Apache.Iggy.Utils;
+
+namespace Apache.Iggy.JsonConverters;
+
+internal sealed class TimeSpanConverter : JsonConverter<TimeSpan>
+{
+ public override TimeSpan Read(ref Utf8JsonReader reader, Type
typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.Number)
+ {
+ throw new JsonException("Expected a number token for TimeSpan
conversion.");
+ }
+
+ var microseconds = reader.GetUInt64();
+ return DurationHelpers.FromDuration(microseconds);
+ }
+
+ public override void Write(Utf8JsonWriter writer, TimeSpan value,
JsonSerializerOptions options)
+ {
+ var microseconds = DurationHelpers.ToDuration(value);
+ writer.WriteNumberValue(microseconds);
+ }
+}
diff --git a/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
b/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
index 576204e4e..6c3435d2a 100644
--- a/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
+++ b/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
@@ -24,6 +24,7 @@ using Apache.Iggy.Enums;
using Apache.Iggy.Extensions;
using Apache.Iggy.Headers;
using Apache.Iggy.Messages;
+using Apache.Iggy.Utils;
namespace Apache.Iggy.Mappers;
@@ -622,7 +623,7 @@ internal static class BinaryMapper
MessagesCount = messagesCount,
Size = sizeBytes,
CreatedAt =
DateTimeOffsetUtils.FromUnixTimeMicroSeconds(createdAt).LocalDateTime,
- MessageExpiry = messageExpiry,
+ MessageExpiry = DurationHelpers.FromDuration(messageExpiry),
ReplicationFactor = replicationFactor,
MaxTopicSize = maxTopicSize
}, readBytes);
diff --git a/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherBuilder.cs
b/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherBuilder.cs
index 4e87dd814..53fe68ee5 100644
--- a/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherBuilder.cs
+++ b/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherBuilder.cs
@@ -166,7 +166,7 @@ public class IggyPublisherBuilder
/// <returns>The builder instance for method chaining.</returns>
public IggyPublisherBuilder CreateTopicIfNotExists(string name, uint
topicPartitionsCount = 1,
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.None,
byte? replicationFactor = null,
- ulong messageExpiry = 0, ulong maxTopicSize = 0)
+ TimeSpan messageExpiry = default, ulong maxTopicSize = 0)
{
Config.CreateTopic = true;
Config.TopicName = name;
@@ -216,7 +216,6 @@ public class IggyPublisherBuilder
}
-
/// <summary>
/// Configures retry behavior for failed message sends.
/// Uses exponential backoff with configurable parameters.
diff --git a/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherConfig.cs
b/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherConfig.cs
index 3419f2c33..2d44fa789 100644
--- a/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherConfig.cs
+++ b/foreign/csharp/Iggy_SDK/Publishers/IggyPublisherConfig.cs
@@ -166,11 +166,11 @@ public class IggyPublisherConfig
public byte? TopicReplicationFactor { get; set; }
/// <summary>
- /// Gets or sets the message expiry time in seconds (0 for no expiry).
+ /// Gets or sets the message expiry time (0 for server default,
TimeSpan.MaxValue for no expiry).
/// Messages older than this will be automatically deleted.
/// Only used when <see cref="CreateTopic" /> is true.
/// </summary>
- public ulong TopicMessageExpiry { get; set; }
+ public TimeSpan TopicMessageExpiry { get; set; } = TimeSpan.Zero;
/// <summary>
/// Gets or sets the maximum size of the topic in bytes (0 for
unlimited).
diff --git a/foreign/csharp/Iggy_SDK/Utils/DurationHelpers.cs
b/foreign/csharp/Iggy_SDK/Utils/DurationHelpers.cs
new file mode 100644
index 000000000..12a19d7c6
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK/Utils/DurationHelpers.cs
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 Apache.Iggy.Utils;
+
+/// <summary>
+/// Converts between Iggy durations (ulong microseconds) and .NET TimeSpan.
+/// </summary>
+/// <remarks>
+/// Iggy stores durations (e.g., message expiry) as ulong microseconds,
but TimeSpan uses long ticks internally.
+/// Since ulong.MaxValue exceeds what TimeSpan can represent from
microseconds, we map ulong.MaxValue to
+/// TimeSpan.MaxValue and vice versa.
+/// </remarks>
+public static class DurationHelpers
+{
+ /// <summary>
+ /// Converts microseconds to TimeSpan. Returns TimeSpan.MaxValue if
duration exceeds representable range.
+ /// </summary>
+ public static TimeSpan FromDuration(ulong duration)
+ {
+ if (duration >= long.MaxValue / TimeSpan.TicksPerMicrosecond)
+ {
+ return TimeSpan.MaxValue;
+ }
+
+ return TimeSpan.FromMicroseconds(duration);
+ }
+
+ /// <summary>
+ /// Converts TimeSpan to microseconds. Returns ulong.MaxValue if
TimeSpan.MaxValue is passed.
+ /// </summary>
+ public static ulong ToDuration(TimeSpan? duration)
+ {
+ if (duration == null)
+ {
+ return 0;
+ }
+
+ if (duration == TimeSpan.MaxValue)
+ {
+ return ulong.MaxValue;
+ }
+
+ return (ulong)duration.Value.TotalMicroseconds;
+ }
+}
diff --git a/foreign/csharp/Iggy_SDK_Tests/UtilityTests/DurationHelperTests.cs
b/foreign/csharp/Iggy_SDK_Tests/UtilityTests/DurationHelperTests.cs
new file mode 100644
index 000000000..7e44b7415
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK_Tests/UtilityTests/DurationHelperTests.cs
@@ -0,0 +1,231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+using Apache.Iggy.Utils;
+
+namespace Apache.Iggy.Tests.UtilityTests;
+
+public sealed class DurationHelperTests
+{
+ [Fact]
+ public void FromDuration_Zero_ReturnsZeroTimeSpan()
+ {
+ var result = DurationHelpers.FromDuration(0);
+
+ Assert.Equal(TimeSpan.Zero, result);
+ }
+
+ [Fact]
+ public void FromDuration_NormalValue_ReturnsCorrectTimeSpan()
+ {
+ ulong microseconds = 1_000_000;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.FromSeconds(1), result);
+ }
+
+ [Fact]
+ public void FromDuration_MillisecondsValue_ReturnsCorrectTimeSpan()
+ {
+ ulong microseconds = 5_000;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.FromMilliseconds(5), result);
+ }
+
+ [Fact]
+ public void FromDuration_LargeValidValue_ReturnsCorrectTimeSpan()
+ {
+ var microseconds = (ulong)TimeSpan.MaxValue.TotalMicroseconds;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.FromMicroseconds(microseconds), result);
+ }
+
+ [Fact]
+ public void FromDuration_ValueExceedsLongMaxValue_ReturnsTimeSpanMaxValue()
+ {
+ var microseconds = (ulong)long.MaxValue + 1;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.MaxValue, result);
+ }
+
+ [Fact]
+ public void
FromDuration_ValueLongMaxValueDividedBy100_ReturnsNotTimeSpanMaxValue()
+ {
+ var microseconds = (ulong)long.MaxValue / 100;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.NotEqual(TimeSpan.MaxValue, result);
+ }
+
+ [Fact]
+ public void
FromDuration_ValueLongMaxValueDividedByTicksPerMicrosecond_ReturnsTimeSpanMaxValue()
+ {
+ var microseconds = (ulong)long.MaxValue / TimeSpan.TicksPerMicrosecond;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.MaxValue, result);
+ }
+
+ [Fact]
+ public void FromDuration_ValueLongMaxValue_ReturnsTimeSpanMaxValue()
+ {
+ var microseconds = (ulong)long.MaxValue;
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.MaxValue, result);
+ }
+
+ [Fact]
+ public void FromDuration_UlongMaxValue_ReturnsTimeSpanMaxValue()
+ {
+ var result = DurationHelpers.FromDuration(ulong.MaxValue);
+
+ Assert.Equal(TimeSpan.MaxValue, result);
+ }
+
+ [Fact]
+ public void ToDuration_Null_ReturnsZero()
+ {
+ var result = DurationHelpers.ToDuration(null);
+
+ Assert.Equal(0UL, result);
+ }
+
+ [Fact]
+ public void ToDuration_ZeroTimeSpan_ReturnsZero()
+ {
+ var result = DurationHelpers.ToDuration(TimeSpan.Zero);
+
+ Assert.Equal(0UL, result);
+ }
+
+ [Fact]
+ public void ToDuration_NormalValue_ReturnsCorrectMicroseconds()
+ {
+ var duration = TimeSpan.FromSeconds(1);
+ var result = DurationHelpers.ToDuration(duration);
+
+ Assert.Equal(1_000_000UL, result);
+ }
+
+ [Fact]
+ public void ToDuration_MillisecondsValue_ReturnsCorrectMicroseconds()
+ {
+ var duration = TimeSpan.FromMilliseconds(5);
+ var result = DurationHelpers.ToDuration(duration);
+
+ Assert.Equal(5_000UL, result);
+ }
+
+ [Fact]
+ public void ToDuration_TimeSpanMaxValue_ReturnsUlongMaxValue()
+ {
+ var result = DurationHelpers.ToDuration(TimeSpan.MaxValue);
+
+ Assert.Equal(ulong.MaxValue, result);
+ }
+
+ [Fact]
+ public void ToDuration_LargeValue_ReturnsCorrectMicroseconds()
+ {
+ var duration = TimeSpan.FromDays(365);
+ var result = DurationHelpers.ToDuration(duration);
+
+ var expectedMicroseconds = (ulong)(365 * 24 * 60 * 60 * 1_000_000L);
+ Assert.Equal(expectedMicroseconds, result);
+ }
+
+ [Fact]
+ public void RoundTrip_NormalValue_PreservesValue()
+ {
+ ulong originalMicroseconds = 1_000_000;
+ var timeSpan = DurationHelpers.FromDuration(originalMicroseconds);
+ var result = DurationHelpers.ToDuration(timeSpan);
+
+ Assert.Equal(originalMicroseconds, result);
+ }
+
+ [Fact]
+ public void RoundTrip_Zero_PreservesValue()
+ {
+ ulong originalMicroseconds = 0;
+ var timeSpan = DurationHelpers.FromDuration(originalMicroseconds);
+ var result = DurationHelpers.ToDuration(timeSpan);
+
+ Assert.Equal(originalMicroseconds, result);
+ }
+
+ [Fact]
+ public void RoundTrip_UlongMaxValue_PreservesValue()
+ {
+ var originalMicroseconds = ulong.MaxValue;
+ var timeSpan = DurationHelpers.FromDuration(originalMicroseconds);
+ var result = DurationHelpers.ToDuration(timeSpan);
+
+ Assert.Equal(originalMicroseconds, result);
+ }
+
+ [Fact]
+ public void RoundTrip_TimeSpan_PreservesValue()
+ {
+ var originalTimeSpan = TimeSpan.FromHours(2);
+ var microseconds = DurationHelpers.ToDuration(originalTimeSpan);
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(originalTimeSpan, result);
+ }
+
+ [Fact]
+ public void RoundTrip_TimeSpanMaxValue_PreservesValue()
+ {
+ var originalTimeSpan = TimeSpan.MaxValue;
+ var microseconds = DurationHelpers.ToDuration(originalTimeSpan);
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(originalTimeSpan, result);
+ }
+
+ [Theory]
+ [InlineData(1UL)]
+ [InlineData(1000UL)]
+ [InlineData(1_000_000UL)]
+ [InlineData(60_000_000UL)]
+ [InlineData(3_600_000_000UL)]
+ public void FromDuration_VariousValues_ReturnsExpectedTimeSpan(ulong
microseconds)
+ {
+ var result = DurationHelpers.FromDuration(microseconds);
+
+ Assert.Equal(TimeSpan.FromMicroseconds(microseconds), result);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(100)]
+ [InlineData(1000)]
+ [InlineData(60000)]
+ [InlineData(3600000)]
+ public void ToDuration_VariousMilliseconds_ReturnsExpectedMicroseconds(int
milliseconds)
+ {
+ var duration = TimeSpan.FromMilliseconds(milliseconds);
+ var result = DurationHelpers.ToDuration(duration);
+
+ Assert.Equal((ulong)milliseconds * 1000, result);
+ }
+}