From 3ed2d7324d06a670f175cdc31f96dd297b62ee1b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 6 Jan 2026 11:36:13 -0500 Subject: [PATCH] test: add ClientIntegrationTestBase with shared auth HttpClient --- .../ClientIntegrationCollection.cs | 10 +++ .../ClientIntegrationTestBase.cs | 77 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationCollection.cs create mode 100644 NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationTestBase.cs diff --git a/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationCollection.cs b/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationCollection.cs new file mode 100644 index 0000000..60a5f14 --- /dev/null +++ b/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationCollection.cs @@ -0,0 +1,10 @@ +namespace JdeScoping.Api.IntegrationTests.ClientIntegration; + +/// +/// Collection definition for client integration tests. +/// Prevents parallel execution to avoid auth state conflicts. +/// +[CollectionDefinition("ClientIntegration")] +public class ClientIntegrationCollection : ICollectionFixture +{ +} diff --git a/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationTestBase.cs b/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationTestBase.cs new file mode 100644 index 0000000..1851695 --- /dev/null +++ b/NEW/tests/JdeScoping.Api.IntegrationTests/ClientIntegration/ClientIntegrationTestBase.cs @@ -0,0 +1,77 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using JdeScoping.Client.Services; +using JdeScoping.Core.Models.Auth; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace JdeScoping.Api.IntegrationTests.ClientIntegration; + +/// +/// Base class for API client integration tests. +/// Provides shared HttpClient with cookie handling for auth state. +/// +[Collection("ClientIntegration")] +public abstract class ClientIntegrationTestBase : IClassFixture +{ + protected readonly TestWebApplicationFactory Factory; + protected readonly HttpClient SharedClient; + + // API clients share the authenticated HttpClient + protected readonly SearchApiClient SearchClient; + protected readonly LookupApiClient LookupClient; + protected readonly AuthApiClient AuthClient; + protected readonly FileApiClient FileClient; + + protected ClientIntegrationTestBase(TestWebApplicationFactory factory) + { + Factory = factory; + SharedClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + HandleCookies = true, + AllowAutoRedirect = false + }); + + // All clients share the same HttpClient (cookie container) + SearchClient = new SearchApiClient(SharedClient); + LookupClient = new LookupApiClient(SharedClient); + FileClient = new FileApiClient(SharedClient); + AuthClient = new AuthApiClient(SharedClient); + } + + /// + /// Performs login with encrypted credentials. + /// + protected async Task LoginAsync(string username = "testuser", string password = "testpass") + { + // Step 1: Get public key + var publicKeyResult = await AuthClient.GetPublicKeyAsync(); + if (!publicKeyResult.IsSuccess) + throw new Exception("Failed to get public key"); + + // Step 2: Encrypt credentials + var loginModel = new LoginModel { Username = username, Password = password }; + var json = JsonSerializer.Serialize(loginModel); + var plaintext = Encoding.UTF8.GetBytes(json); + + using var rsa = RSA.Create(); + rsa.ImportFromPem(publicKeyResult.Value.PublicKeyPem); + var ciphertext = rsa.Encrypt(plaintext, RSAEncryptionPadding.OaepSHA256); + + // Step 3: Login + var encryptedRequest = new EncryptedLoginRequest(Convert.ToBase64String(ciphertext)); + var loginResult = await AuthClient.LoginAsync(encryptedRequest); + + if (!loginResult.IsSuccess || !loginResult.Value.Success) + throw new Exception($"Login failed: {loginResult.Value?.ErrorMessage ?? "Unknown error"}"); + } + + /// + /// Creates a fresh HttpClient without cookies for testing unauthorized scenarios. + /// + protected HttpClient CreateFreshClient() => Factory.CreateClient(new WebApplicationFactoryClientOptions + { + HandleCookies = false, + AllowAutoRedirect = false + }); +}