diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..db6372ffc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": ".NET Core Docker Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickRemoteProcess}", + "pipeTransport": { + "pipeProgram": "docker", + "pipeArgs": ["exec", "-i", "jasper-api-1"], + "debuggerPath": "/vsdbg/vsdbg", + "pipeCwd": "${workspaceRoot}", + "quoteArgs": false + }, + "sourceFileMap": { + "/opt/app-root/src": "${workspaceRoot}/" + } + } + ] +} diff --git a/api/Controllers/MockOrdersController.cs b/api/Controllers/MockOrdersController.cs deleted file mode 100644 index 31bf284c1..000000000 --- a/api/Controllers/MockOrdersController.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using FluentValidation; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Scv.Api.Infrastructure.Authentication; -using Scv.Api.Infrastructure.Options; -using Scv.Models.Order; - -namespace Scv.Api.Controllers; - - -[Route("api/[controller]")] -[ApiController] -public class MockOrdersController( - IValidator orderActionValidator, - IKeycloakTokenService tokenService, - IOptions keycloakClientOptions, - ILogger logger) : ControllerBase -{ - private readonly IValidator _orderActionValidator = orderActionValidator; - private readonly IKeycloakTokenService _tokenService = tokenService; - private readonly CsoKeycloakClientOptions _keycloakClientOptions = keycloakClientOptions.Value; - private readonly ILogger _logger = logger; - - /// - /// Mock endpoint to simulate a submitted order. - /// - /// The order action payload - /// - [HttpPost] - [Route("action")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task ReviewOrder([FromBody] OrderActionDto orderActionDto) - { - var authHeader = Request.Headers.Authorization.ToString(); - if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.StartsWith("Bearer ")) - { - _logger.LogWarning("Missing or invalid Authorization header for mock order endpoint."); - return Unauthorized(); - } - - var providedToken = authHeader["Bearer ".Length..].Trim(); - if (string.IsNullOrWhiteSpace(providedToken)) - { - _logger.LogWarning("Empty bearer token received for mock order endpoint."); - return Unauthorized(); - } - - var expectedToken = await _tokenService.GetServiceAccountTokenAsync( - _keycloakClientOptions, - HttpContext.RequestAborted); - - if (!string.Equals(providedToken, expectedToken, System.StringComparison.Ordinal)) - { - _logger.LogWarning("Bearer token mismatch for mock order endpoint."); - return Unauthorized(); - } - - _logger.LogInformation("Received order action payload: {Payload}", JsonConvert.SerializeObject(orderActionDto)); - - var basicValidation = await _orderActionValidator.ValidateAsync(orderActionDto); - - if (!basicValidation.IsValid) - { - return UnprocessableEntity(basicValidation.Errors.Select(e => e.ErrorMessage)); - } - - return Ok(); - } -} - - diff --git a/api/Documents/Strategies/OrderDocumentStrategy.cs b/api/Documents/Strategies/OrderDocumentStrategy.cs index 5b42c98af..39681d25f 100644 --- a/api/Documents/Strategies/OrderDocumentStrategy.cs +++ b/api/Documents/Strategies/OrderDocumentStrategy.cs @@ -1,7 +1,11 @@ using System; using System.IO; +using System.Security.Claims; +using System.Text; using System.Threading.Tasks; using CSOCommon.Clients.JudicialServices; +using CSOCommon.Models; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Serialization; using Nutrient.NativeSDK.API.Exceptions; @@ -18,7 +22,7 @@ public class OrderDocumentStrategy : IDocumentStrategy public DocumentType Type => DocumentType.Order; - public OrderDocumentStrategy(IJudicialServicesClient judicialClient, IConfiguration configuration) + public OrderDocumentStrategy(IJudicialServicesClient judicialClient, IConfiguration configuration, ClaimsPrincipal currentUser) { _judicialClient = judicialClient; _judicialClient.JsonSerializerSettings.ContractResolver = new SafeContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }; @@ -37,10 +41,16 @@ public async Task Invoke(PdfDocumentRequestDetails documentRequest throw new InvalidArgumentException("Invalid agency id"); } - var isValidDocumentId = double.TryParse(documentRequest.DocumentId, out var documentId); + if (string.IsNullOrWhiteSpace(documentRequest.DocumentId)) + { + throw new InvalidArgumentException("Invalid document id."); + } + + var decodedDocumentId = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(documentRequest.DocumentId)); + var isValidDocumentId = double.TryParse(decodedDocumentId, out var documentId); if (!isValidDocumentId) { - throw new InvalidArgumentException("Invalid document id"); + throw new InvalidArgumentException("Invalid document id."); } using var response = await _judicialClient.GetJudicialDocumentAsync( diff --git a/api/Infrastructure/Authentication/CsoBearerTokenHandler.cs b/api/Infrastructure/Authentication/CsoBearerTokenHandler.cs index 30b446e73..12433e55f 100644 --- a/api/Infrastructure/Authentication/CsoBearerTokenHandler.cs +++ b/api/Infrastructure/Authentication/CsoBearerTokenHandler.cs @@ -1,21 +1,41 @@ using System; using System.Net.Http; using System.Net.Http.Headers; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Scv.Api.Infrastructure.Options; +using Scv.Core.Helpers.Extensions; namespace Scv.Api.Infrastructure.Authentication { - public sealed class CsoBearerTokenHandler( + public sealed partial class CsoBearerTokenHandler( IKeycloakTokenService tokenService, IOptions options, + IHttpContextAccessor httpContextAccessor, + IHttpClientFactory httpClientFactory, + IConfiguration configuration, ILogger logger) : DelegatingHandler { + /// + /// Url pattern for retrieving CSO documents + /// + /// Returns true if Url path matches the CSO GetJudicialDocument endpoint pattern. + [GeneratedRegex(@"/judicial/[^/]+/document/?$", RegexOptions.IgnoreCase)] + private static partial Regex GetJudicialDocumentUrlRegex(); + private readonly IKeycloakTokenService _tokenService = tokenService; private readonly CsoKeycloakClientOptions _options = options.Value ?? throw new ArgumentNullException(nameof(options)); + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + private readonly IConfiguration _configuration = configuration; private readonly ILogger _logger = logger; protected override async Task SendAsync( @@ -26,7 +46,18 @@ protected override async Task SendAsync( if (request.Headers.Authorization == null) { - var token = await _tokenService.GetServiceAccountTokenAsync(_options, cancellationToken); + string token; + if (IsGetJudicialDocumentRequest(request)) + { + _logger.LogDebug("CSO GetJudicialDocument request detected; using current user's access token."); + token = await GetUserAccessTokenAsync(cancellationToken) + ?? await _tokenService.GetServiceAccountTokenAsync(_options, cancellationToken); + } + else + { + token = await _tokenService.GetServiceAccountTokenAsync(_options, cancellationToken); + } + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } else @@ -34,7 +65,83 @@ protected override async Task SendAsync( _logger.LogDebug("CSO request already contains an Authorization header."); } + _logger.LogInformation("Sending request to {RequestUri} with method {Method}", request.RequestUri, request.Method); + return await base.SendAsync(request, cancellationToken); } + + private static bool IsGetJudicialDocumentRequest(HttpRequestMessage request) + { + if (request.Method != HttpMethod.Get || request.RequestUri == null) + { + return false; + } + + return GetJudicialDocumentUrlRegex().IsMatch(request.RequestUri.AbsolutePath); + } + + /// + /// Retrieves the currently logged-on user's access token + /// + /// + /// User access token + private async Task GetUserAccessTokenAsync(CancellationToken cancellationToken) + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + _logger.LogWarning("No HttpContext available; cannot obtain user access token."); + return null; + } + + var refreshToken = await httpContext.GetTokenAsync( + CookieAuthenticationDefaults.AuthenticationScheme, "refresh_token"); + if (string.IsNullOrWhiteSpace(refreshToken)) + { + _logger.LogWarning("No refresh_token found for the current user; cannot obtain user access token."); + return null; + } + + var client = _httpClientFactory.CreateClient(KeycloakTokenService.HttpClientName); + var response = await client.RequestRefreshTokenAsync(new RefreshTokenRequest + { + Address = _configuration.GetNonEmptyValue("Keycloak:Authority") + "/protocol/openid-connect/token", + ClientId = _configuration.GetNonEmptyValue("Keycloak:Client"), + ClientSecret = _configuration.GetNonEmptyValue("Keycloak:Secret"), + RefreshToken = refreshToken + }, cancellationToken); + + if (response.IsError || string.IsNullOrWhiteSpace(response.AccessToken)) + { + _logger.LogError( + "Failed to exchange user refresh_token for access_token. Error: {Error}. Description: {ErrorDescription}", + response.Error ?? "unknown", response.ErrorDescription); + return null; + } + + try + { + // Persist the new refresh token and expiry time back to the auth cookie so that subsequent requests can use it. + var authResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + if (authResult.Succeeded && authResult.Properties != null) + { + authResult.Properties.UpdateTokenValue("refresh_token", response.RefreshToken); + authResult.Properties.UpdateTokenValue( + "expires_at", + DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn) + .ToString("o", System.Globalization.CultureInfo.InvariantCulture)); + await httpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + authResult.Principal, + authResult.Properties); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to persist rotated refresh_token back to the auth cookie."); + } + + return response.AccessToken; + } } } diff --git a/api/Infrastructure/Authentication/KeycloakTokenService.cs b/api/Infrastructure/Authentication/KeycloakTokenService.cs index 3389bdc96..010b198d2 100644 --- a/api/Infrastructure/Authentication/KeycloakTokenService.cs +++ b/api/Infrastructure/Authentication/KeycloakTokenService.cs @@ -11,187 +11,186 @@ using Scv.Api.Infrastructure.Options; using Scv.Core.Exceptions; -namespace Scv.Api.Infrastructure.Authentication +namespace Scv.Api.Infrastructure.Authentication; + +public interface IKeycloakTokenService { - public interface IKeycloakTokenService - { - Task GetServiceAccountTokenAsync(KeycloakClientOptions options, CancellationToken cancellationToken = default); - } + Task GetServiceAccountTokenAsync(KeycloakClientOptions options, CancellationToken cancellationToken = default); +} + +public sealed class KeycloakTokenService( + IMemoryCache cache, + IHttpClientFactory httpClientFactory, + ILogger logger) : IKeycloakTokenService +{ + public const string HttpClientName = "KeycloakTokenClient"; + + private readonly IMemoryCache _cache = cache; + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + private readonly ILogger _logger = logger; + private readonly ConcurrentDictionary _locks = new(); - public sealed class KeycloakTokenService( - IMemoryCache cache, - IHttpClientFactory httpClientFactory, - ILogger logger) : IKeycloakTokenService + public async Task GetServiceAccountTokenAsync(KeycloakClientOptions options, CancellationToken cancellationToken = default) { - public const string HttpClientName = "KeycloakTokenClient"; + ArgumentNullException.ThrowIfNull(options); - private readonly IMemoryCache _cache = cache; - private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; - private readonly ILogger _logger = logger; - private readonly ConcurrentDictionary _locks = new(); + ValidateOptions(options); + ValidateSecret(options); - public async Task GetServiceAccountTokenAsync(KeycloakClientOptions options, CancellationToken cancellationToken = default) + var cacheKey = BuildCacheKey(options); + if (_cache.TryGetValue(cacheKey, out ServiceAccountToken cachedToken) && + cachedToken.IsValid(options.RefreshSkewSeconds, options.ClockSkewSeconds)) { - ArgumentNullException.ThrowIfNull(options); - - ValidateOptions(options); - ValidateSecret(options); + return cachedToken.AccessToken; + } - var cacheKey = BuildCacheKey(options); - if (_cache.TryGetValue(cacheKey, out ServiceAccountToken cachedToken) && + var tokenLock = _locks.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1)); + await tokenLock.WaitAsync(cancellationToken); + try + { + if (_cache.TryGetValue(cacheKey, out cachedToken) && cachedToken.IsValid(options.RefreshSkewSeconds, options.ClockSkewSeconds)) { return cachedToken.AccessToken; } - var tokenLock = _locks.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1)); - await tokenLock.WaitAsync(cancellationToken); - try - { - if (_cache.TryGetValue(cacheKey, out cachedToken) && - cachedToken.IsValid(options.RefreshSkewSeconds, options.ClockSkewSeconds)) - { - return cachedToken.AccessToken; - } - - var token = await RequestServiceAccountTokenAsync(options, cancellationToken); - var cacheDuration = GetCacheDuration(token.ExpiresAt, options.RefreshSkewSeconds, options.ClockSkewSeconds); - - if (cacheDuration.TotalSeconds > options.RefreshSkewSeconds) - { - _cache.Set(cacheKey, token, cacheDuration); - } - else - { - _logger.LogWarning("Unable to set valid cache duration, below skew threshold."); - } - - - return token.AccessToken; - } - finally - { - tokenLock.Release(); - } - } + var token = await RequestServiceAccountTokenAsync(options, cancellationToken); + var cacheDuration = GetCacheDuration(token.ExpiresAt, options.RefreshSkewSeconds, options.ClockSkewSeconds); - private async Task RequestServiceAccountTokenAsync( - KeycloakClientOptions options, - CancellationToken cancellationToken) - { - var client = _httpClientFactory.CreateClient(HttpClientName); - var tokenEndpoint = options.Authority.TrimEnd('/') + "/protocol/openid-connect/token"; - - _logger.LogInformation( - "Requesting Keycloak service account token for authority {Authority}, clientId {ClientId}, audience {Audience}, scope {Scope}.", - options.Authority, - options.ClientId, - string.IsNullOrWhiteSpace(options.Audience) ? "(null)" : options.Audience, - string.IsNullOrWhiteSpace(options.Scope) ? "(null)" : options.Scope); - - var request = new ClientCredentialsTokenRequest + if (cacheDuration.TotalSeconds > options.RefreshSkewSeconds) { - Address = tokenEndpoint, - ClientId = options.ClientId, - ClientSecret = options.ServiceAccountSecret, - Scope = string.IsNullOrWhiteSpace(options.Scope) ? null : options.Scope - }; - - if (!string.IsNullOrWhiteSpace(options.Audience)) - { - _logger.LogDebug("Adding audience parameter to token request: {Audience}", options.Audience); - request.Parameters.Add("audience", options.Audience); + _cache.Set(cacheKey, token, cacheDuration); } else { - _logger.LogWarning("No audience configured in KeycloakClientOptions. Token may not include audience claim."); + _logger.LogWarning("Unable to set valid cache duration, below skew threshold."); } - var response = await client.RequestClientCredentialsTokenAsync(request, cancellationToken); - if (response.IsError || string.IsNullOrWhiteSpace(response.AccessToken)) - { - _logger.LogError( - "Failed to retrieve Keycloak service account token. Error: {Error}. Description: {ErrorDescription}", - response.Error ?? "unknown", response.ErrorDescription); - throw new InvalidOperationException("Unable to retrieve service account token from Keycloak."); - } - if (response.ExpiresIn <= 0) - { - _logger.LogWarning("Keycloak token response did not include a valid expires_in value. Falling back to JWT exp claim or default expiry."); - } + return token.AccessToken; + } + finally + { + tokenLock.Release(); + } + } - var expiresAt = ComputeExpiry(response); + private async Task RequestServiceAccountTokenAsync( + KeycloakClientOptions options, + CancellationToken cancellationToken) + { + var client = _httpClientFactory.CreateClient(HttpClientName); + var tokenEndpoint = options.Authority.TrimEnd('/') + "/protocol/openid-connect/token"; + + _logger.LogInformation( + "Requesting Keycloak service account token for authority {Authority}, clientId {ClientId}, audience {Audience}, scope {Scope}.", + options.Authority, + options.ClientId, + string.IsNullOrWhiteSpace(options.Audience) ? "(null)" : options.Audience, + string.IsNullOrWhiteSpace(options.Scope) ? "(null)" : options.Scope); - return new ServiceAccountToken(response.AccessToken, expiresAt); + var request = new ClientCredentialsTokenRequest + { + Address = tokenEndpoint, + ClientId = options.ClientId, + ClientSecret = options.ServiceAccountSecret, + Scope = string.IsNullOrWhiteSpace(options.Scope) ? null : options.Scope + }; + + if (!string.IsNullOrWhiteSpace(options.Audience)) + { + _logger.LogDebug("Adding audience parameter to token request: {Audience}", options.Audience); + request.Parameters.Add("audience", options.Audience); + } + else + { + _logger.LogWarning("No audience configured in KeycloakClientOptions. Token may not include audience claim."); } - private static string BuildCacheKey(KeycloakClientOptions options) + var response = await client.RequestClientCredentialsTokenAsync(request, cancellationToken); + if (response.IsError || string.IsNullOrWhiteSpace(response.AccessToken)) { - return $"keycloak-token::{options.Authority}::{options.ClientId}::{options.Audience}::{options.Scope}"; + _logger.LogError( + "Failed to retrieve Keycloak service account token. Error: {Error}. Description: {ErrorDescription}", + response.Error ?? "unknown", response.ErrorDescription); + throw new InvalidOperationException("Unable to retrieve service account token from Keycloak."); } - private static TimeSpan GetCacheDuration(DateTimeOffset expiresAt, int refreshSkewSeconds, int clockSkewSeconds) + if (response.ExpiresIn <= 0) { - var skew = TimeSpan.FromSeconds(Math.Max(0, refreshSkewSeconds) + Math.Max(0, clockSkewSeconds)); - var duration = expiresAt - DateTimeOffset.UtcNow - skew; + _logger.LogWarning("Keycloak token response did not include a valid expires_in value. Falling back to JWT exp claim or default expiry."); + } - return duration > TimeSpan.Zero ? duration : TimeSpan.FromSeconds(1); + var expiresAt = ComputeExpiry(response); + + return new ServiceAccountToken(response.AccessToken, expiresAt); + } + + private static string BuildCacheKey(KeycloakClientOptions options) + { + return $"keycloak-token::{options.Authority}::{options.ClientId}::{options.Audience}::{options.Scope}"; + } + + private static TimeSpan GetCacheDuration(DateTimeOffset expiresAt, int refreshSkewSeconds, int clockSkewSeconds) + { + var skew = TimeSpan.FromSeconds(Math.Max(0, refreshSkewSeconds) + Math.Max(0, clockSkewSeconds)); + var duration = expiresAt - DateTimeOffset.UtcNow - skew; + + return duration > TimeSpan.Zero ? duration : TimeSpan.FromSeconds(1); + } + + private DateTimeOffset ComputeExpiry(TokenResponse response) + { + if (response.ExpiresIn > 0) + { + return DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn); } - private DateTimeOffset ComputeExpiry(TokenResponse response) + try { - if (response.ExpiresIn > 0) + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(response.AccessToken); + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp"); + if (expClaim != null && long.TryParse(expClaim.Value, out var expUnix)) { - return DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn); + return DateTimeOffset.FromUnixTimeSeconds(expUnix); } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Unable to parse Keycloak access token as JWT for expiry fallback. Using default expiry of 300 seconds."); + } - try - { - var handler = new JwtSecurityTokenHandler(); - var jwt = handler.ReadJwtToken(response.AccessToken); - var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp"); - if (expClaim != null && long.TryParse(expClaim.Value, out var expUnix)) - { - return DateTimeOffset.FromUnixTimeSeconds(expUnix); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Unable to parse Keycloak access token as JWT for expiry fallback. Using default expiry of 300 seconds."); - } + return DateTimeOffset.UtcNow.AddSeconds(300); + } - return DateTimeOffset.UtcNow.AddSeconds(300); + private static void ValidateOptions(KeycloakClientOptions options) + { + if (string.IsNullOrWhiteSpace(options.Authority)) + { + throw new ConfigurationException("Configuration 'Authority' is invalid or missing."); } - private static void ValidateOptions(KeycloakClientOptions options) + if (string.IsNullOrWhiteSpace(options.ClientId)) { - if (string.IsNullOrWhiteSpace(options.Authority)) - { - throw new ConfigurationException("Configuration 'Authority' is invalid or missing."); - } - - if (string.IsNullOrWhiteSpace(options.ClientId)) - { - throw new ConfigurationException("Configuration 'ClientId' is invalid or missing."); - } + throw new ConfigurationException("Configuration 'ClientId' is invalid or missing."); } + } - private static void ValidateSecret(KeycloakClientOptions options) + private static void ValidateSecret(KeycloakClientOptions options) + { + if (string.IsNullOrWhiteSpace(options.ServiceAccountSecret)) { - if (string.IsNullOrWhiteSpace(options.ServiceAccountSecret)) - { - throw new ConfigurationException("Configuration 'ServiceAccountSecret' is invalid or missing."); - } + throw new ConfigurationException("Configuration 'ServiceAccountSecret' is invalid or missing."); } + } - private sealed record ServiceAccountToken(string AccessToken, DateTimeOffset ExpiresAt) + private sealed record ServiceAccountToken(string AccessToken, DateTimeOffset ExpiresAt) + { + public bool IsValid(int refreshSkewSeconds, int clockSkewSeconds) { - public bool IsValid(int refreshSkewSeconds, int clockSkewSeconds) - { - var skew = TimeSpan.FromSeconds(Math.Max(0, refreshSkewSeconds) + Math.Max(0, clockSkewSeconds)); - return ExpiresAt - skew > DateTimeOffset.UtcNow; - } + var skew = TimeSpan.FromSeconds(Math.Max(0, refreshSkewSeconds) + Math.Max(0, clockSkewSeconds)); + return ExpiresAt - skew > DateTimeOffset.UtcNow; } } } diff --git a/api/Infrastructure/Mappings/AccessControlManagementMapping.cs b/api/Infrastructure/Mappings/AccessControlManagementMapping.cs index f8d71477b..5874dd6ce 100644 --- a/api/Infrastructure/Mappings/AccessControlManagementMapping.cs +++ b/api/Infrastructure/Mappings/AccessControlManagementMapping.cs @@ -1,4 +1,5 @@ -using Mapster; +using System.Collections.Generic; +using Mapster; using Scv.Core.Helpers.Extensions; using Scv.Db.Models; using Scv.Models.AccessControlManagement; @@ -28,7 +29,8 @@ public static void Register(TypeAdapterConfig config) .Map(dest => dest.RoleIds, src => src.RoleIds.DistinctList()) .IgnoreNullValues(true); - config.NewConfig(); + config.NewConfig() + .Map(dest => dest.RoleIds, src => src.RoleIds ?? new List()); config.NewConfig() .Map(dest => dest.GroupIds, src => src.GroupIds.DistinctList()) diff --git a/api/Infrastructure/Mappings/OrderMapping.cs b/api/Infrastructure/Mappings/OrderMapping.cs index dd120645e..18de266d5 100644 --- a/api/Infrastructure/Mappings/OrderMapping.cs +++ b/api/Infrastructure/Mappings/OrderMapping.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using CSOCommon.Models; using Mapster; using Scv.Db.Models; using Scv.Models.Order; @@ -50,24 +51,15 @@ public static void Register(TypeAdapterConfig config) dest.ProcessedDate = src.ProcessedDate?.ToString(PCSSCommonConstants.DATE_FORMAT, CultureInfo.InvariantCulture); }); - config.NewConfig() - .Map(dest => dest.ReferredDocumentId, src => src.OrderRequest.Referral.ReferredDocumentId.GetValueOrDefault()) - .Map(dest => dest.ReviewedByAgenId, src => src.OrderRequest.Referral.ReferredByAgenId) - .Map(dest => dest.ReviewedByPartId, src => src.OrderRequest.Referral.ReferredByPartId) - .Map(dest => dest.ReviewedByPaasSeqNo, src => src.OrderRequest.Referral.ReferredByPaasSeqNo) - .Map(dest => dest.SentToAgenId, src => src.OrderRequest.Referral.SentToAgenId) - .Map(dest => dest.SentToPartId, src => src.OrderRequest.Referral.SentToPartId) - .Map(dest => dest.DigitalSignatureApplied, src => src.Signed) - .Map(dest => dest.CommentTxt, src => src.Comments) - .Map(dest => dest.PdfObject, src => !string.IsNullOrWhiteSpace(src.DocumentData) ? src.DocumentData : src.SupportingDocumentData) + config.NewConfig() + .Map(dest => dest.SignatureApplied, src => src.Signed) + .Map(dest => dest.Comment, src => src.Comments) + .Map(dest => dest.Document, src => GetDocumentData(src)) .Map(dest => dest.OrderTerms, _ => Array.Empty()) .AfterMapping((src, dest) => { - dest.JudicialActionDt = src.ProcessedDate.HasValue - ? src.ProcessedDate.Value.ToString(CultureInfo.InvariantCulture) - : null; - - dest.JudicialDecisionCd = src.Status switch + dest.ActionDate = src.ProcessedDate.HasValue ? src.ProcessedDate.Value : default; + dest.DecisionCode = src.Status switch { OrderStatus.Approved => nameof(JudicialDecisionCd.APPR), OrderStatus.Unapproved => nameof(JudicialDecisionCd.NAPP), @@ -95,4 +87,35 @@ private static string MapCourtListType(string courtListTypeCode) => private static byte[] FromBase64OrNull(string value) => string.IsNullOrWhiteSpace(value) ? [] : Convert.FromBase64String(value); + + private static byte[] FromBase64OrThrow(string value, string fieldName) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException($"'{fieldName}' is required for order submission but was null or empty."); + } + + try + { + return Convert.FromBase64String(value); + } + catch (FormatException ex) + { + throw new InvalidOperationException($"'{fieldName}' contains invalid base64 content and cannot be decoded.", ex); + } + } + + private static byte[] GetDocumentData(OrderDto src) + { + if (src.Status != OrderStatus.Approved) + { + return null; + } + + var data = !string.IsNullOrWhiteSpace(src.DocumentData) + ? src.DocumentData + : src.SupportingDocumentData; + + return FromBase64OrThrow(data, nameof(src.DocumentData)); + } } diff --git a/api/Infrastructure/Middleware/ErrorHandlingMiddleware.cs b/api/Infrastructure/Middleware/ErrorHandlingMiddleware.cs index d38fdc3e4..996fc1f87 100644 --- a/api/Infrastructure/Middleware/ErrorHandlingMiddleware.cs +++ b/api/Infrastructure/Middleware/ErrorHandlingMiddleware.cs @@ -4,6 +4,8 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; +using CSOCommon.Clients.JudicialServices; +using CSOCommon.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -11,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using Scv.Core.Exceptions; using Scv.Core.Helpers.Extensions; using Scv.Db.Models; @@ -147,6 +150,12 @@ private async Task HandleExceptionAsync(HttpContext context, Exception ex) _logger.LogError(ex, "{Message}", ex.Message); break; + case ApiException exception: + code = (HttpStatusCode)exception.StatusCode; + _logger.LogError(ex, "CSO Client Failed with status: {Status} with detailed error: {Error}", exception.StatusCode, JsonSerializer.Serialize(exception.Result)); + break; + + case JCCommon.Clients.LocationServices.ApiException: case JCCommon.Clients.LookupCodeServices.ApiException: code = HttpStatusCode.InternalServerError; diff --git a/cso-client/Infrastructure/Options/CsoOptions.cs b/api/Infrastructure/Options/CsoOptions.cs similarity index 100% rename from cso-client/Infrastructure/Options/CsoOptions.cs rename to api/Infrastructure/Options/CsoOptions.cs diff --git a/api/Infrastructure/Options/KeycloakClientOptions.cs b/api/Infrastructure/Options/KeycloakClientOptions.cs index f7294499e..4654907f4 100644 --- a/api/Infrastructure/Options/KeycloakClientOptions.cs +++ b/api/Infrastructure/Options/KeycloakClientOptions.cs @@ -1,64 +1,63 @@ using System.ComponentModel.DataAnnotations; -namespace Scv.Api.Infrastructure.Options +namespace Scv.Api.Infrastructure.Options; + +/// +/// Configuration options for service account token requests. +/// +public class KeycloakClientOptions { /// - /// Configuration options for service account token requests. + /// Keycloak authority URL (e.g., https://keycloak.example.com/realms/your-realm) /// - public class KeycloakClientOptions - { - /// - /// Keycloak authority URL (e.g., https://keycloak.example.com/realms/your-realm) - /// - [Required] - public string Authority { get; set; } = default!; - - /// - /// Expected audience in the JWT token. - /// - public string Audience { get; set; } - - /// - /// Client ID for the service account. - /// - [Required] - public string ClientId { get; set; } = default!; + [Required] + public string Authority { get; set; } = default!; - /// - /// Service account secret used to retrieve tokens. Value is expected from environment variables. - /// - [Required] - public string ServiceAccountSecret { get; set; } = default!; + /// + /// Expected audience in the JWT token. + /// + public string Audience { get; set; } - /// - /// Refresh skew in seconds for service account tokens. - /// - [Range(0, 3600)] - public int RefreshSkewSeconds { get; set; } = 60; + /// + /// Client ID for the service account. + /// + [Required] + public string ClientId { get; set; } = default!; - /// - /// Optional token scope for the client credentials request. - /// - public string Scope { get; set; } + /// + /// Service account secret used to retrieve tokens. Value is expected from environment variables. + /// + [Required] + public string ServiceAccountSecret { get; set; } = default!; - /// - /// Additional clock skew in seconds used when determining token freshness. - /// - [Range(0, 3600)] - public int ClockSkewSeconds { get; set; } = 0; - } + /// + /// Refresh skew in seconds for service account tokens. + /// + [Range(0, 3600)] + public int RefreshSkewSeconds { get; set; } = 60; /// - /// Keycloak client options for Transitory Documents integrations. + /// Optional token scope for the client credentials request. /// - public sealed class TdKeycloakClientOptions : KeycloakClientOptions - { - } + public string Scope { get; set; } /// - /// Keycloak client options for CSO integrations. + /// Additional clock skew in seconds used when determining token freshness. /// - public sealed class CsoKeycloakClientOptions : KeycloakClientOptions - { - } + [Range(0, 3600)] + public int ClockSkewSeconds { get; set; } = 0; +} + +/// +/// Keycloak client options for Transitory Documents integrations. +/// +public sealed class TdKeycloakClientOptions : KeycloakClientOptions +{ +} + +/// +/// Keycloak client options for CSO integrations. +/// +public sealed class CsoKeycloakClientOptions : KeycloakClientOptions +{ } diff --git a/api/Infrastructure/ServiceCollectionExtensions.cs b/api/Infrastructure/ServiceCollectionExtensions.cs index 739b5e2ec..97c49949f 100644 --- a/api/Infrastructure/ServiceCollectionExtensions.cs +++ b/api/Infrastructure/ServiceCollectionExtensions.cs @@ -44,7 +44,6 @@ using Scv.Core.Exceptions; using Scv.Core.Handler; using Scv.Core.Helpers.Extensions; -using Scv.Cso; using Scv.Cso.Infrastructure.Options; using Scv.Db.Contexts; using Scv.Db.Repositories; @@ -91,6 +90,7 @@ public static void AddNutrient(this IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } public static IServiceCollection AddMapster(this IServiceCollection services, Action options = null) @@ -257,13 +257,6 @@ public static IServiceCollection AddHttpClientsAndScvServices(this IServiceColle .Bind(configuration.GetSection("CsoClientKeycloak")) .ValidateDataAnnotations(); - services - .AddHttpClient((sp, client) => - { - var options = sp.GetRequiredService>().Value; - client.BaseAddress = new Uri(options.BaseUrl.EnsureEndingForwardSlash()); - }) - .AddHttpMessageHandler(); services .AddHttpClient((sp, client) => { diff --git a/api/Services/OrderService.cs b/api/Services/OrderService.cs index 956d3dd3a..3dccca1d8 100644 --- a/api/Services/OrderService.cs +++ b/api/Services/OrderService.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; +using CSOCommon.Clients.JudicialServices; +using CSOCommon.Models; using Hangfire; using JCCommon.Clients.FileServices; using LazyCache; @@ -17,7 +18,6 @@ using Scv.Core.Helpers; using Scv.Core.Helpers.Extensions; using Scv.Core.Infrastructure; -using Scv.Cso; using Scv.Db.Models; using Scv.Db.Repositories; using Scv.Models.Order; @@ -42,8 +42,7 @@ public class OrderService : CrudServiceBase, Order, Order private readonly string _requestAgencyIdentifierId; private readonly string _requestPartId; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ICsoClient _csoClient; - private readonly IUserService _userService; + private readonly IJudicialServicesClient _judicialClient; public override string CacheName => "GetOrdersAsync"; @@ -57,8 +56,7 @@ public OrderService( IJudgeService judgeService, IBackgroundJobClient backgroundJobClient, IHttpContextAccessor httpContextAccessor, - ICsoClient csoClient, - IUserService userService + IJudicialServicesClient judicialClient ) : base( cache, mapper, @@ -74,8 +72,7 @@ IUserService userService _requestAgencyIdentifierId = configuration.GetNonEmptyValue("Request:AgencyIdentifierId"); _requestPartId = configuration.GetNonEmptyValue("Request:PartId"); _httpContextAccessor = httpContextAccessor; - _csoClient = csoClient; - _userService = userService; + _judicialClient = judicialClient; } public async Task ValidateOrderRequestAsync(OrderRequestDto dto) @@ -134,7 +131,8 @@ public async Task> ProcessOrderRequestAsync(OrderReque var existingOrders = await this.Repo .FindAsync(o => o.OrderRequest.PhysicalFileId == fileId && o.OrderRequest.Referral.SentToPartId.Equals(dto.Referral.SentToPartId) - && o.OrderRequest.Referral.ReferredDocumentId == dto.Referral.ReferredDocumentId); + && o.OrderRequest.Referral.ReferredDocumentId == dto.Referral.ReferredDocumentId + && (o.Status == OrderStatus.Pending || o.Status == OrderStatus.AwaitingDocumentation)); var existingOrder = existingOrders?.FirstOrDefault(); OrderDto orderDto; @@ -278,6 +276,8 @@ public async Task SubmitOrder(string id) orderDto.SubmitAttempts = order.SubmitAttempts.HasValue ? order.SubmitAttempts.Value + 1 : 1; + var correlationId = Guid.NewGuid(); + try { var actionDto = await MapToOrderAction(orderDto); @@ -292,19 +292,12 @@ public async Task SubmitOrder(string id) return OperationResult.Failure("Failed to map Order to OrderAction."); } - var success = await _csoClient.SendOrderAsync(actionDto, default); - if (!success) - { - this.Logger.LogWarning("Order {OrderId} submission to CSO failed.", id); - orderDto.SubmitStatus = SubmitStatus.Error; - var failedStatusResult = await UpdateAsync(orderDto); - if (!failedStatusResult.Succeeded) - { - return failedStatusResult; - } + double documentId = orderDto.OrderRequest?.Referral?.ReferredDocumentId.GetValueOrDefault() ?? 0; - return OperationResult.Failure("Failed to submit order to CSO."); - } + await _judicialClient.SaveJudicialActionAsync( + correlationId, + documentId, + actionDto); // Cleanup the successful, submitted order to remove potentially private document data and comments. orderDto.DocumentData = null; @@ -392,7 +385,7 @@ private async Task> PopulateOrder(OrderRequestDto dto, return OperationResult.Success(orderDto); } - private async Task MapToOrderAction(OrderDto orderDto) + private async Task MapToOrderAction(OrderDto orderDto) { var referral = orderDto.OrderRequest?.Referral; if (referral?.ReferredDocumentId == null) @@ -401,44 +394,51 @@ private async Task MapToOrderAction(OrderDto orderDto) return null; } - var userGuid = _httpContextAccessor.HttpContext?.User?.ProvjudUserGuid(); - if (string.IsNullOrWhiteSpace(userGuid)) + var judges = await _judgeService.GetJudges(); + var judge = judges.FirstOrDefault(j => j.PersonId == orderDto.JudgeId); + if (judge == null) { - if (!referral.SentToPartId.HasValue) - { - this.Logger.LogError("Order {OrderId} is missing SentToPartId for submitter lookup.", orderDto.Id); - return null; - } + this.Logger.LogError("Judge with id: {JudgeId} is not found for Order {OrderId}.", orderDto.JudgeId, orderDto.Id); + return null; + } - var user = await _userService.GetByJudgeIdAsync(orderDto.JudgeId); - userGuid = user?.NativeGuid; + var isValidAgencyId = double.TryParse(_requestAgencyIdentifierId, out var agencyId); + if (!isValidAgencyId) + { + this.Logger.LogError("Invalid AgencyIdentifierId configuration value: {AgencyIdentifierId}.", _requestAgencyIdentifierId); + return null; } - if (string.IsNullOrWhiteSpace(userGuid)) + if (judge.ParticipantId is null or 0) { - this.Logger.LogError("Order {OrderId} is missing a submitter user guid.", orderDto.Id); + this.Logger.LogError("Invalid ParticipantId for Judge with id: {JudgeId}.", orderDto.JudgeId); return null; } - var actionDto = Mapper.Map(orderDto); - actionDto.UserGuid = userGuid; + var actionDto = Mapper.Map(orderDto); actionDto.OrderTerms ??= []; + actionDto.ReviewedBy = new Reviewer + { + AgencyId = agencyId, + PaasSeqNo = orderDto.OrderRequest?.Referral?.ReferredByPaasSeqNo.GetValueOrDefault() ?? 0, + PartId = judge.ParticipantId.Value + }; if (orderDto.Status == OrderStatus.Unapproved && orderDto.ProcessedDate.HasValue) { - actionDto.RejectedDt = orderDto.ProcessedDate.Value.ToString(CultureInfo.InvariantCulture); + actionDto.RejectedDate = orderDto.ProcessedDate.Value; } else { - actionDto.RejectedDt = null; + actionDto.RejectedDate = null; } if (orderDto.Signed && orderDto.ProcessedDate.HasValue) { - actionDto.SignedDt = orderDto.ProcessedDate.Value.ToString(CultureInfo.InvariantCulture); + actionDto.SignedDate = orderDto.ProcessedDate.Value; } else { - actionDto.SignedDt = null; + actionDto.SignedDate = null; } return actionDto; diff --git a/api/Validators/Order/OrderActionDtoValidator.cs b/api/Validators/Order/OrderActionDtoValidator.cs deleted file mode 100644 index cd8edb938..000000000 --- a/api/Validators/Order/OrderActionDtoValidator.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using FluentValidation; -using Scv.Models.Order; - -namespace Scv.Api.Validators.Order; - -public class OrderActionDtoValidator : AbstractValidator -{ - public OrderActionDtoValidator() - { - RuleFor(r => r.ReferredDocumentId) - .NotNull().WithMessage("ReferredDocumentId is required."); - - RuleFor(r => r.CommentTxt) - .NotNull().WithMessage("CommentTxt is required."); - - RuleFor(r => r.JudicialDecisionCd) - .NotEmpty().WithMessage("JudicialDecisionCd is required.") - .Must(value => Enum.TryParse(value, out _)) - .WithMessage("JudicialDecisionCd must be one of: APPR, NAPP, AFDC."); - - RuleFor(r => r.ReviewedByAgenId) - .NotEmpty().WithMessage("ReviewedByAgenId is required.") - .When(r => r.ReviewedByPartId == null && r.ReviewedByPaasSeqNo == null); - - RuleFor(r => r.ReviewedByPartId) - .NotEmpty().WithMessage("ReviewedByPartId is required.") - .When(r => r.ReviewedByAgenId == null && r.ReviewedByPaasSeqNo == null); - - RuleFor(r => r.ReviewedByPaasSeqNo) - .NotEmpty().WithMessage("ReviewedByPaasSeqNo is required.") - .When(r => r.ReviewedByAgenId == null && r.ReviewedByPartId == null); - - RuleFor(r => r.RejectedDt) - .NotEmpty().WithMessage("RejectedDt or SignedDt is required.") - .When(r => r.SignedDt == null); - - RuleFor(r => r.SignedDt) - .NotEmpty().WithMessage("SignedDt or RejectedDt is required.") - .When(r => r.RejectedDt == null); - } -} diff --git a/cso-client/Clients/JudicialServicesClient.cs b/cso-client/Clients/JudicialServicesClient.cs index 90c9b45b2..eec73a35c 100644 --- a/cso-client/Clients/JudicialServicesClient.cs +++ b/cso-client/Clients/JudicialServicesClient.cs @@ -20,757 +20,604 @@ #pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type" #pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)." -namespace CSOCommon.Clients.JudicialServices +namespace CSOCommon.Clients.JudicialServices; + +using CSOCommon.Models; +using Newtonsoft.Json; +using System = global::System; + +[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial interface IJudicialServicesClient { - using Newtonsoft.Json; - using System = global::System; + JsonSerializerSettings JsonSerializerSettings { get; } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial interface IJudicialServicesClient - { - JsonSerializerSettings JsonSerializerSettings { get; } - - /// - /// Get judicial document - /// - /// A unique Transaction Id - /// Document identifier - /// Requested documents associated application - /// application - /// does the document require flattening. Will it be displayed in the browser? Then use Y - /// Judicial document returned - /// A server side error occurred. - System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get judicial document - /// - /// A unique Transaction Id - /// Document identifier - /// Requested documents associated application - /// application - /// does the document require flattening. Will it be displayed in the browser? Then use Y - /// Judicial document returned - /// A server side error occurred. - System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten, System.Threading.CancellationToken cancellationToken); - - /// - /// Save jusdicial action - /// - /// A unique Transaction Id - /// Judicial action save body - /// Report returned as pdf - /// A server side error occurred. - System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, JudicialAction body); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Save jusdicial action - /// - /// A unique Transaction Id - /// Judicial action save body - /// Report returned as pdf - /// A server side error occurred. - System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, JudicialAction body, System.Threading.CancellationToken cancellationToken); + /// + /// Get judicial document + /// + /// A unique Transaction Id + /// Document identifier + /// Requested documents associated application + /// application + /// does the document require flattening. Will it be displayed in the browser? Then use Y + /// Judicial document returned + /// A server side error occurred. + System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get judicial document + /// + /// A unique Transaction Id + /// Document identifier + /// Requested documents associated application + /// application + /// does the document require flattening. Will it be displayed in the browser? Then use Y + /// User GUID + /// Judicial document returned + /// A server side error occurred. + System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten, System.Threading.CancellationToken cancellationToken); - } + /// + /// Save jusdicial action + /// + /// A unique Transaction Id + /// Document identifier + /// Judicial action save body + /// Report returned as pdf + /// A server side error occurred. + System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, double documentId, JudicialAction body); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Save jusdicial action + /// + /// A unique Transaction Id + /// Document identifier + /// Judicial action save body + /// Report returned as pdf + /// A server side error occurred. + System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, double documentId, JudicialAction body, System.Threading.CancellationToken cancellationToken); - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class JudicialServicesClient : IJudicialServicesClient - { +} + +[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class JudicialServicesClient : IJudicialServicesClient +{ #pragma warning disable 8618 - private string _baseUrl; + private string _baseUrl; #pragma warning restore 8618 - private System.Net.Http.HttpClient _httpClient; - private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); - private Newtonsoft.Json.JsonSerializerSettings _instanceSettings; + private System.Net.Http.HttpClient _httpClient; + private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); + private Newtonsoft.Json.JsonSerializerSettings _instanceSettings; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public JudicialServicesClient(string baseUrl, System.Net.Http.HttpClient httpClient) + public JudicialServicesClient(System.Net.Http.HttpClient httpClient) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - { - BaseUrl = baseUrl; - _httpClient = httpClient; - Initialize(); - } + { + _httpClient = httpClient; + Initialize(); + } - private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() - { - var settings = new Newtonsoft.Json.JsonSerializerSettings(); - UpdateJsonSerializerSettings(settings); - return settings; - } + private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } - public string BaseUrl + public string BaseUrl + { + get { return _baseUrl; } + set { - get { return _baseUrl; } - set - { - _baseUrl = value; - if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) - _baseUrl += '/'; - } + _baseUrl = value; + if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) + _baseUrl += '/'; } + } - public Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } } + public Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } } - static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); - partial void Initialize(); + partial void Initialize(); - partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); - partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); - partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); - /// - /// Get judicial document - /// - /// A unique Transaction Id - /// Document identifier - /// Requested documents associated application - /// application - /// does the document require flattening. Will it be displayed in the browser? Then use Y - /// Judicial document returned - /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten) - { - return GetJudicialDocumentAsync(transaction_Id, agencyId, documentId, documentApplicationName, applicationCode, documentFlatten, System.Threading.CancellationToken.None); - } + /// + /// Get judicial document + /// + /// A unique Transaction Id + /// Document identifier + /// Requested documents associated application + /// application + /// does the document require flattening. Will it be displayed in the browser? Then use Y + /// Judicial document returned + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten) + { + return GetJudicialDocumentAsync(transaction_Id, agencyId, documentId, documentApplicationName, applicationCode, documentFlatten, System.Threading.CancellationToken.None); + } - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get judicial document - /// - /// A unique Transaction Id - /// Document identifier - /// Requested documents associated application - /// application - /// does the document require flattening. Will it be displayed in the browser? Then use Y - /// Judicial document returned - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten, System.Threading.CancellationToken cancellationToken) - { - if (documentId == null) - throw new System.ArgumentNullException("documentId"); + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get judicial document + /// + /// A unique Transaction Id + /// Document identifier + /// Requested documents associated application + /// application + /// does the document require flattening. Will it be displayed in the browser? Then use Y + /// Judicial document returned + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetJudicialDocumentAsync(System.Guid transaction_Id, double? agencyId, double documentId, DocumentApplicationName documentApplicationName, string applicationCode, string documentFlatten, System.Threading.CancellationToken cancellationToken) + { + if (documentId == null) + throw new System.ArgumentNullException("documentId"); - if (documentApplicationName == null) - throw new System.ArgumentNullException("documentApplicationName"); + if (documentApplicationName == null) + throw new System.ArgumentNullException("documentApplicationName"); - if (applicationCode == null) - throw new System.ArgumentNullException("applicationCode"); + if (applicationCode == null) + throw new System.ArgumentNullException("applicationCode"); - if (documentFlatten == null) - throw new System.ArgumentNullException("documentFlatten"); + if (documentFlatten == null) + throw new System.ArgumentNullException("documentFlatten"); - var client_ = _httpClient; - var disposeClient_ = false; - try + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - if (transaction_Id == null) - throw new System.ArgumentNullException("transaction_Id"); - request_.Headers.TryAddWithoutValidation("Transaction-Id", ConvertToString(transaction_Id, System.Globalization.CultureInfo.InvariantCulture)); - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/pdf")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "judicial/{documentId}/document" - urlBuilder_.Append("judicial/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(documentId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/document"); - urlBuilder_.Append('?'); - if (agencyId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("agencyId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(agencyId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Append(System.Uri.EscapeDataString("documentApplicationName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(documentApplicationName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Append(System.Uri.EscapeDataString("applicationCode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(applicationCode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Append(System.Uri.EscapeDataString("documentFlatten")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(documentFlatten, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Length--; + if (transaction_Id == null) + throw new System.ArgumentNullException("transaction_Id"); + request_.Headers.TryAddWithoutValidation("Transaction-Id", ConvertToString(transaction_Id, System.Globalization.CultureInfo.InvariantCulture)); + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/pdf")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "judicial/{documentId}/document" + urlBuilder_.Append("judicial/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(documentId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/document"); + urlBuilder_.Append('?'); + if (agencyId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("agencyId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(agencyId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Append(System.Uri.EscapeDataString("documentApplicationName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(documentApplicationName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("applicationCode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(applicationCode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("documentFlatten")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(documentFlatten, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Length--; - PrepareRequest(client_, request_, urlBuilder_); + PrepareRequest(client_, request_, urlBuilder_); - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + PrepareRequest(client_, request_, url_); - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) + foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } + } - ProcessResponse(client_, response_); + ProcessResponse(client_, response_); - var status_ = (int)response_.StatusCode; - if (status_ == 200 || status_ == 206) + var status_ = (int)response_.StatusCode; + if (status_ == 200 || status_ == 206) + { + var responseStream_ = response_.Content == null ? System.IO.Stream.Null : await ReadAsStreamAsync(response_.Content, cancellationToken).ConfigureAwait(false); + var fileResponse_ = new FileResponse(status_, headers_, responseStream_, null, response_); + disposeClient_ = false; disposeResponse_ = false; // response and client are disposed by FileResponse + return fileResponse_; + } + else + if (status_ == 400) { - var responseStream_ = response_.Content == null ? System.IO.Stream.Null : await ReadAsStreamAsync(response_.Content, cancellationToken).ConfigureAwait(false); - var fileResponse_ = new FileResponse(status_, headers_, responseStream_, null, response_); - disposeClient_ = false; disposeResponse_ = false; // response and client are disposed by FileResponse - return fileResponse_; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Request is not valid", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 400) + if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Request is not valid", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Incorrect or no authorization provided", status_, responseText_, headers_, null); } else - if (status_ == 401) + if (status_ == 403) { string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Incorrect or no authorization provided", status_, responseText_, headers_, null); + throw new ApiException("You do not have correct permissions to access this resource", status_, responseText_, headers_, null); } else - if (status_ == 403) + if (status_ == 500) { - string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("You do not have correct permissions to access this resource", status_, responseText_, headers_, null); - } - else - if (status_ == 500) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Internal server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) { - var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } + throw new ApiException("Internal server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); } - } - finally - { - if (disposeClient_) - client_.Dispose(); } } - - /// - /// Save jusdicial action - /// - /// A unique Transaction Id - /// Judicial action save body - /// Report returned as pdf - /// A server side error occurred. - public virtual System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, JudicialAction body) + finally { - return SaveJudicialActionAsync(transaction_Id, body, System.Threading.CancellationToken.None); + if (disposeClient_) + client_.Dispose(); } + } + + /// + /// Save jusdicial action + /// + /// A unique Transaction Id + /// Document identifier + /// Judicial action save body + /// Report returned as pdf + /// A server side error occurred. + public virtual System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, double documentId, JudicialAction body) + { + return SaveJudicialActionAsync(transaction_Id, documentId, body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Save jusdicial action + /// + /// A unique Transaction Id + /// Document identifier + /// Judicial action save body + /// Report returned as pdf + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, double documentId, JudicialAction body, System.Threading.CancellationToken cancellationToken) + { + if (documentId == null) + throw new System.ArgumentNullException("documentId"); - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Save jusdicial action - /// - /// A unique Transaction Id - /// Judicial action save body - /// Report returned as pdf - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task SaveJudicialActionAsync(System.Guid transaction_Id, JudicialAction body, System.Threading.CancellationToken cancellationToken) + var client_ = _httpClient; + var disposeClient_ = false; + try { - var client_ = _httpClient; - var disposeClient_ = false; - try + using (var request_ = new System.Net.Http.HttpRequestMessage()) { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - if (transaction_Id == null) - throw new System.ArgumentNullException("transaction_Id"); - request_.Headers.TryAddWithoutValidation("Transaction-Id", ConvertToString(transaction_Id, System.Globalization.CultureInfo.InvariantCulture)); - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); - var content_ = new System.Net.Http.StringContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("POST"); + if (transaction_Id == null) + throw new System.ArgumentNullException("transaction_Id"); + request_.Headers.TryAddWithoutValidation("Transaction-Id", ConvertToString(transaction_Id, System.Globalization.CultureInfo.InvariantCulture)); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "judicial/{documentId}/action" - urlBuilder_.Append("judicial/"); - urlBuilder_.Append("/action"); + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "judicial/{documentId}/action" + urlBuilder_.Append("judicial/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(documentId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/action"); - PrepareRequest(client_, request_, urlBuilder_); + PrepareRequest(client_, request_, urlBuilder_); - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + PrepareRequest(client_, request_, url_); - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) + foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } + } - ProcessResponse(client_, response_); + ProcessResponse(client_, response_); - var status_ = (int)response_.StatusCode; - if (status_ == 204) + var status_ = (int)response_.StatusCode; + if (status_ == 204) + { + return; + } + else + if (status_ == 400) { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Request is not valid", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 400) + if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Request is not valid", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Incorrect or no authorization provided", status_, responseText_, headers_, null); } else - if (status_ == 401) + if (status_ == 403) { string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Incorrect or no authorization provided", status_, responseText_, headers_, null); + throw new ApiException("You do not have correct permissions to access this resource", status_, responseText_, headers_, null); } else - if (status_ == 403) + if (status_ == 500) { - string responseText_ = (response_.Content == null) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("You do not have correct permissions to access this resource", status_, responseText_, headers_, null); - } - else - if (status_ == 500) + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Internal server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - else - { - var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } + throw new ApiException("Internal server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); } - } - finally - { - if (disposeClient_) - client_.Dispose(); } } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } - protected struct ObjectResponseResult + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) { - public ObjectResponseResult(T responseObject, string responseText) - { - this.Object = responseObject; - this.Text = responseText; - } + this.Object = responseObject; + this.Text = responseText; + } - public T Object { get; } + public T Object { get; } - public string Text { get; } - } + public string Text { get; } + } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - private static System.Threading.Tasks.Task ReadAsStringAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) - { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStringAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { #if NET5_0_OR_GREATER - return content.ReadAsStringAsync(cancellationToken); + return content.ReadAsStringAsync(cancellationToken); #else - return content.ReadAsStringAsync(); + return content.ReadAsStringAsync(); #endif - } + } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - private static System.Threading.Tasks.Task ReadAsStreamAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) - { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStreamAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { #if NET5_0_OR_GREATER - return content.ReadAsStreamAsync(cancellationToken); + return content.ReadAsStreamAsync(cancellationToken); #else - return content.ReadAsStreamAsync(); + return content.ReadAsStreamAsync(); #endif - } + } - public bool ReadResponseAsString { get; set; } + public bool ReadResponseAsString { get; set; } - protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) { - if (response == null || response.Content == null) - { - return new ObjectResponseResult(default(T), string.Empty); - } + return new ObjectResponseResult(default(T), string.Empty); + } - if (ReadResponseAsString) + if (ReadResponseAsString) + { + var responseText = await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(false); + try { - var responseText = await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(false); - try - { - var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); - return new ObjectResponseResult(typedBody, responseText); - } - catch (Newtonsoft.Json.JsonException exception) - { - var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; - throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); - } + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); } - else + catch (Newtonsoft.Json.JsonException exception) { - try - { - using (var responseStream = await ReadAsStreamAsync(response.Content, cancellationToken).ConfigureAwait(false)) - using (var streamReader = new System.IO.StreamReader(responseStream)) - using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) - { - var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); - var typedBody = serializer.Deserialize(jsonTextReader); - return new ObjectResponseResult(typedBody, string.Empty); - } - } - catch (Newtonsoft.Json.JsonException exception) - { - var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; - throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); - } + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); } } - - private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + else { - if (value == null) - { - return ""; - } - - if (value is System.Enum) + try { - var name = System.Enum.GetName(value.GetType(), value); - if (name != null) + using (var responseStream = await ReadAsStreamAsync(response.Content, cancellationToken).ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) { - var field_ = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); - if (field_ != null) - { - var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field_, typeof(System.Runtime.Serialization.EnumMemberAttribute)) - as System.Runtime.Serialization.EnumMemberAttribute; - if (attribute != null) - { - return attribute.Value != null ? attribute.Value : name; - } - } - - var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); - return converted == null ? string.Empty : converted; + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); } } - else if (value is bool) + catch (Newtonsoft.Json.JsonException exception) { - return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); - } - else if (value is byte[]) - { - return System.Convert.ToBase64String((byte[])value); - } - else if (value is string[]) - { - return string.Join(",", (string[])value); - } - else if (value.GetType().IsArray) - { - var valueArray = (System.Array)value; - var valueTextArray = new string[valueArray.Length]; - for (var i = 0; i < valueArray.Length; i++) - { - valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo); - } - return string.Join(",", valueTextArray); + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); } - - var result = System.Convert.ToString(value, cultureInfo); - return result == null ? "" : result; } } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public enum ErrorCode - { - - [System.Runtime.Serialization.EnumMember(Value = @"RECORDNOTFOUND")] - RECORDNOTFOUND = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"DATALOADERROR")] - DATALOADERROR = 1, - - [System.Runtime.Serialization.EnumMember(Value = @"INVALIDUSER")] - INVALIDUSER = 2, - - [System.Runtime.Serialization.EnumMember(Value = @"INVALIDREQUEST")] - INVALIDREQUEST = 3, - - [System.Runtime.Serialization.EnumMember(Value = @"UNKNOWN")] - UNKNOWN = 4, - - [System.Runtime.Serialization.EnumMember(Value = @"VALIDATIONERROR")] - VALIDATIONERROR = 5, - - } - - /// - /// Error response during request. - /// - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class APIError + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) { - - [Newtonsoft.Json.JsonProperty("code", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public ErrorCode Code { get; set; } - - [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Message { get; set; } - - private System.Collections.Generic.IDictionary _additionalProperties; - - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties + if (value == null) { - get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } - set { _additionalProperties = value; } + return ""; } - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class JudicialAction - { - - [Newtonsoft.Json.JsonProperty("actionDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] - public System.DateTimeOffset ActionDate { get; set; } - - [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Description { get; set; } - - [Newtonsoft.Json.JsonProperty("reviewedBy", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public Reviewer ReviewedBy { get; set; } - - [Newtonsoft.Json.JsonProperty("decisionCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string DecisionCode { get; set; } - - [Newtonsoft.Json.JsonProperty("signatureApplied", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public bool SignatureApplied { get; set; } - - [Newtonsoft.Json.JsonProperty("rejectedDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] - public System.DateTimeOffset RejectedDate { get; set; } - - [Newtonsoft.Json.JsonProperty("signedDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] - public System.DateTimeOffset SignedDate { get; set; } - - [Newtonsoft.Json.JsonProperty("comment", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Comment { get; set; } - - /// - /// base 64 encoded pdf document - /// - [Newtonsoft.Json.JsonProperty("document", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public byte[] Document { get; set; } - - [Newtonsoft.Json.JsonProperty("orderTerms", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection OrderTerms { get; set; } - - private System.Collections.Generic.IDictionary _additionalProperties; + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field_ = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field_ != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field_, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) { - get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } - set { _additionalProperties = value; } + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class Reviewer - { - - [Newtonsoft.Json.JsonProperty("agencyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double AgencyId { get; set; } - - [Newtonsoft.Json.JsonProperty("partId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double PartId { get; set; } - - [Newtonsoft.Json.JsonProperty("paasSeqNo", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double PaasSeqNo { get; set; } - - private System.Collections.Generic.IDictionary _additionalProperties; - - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties + else if (value is byte[]) { - get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } - set { _additionalProperties = value; } + return System.Convert.ToBase64String((byte[])value); + } + else if (value is string[]) + { + return string.Join(",", (string[])value); + } + else if (value.GetType().IsArray) + { + var valueArray = (System.Array)value; + var valueTextArray = new string[valueArray.Length]; + for (var i = 0; i < valueArray.Length; i++) + { + valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo); + } + return string.Join(",", valueTextArray); } + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; } +} - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class OrderTerm +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter +{ + public DateFormatConverter() { + DateTimeFormat = "yyyy-MM-dd"; + } +} - [Newtonsoft.Json.JsonProperty("sequenceNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double SequenceNumber { get; set; } - - [Newtonsoft.Json.JsonProperty("text", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Text { get; set; } - - [Newtonsoft.Json.JsonProperty("displaySortNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double DisplaySortNumber { get; set; } +[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class FileResponse : System.IDisposable +{ + private System.IDisposable _client; + private System.IDisposable _response; - private System.Collections.Generic.IDictionary _additionalProperties; + public int StatusCode { get; private set; } - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties - { - get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } - set { _additionalProperties = value; } - } + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } - } + public System.IO.Stream Stream { get; private set; } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public enum DocumentApplicationName + public bool IsPartial { - - [System.Runtime.Serialization.EnumMember(Value = @"CEIS")] - CEIS = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"CSO")] - CSO = 1, - + get { return StatusCode == 206; } } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter + public FileResponse(int statusCode, System.Collections.Generic.IReadOnlyDictionary> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response) { - public DateFormatConverter() - { - DateTimeFormat = "yyyy-MM-dd"; - } + StatusCode = statusCode; + Headers = headers; + Stream = stream; + _client = client; + _response = response; } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class FileResponse : System.IDisposable + public void Dispose() { - private System.IDisposable _client; - private System.IDisposable _response; - - public int StatusCode { get; private set; } + Stream.Dispose(); + if (_response != null) + _response.Dispose(); + if (_client != null) + _client.Dispose(); + } +} - public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } - public System.IO.Stream Stream { get; private set; } +[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class ApiException : System.Exception +{ + public int StatusCode { get; private set; } - public bool IsPartial - { - get { return StatusCode == 206; } - } + public string Response { get; private set; } - public FileResponse(int statusCode, System.Collections.Generic.IReadOnlyDictionary> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response) - { - StatusCode = statusCode; - Headers = headers; - Stream = stream; - _client = client; - _response = response; - } + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } - public void Dispose() - { - Stream.Dispose(); - if (_response != null) - _response.Dispose(); - if (_client != null) - _client.Dispose(); - } + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; } - - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ApiException : System.Exception + public override string ToString() { - public int StatusCode { get; private set; } - - public string Response { get; private set; } - - public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } - - public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) - : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) - { - StatusCode = statusCode; - Response = response; - Headers = headers; - } - - public override string ToString() - { - return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); - } + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); } +} - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ApiException : ApiException - { - public TResult Result { get; private set; } +[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class ApiException : ApiException +{ + public TResult Result { get; private set; } - public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) - : base(message, statusCode, response, headers, innerException) - { - Result = result; - } + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; } - } #pragma warning restore 108 @@ -787,4 +634,4 @@ public ApiException(string message, int statusCode, string response, System.Coll #pragma warning restore 8603 #pragma warning restore 8604 #pragma warning restore 8625 -#pragma warning restore 8765 \ No newline at end of file +#pragma warning restore 8765 diff --git a/cso-client/CsoClient.cs b/cso-client/CsoClient.cs deleted file mode 100644 index 63ddfdee7..000000000 --- a/cso-client/CsoClient.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Scv.Cso.Infrastructure.Options; -using Scv.Models.Order; - -namespace Scv.Cso; - -// This will be replaced by the JudicialServicesClient in the future -// while team is still waiting for the details from the PROD instance of Keycloak. -public interface ICsoClient -{ - Task SendOrderAsync(OrderActionDto order, CancellationToken cancellationToken = default); -} - -public class CsoClient(HttpClient httpClient, IOptions options, ILogger logger) : ICsoClient -{ - private static readonly JsonSerializerSettings SnakeCaseSerializerSettings = new() - { - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new SnakeCaseNamingStrategy() - }, - NullValueHandling = NullValueHandling.Ignore - }; - - private readonly HttpClient _httpClient = httpClient; - private readonly CsoOptions _options = options.Value; - private readonly ILogger _logger = logger; - - public async Task SendOrderAsync(OrderActionDto order, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(order); - - var actionUri = _options.ActionUri?.TrimStart('/') ?? string.Empty; - if (string.IsNullOrWhiteSpace(actionUri)) - { - throw new InvalidOperationException("CSO ActionUri is not configured."); - } - - var json = JsonConvert.SerializeObject(order, SnakeCaseSerializerSettings); - using var content = new StringContent(json, Encoding.UTF8, "application/json"); - using var response = await _httpClient.PostAsync(actionUri, content, cancellationToken); - if (!response.IsSuccessStatusCode) - { - var body = response.Content == null - ? null - : await response.Content.ReadAsStringAsync(cancellationToken); - var truncatedBody = body is { Length: > 500 } - ? body[..500] - : body; - - _logger.LogWarning( - "CSO order submit failed with status {StatusCode} {ReasonPhrase} {Body}.", - (int)response.StatusCode, - response.ReasonPhrase, - truncatedBody); - } - - return response.IsSuccessStatusCode; - } -} diff --git a/models/Order/JudicialDecisionCd.cs b/cso-client/Models/JudicialDecisionCd.cs similarity index 67% rename from models/Order/JudicialDecisionCd.cs rename to cso-client/Models/JudicialDecisionCd.cs index e2c649644..aa7d5a464 100644 --- a/models/Order/JudicialDecisionCd.cs +++ b/cso-client/Models/JudicialDecisionCd.cs @@ -1,4 +1,4 @@ -namespace Scv.Models.Order; +namespace CSOCommon.Models; public enum JudicialDecisionCd { diff --git a/cso-client/Models/Models.cs b/cso-client/Models/Models.cs new file mode 100644 index 000000000..3d9b69eca --- /dev/null +++ b/cso-client/Models/Models.cs @@ -0,0 +1,169 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +using System; +using System.Collections.Generic; +using System.Text; +using CSOCommon.Clients.JudicialServices; + +namespace CSOCommon.Models; + +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public enum ErrorCode +{ + + [System.Runtime.Serialization.EnumMember(Value = @"RECORDNOTFOUND")] + RECORDNOTFOUND = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"DATALOADERROR")] + DATALOADERROR = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"INVALIDUSER")] + INVALIDUSER = 2, + + [System.Runtime.Serialization.EnumMember(Value = @"INVALIDREQUEST")] + INVALIDREQUEST = 3, + + [System.Runtime.Serialization.EnumMember(Value = @"UNKNOWN")] + UNKNOWN = 4, + + [System.Runtime.Serialization.EnumMember(Value = @"VALIDATIONERROR")] + VALIDATIONERROR = 5, + +} + +/// +/// Error response during request. +/// +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class APIError +{ + + [Newtonsoft.Json.JsonProperty("code", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public ErrorCode Code { get; set; } + + [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + +} + +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class JudicialAction +{ + + [Newtonsoft.Json.JsonProperty("actionDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.DateTime ActionDate { get; set; } + + [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Description { get; set; } + + [Newtonsoft.Json.JsonProperty("reviewedBy", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Reviewer ReviewedBy { get; set; } + + [Newtonsoft.Json.JsonProperty("decisionCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string DecisionCode { get; set; } + + [Newtonsoft.Json.JsonProperty("signatureApplied", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool SignatureApplied { get; set; } + + [Newtonsoft.Json.JsonProperty("rejectedDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.DateTime? RejectedDate { get; set; } + + [Newtonsoft.Json.JsonProperty("signedDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.DateTime? SignedDate { get; set; } + + [Newtonsoft.Json.JsonProperty("comment", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Comment { get; set; } + + /// + /// base 64 encoded pdf document + /// + [Newtonsoft.Json.JsonProperty("document", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Document { get; set; } + + [Newtonsoft.Json.JsonProperty("orderTerms", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection OrderTerms { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + +} + +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class Reviewer +{ + + [Newtonsoft.Json.JsonProperty("agencyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double AgencyId { get; set; } + + [Newtonsoft.Json.JsonProperty("partId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double PartId { get; set; } + + [Newtonsoft.Json.JsonProperty("paasSeqNo", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double PaasSeqNo { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + +} + +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public partial class OrderTerm +{ + + [Newtonsoft.Json.JsonProperty("sequenceNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double SequenceNumber { get; set; } + + [Newtonsoft.Json.JsonProperty("text", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Text { get; set; } + + [Newtonsoft.Json.JsonProperty("displaySortNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double DisplaySortNumber { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + +} + +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.0.0 (NJsonSchema v11.6.0.0 (Newtonsoft.Json v13.0.0.0))")] +public enum DocumentApplicationName +{ + + [System.Runtime.Serialization.EnumMember(Value = @"CEIS")] + CEIS = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"CSO")] + CSO = 1, + +} diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 0b9706adf..e50f59519 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -81,11 +81,11 @@ services: - CsoClientKeycloak__Audience=${CsoClientKeycloakAudience} - CsoClientKeycloak__Authority=${CsoClientKeycloakAuthority:-https://common-sso.justice.gov.bc.ca/auth/realms/Judiciary} - CsoClientKeycloak__Audience=${CsoClientKeycloakAudience:-CSO} - - CsoClientKeycloak__ClientId=${CsoClientKeycloakClientId:-JASPER-CSO-Mock} + - CsoClientKeycloak__ClientId=${CsoClientKeycloakClientId:-JASPER-CSO-Dev} - CsoClientKeycloak__ServiceAccountSecret=${CsoClientKeycloakSecret:-} - CsoClientKeycloak__RefreshSkewSeconds=${CsoClientKeycloakRefreshSkewSeconds:-60} - - CSO__BaseUrl=${BaseUrl:-http://localhost:5000/api} - - CSO__ActionUri=${ActionUri:-/MockOrders/action} + - CSO__BaseUrl=${CsoBaseUrl:-http://localhost:5000/api} + - CSO__ActionUri=${CsoActionUri:-/MockOrders/action} - LocationServicesClient__Password=${LocationServicesClientPassword} - LocationServicesClient__Url=${LocationServicesClientUrl} - LocationServicesClient__Username=${LocationServicesClientUsername} diff --git a/models/Order/OrderActionDto.cs b/models/Order/OrderActionDto.cs deleted file mode 100644 index 8a262051b..000000000 --- a/models/Order/OrderActionDto.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Newtonsoft.Json; - -namespace Scv.Models.Order; - -public class OrderActionDto -{ - [JsonProperty("referred_document_id")] - public int ReferredDocumentId { get; set; } - [JsonProperty("reviewed_by_agen_id")] - public int? ReviewedByAgenId { get; set; } - [JsonProperty("reviewed_by_part_id")] - public int? ReviewedByPartId { get; set; } - [JsonProperty("reviewed_by_paas_seq_no")] - public int? ReviewedByPaasSeqNo { get; set; } - [JsonProperty("sent_to_agen_id")] - public int? SentToAgenId { get; set; } - [JsonProperty("sent_to_part_id")] - public int? SentToPartId { get; set; } - [JsonProperty("digital_signature_applied")] - public bool DigitalSignatureApplied { get; set; } - [JsonProperty("judicial_action_dt")] - public string? JudicialActionDt { get; set; } - [JsonProperty("rejected_dt")] - public string? RejectedDt { get; set; } - [JsonProperty("signed_dt")] - public string? SignedDt { get; set; } - [JsonProperty("comment_txt")] - public string? CommentTxt { get; set; } - [JsonProperty("user_guid")] - public string? UserGuid { get; set; } - [JsonProperty("judicial_decision_cd")] - public string? JudicialDecisionCd { get; set; } - [JsonProperty("pdf_object")] - public string? PdfObject { get; set; } - - [JsonProperty("order_terms")] - public IEnumerable? OrderTerms { get; set; } -} diff --git a/models/Order/OrderTerm.cs b/models/Order/OrderTerm.cs deleted file mode 100644 index b9c29c551..000000000 --- a/models/Order/OrderTerm.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Scv.Models.Order; - -public class OrderTerm -{ - [Newtonsoft.Json.JsonProperty("term_seq_no")] - public int TermSeqNo { get; set; } - [Newtonsoft.Json.JsonProperty("term_txt")] - public string? TermTxt { get; set; } - [Newtonsoft.Json.JsonProperty("term_display_sort_no")] - public int TermDisplaySortNo { get; set; } -} diff --git a/tests/api/Documents/Strategies/OrderDocumentStrategyTest.cs b/tests/api/Documents/Strategies/OrderDocumentStrategyTest.cs new file mode 100644 index 000000000..6b3d341a9 --- /dev/null +++ b/tests/api/Documents/Strategies/OrderDocumentStrategyTest.cs @@ -0,0 +1,191 @@ +using System.Collections.Generic; +using System.IO; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using CSOCommon.Clients.JudicialServices; +using CSOCommon.Models; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; +using Moq; +using Nutrient.NativeSDK.API.Exceptions; +using Scv.Api.Documents.Strategies; +using Scv.Core.Helpers; +using Scv.Models.Document; +using tests.api.Services; +using Xunit; + +namespace tests.api.Documents.Strategies; + +public class OrderDocumentStrategyTest : ServiceTestBase +{ + private readonly Mock _mockJudicialClient; + private readonly Mock _mockConfiguration; + private readonly string _fakeContent = "fake-pdf-bytes"; + private readonly byte[] _fakeContentBytes; + + public OrderDocumentStrategyTest() + { + _fakeContentBytes = Encoding.UTF8.GetBytes(_fakeContent); + _mockJudicialClient = new Mock(); + _mockConfiguration = new Mock(); + + var mockAgencySection = Mock.Of(s => s.Value == "123"); + var mockAppCdSection = Mock.Of(s => s.Value == "TESTAPP"); + + _mockConfiguration + .Setup(c => c.GetSection("Request:AgencyIdentifierId")) + .Returns(mockAgencySection); + _mockConfiguration + .Setup(c => c.GetSection("Request:ApplicationCd")) + .Returns(mockAppCdSection); + + _mockJudicialClient.Setup(j => j.JsonSerializerSettings).Returns(new Newtonsoft.Json.JsonSerializerSettings()); + } + + private static ClaimsPrincipal BuildUser(string provjudGuid = null, string idirGuid = null) + { + var claims = new List(); + if (provjudGuid != null) + { + claims.Add(new Claim(CustomClaimTypes.ProvjudUserGuid, provjudGuid)); + } + if (idirGuid != null) + { + claims.Add(new Claim(CustomClaimTypes.UserGuid, idirGuid)); + } + + return new ClaimsPrincipal(new ClaimsIdentity(claims, "test")); + } + + private PdfDocumentRequestDetails BuildRequest(string rawDocumentId = "12345", string correlationId = null) + { + var encoded = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(rawDocumentId)); + return new PdfDocumentRequestDetails + { + DocumentId = encoded, + CorrelationId = correlationId + }; + } + + private void SetupJudicialClientResponse() + { + var fakeStream = new MemoryStream(_fakeContentBytes); + var fakeResponse = new FileResponse( + 200, + new Dictionary>(), + fakeStream, + null, + null); + + _mockJudicialClient + .Setup(c => c.GetJudicialDocumentAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(fakeResponse); + } + + [Fact] + public void Type_ReturnsOrder() + { + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + Assert.Equal(DocumentType.Order, strategy.Type); + } + + [Fact] + public async Task Invoke_ReturnsMemoryStreamWithDocumentContent() + { + SetupJudicialClientResponse(); + var user = BuildUser(); + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + user); + + var result = await strategy.Invoke(BuildRequest()); + + Assert.NotNull(result); + result.Position = 0; + Assert.Equal(_fakeContentBytes, result.ToArray()); + } + + [Fact] + public async Task Invoke_AssignsCorrelationId_WhenNotProvided() + { + SetupJudicialClientResponse(); + var request = BuildRequest(correlationId: null); + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + await strategy.Invoke(request); + + Assert.NotNull(request.CorrelationId); + Assert.True(System.Guid.TryParse(request.CorrelationId, out _)); + } + + [Fact] + public async Task Invoke_PreservesCorrelationId_WhenAlreadyProvided() + { + SetupJudicialClientResponse(); + var request = BuildRequest(correlationId: "existing-correlation-id"); + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + await strategy.Invoke(request); + + Assert.Equal("existing-correlation-id", request.CorrelationId); + } + + [Fact] + public async Task Invoke_ThrowsInvalidArgumentException_WhenDocumentIdIsNull() + { + var request = new PdfDocumentRequestDetails { DocumentId = null }; + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + await Assert.ThrowsAsync(() => strategy.Invoke(request)); + } + + [Fact] + public async Task Invoke_ThrowsInvalidArgumentException_WhenDocumentIdIsNotValidBase64Number() + { + var encoded = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes("not-a-number")); + var request = new PdfDocumentRequestDetails { DocumentId = encoded }; + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + await Assert.ThrowsAsync(() => strategy.Invoke(request)); + } + + [Fact] + public async Task Invoke_ThrowsInvalidArgumentException_WhenAgencyIdIsInvalid() + { + var badAgencySection = Mock.Of(s => s.Value == "not-a-number"); + _mockConfiguration + .Setup(c => c.GetSection("Request:AgencyIdentifierId")) + .Returns(badAgencySection); + + var strategy = new OrderDocumentStrategy( + _mockJudicialClient.Object, + _mockConfiguration.Object, + BuildUser()); + + await Assert.ThrowsAsync(() => strategy.Invoke(BuildRequest())); + } +} diff --git a/tests/api/Infrastructure/Mappings/OrderMappingTests.cs b/tests/api/Infrastructure/Mappings/OrderMappingTests.cs index 7b680f1ab..855d7a650 100644 --- a/tests/api/Infrastructure/Mappings/OrderMappingTests.cs +++ b/tests/api/Infrastructure/Mappings/OrderMappingTests.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using Bogus; +using CSOCommon.Models; using Mapster; using Scv.Api.Infrastructure.Mappings; using Scv.Db.Models; @@ -431,6 +432,217 @@ public void Order_To_OrderViewDto_MapsId() #endregion + #region OrderDto -> JudicialAction Mapping Tests + + [Fact] + public void OrderDto_To_JudicialAction_MapsDocumentData_WhenStatusIsApproved() + { + var expectedBytes = _faker.Random.Bytes(64); + var orderDto = CreateOrderDto(); + orderDto.DocumentData = Convert.ToBase64String(expectedBytes); + orderDto.SupportingDocumentData = Convert.ToBase64String(_faker.Random.Bytes(64)); + orderDto.Status = OrderStatus.Approved; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedBytes, result.Document); + } + + [Fact] + public void OrderDto_To_JudicialAction_DoesNotMapDocument_WhenStatusIsNotApproved() + { + var expectedBytes = _faker.Random.Bytes(64); + var orderDto = CreateOrderDto(); + orderDto.DocumentData = Convert.ToBase64String(expectedBytes); + orderDto.SupportingDocumentData = Convert.ToBase64String(_faker.Random.Bytes(64)); + orderDto.Status = OrderStatus.Unapproved; + + var result = orderDto.Adapt(_config); + + Assert.Null(result.Document); + } + + [Fact] + public void OrderDto_To_JudicialAction_DoesNotMapDocument_WhenStatusIsAwaitingDocumentation() + { + var expectedBytes = _faker.Random.Bytes(64); + var orderDto = CreateOrderDto(); + orderDto.DocumentData = Convert.ToBase64String(expectedBytes); + orderDto.SupportingDocumentData = Convert.ToBase64String(_faker.Random.Bytes(64)); + orderDto.Status = OrderStatus.AwaitingDocumentation; + + var result = orderDto.Adapt(_config); + + Assert.Null(result.Document); + } + + [Fact] + public void OrderDto_To_JudicialAction_FallsBackToSupportingDocumentData_WhenDocumentDataIsNull() + { + var expectedBytes = _faker.Random.Bytes(64); + var orderDto = CreateOrderDto(); + orderDto.DocumentData = null; + orderDto.SupportingDocumentData = Convert.ToBase64String(expectedBytes); + orderDto.Status = OrderStatus.Approved; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedBytes, result.Document); + } + + [Fact] + public void OrderDto_To_JudicialAction_FallsBackToSupportingDocumentData_WhenDocumentDataIsEmpty() + { + var expectedBytes = _faker.Random.Bytes(64); + var orderDto = CreateOrderDto(); + orderDto.DocumentData = string.Empty; + orderDto.SupportingDocumentData = Convert.ToBase64String(expectedBytes); + orderDto.Status = OrderStatus.Approved; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedBytes, result.Document); + } + + [Fact] + public void OrderDto_To_JudicialAction_Throws_WhenBothDocumentDataAndSupportingDocumentDataAreNullAndStatusIsApproved() + { + var orderDto = CreateOrderDto(); + orderDto.DocumentData = null; + orderDto.SupportingDocumentData = null; + orderDto.Status = OrderStatus.Approved; + + Assert.Throws(() => orderDto.Adapt(_config)); + } + + [Fact] + public void OrderDto_To_JudicialAction_Throws_WhenDocumentDataIsInvalidBase64() + { + var orderDto = CreateOrderDto(); + orderDto.DocumentData = "not-valid-base64!!!"; + orderDto.Status = OrderStatus.Approved; + + Assert.Throws(() => orderDto.Adapt(_config)); + } + + [Fact] + public void OrderDto_To_JudicialAction_Throws_WhenSupportingDocumentDataIsInvalidBase64() + { + var orderDto = CreateOrderDto(); + orderDto.DocumentData = null; + orderDto.SupportingDocumentData = "not-valid-base64!!!"; + orderDto.Status = OrderStatus.Approved; + + Assert.Throws(() => orderDto.Adapt(_config)); + } + + [Fact] + public void OrderDto_To_JudicialAction_MapsSignatureAppliedFromSigned() + { + var orderDto = CreateOrderDto(); + orderDto.Signed = true; + + var result = orderDto.Adapt(_config); + + Assert.True(result.SignatureApplied); + } + + [Fact] + public void OrderDto_To_JudicialAction_MapsCommentFromComments() + { + var expectedComment = _faker.Lorem.Sentence(); + var orderDto = CreateOrderDto(); + orderDto.Comments = expectedComment; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedComment, result.Comment); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsEmptyOrderTerms() + { + var orderDto = CreateOrderDto(); + + var result = orderDto.Adapt(_config); + + Assert.NotNull(result.OrderTerms); + Assert.Empty(result.OrderTerms); + } + + [Theory] + [InlineData(OrderStatus.Approved, "APPR")] + [InlineData(OrderStatus.Unapproved, "NAPP")] + [InlineData(OrderStatus.AwaitingDocumentation, "AFDC")] + public void OrderDto_To_JudicialAction_MapsDecisionCode(OrderStatus status, string expectedCode) + { + var orderDto = CreateOrderDto(); + orderDto.Status = status; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedCode, result.DecisionCode); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsNullDecisionCode_ForUnmappedStatus() + { + var orderDto = CreateOrderDto(); + orderDto.Status = OrderStatus.Pending; + + var result = orderDto.Adapt(_config); + + Assert.Null(result.DecisionCode); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsActionDateFromProcessedDate_WhenNotNull() + { + var expectedDate = _faker.Date.Recent(); + var orderDto = CreateOrderDto(); + orderDto.ProcessedDate = expectedDate; + + var result = orderDto.Adapt(_config); + + Assert.Equal(expectedDate, result.ActionDate); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsDefaultActionDate_WhenProcessedDateIsNull() + { + var orderDto = CreateOrderDto(); + orderDto.ProcessedDate = null; + + var result = orderDto.Adapt(_config); + + Assert.Equal(default, result.ActionDate); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsNullRejectedDate_WhenApproved() + { + var orderDto = CreateOrderDto(); + orderDto.Status = OrderStatus.Approved; + orderDto.ProcessedDate = _faker.Date.Recent(); + + var result = orderDto.Adapt(_config); + + Assert.Null(result.RejectedDate); + } + + [Fact] + public void OrderDto_To_JudicialAction_SetsNullSignedDate_WhenNotSigned() + { + var orderDto = CreateOrderDto(); + orderDto.Signed = false; + + var result = orderDto.Adapt(_config); + + Assert.Null(result.SignedDate); + } + + #endregion + #region Helper Methods private Order CreateOrder() diff --git a/tests/api/Jobs/SendOrderNotificationJobTests.cs b/tests/api/Jobs/SendOrderNotificationJobTests.cs index 721f787a4..bc3c75b88 100644 --- a/tests/api/Jobs/SendOrderNotificationJobTests.cs +++ b/tests/api/Jobs/SendOrderNotificationJobTests.cs @@ -573,4 +573,4 @@ private Scv.Models.Person CreateInactiveJudge(int judgeId) ] }; } -} \ No newline at end of file +} diff --git a/tests/api/Repositories/CsoClientTests.cs b/tests/api/Repositories/CsoClientTests.cs deleted file mode 100644 index 5daed62f3..000000000 --- a/tests/api/Repositories/CsoClientTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Scv.Cso; -using Scv.Cso.Infrastructure.Options; -using Scv.Models.Order; -using tests.api.Services; -using Xunit; - -namespace tests.api.Repositories; - -public class CsoClientTests : ServiceTestBase -{ - private static OrderActionDto CreateOrderAction() => new() - { - ReferredDocumentId = 1, - DigitalSignatureApplied = true, - JudicialDecisionCd = nameof(JudicialDecisionCd.APPR) - }; - - [Fact] - public async Task SendOrderAsync_ReturnsTrue_WhenResponseIsSuccess() - { - var options = Options.Create(new CsoOptions { BaseUrl = "https://example.test", ActionUri = "/MockOrders/action" }); - SetupMockResponse(HttpStatusCode.OK, new { }); - - var client = new CsoClient(HttpClient, options, NullLogger.Instance); - - var result = await client.SendOrderAsync(CreateOrderAction()); - - Assert.True(result); - VerifyHttpRequest(HttpMethod.Post, "MockOrders/action"); - } - - [Fact] - public async Task SendOrderAsync_ReturnsFalse_WhenResponseIsFailure() - { - var options = Options.Create(new CsoOptions { BaseUrl = "https://example.test", ActionUri = "/MockOrders/action" }); - SetupMockResponse(HttpStatusCode.BadRequest, new { }); - - var client = new CsoClient(HttpClient, options, NullLogger.Instance); - - var result = await client.SendOrderAsync(CreateOrderAction()); - - Assert.False(result); - VerifyHttpRequest(HttpMethod.Post, "MockOrders/action"); - } - - [Fact] - public async Task SendOrderAsync_Throws_WhenOrderIsNull() - { - var options = Options.Create(new CsoOptions { BaseUrl = "https://example.test", ActionUri = "/MockOrders/action" }); - var client = new CsoClient(HttpClient, options, NullLogger.Instance); - - await Assert.ThrowsAsync(() => client.SendOrderAsync(null)); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public async Task SendOrderAsync_Throws_WhenActionUriIsMissing(string actionUri) - { - var options = Options.Create(new CsoOptions { BaseUrl = "https://example.test", ActionUri = actionUri }); - var client = new CsoClient(HttpClient, options, NullLogger.Instance); - - await Assert.ThrowsAsync(() => client.SendOrderAsync(CreateOrderAction())); - } -} diff --git a/tests/api/Services/OrderServiceTests.cs b/tests/api/Services/OrderServiceTests.cs index fa9e04d2c..85f318bcf 100644 --- a/tests/api/Services/OrderServiceTests.cs +++ b/tests/api/Services/OrderServiceTests.cs @@ -5,6 +5,8 @@ using System.Security.Claims; using System.Threading.Tasks; using Bogus; +using CSOCommon.Clients.JudicialServices; +using CSOCommon.Models; using JCCommon.Clients.FileServices; using LazyCache; using LazyCache.Providers; @@ -18,7 +20,6 @@ using PCSSCommon.Models; using Scv.Api.Infrastructure.Mappings; using Scv.Api.Services; -using Scv.Cso; using Scv.Db.Models; using Scv.Db.Repositories; using Scv.Models.Order; @@ -36,8 +37,7 @@ public class OrderServiceTests : ServiceTestBase private readonly Mock> _mockLogger; private readonly Mock _mockBackgroundJobClient; private readonly Mock _mockHttpContextAccessor; - private readonly Mock _mockCsoClient; - private readonly Mock _mockUserService; + private readonly Mock _mockJudicialClient; private readonly IMapper _mapper; private readonly IAppCache _cache; private readonly OrderService _orderService; @@ -65,10 +65,9 @@ public OrderServiceTests() _mockConfiguration = new Mock(); _mockBackgroundJobClient = new Mock(); _mockHttpContextAccessor = new Mock(); - _mockCsoClient = new Mock(); - _mockUserService = new Mock(); + _mockJudicialClient = new Mock(); - _requestAgencyIdentifierId = _faker.Random.AlphaNumeric(10); + _requestAgencyIdentifierId = _faker.Random.Double().ToString(); _requestPartId = _faker.Random.AlphaNumeric(10); _applicationCode = "SCV"; @@ -86,8 +85,7 @@ public OrderServiceTests() _mockJudgeService.Object, _mockBackgroundJobClient.Object, _mockHttpContextAccessor.Object, - _mockCsoClient.Object, - _mockUserService.Object); + _mockJudicialClient.Object); } private void SetupConfiguration() @@ -967,7 +965,7 @@ public async Task SubmitOrder_ReturnsFailure_WhenMappingFails() Assert.False(result.Succeeded); Assert.Contains("Failed to map Order to OrderAction.", result.Errors); _mockOrderRepo.Verify(r => r.UpdateAsync(It.Is(o => o.SubmitAttempts == 3)), Times.Once); - _mockCsoClient.Verify(c => c.SendOrderAsync(It.IsAny(), default), Times.Never); + _mockJudicialClient.Verify(c => c.SaveJudicialActionAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -985,14 +983,25 @@ public async Task SubmitOrder_ReturnsSuccess_WhenCsoSubmitSucceeds() .Setup(r => r.UpdateAsync(It.IsAny())) .Returns(Task.CompletedTask); - _mockCsoClient - .Setup(c => c.SendOrderAsync(It.IsAny(), default)) - .ReturnsAsync(true); + _mockJudicialClient + .Setup(c => c.SaveJudicialActionAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(() => Task.CompletedTask); + + _mockJudgeService + .Setup(d => d.GetJudges(null, null)) + .ReturnsAsync( + [ + new PersonSearchItem + { + PersonId = order.JudgeId, + ParticipantId = order.OrderRequest.Referral.SentToPartId.GetValueOrDefault() + } + ]); var result = await _orderService.SubmitOrder(order.Id); Assert.True(result.Succeeded); - _mockCsoClient.Verify(c => c.SendOrderAsync(It.IsAny(), default), Times.Once); + _mockJudicialClient.Verify(c => c.SaveJudicialActionAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockOrderRepo.Verify(r => r.UpdateAsync(It.Is(o => o.SubmitAttempts == 3)), Times.Once); } @@ -1011,16 +1020,21 @@ public async Task SubmitOrder_ReturnsFailure_WhenCsoSubmitFails() .Setup(r => r.UpdateAsync(It.IsAny())) .Returns(Task.CompletedTask); - _mockCsoClient - .Setup(c => c.SendOrderAsync(It.IsAny(), default)) - .ReturnsAsync(false); + _mockJudgeService + .Setup(d => d.GetJudges(null, null)) + .ReturnsAsync([new PersonSearchItem { PersonId = order.JudgeId, ParticipantId = order.OrderRequest.Referral.SentToPartId }]); + + _mockJudicialClient + .Setup(c => c.SaveJudicialActionAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("CSO submission failed")); var result = await _orderService.SubmitOrder(order.Id); Assert.False(result.Succeeded); - Assert.Contains("Failed to submit order to CSO.", result.Errors); - _mockCsoClient.Verify(c => c.SendOrderAsync(It.IsAny(), default), Times.Once); + _mockJudicialClient.Verify(c => c.SaveJudicialActionAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockJudgeService.Verify(d => d.GetJudges(null, null), Times.Once); _mockOrderRepo.Verify(r => r.UpdateAsync(It.Is(o => o.SubmitAttempts == 3)), Times.Once); + Assert.Contains("Failed to submit order to CSO.", result.Errors); } #endregion @@ -1075,7 +1089,8 @@ private Order CreateOrder() DocumentTypeCd = _faker.Lorem.Word() } ] - } + }, + JudgeId = _faker.Random.Int(1, 100), }; } diff --git a/web/src/components/orders/Orders.vue b/web/src/components/orders/Orders.vue index fc9af5057..deec54d24 100644 --- a/web/src/components/orders/Orders.vue +++ b/web/src/components/orders/Orders.vue @@ -16,12 +16,14 @@
For signing - {{ pendingOrders.length > 0 ? `(${pendingOrders.length})` : '' }} + {{ + forSigningOrders.length > 0 ? `(${forSigningOrders.length})` : '' + }}
- + {{ item.packageId }} + {{ item.packageId }}