From 3f5c390fa222f537d0a245f843f15b6efe40758a Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Fri, 28 Aug 2026 15:28:06 -0700 Subject: [PATCH] feat(financial): add chart type and computed message to validation results Introduces a `chartType` field (GL, PPM, INVALID) to financial validation responses to explicitly state the detected chart string type. The `message` field is now a computed property within the `FinancialValidationResult` model, automatically generating the appropriate user-friendly message based on the chart string's validity and type. This ensures consistent messaging directly from the model and simplifies integration for consumers. --- README.md | 9 +- .../Models/FinancialValidationResult.cs | 19 ++++ .../Financial/FinancialFunctionTests.cs | 98 ++++++++++++++++++- .../ResponseContractTests.cs | 46 ++++++++- 4 files changed, 164 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e5ac043..92fe309 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/Koi.Functions/Financial/Models/FinancialValidationResult.cs b/src/Koi.Functions/Financial/Models/FinancialValidationResult.cs index 8f1059d..735abb6 100644 --- a/src/Koi.Functions/Financial/Models/FinancialValidationResult.cs +++ b/src/Koi.Functions/Financial/Models/FinancialValidationResult.cs @@ -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; diff --git a/tests/Koi.Functions.Tests/Financial/FinancialFunctionTests.cs b/tests/Koi.Functions.Tests/Financial/FinancialFunctionTests.cs index 788761b..a5f5cf6 100644 --- a/tests/Koi.Functions.Tests/Financial/FinancialFunctionTests.cs +++ b/tests/Koi.Functions.Tests/Financial/FinancialFunctionTests.cs @@ -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() { @@ -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? validationResultFactory = null) : IAggieEnterpriseService { private int _activeCalls; @@ -244,7 +337,8 @@ public async Task ValidateAsync( await Task.Delay(delayMultiplier * 5, cancellationToken); } - return new FinancialValidationResult { ChartString = segmentString }; + return validationResultFactory?.Invoke(segmentString) + ?? new FinancialValidationResult { ChartString = segmentString }; } finally { diff --git a/tests/Koi.Functions.Tests/ResponseContractTests.cs b/tests/Koi.Functions.Tests/ResponseContractTests.cs index 0ba8d57..7482fb9 100644 --- a/tests/Koi.Functions.Tests/ResponseContractTests.cs +++ b/tests/Koi.Functions.Tests/ResponseContractTests.cs @@ -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); + } }