using System.Net; using System.Net.Http.Json; using System.Security.Cryptography; using System.Text; using System.Text.Json; using JdeScoping.Core.Models; using JdeScoping.Core.Models.Auth; using Microsoft.AspNetCore.Mvc.Testing; using Shouldly; namespace JdeScoping.Api.IntegrationTests; /// /// Integration tests for authentication flow with encrypted login. /// Note: These tests require a running test server with UseFakeAuth=true /// public class AuthenticationTests : IClassFixture { private readonly TestWebApplicationFactory _factory; private readonly HttpClient _client; public AuthenticationTests(TestWebApplicationFactory factory) { _factory = factory; _client = factory.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = true, AllowAutoRedirect = false }); } /// /// Fetches the server's public key and encrypts login credentials. /// private async Task EncryptLoginAsync(HttpClient client, string username, string password) { // Step 1: Fetch the public key from the server var publicKeyResponse = await client.GetFromJsonAsync("/api/auth/public-key"); publicKeyResponse.ShouldNotBeNull(); publicKeyResponse.PublicKeyPem.ShouldStartWith("-----BEGIN PUBLIC KEY-----"); // Step 2: Create login model and serialize to JSON var loginModel = new LoginModel { Username = username, Password = password }; var json = JsonSerializer.Serialize(loginModel); var plaintext = Encoding.UTF8.GetBytes(json); // Step 3: Import the public key and encrypt using var rsa = RSA.Create(); rsa.ImportFromPem(publicKeyResponse.PublicKeyPem); var ciphertext = rsa.Encrypt(plaintext, RSAEncryptionPadding.OaepSHA256); return new EncryptedLoginRequest(Convert.ToBase64String(ciphertext)); } [Fact] public async Task GetPublicKey_ReturnsValidPemKey() { // Act var response = await _client.GetAsync("/api/auth/public-key"); // Assert response.StatusCode.ShouldBe(HttpStatusCode.OK); var publicKeyResponse = await response.Content.ReadFromJsonAsync(); publicKeyResponse.ShouldNotBeNull(); publicKeyResponse.PublicKeyPem.ShouldStartWith("-----BEGIN PUBLIC KEY-----"); publicKeyResponse.PublicKeyPem.ShouldContain("-----END PUBLIC KEY-----"); } [Fact] public async Task FullLoginLogoutFlow_WithCookies() { // Step 1: Login with encrypted credentials var encryptedRequest = await EncryptLoginAsync(_client, "testuser", "testpass"); var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", encryptedRequest); loginResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var loginResult = await loginResponse.Content.ReadFromJsonAsync(); loginResult.ShouldNotBeNull(); loginResult.Success.ShouldBeTrue(); loginResult.User.ShouldNotBeNull(); loginResult.User.Username.ShouldBe("testuser"); // Step 2: Verify we can access protected endpoint var meResponse = await _client.GetAsync("/api/auth/me"); meResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var meUser = await meResponse.Content.ReadFromJsonAsync(); meUser.ShouldNotBeNull(); meUser.Username.ShouldBe("testuser"); // Step 3: Logout var logoutResponse = await _client.PostAsync("/api/auth/logout", null); logoutResponse.StatusCode.ShouldBe(HttpStatusCode.OK); // Step 4: Verify protected endpoint returns 401 after logout var afterLogoutResponse = await _client.GetAsync("/api/auth/me"); afterLogoutResponse.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } [Fact] public async Task ProtectedEndpoints_Return401_WithoutAuth() { // Use a fresh client without cookies (using factory to connect to test server) var freshClient = _factory.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = false, AllowAutoRedirect = false }); // Search endpoints require auth var searchResponse = await freshClient.GetAsync("/api/search"); searchResponse.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); // Auth me endpoint requires auth var meResponse = await freshClient.GetAsync("/api/auth/me"); meResponse.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } [Fact] public async Task ProtectedEndpoints_Work_WithAuthCookie() { // Login first with encrypted credentials var encryptedRequest = await EncryptLoginAsync(_client, "testuser", "testpass"); var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", encryptedRequest); loginResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var loginResult = await loginResponse.Content.ReadFromJsonAsync(); loginResult.ShouldNotBeNull(); loginResult.Success.ShouldBeTrue(); // Now search endpoint should work var searchResponse = await _client.GetAsync("/api/search"); searchResponse.StatusCode.ShouldBe(HttpStatusCode.OK); } [Fact] public async Task LookupEndpoints_DoNotRequireAuth() { // Use a fresh client without cookies (using factory to connect to test server) var freshClient = _factory.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = false, AllowAutoRedirect = false }); // Lookup endpoints should work without auth var itemsResponse = await freshClient.GetAsync("/api/lookup/items?q=test"); itemsResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var profitCentersResponse = await freshClient.GetAsync("/api/lookup/profit-centers?q=test"); profitCentersResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var workCentersResponse = await freshClient.GetAsync("/api/lookup/work-centers?q=test"); workCentersResponse.StatusCode.ShouldBe(HttpStatusCode.OK); var operatorsResponse = await freshClient.GetAsync("/api/lookup/operators?q=test"); operatorsResponse.StatusCode.ShouldBe(HttpStatusCode.OK); } [Fact] public async Task Login_WithInvalidEncryptedData_ReturnsBadRequest() { // Arrange - send invalid encrypted data var invalidRequest = new EncryptedLoginRequest("not-valid-base64!!!"); // Act var response = await _client.PostAsJsonAsync("/api/auth/login", invalidRequest); // Assert response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); var result = await response.Content.ReadFromJsonAsync(); result.ShouldNotBeNull(); result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldBe("Invalid encrypted payload"); } }