blob: c15f6cdb99b1cf6bb5e0c665f2f34692b0ebafe5 [file]
// // 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;
using Apache.Iggy.Contracts;
using Apache.Iggy.Enums;
using Apache.Iggy.Exceptions;
using Apache.Iggy.Messages;
using Apache.Iggy.Tests.Integrations.Fixtures;
using Shouldly;
using Partitioning = Apache.Iggy.Kinds.Partitioning;
namespace Apache.Iggy.Tests.Integrations;
public class TopicsTests
{
[ClassDataSource<IggyServerFixture>(Shared = SharedType.PerAssembly)]
public required IggyServerFixture Fixture { get; init; }
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Create_NewTopic_Should_Return_Successfully(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-create-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
var response = await client.CreateTopicAsync(
Identifier.String(streamName), "Test Topic", 2, CompressionAlgorithm.Gzip,
1, TimeSpan.FromMinutes(10), 2_000_000_000);
response.ShouldNotBeNull();
response.Id.ShouldBeGreaterThanOrEqualTo(0u);
response.CreatedAt.UtcDateTime.ShouldBe(DateTimeOffset.UtcNow.UtcDateTime, TimeSpan.FromMinutes(1));
response.Name.ShouldBe("Test Topic");
response.CompressionAlgorithm.ShouldBe(CompressionAlgorithm.Gzip);
response.Partitions!.Count().ShouldBe(2);
response.MessageExpiry.ShouldBe(TimeSpan.FromMinutes(10));
response.Size.ShouldBe(0u);
response.PartitionsCount.ShouldBe(2u);
response.ReplicationFactor.ShouldBe((byte?)1);
response.MaxTopicSize.ShouldBe(2_000_000_000u);
response.MessagesCount.ShouldBe(0u);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Create_DuplicateTopic_Should_Throw_InvalidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-dup-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "Dup Topic", 1);
await Should.ThrowAsync<IggyInvalidStatusCodeException>(
client.CreateTopicAsync(Identifier.String(streamName), "Dup Topic", 1));
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Get_ExistingTopic_Should_ReturnValidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-get-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "Get Topic", 2,
CompressionAlgorithm.Gzip, 1, TimeSpan.FromMinutes(10), 2_000_000_000);
var response = await client.GetTopicByIdAsync(Identifier.String(streamName), Identifier.Numeric(0));
response.ShouldNotBeNull();
response.Id.ShouldBeGreaterThanOrEqualTo(0u);
response.CreatedAt.UtcDateTime.ShouldBe(DateTimeOffset.UtcNow.UtcDateTime, TimeSpan.FromMinutes(1));
response.Name.ShouldBe("Get Topic");
response.CompressionAlgorithm.ShouldBe(CompressionAlgorithm.Gzip);
response.Partitions!.Count().ShouldBe(2);
response.MessageExpiry.ShouldBe(TimeSpan.FromMinutes(10));
response.Size.ShouldBe(0u);
response.PartitionsCount.ShouldBe(2u);
response.ReplicationFactor.ShouldBe((byte?)1);
response.MaxTopicSize.ShouldBe(2_000_000_000u);
response.MessagesCount.ShouldBe(0u);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Get_ExistingTopic_ByName_Should_ReturnValidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-getname-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "Name Topic", 2,
CompressionAlgorithm.Gzip, 1, TimeSpan.FromMinutes(10), 2_000_000_000);
var response = await client.GetTopicByIdAsync(Identifier.String(streamName),
Identifier.String("Name Topic"));
response.ShouldNotBeNull();
response.Id.ShouldBeGreaterThanOrEqualTo(0u);
response.Name.ShouldBe("Name Topic");
response.CompressionAlgorithm.ShouldBe(CompressionAlgorithm.Gzip);
response.Partitions!.Count().ShouldBe(2);
response.MessageExpiry.ShouldBe(TimeSpan.FromMinutes(10));
response.Size.ShouldBe(0u);
response.PartitionsCount.ShouldBe(2u);
response.ReplicationFactor.ShouldBe((byte?)1);
response.MaxTopicSize.ShouldBe(2_000_000_000u);
response.MessagesCount.ShouldBe(0u);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Get_ExistingTopics_Should_ReturnValidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-list-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "List Topic 1", 2,
CompressionAlgorithm.Gzip, 1, TimeSpan.FromMinutes(10), 2_000_000_000);
await client.CreateTopicAsync(Identifier.String(streamName), "List Topic 2", 2,
CompressionAlgorithm.Gzip, 1, TimeSpan.FromMinutes(10), 2_000_000_000);
IReadOnlyList<TopicResponse> response = await client.GetTopicsAsync(Identifier.String(streamName));
response.ShouldNotBeNull();
response.Count().ShouldBe(2);
response.Select(x => x.Name).ShouldContain("List Topic 1");
response.Select(x => x.Name).ShouldContain("List Topic 2");
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Get_Topic_WithPartitions_Should_ReturnValidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-parts-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "Parts Topic", 1);
await client.CreatePartitionsAsync(Identifier.String(streamName),
Identifier.String("Parts Topic"), 2);
for (var i = 0; i < 3; i++)
{
await client.SendMessagesAsync(Identifier.String(streamName),
Identifier.String("Parts Topic"),
Partitioning.None(), GetMessages(i + 2));
}
var response = await client.GetTopicByIdAsync(Identifier.String(streamName),
Identifier.String("Parts Topic"));
response.ShouldNotBeNull();
response.Id.ShouldBeGreaterThanOrEqualTo(0u);
response.Name.ShouldBe("Parts Topic");
response.Partitions!.Count().ShouldBe(3);
response.Size.ShouldBeGreaterThan(0u);
response.PartitionsCount.ShouldBe(3u);
response.MessagesCount.ShouldBe(9u);
response.Partitions.ShouldNotBeNull();
response.Partitions.ShouldAllBe(x => x.MessagesCount > 0);
response.Partitions.ShouldAllBe(x => x.CreatedAt > DateTimeOffset.UtcNow.AddMinutes(-5));
response.Partitions.ShouldAllBe(x => x.SegmentsCount > 0);
response.Partitions.ShouldAllBe(x => x.CurrentOffset > 0);
response.Partitions.ShouldAllBe(x => x.Size > 0);
response.Partitions.ShouldAllBe(x => x.Id >= 0);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Update_ExistingTopic_Should_UpdateTopic_Successfully(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-update-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
var topicToUpdate = await client.CreateTopicAsync(Identifier.String(streamName), "topic-to-update", 1);
topicToUpdate.ShouldNotBeNull();
await Should.NotThrowAsync(client.UpdateTopicAsync(
Identifier.String(streamName),
Identifier.Numeric(topicToUpdate.Id), "Updated Topic",
CompressionAlgorithm.Gzip, 3_000_000_000, TimeSpan.FromMinutes(10), 3));
var result = await client.GetTopicByIdAsync(
Identifier.String(streamName),
Identifier.Numeric(topicToUpdate.Id));
result.ShouldNotBeNull();
result!.Name.ShouldBe("Updated Topic");
result.MessageExpiry.ShouldBe(TimeSpan.FromMinutes(10));
result.CompressionAlgorithm.ShouldBe(CompressionAlgorithm.Gzip);
result.MaxTopicSize.ShouldBe(3_000_000_000u);
result.ReplicationFactor.ShouldBe((byte?)3);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Purge_ExistingTopic_Should_PurgeTopic_Successfully(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-purge-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await client.CreateTopicAsync(Identifier.String(streamName), "Purge Topic", 1);
await client.SendMessagesAsync(Identifier.String(streamName),
Identifier.String("Purge Topic"), Partitioning.None(), GetMessages(5));
var beforePurge = await client.GetTopicByIdAsync(Identifier.String(streamName),
Identifier.String("Purge Topic"));
beforePurge.ShouldNotBeNull();
beforePurge.MessagesCount.ShouldBe(5u);
beforePurge.Size.ShouldBeGreaterThan(0u);
await Should.NotThrowAsync(client.PurgeTopicAsync(
Identifier.String(streamName), Identifier.String("Purge Topic")));
var afterPurge = await client.GetTopicByIdAsync(Identifier.String(streamName),
Identifier.String("Purge Topic"));
afterPurge.ShouldNotBeNull();
afterPurge!.MessagesCount.ShouldBe(0u);
afterPurge.Size.ShouldBe(0u);
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Delete_ExistingTopic_Should_DeleteTopic_Successfully(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-del-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
var topicToDelete = await client.CreateTopicAsync(Identifier.String(streamName), "topic-to-delete", 1);
topicToDelete.ShouldNotBeNull();
await Should.NotThrowAsync(client.DeleteTopicAsync(
Identifier.String(streamName), Identifier.Numeric(topicToDelete.Id)));
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Delete_NonExistingTopic_Should_Throw_InvalidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-delnone-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
await Should.ThrowAsync<IggyInvalidStatusCodeException>(client.DeleteTopicAsync(
Identifier.String(streamName), Identifier.String("nonexistent-topic")));
}
[Test]
[MethodDataSource<IggyServerFixture>(nameof(IggyServerFixture.ProtocolData))]
public async Task Get_NonExistingTopic_Should_Throw_InvalidResponse(Protocol protocol)
{
var client = await Fixture.CreateAuthenticatedClient(protocol);
var streamName = $"topic-getnone-{Guid.NewGuid():N}";
await client.CreateStreamAsync(streamName);
var topic = await client.GetTopicByIdAsync(
Identifier.String(streamName), Identifier.String("nonexistent-topic"));
topic.ShouldBeNull();
}
private static Message[] GetMessages(int count)
{
var messages = new List<Message>(count);
for (var i = 0; i < count; i++)
{
messages.Add(new Message(Guid.NewGuid(), Encoding.UTF8.GetBytes($"Test message {i + 1}")));
}
return messages.ToArray();
}
}