Skip to content

Commit 53d2235

Browse files
introduced commands and queries + table creation validation tests
1 parent cf98794 commit 53d2235

25 files changed

+884
-5
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
13+
<PackageReference Include="xunit" Version="2.9.2" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\Arian.Quantiq\Arian.Quantiq.csproj" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<Using Include="Xunit" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
using Arian.Quantiq.Application.Features.SQLTable.Commands.CreateTable;
2+
using Arian.Querium.SQL.QueryBuilders;
3+
using FluentValidation.TestHelper;
4+
using System.Net;
5+
6+
namespace Arian.Quantiq.Tests;
7+
8+
[Collection("Database collection")]
9+
public class CreateTableCommandValidatorTests
10+
{
11+
private readonly CreateTableCommandValidator _validator = new();
12+
13+
[Fact]
14+
public void ShouldFail_WhenTableNameIsEmpty()
15+
{
16+
// Arrange
17+
CreateTableCommand command = new()
18+
{
19+
TableName = "",
20+
PrimaryKeyColumn = "Id",
21+
Columns =
22+
[
23+
new() { Name = "Id", Type = ColumnType.Integer }
24+
]
25+
};
26+
27+
// Act
28+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
29+
30+
// Assert
31+
result.ShouldHaveValidationErrorFor(x => x.TableName)
32+
.WithErrorMessage("Table name is required.");
33+
}
34+
35+
[Fact]
36+
public void ShouldFail_WhenTableNameIsInvalid()
37+
{
38+
// Arrange
39+
CreateTableCommand command = new()
40+
{
41+
TableName = "1_invalid_table_name",
42+
PrimaryKeyColumn = "Id",
43+
Columns =
44+
[
45+
new() { Name = "Id", Type = ColumnType.Integer }
46+
]
47+
};
48+
49+
// Act
50+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
51+
52+
// Assert
53+
result.ShouldHaveValidationErrorFor(x => x.TableName)
54+
.WithErrorMessage("Table name must be a valid SQLite identifier.");
55+
}
56+
57+
[Fact]
58+
public void ShouldFail_WhenPrimaryKeyColumnIsEmpty()
59+
{
60+
// Arrange
61+
CreateTableCommand command = new()
62+
{
63+
TableName = "users",
64+
PrimaryKeyColumn = "",
65+
Columns =
66+
[
67+
new() { Name = "Id", Type = ColumnType.Integer }
68+
]
69+
};
70+
71+
// Act
72+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
73+
74+
// Assert
75+
result.ShouldHaveValidationErrorFor(x => x.PrimaryKeyColumn)
76+
.WithErrorMessage("Primary key column is required.");
77+
}
78+
79+
[Fact]
80+
public void ShouldFail_WhenPrimaryKeyColumnIsInvalid()
81+
{
82+
// Arrange
83+
CreateTableCommand command = new()
84+
{
85+
TableName = "users",
86+
PrimaryKeyColumn = "invalid-id",
87+
Columns =
88+
[
89+
new() { Name = "invalid-id", Type = ColumnType.Integer }
90+
]
91+
};
92+
93+
// Act
94+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
95+
96+
// Assert
97+
result.ShouldHaveValidationErrorFor(x => x.PrimaryKeyColumn)
98+
.WithErrorMessage("Primary key column name must be a valid SQLite identifier.");
99+
}
100+
101+
[Fact]
102+
public void ShouldFail_WhenPrimaryKeyColumnIsNotInteger()
103+
{
104+
// Arrange
105+
CreateTableCommand command = new()
106+
{
107+
TableName = "users",
108+
PrimaryKeyColumn = "Id",
109+
Columns =
110+
[
111+
new() { Name = "Id", Type = ColumnType.Text } // Primary key is not an integer
112+
]
113+
};
114+
115+
// Act
116+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
117+
118+
// Assert
119+
result.ShouldHaveValidationErrorFor(x => x.PrimaryKeyColumn)
120+
.WithErrorMessage("Primary key column must be of type Integer.");
121+
}
122+
123+
[Fact]
124+
public void ShouldFail_WhenColumnsListIsEmpty()
125+
{
126+
// Arrange
127+
CreateTableCommand command = new()
128+
{
129+
TableName = "users",
130+
PrimaryKeyColumn = "Id",
131+
Columns = new List<ColumnDefinition>()
132+
};
133+
134+
// Act
135+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
136+
137+
// Assert
138+
result.ShouldHaveValidationErrorFor(x => x.Columns)
139+
.WithErrorMessage("At least one column is required.");
140+
}
141+
142+
[Fact]
143+
public void ShouldFail_WhenColumnNameIsEmpty()
144+
{
145+
// Arrange
146+
CreateTableCommand command = new()
147+
{
148+
TableName = "users",
149+
PrimaryKeyColumn = "Id",
150+
Columns =
151+
[
152+
new() { Name = "", Type = ColumnType.Integer }
153+
]
154+
};
155+
156+
// Act
157+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
158+
159+
// Assert
160+
result.ShouldHaveValidationErrorFor("Columns[0].Name")
161+
.WithErrorMessage("Column name is required.");
162+
}
163+
164+
[Fact]
165+
public void ShouldFail_WhenColumnNameIsInvalid()
166+
{
167+
// Arrange
168+
CreateTableCommand command = new()
169+
{
170+
TableName = "users",
171+
PrimaryKeyColumn = "Id",
172+
Columns =
173+
[
174+
new() { Name = "invalid-name", Type = ColumnType.Integer }
175+
]
176+
};
177+
178+
// Act
179+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
180+
181+
// Assert
182+
result.ShouldHaveValidationErrorFor("Columns[0].Name")
183+
.WithErrorMessage("Column name must be a valid SQLite identifier.");
184+
}
185+
186+
[Fact]
187+
public void ShouldFail_WhenColumnTypeIsInvalid()
188+
{
189+
// Arrange
190+
CreateTableCommand command = new()
191+
{
192+
TableName = "users",
193+
PrimaryKeyColumn = "Id",
194+
Columns =
195+
[
196+
new() { Name = "Id", Type = (ColumnType)999 }, // Invalid enum value
197+
new() { Name = "Name", Type = ColumnType.Text }
198+
]
199+
};
200+
201+
// Act
202+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
203+
204+
// Assert
205+
result.ShouldHaveValidationErrorFor("Columns[0].Type")
206+
.WithErrorMessage("Column type must be a valid SQLite data type: Integer, Real, Text, Blob, Numeric, Boolean.");
207+
}
208+
209+
[Fact]
210+
public void ShouldPass_WhenColumnTypeIsValid()
211+
{
212+
// Arrange
213+
CreateTableCommand command = new()
214+
{
215+
TableName = "users",
216+
PrimaryKeyColumn = "Id",
217+
Columns =
218+
[
219+
new() { Name = "Id", Type = ColumnType.Integer },
220+
new() { Name = "Name", Type = ColumnType.Text }
221+
]
222+
};
223+
224+
// Act
225+
TestValidationResult<CreateTableCommand> result = _validator.TestValidate(command);
226+
227+
// Assert
228+
result.ShouldNotHaveAnyValidationErrors();
229+
}
230+
231+
[Fact]
232+
public async Task CreateTableCommandValidator_ShouldFail_WhenPrimaryKeyColumnMissing()
233+
{
234+
CreateTableCommandValidator validator = new();
235+
CreateTableCommand command = new()
236+
{
237+
TableName = "users",
238+
PrimaryKeyColumn = "UserId",
239+
Columns = new List<ColumnDefinition>
240+
{
241+
new() { Name = "Name", Type = ColumnType.Text }
242+
}
243+
};
244+
245+
FluentValidation.Results.ValidationResult result = await validator.ValidateAsync(command);
246+
Assert.False(result.IsValid);
247+
Assert.Contains(result.Errors, e => e.ErrorMessage.Contains("Primary key column must exist in the columns list."));
248+
}
249+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Arian.Querium.SQL.QueryBuilders;
2+
3+
namespace Arian.Quantiq.Application.Features.SQLTable.Commands.CreateTable;
4+
5+
/// <summary>
6+
/// Represents a column definition for a table.
7+
/// </summary>
8+
public class ColumnDefinition
9+
{
10+
/// <summary>
11+
/// Gets or sets the name of the column.
12+
/// </summary>
13+
public string Name { get; set; } = string.Empty;
14+
15+
/// <summary>
16+
/// Gets or sets the data type of the column (e.g., INTEGER, TEXT).
17+
/// </summary>
18+
public ColumnType Type { get; set; }
19+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Arian.Querium.Common.Results;
2+
using Mediator;
3+
4+
namespace Arian.Quantiq.Application.Features.SQLTable.Commands.CreateTable;
5+
6+
/// <summary>
7+
/// Represents a command to create a new table in the database.
8+
/// </summary>
9+
public class CreateTableCommand : ICommand<ApplicationResult<AppVoid>>
10+
{
11+
/// <summary>
12+
/// Gets or sets the name of the table to create.
13+
/// </summary>
14+
public string TableName { get; set; } = string.Empty;
15+
16+
/// <summary>
17+
/// Gets or sets the list of columns for the table.
18+
/// </summary>
19+
public List<ColumnDefinition> Columns { get; set; } = [];
20+
21+
/// <summary>
22+
/// Gets or sets the name of the primary key column.
23+
/// </summary>
24+
public string PrimaryKeyColumn { get; set; } = string.Empty;
25+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Arian.Quantiq.Extensions;
2+
using Arian.Querium.Common.Results;
3+
using Arian.Querium.SQL.QueryBuilders;
4+
using Arian.Querium.SQL.Repositories;
5+
using FluentValidation;
6+
using FluentValidation.Results;
7+
using Mediator;
8+
using System.Net;
9+
10+
namespace Arian.Quantiq.Application.Features.SQLTable.Commands.CreateTable;
11+
12+
/// <summary>
13+
/// Handles the creation of a new table in the database.
14+
/// </summary>
15+
public class CreateTableCommandHandler(
16+
IDynamicSQLRepository repository,
17+
IValidator<CreateTableCommand> validator) : ICommandHandler<CreateTableCommand, ApplicationResult<AppVoid>>
18+
{
19+
20+
/// <summary>
21+
/// Creates a new table with the specified name, columns, and primary key.
22+
/// </summary>
23+
/// <param name="request">The command containing table name, columns, and primary key column.</param>
24+
/// <param name="cancellationToken">The cancellation token.</param>
25+
/// <returns>An <see cref="ApplicationResult{AppVoid}"/> indicating success or failure.</returns>
26+
public async ValueTask<ApplicationResult<AppVoid>> Handle(CreateTableCommand request, CancellationToken cancellationToken)
27+
{
28+
ValidationResult validationResult = await validator.ValidateAsync(request, cancellationToken);
29+
30+
if (!validationResult.IsValid)
31+
{
32+
return (validationResult.ToErrorContainer(), HttpStatusCode.BadRequest);
33+
}
34+
35+
try
36+
{
37+
Dictionary<string, ColumnType> columns = request.Columns.ToDictionary(c => c.Name, c => c.Type);
38+
await repository.CreateTableAsync(request.TableName, columns, request.PrimaryKeyColumn);
39+
return new ApplicationResult<AppVoid>(AppVoid.Instance, HttpStatusCode.OK);
40+
}
41+
catch (Exception ex)
42+
{
43+
ErrorContainer error = new(ex.Message);
44+
return new ApplicationResult<AppVoid>(error, HttpStatusCode.InternalServerError);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)