Harden timestamp filter validation null handling

This commit is contained in:
Sipke Schoorstra 2026-05-20 22:50:13 +02:00
parent a77c1af817
commit f72ed4edab
No known key found for this signature in database
GPG key ID: 5C10502B28A4268F
4 changed files with 48 additions and 1 deletions

View file

@ -53,7 +53,7 @@ public class Submit : ElsaEndpoint<AlterationPlanParams, Response>
private bool ValidateInput(AlterationPlanParams planParams)
{
var errors = WorkflowInstanceFilter.ValidateTimestampFilters(planParams.Filter.TimestampFilters).ToList();
var errors = WorkflowInstanceFilter.ValidateTimestampFilters(planParams.Filter?.TimestampFilters).ToList();
foreach (var error in errors)
AddError(error);

View file

@ -217,6 +217,12 @@ public class WorkflowInstanceFilter
foreach (var timestampFilter in timestampFilters)
{
if (timestampFilter == null)
{
yield return "Timestamp filter must be specified.";
continue;
}
if (!TryNormalizeTimestampFilterColumn(timestampFilter.Column, out _, out var error))
yield return error;
}

View file

@ -78,6 +78,26 @@ public class AlterationsApiTimestampFilterTests : IAsyncLifetime
Assert.Contains("Invalid timestamp filter column", body);
}
[Fact]
public async Task Submit_WithNullFilter_DoesNotReturnServerError()
{
var response = await _httpClient.PostAsJsonAsync("/alterations/submit", new { filter = (object?)null });
Assert.NotEqual(HttpStatusCode.InternalServerError, response.StatusCode);
}
[Theory]
[InlineData("/alterations/dry-run")]
[InlineData("/alterations/submit")]
public async Task Post_WithNullTimestampFilter_ReturnsBadRequest(string path)
{
var response = await _httpClient.PostAsJsonAsync(path, CreateRequestWithNullTimestampFilter(path));
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("Timestamp filter must be specified.", body);
}
private static object CreateRequest(string path)
{
var filter = new
@ -97,4 +117,16 @@ public class AlterationsApiTimestampFilterTests : IAsyncLifetime
? new { filter }
: filter;
}
private static object CreateRequestWithNullTimestampFilter(string path)
{
var filter = new
{
timestampFilters = new object?[] { null }
};
return path == "/alterations/submit"
? new { filter }
: filter;
}
}

View file

@ -87,6 +87,15 @@ public class WorkflowInstanceFilterTimestampTests
Assert.Equal("Timestamp filter column must be specified.", error);
}
[Fact]
public void ValidateTimestampFilters_WithNullFilter_ReturnsClearValidationError()
{
var errors = WorkflowInstanceFilter.ValidateTimestampFilters([null!]).ToList();
var error = Assert.Single(errors);
Assert.Equal("Timestamp filter must be specified.", error);
}
private DateTimeOffset GetMatchingTimestamp(string column) => column switch
{
nameof(WorkflowInstance.CreatedAt) => _matchingCreatedAt,