Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion API/Controller/Admin/GetUsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using OpenShock.Common.Utils;
using Z.EntityFramework.Plus;
using OpenShock.Common.OpenShockDb;
using OpenShock.Common.Query;

namespace OpenShock.API.Controller.Admin;

Expand Down Expand Up @@ -47,7 +48,11 @@ public async Task<IActionResult> GetUsers(
query = query.OrderBy(u => u.CreatedAt);
}
}
catch (ExpressionBuilder.ExpressionException e)
catch (QueryStringTokenizerException e)
{
return Problem(ExpressionError.QueryStringInvalidError(e.Message));
}
catch (DBExpressionBuilderException e)
{
return Problem(ExpressionError.ExpressionExceptionError(e.Message));
}
Expand Down
1 change: 1 addition & 0 deletions Common.Tests/Common.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<PackageReference Include="Testcontainers" Version="4.1.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.1.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.1.0" />
<PackageReference Include="Bogus" Version="35.6.1" />
<PackageReference Include="TUnit" Version="0.11.0" />
</ItemGroup>

Expand Down
33 changes: 15 additions & 18 deletions Common.Tests/Geo/Alpha2CountryCodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OpenShock.Common.Geo;
using TUnit.Assertions.AssertConditions.Throws;

namespace OpenShock.Common.Tests.Geo;

Expand All @@ -22,15 +23,13 @@ public async Task ValidCode_ShouldParse(string str, char char1, char char2)
[Arguments("INVALID")]
public async Task InvalidCharCount_ShouldThrow_InvalidLength(string str)
{
// Act
var ex = await Assert.ThrowsAsync<ArgumentException>(() =>
{
Alpha2CountryCode c = str;
return Task.CompletedTask;
});

// Assert
await Assert.That(ex.Message).IsEqualTo("Country code must be exactly 2 characters long (Parameter 'str')");
// Act & Assert
await Assert.That(() =>
{
Alpha2CountryCode c = str;
})
.ThrowsExactly<ArgumentOutOfRangeException>()
.WithMessage("Country code must be exactly 2 characters long (Parameter 'str')");
}

[Test]
Expand All @@ -44,15 +43,13 @@ public async Task InvalidCharCount_ShouldThrow_InvalidLength(string str)
[Arguments(":D")]
public async Task InvalidCharTypes_ShouldThrow(string str)
{
// Act
var ex = await Assert.ThrowsAsync<ArgumentException>(() =>
{
Alpha2CountryCode c = str;
return Task.CompletedTask;
});

// Assert
await Assert.That(ex.Message).IsEqualTo("Country code must be uppercase ASCII characters only (Parameter 'str')");
// Act & Assert
await Assert.That(() =>
{
Alpha2CountryCode c = str;
})
.ThrowsExactly<ArgumentOutOfRangeException>()
.WithMessage("Country code must be uppercase ASCII characters only (Parameter 'str')");
}

[Test]
Expand Down
199 changes: 199 additions & 0 deletions Common.Tests/Query/DBExpressionBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using OpenShock.Common.Query;
using TUnit.Assertions.AssertConditions.Throws;
using Bogus;

namespace OpenShock.Common.Tests.Query;

public class DBExpressionBuilderTests
{
public sealed class TestClass
{
public required Guid Id { get; set; }
public required string Name { get; set; }
public required int Age { get; set; }
public required uint Height { get; set; }
public required bool IsActive { get; set; }
public required DateTime CreatedAt { get; set; }
public required TestEnum Status { get; set; }
public required float Score { get; set; }
public required double Precision { get; set; }
}

public enum TestEnum
{
Pending,
Active,
Inactive
}

private readonly TestClass[] TestArray;

public DBExpressionBuilderTests()
{
var faker = new Faker<TestClass>()
.UseSeed(12345)
.RuleFor(t => t.Id, f => Guid.CreateVersion7())
.RuleFor(t => t.Name, f => f.Name.FullName())
.RuleFor(t => t.Age, f => f.Random.Int(18, 99))
.RuleFor(t => t.Height, f => f.Random.UInt())
.RuleFor(t => t.IsActive, f => f.Random.Bool())
.RuleFor(t => t.CreatedAt, f => f.Date.Past(10))
.RuleFor(t => t.Status, f => f.PickRandom<TestEnum>())
.RuleFor(t => t.Score, f => f.Random.Float(0, 100))
.RuleFor(t => t.Precision, f => f.Random.Double(0, 100));

TestArray = faker.Generate(100).ToArray();
}

[Test]
public async Task EmptyString_ThrowsException()
{
// Act & Assert
await Assert
.That(() => DBExpressionBuilder.GetFilterExpression<TestClass>(""))
.ThrowsExactly<DBExpressionBuilderException>();
}

[Test]
public async Task IntegerBounds_ThrowsExceptionOnOverflow()
{
// Act & Assert
await Assert
.That(() => DBExpressionBuilder.GetFilterExpression<TestClass>("age eq 2147483648"))
.ThrowsExactly<OverflowException>();
}

[Test]
public async Task UnsignedIntegerBounds_ThrowsExceptionOnNegative()
{
// Act & Assert
await Assert
.That(() => DBExpressionBuilder.GetFilterExpression<TestClass>("height eq -1"))
.ThrowsExactly<OverflowException>();
}

[Test]
public async Task Guid_ExactMatch()
{
// Act
var testGuid = TestArray.First().Id; // Grab a Guid from the test data
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>($"id eq {testGuid}");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.Id == testGuid);
}

[Test]
public async Task Integer_GreaterThanOrEquals()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("age gte 42");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.Age >= 42);
}

[Test]
public async Task Integer_LessThanOrEquals()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("age lte 51");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.Age <= 51);
}

// TODO: Make enums work
/*
[Test]
public async Task Enum_ChecksValidValues()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("status eq Active");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).HasCount().GreaterThan(0);
}

[Test]
public async Task Enum_InvalidValue_ThrowsException()
{
// Act & Assert
await Assert
.That(() => DBExpressionBuilder.GetFilterExpression<TestClass>("status eq Invalid"))
.ThrowsExactly<DBExpressionBuilderException>();
}
*/

[Test]
public async Task Boolean_TrueMatches()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("isActive eq true");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.IsActive == true);
}

[Test]
public async Task Boolean_FalseMatches()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("isActive eq false");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.IsActive == false);
}

[Test]
public async Task DateTime_ExactMatch()
{
// Act
var testDate = TestArray[20].CreatedAt;
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>($"createdAt eq {testDate:O}");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.CreatedAt == testDate);
}

[Test]
public async Task DateTime_LessThan()
{
// Act
var referenceDate = DateTime.UtcNow.AddMonths(-6);
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>($"createdAt lt {referenceDate:O}");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.CreatedAt < referenceDate);
}

[Test]
public async Task Float_GreaterThan()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("score gt 50");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.Score > 50f);
}

[Test]
public async Task Double_LessThan()
{
// Act
var expression = DBExpressionBuilder.GetFilterExpression<TestClass>("precision lt 50");
var result = TestArray.AsQueryable().Where(expression).ToArray();

// Assert
await Assert.That(result).ContainsOnly(x => x.Precision < 50f);
}
}
Loading
Loading