Anapher/Strive

View on GitHub
src/Services/ConferenceManagement/Strive.Infrastructure.Tests/KeyValue/InMemory/InMemoryKeyValueDatabaseTests.cs

Summary

Maintainability
D
2 days
Test Coverage
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Strive.Infrastructure.KeyValue;
using Strive.Infrastructure.KeyValue.InMemory;
using Strive.Tests.Utils;
using Xunit;

namespace Strive.Infrastructure.Tests.KeyValue.InMemory
{
    public class InMemoryKeyValueDatabaseTests
    {
        private readonly InMemoryKeyValueData _data = new();
        private readonly InMemoryKeyValueDatabase _database;

        public InMemoryKeyValueDatabaseTests()
        {
            _database = new InMemoryKeyValueDatabase(_data,
                new OptionsWrapper<KeyValueDatabaseOptions>(new KeyValueDatabaseOptions()));
        }

        [Fact]
        public async Task KeySetAsync_WithValidKey_KeyExistsInDictionary()
        {
            const string key = "test";
            const string value = "hello world";

            // act
            await _database.SetAsync(key, value);

            // assert
            var entry = Assert.Single(_data.Data);
            Assert.Equal(key, entry.Key);
            Assert.Equal(value, entry.Value);
        }

        [Fact]
        public async Task KeyGetAsync_KeyExists_ReturnValue()
        {
            const string key = "test";
            const string value = "hello world";

            // act
            await _database.SetAsync(key, value);
            var result = await _database.GetAsync(key);

            // assert
            Assert.Equal(value, result);
        }

        [Fact]
        public async Task KeyGetAsync_KeyDoesNotExist_ReturnNull()
        {
            const string key = "test";

            // act
            var result = await _database.GetAsync(key);

            // assert
            Assert.Null(result);
        }

        [Fact]
        public async Task KeyDeleteAsync_KeyDoesNotExist_ReturnFalse()
        {
            const string key = "test";

            // act
            var result = await _database.KeyDeleteAsync(key);

            // assert
            Assert.False(result);
        }

        [Fact]
        public async Task KeyDeleteAsync_KeyDoesExist_DeleteAndReturnTrue()
        {
            const string key = "test";
            const string value = "hello world";

            // act
            await _database.SetAsync(key, value);

            // assert
            var result = await _database.KeyDeleteAsync(key);
            Assert.True(result);
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task HashGetAsync_KeyDoesNotExist_ReturnNull()
        {
            const string key = "test";
            const string field = "field1";

            // act
            var result = await _database.HashGetAsync(key, field);

            // assert
            Assert.Null(result);
        }

        [Fact]
        public async Task HashGetAsync_KeyDoesExist_ReturnValue()
        {
            const string key = "test";
            const string field = "field1";
            const string value = "hello world";

            // act
            await _database.HashSetAsync(key, field, value);

            // assert
            var result = await _database.HashGetAsync(key, field);
            Assert.Equal(value, result);
        }

        [Fact]
        public async Task HashSetAsync_KeyDoesNotExist_CreateKeyAndAddField()
        {
            const string key = "test";
            const string field = "field1";
            const string value = "hello world";

            // act
            await _database.HashSetAsync(key, field, value);

            // assert
            Assert.Single(_data.Data);
        }

        [Fact]
        public async Task HashSetAsync_KeyDoesExist_AddAnotherField()
        {
            const string key = "test";
            const string field = "field1";
            const string field2 = "field2";
            const string value = "hello world";
            const string value2 = "hello world2";

            // act
            await _database.HashSetAsync(key, field, value);
            await _database.HashSetAsync(key, field2, value2);

            // assert
            var result1 = await _database.HashGetAsync(key, field);
            var result2 = await _database.HashGetAsync(key, field2);

            Assert.Equal(value, result1);
            Assert.Equal(value2, result2);
        }

        [Fact]
        public async Task HashSetAsync_KeyDoesNotExist_SetMultipleFields()
        {
            const string key = "test";

            var fields = new Dictionary<string, string> {{"field1", "test1"}, {"field2", "test2"}};

            // act
            await _database.HashSetAsync(key, fields);

            // assert
            var result = await _database.HashGetAllAsync(key);
            AssertKeyValuePairsMatchIgnoreOrder(fields, result);
        }

        [Fact]
        public async Task HashSetAsync_KeyDoesExist_SetMultipleFields()
        {
            const string key = "test";
            const string existingField = "field";
            const string existingFieldValue = "test";

            var fields = new Dictionary<string, string> {{"field1", "test1"}, {"field2", "test2"}};

            // arrange
            await _database.HashSetAsync(key, existingField, existingFieldValue);

            // act
            await _database.HashSetAsync(key, fields);

            // assert
            var result = await _database.HashGetAllAsync(key);
            var expectedFields =
                fields.Concat(new[] {new KeyValuePair<string, string>(existingField, existingFieldValue)});

            AssertKeyValuePairsMatchIgnoreOrder(expectedFields, result);
        }

        [Fact]
        public async Task HashExistsAsync_KeyDoesNotExist_ReturnFalse()
        {
            const string key = "test";
            const string field = "field";

            // act
            var result = await _database.HashExistsAsync(key, field);

            // assert
            Assert.False(result);
        }

        [Fact]
        public async Task HashExistsAsync_KeyExistsButFieldDoesNotExist_ReturnFalse()
        {
            const string key = "test";
            const string field = "field";

            await _database.HashSetAsync(key, "randomField", "test");

            // act
            var result = await _database.HashExistsAsync(key, field);

            // assert
            Assert.False(result);
        }

        [Fact]
        public async Task HashDeleteAsync_KeyDoesNotExist_ReturnFalse()
        {
            const string key = "test";
            const string field = "field";

            // act
            var result = await _database.HashDeleteAsync(key, field);

            // assert
            Assert.False(result);
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task HashDeleteAsync_KeyExistsButFieldDoesNotExist_ReturnFalse()
        {
            const string key = "test";
            const string field = "field";

            await _database.HashSetAsync(key, "randomField", "test");

            // act
            var result = await _database.HashDeleteAsync(key, field);

            // assert
            Assert.False(result);
        }

        [Fact]
        public async Task HashDeleteAsync_SingleFieldExists_DeleteKeyAndReturnTrue()
        {
            const string key = "test";
            const string field = "field";

            await _database.HashSetAsync(key, field, "test");

            // act
            var result = await _database.HashDeleteAsync(key, field);

            // assert
            Assert.True(result);
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task HashDeleteAsync_MultipleFieldsExists_PreserveKeyAndReturnTrue()
        {
            const string key = "test";
            const string fieldToDelete = "field";
            const string preservedField = "field2";
            const string preservedFieldValue = "test";

            // arrange
            await _database.HashSetAsync(key, fieldToDelete, "test");
            await _database.HashSetAsync(key, preservedField, preservedFieldValue);

            // act
            var result = await _database.HashDeleteAsync(key, fieldToDelete);

            // assert
            Assert.True(result);

            var preservedFieldExists = await _database.HashExistsAsync(key, preservedField);
            Assert.True(preservedFieldExists);

            var fieldToDeleteExists = await _database.HashExistsAsync(key, fieldToDelete);
            Assert.False(fieldToDeleteExists);
        }

        [Fact]
        public async Task HashGetAllAsync_KeyDoesNotExist_ReturnEmptyResult()
        {
            const string key = "test";

            // act
            var result = await _database.HashGetAllAsync(key);

            // assert
            Assert.NotNull(result);
            Assert.Empty(result);
        }

        [Fact]
        public async Task HashGetAllAsync_FieldsExist_ReturnFields()
        {
            const string key = "test";
            const string field = "field";
            const string fieldValue = "val";

            // arrange
            await _database.HashSetAsync(key, field, fieldValue);

            // act
            var result = await _database.HashGetAllAsync(key);

            // assert
            Assert.NotNull(result);
            Assert.NotEmpty(result);

            var expectedResult = new Dictionary<string, string> {{field, fieldValue}};

            AssertKeyValuePairsMatchIgnoreOrder(expectedResult, result);
        }

        [Fact]
        public async Task GetSetAsync_KeyDoesNotExist_SetValueAndReturnNull()
        {
            const string key = "test";
            const string value = "testValue";

            // act
            var oldVal = await _database.GetSetAsync(key, value);

            // assert
            Assert.Null(oldVal);

            var actualKeyValue = await _database.GetAsync(key);
            Assert.Equal(value, actualKeyValue);
        }

        [Fact]
        public async Task GetSetAsync_KeyDoesExist_SetValueAndReturnOldValue()
        {
            const string key = "test";
            const string oldValue = "oldValue";
            const string value = "testValue";

            // arrange
            await _database.SetAsync(key, oldValue);

            // act
            var actualOldValue = await _database.GetSetAsync(key, value);

            // assert
            Assert.Equal(oldValue, actualOldValue);

            var actualKeyValue = await _database.GetAsync(key);
            Assert.Equal(value, actualKeyValue);
        }

        [Fact]
        public async Task ListRightPushAsync_KeyDoesNotExist_CreateListAndAddItem()
        {
            const string key = "test";
            const string value = "testValue";

            // act
            await _database.ListRightPushAsync(key, value);

            // assert
            var entry = Assert.Single(_data.Data);
            Assert.Equal(key, entry.Key);
            var list = Assert.IsType<List<string>>(entry.Value);
            Assert.Equal(value, Assert.Single(list));
        }

        [Fact]
        public async Task ListRightPushAsync_ListAlreadyContainsItem_AddItemToEndOfList()
        {
            const string key = "test";
            const string initialValue = "init";
            const string value = "testValue";

            // arrange
            await _database.ListRightPushAsync(key, initialValue);

            // act
            await _database.ListRightPushAsync(key, value);

            // assert
            var entry = Assert.Single(_data.Data);
            var list = Assert.IsType<List<string>>(entry.Value);
            Assert.Equal(new[] {initialValue, value}, list);
        }

        [Fact]
        public async Task ListLenAsync_KeyDoesNotExist_ReturnZero()
        {
            const string key = "test";

            // act
            var actual = await _database.ListLenAsync(key);

            // assert
            Assert.Equal(0, actual);
        }

        [Fact]
        public async Task ListLenAsync_ListExists_ReturnListCount()
        {
            const string key = "test";

            await _database.ListRightPushAsync(key, "test1");
            await _database.ListRightPushAsync(key, "test2");

            // act
            var actual = await _database.ListLenAsync(key);

            // assert
            Assert.Equal(2, actual);
        }

        [Fact]
        public async Task ListRangeAsync_KeyDoesNotExist_ReturnEmptyCollection()
        {
            const string key = "test";

            // act
            var actual = await _database.ListRangeAsync(key, 0, -1);

            // assert
            Assert.Empty(actual);
        }

        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, -1)]
        [InlineData(0, 200)]
        [InlineData(-1, 0)]
        public async Task ListRangeAsync_SingleItemExists_ReturnThisItem(int start, int end)
        {
            const string key = "test";
            const string item = "testItem";

            await _database.ListRightPushAsync(key, item);

            // act
            var actual = await _database.ListRangeAsync(key, start, end);

            // assert
            Assert.Equal(item, Assert.Single(actual));
        }

        [Theory]
        [InlineData(1, 1)]
        [InlineData(5, 100)]
        public async Task ListRangeAsync_SingleItemExists_ReturnEmptyCollection(int start, int end)
        {
            const string key = "test";
            const string item = "testItem";

            await _database.ListRightPushAsync(key, item);

            // act
            var actual = await _database.ListRangeAsync(key, start, end);

            // assert
            Assert.Empty(actual);
        }

        [Theory]
        [InlineData(1, 1, new[] {"item1"})]
        [InlineData(1, 2, new[] {"item1", "item2"})]
        [InlineData(0, 2, new[] {"item0", "item1", "item2"})]
        [InlineData(-2, -1, new[] {"item8", "item9"})]
        [InlineData(8, 100, new[] {"item8", "item9"})]
        [InlineData(6, -1, new[] {"item6", "item7", "item8", "item9"})]
        [InlineData(10, 11, new string[0])]
        public async Task ListRangeAsync_MultipleItems_CorrectlyReturnRange(int start, int end, string[] expected)
        {
            const string key = "test";

            for (var i = 0; i < 10; i++)
            {
                await _database.ListRightPushAsync(key, $"item{i}");
            }

            // act
            var actual = await _database.ListRangeAsync(key, start, end);

            // assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public async Task SetAddAsync_SetDoesNotExist_CreateSetAndReturnTrue()
        {
            const string key = "test";
            const string value = "test1";

            // act
            var result = await _database.SetAddAsync(key, value);

            // assert
            Assert.True(result);
            Assert.NotEmpty(_data.Data);
        }

        [Fact]
        public async Task SetAddAsync_SetDoesExist_AddValueToExistingSet()
        {
            const string key = "test";
            const string value1 = "test1";
            const string value2 = "test2";

            // arrange
            await _database.SetAddAsync(key, value1);

            // act
            var result = await _database.SetAddAsync(key, value2);

            // assert
            Assert.True(result);

            var actualSet = await _database.SetMembersAsync(key);
            AssertHelper.AssertScrambledEquals(new[] {value1, value2}, actualSet);
        }

        [Fact]
        public async Task SetAddAsync_ItemAlreadyExists_ReturnFalse()
        {
            const string key = "test";
            const string value1 = "test1";

            await _database.SetAddAsync(key, value1);

            // act
            var result = await _database.SetAddAsync(key, value1);

            // assert
            Assert.False(result);

            var actualSet = await _database.SetMembersAsync(key);
            AssertHelper.AssertScrambledEquals(new[] {value1}, actualSet);
        }

        [Fact]
        public async Task SetRemoveAsync_SetDoesNotExist_ReturnFalse()
        {
            const string key = "test";

            // act
            var result = await _database.SetRemoveAsync(key, "test");

            // assert
            Assert.False(result);
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task SetRemoveAsync_LastItem_ReturnTrueAndDeleteKey()
        {
            const string key = "test";
            const string item = "item";

            await _database.SetAddAsync(key, item);

            // act
            var result = await _database.SetRemoveAsync(key, item);

            // assert
            Assert.True(result);
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task SetRemoveAsync_ItemExists_ReturnTrueAndDeleteItem()
        {
            const string key = "test";
            const string item1 = "item1";
            const string item2 = "item2";

            // arrange
            await _database.SetAddAsync(key, item1);
            await _database.SetAddAsync(key, item2);

            // act
            var result = await _database.SetRemoveAsync(key, item1);

            // assert
            Assert.True(result);

            var items = await _database.SetMembersAsync(key);
            AssertHelper.AssertScrambledEquals(new[] {item2}, items);
        }

        [Fact]
        public async Task SetMembersAsync_KeyDoesNotExist_ReturnEmptyList()
        {
            const string key = "test";

            // act
            var result = await _database.SetMembersAsync(key);

            // assert
            Assert.Empty(result);
        }

        [Fact]
        public async Task SetMembersAsync_MembersExist_ReturnList()
        {
            const string key = "test";
            const string value = "value";

            await _database.SetAddAsync(key, value);

            // act
            var result = await _database.SetMembersAsync(key);

            // assert
            var actualValue = Assert.Single(result);
            Assert.Equal(value, actualValue);
        }

        [Fact]
        public void CreateTransaction_ExecuteStatements_DontExecuteStatementsBeforeStatement()
        {
            // act
            using var transaction = _database.CreateTransaction();
            _ = transaction.SetAsync("hello", "world");

            // assert
            Assert.Empty(_data.Data);
        }

        [Fact]
        public async Task CreateTransaction_ExecuteStatements_ExecuteStatementsOnExecuteAsync()
        {
            // act
            using var transaction = _database.CreateTransaction();
            _ = transaction.SetAsync("hello", "world");

            await transaction.ExecuteAsync();

            // assert
            Assert.NotEmpty(_data.Data);
        }

        private static void AssertKeyValuePairsMatchIgnoreOrder<T>(IEnumerable<KeyValuePair<string, T>> expected,
            IEnumerable<KeyValuePair<string, T>> actual)
        {
            Assert.Equal(expected.OrderBy(x => x.Key), actual.OrderBy(x => x.Key));
        }
    }
}