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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ Build, including financial chart details from Aggie Enterprise.
| `GET` | `/api/v1/financial/validate/{value}` | Bearer token | Validation result for one chart string |
| `POST` | `/api/v1/financial/validate` | Bearer token | Validation results for an array of chart strings |

Financial details include a `message` for direct display in Kuali Build. Valid
GL and PPM results return `This is a valid GL chart string.` and
`This is a valid PPM chart string.`, respectively. Invalid results return
`This is not a valid chart string.`
Financial details and validation results include `chartType` and `message` for
direct display in Kuali Build. Chart types are returned as `GL`, `PPM`, or
`INVALID`. Valid GL and PPM results return
`This is a valid GL chart string.` and `This is a valid PPM chart string.`,
respectively. Invalid results return `This is not a valid chart string.`

All HTTP functions require authentication by default. The health function is
the only explicit anonymous exception.
Expand Down
19 changes: 19 additions & 0 deletions src/Koi.Functions/Financial/Models/FinancialValidationResult.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
using AggieEnterpriseApi.Validation;

namespace Koi.Functions.Financial.Models;

public sealed class FinancialValidationResult
{
private const string InvalidChartStringMessage = "This is not a valid chart string.";

private FinancialChartStringType ChartStringType => string.IsNullOrWhiteSpace(ChartString)
? FinancialChartStringType.Invalid
: FinancialChartValidation.GetFinancialChartStringType(ChartString);

public string ChartString { get; set; } = string.Empty;

public string ChartType => ChartStringType.ToString().ToUpperInvariant();

public bool IsValid { get; set; }

public string Message => IsValid
? ChartStringType switch
{
FinancialChartStringType.Gl => "This is a valid GL chart string.",
FinancialChartStringType.Ppm => "This is a valid PPM chart string.",
_ => InvalidChartStringMessage
}
: InvalidChartStringMessage;

public bool IsWarning { get; set; }

public string ErrorMessage { get; set; } = string.Empty;
Expand Down
98 changes: 96 additions & 2 deletions tests/Koi.Functions.Tests/Financial/FinancialFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,47 @@ public async Task RunValidationPassesValueAndCancellationTokenToService()
Assert.Equal([cancellationTokenSource.Token], service.ValidationCancellationTokens);
}

[Theory]
[InlineData(
true,
"0000-00000-0000000-000000-00-000-0000000000-000000-0000-000000-000000",
"GL",
"This is a valid GL chart string.")]
[InlineData(
true,
"0000000000-000000-0000000-000000",
"PPM",
"This is a valid PPM chart string.")]
[InlineData(false, "invalid", "INVALID", "This is not a valid chart string.")]
public async Task RunValidationSerializesMessageForKualiBuild(
bool isValid,
string chartString,
string expectedChartType,
string expectedMessage)
{
var service = new TrackingAggieEnterpriseService(
validationResultFactory: value => new FinancialValidationResult
{
ChartString = value,
IsValid = isValid
});
var function = new FinancialFunction(service);
var request = TestHttpRequestData.Create(string.Empty);

var response = await function.RunValidation(
request,
chartString,
CancellationToken.None);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
response.Body.Position = 0;
using var document = await JsonDocument.ParseAsync(response.Body);
var root = document.RootElement;
Assert.Equal(chartString, root.GetProperty("ChartString").GetString());
Assert.Equal(expectedChartType, root.GetProperty("ChartType").GetString());
Assert.Equal(expectedMessage, root.GetProperty("Message").GetString());
}

[Fact]
public async Task RunBulkReturnsBadRequestWhenBatchExceedsMaximum()
{
Expand Down Expand Up @@ -161,7 +202,59 @@ public async Task RunBulkValidationProcessesInputInOrder()
Assert.InRange(service.MaxConcurrentValidationCalls, 2, FinancialFunction.MaxConcurrency);
}

private sealed class TrackingAggieEnterpriseService(bool delayCalls = false)
[Fact]
public async Task RunBulkValidationSerializesMessagesForKualiBuild()
{
const string glChartString =
"0000-00000-0000000-000000-00-000-0000000000-000000-0000-000000-000000";
const string ppmChartString = "0000000000-000000-0000000-000000";
const string invalidChartString = "invalid";
var chartStrings = new[] { glChartString, ppmChartString, invalidChartString };
var service = new TrackingAggieEnterpriseService(
validationResultFactory: value => new FinancialValidationResult
{
ChartString = value,
IsValid = value != invalidChartString
});
var function = new FinancialFunction(service);
var request = TestHttpRequestData.Create(JsonSerializer.Serialize(chartStrings));

var response = await function.RunBulkValidation(request, CancellationToken.None);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
response.Body.Position = 0;
using var document = await JsonDocument.ParseAsync(response.Body);
var results = document.RootElement.EnumerateArray().ToArray();
Assert.Equal(chartStrings, results.Select(result =>
result.GetProperty("ChartString").GetString()));
Assert.Collection(
results,
result =>
{
Assert.Equal("GL", result.GetProperty("ChartType").GetString());
Assert.Equal(
"This is a valid GL chart string.",
result.GetProperty("Message").GetString());
},
result =>
{
Assert.Equal("PPM", result.GetProperty("ChartType").GetString());
Assert.Equal(
"This is a valid PPM chart string.",
result.GetProperty("Message").GetString());
},
result =>
{
Assert.Equal("INVALID", result.GetProperty("ChartType").GetString());
Assert.Equal(
"This is not a valid chart string.",
result.GetProperty("Message").GetString());
});
}

private sealed class TrackingAggieEnterpriseService(
bool delayCalls = false,
Func<string, FinancialValidationResult>? validationResultFactory = null)
: IAggieEnterpriseService
{
private int _activeCalls;
Expand Down Expand Up @@ -244,7 +337,8 @@ public async Task<FinancialValidationResult> ValidateAsync(
await Task.Delay(delayMultiplier * 5, cancellationToken);
}

return new FinancialValidationResult { ChartString = segmentString };
return validationResultFactory?.Invoke(segmentString)
?? new FinancialValidationResult { ChartString = segmentString };
}
finally
{
Expand Down
46 changes: 44 additions & 2 deletions tests/Koi.Functions.Tests/ResponseContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,59 @@ public void FinancialDetailsMessageMatchesValidationResult(
[Fact]
public void FinancialValidationContractIsStable()
{
const string chartString =
"0000-00000-0000000-000000-00-000-0000000000-000000-0000-000000-000000";
var response = new FinancialValidationResult
{
ChartString = "example",
ChartString = chartString,
IsValid = true,
IsWarning = true,
ErrorMessage = "example warning"
};

Assert.Equal("example", response.ChartString);
Assert.Equal(chartString, response.ChartString);
Assert.Equal("GL", response.ChartType);
Assert.True(response.IsValid);
Assert.Equal("This is a valid GL chart string.", response.Message);
Assert.True(response.IsWarning);
Assert.Equal("example warning", response.ErrorMessage);
}

[Theory]
[InlineData(
true,
"0000-00000-0000000-000000-00-000-0000000000-000000-0000-000000-000000",
"GL",
"This is a valid GL chart string.")]
[InlineData(
true,
"0000000000-000000-0000000-000000",
"PPM",
"This is a valid PPM chart string.")]
[InlineData(
false,
"0000-00000-0000000-000000-00-000-0000000000-000000-0000-000000-000000",
"GL",
"This is not a valid chart string.")]
[InlineData(
false,
"0000000000-000000-0000000-000000",
"PPM",
"This is not a valid chart string.")]
[InlineData(false, "invalid", "INVALID", "This is not a valid chart string.")]
public void FinancialValidationMessageMatchesValidationResult(
bool isValid,
string chartString,
string expectedChartType,
string expectedMessage)
{
var response = new FinancialValidationResult
{
IsValid = isValid,
ChartString = chartString
};

Assert.Equal(expectedChartType, response.ChartType);
Assert.Equal(expectedMessage, response.Message);
}
}