Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions SmartHub.Tests/AuthIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.EntityFrameworkCore;
using SmartHub.Api;
Expand All @@ -21,28 +24,49 @@ public AuthIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
builder.ConfigureServices((context, services) =>
{
// replace SmartHubDbContext with in-memory test DB
services.RemoveAll(typeof(DbContextOptions<SmartHubDbContext>));
services.RemoveAll(typeof(SmartHubDbContext));
services.AddDbContext<SmartHubDbContext>(options =>
options.UseInMemoryDatabase("IntegrationTestDb"));

// Register JWT authentication in the test host using the configuration
var jwtKey = context.Configuration["Jwt:Key"];
if (!string.IsNullOrEmpty(jwtKey))
{
var keyBytes = Encoding.UTF8.GetBytes(jwtKey);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = context.Configuration["Jwt:Issuer"],
ValidAudience = context.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
};
});
}
});

// Provide Jwt settings for integration test so tokens are generated
builder.ConfigureAppConfiguration((context, config) =>
{
// Generate a runtime JWT key for the test host (avoid hardcoded secrets in source)
var rnd = System.Security.Cryptography.RandomNumberGenerator.GetBytes(32);
var runtimeKey = Convert.ToBase64String(rnd);
var dict = new System.Collections.Generic.Dictionary<string, string?>
var dict = new System.Collections.Generic.Dictionary<string, string>
{
{ "Jwt:Key", runtimeKey },
{ "Jwt:Issuer", "SmartHub" },
{ "Jwt:Audience", "SmartHubClient" },
{ "Jwt:ExpireMinutes", "60" }
};
config.AddInMemoryCollection(dict.Where(kvp => kvp.Value != null).ToDictionary(kvp => kvp.Key, kvp => kvp.Value!));
config.AddInMemoryCollection(dict.Select(kvp => new System.Collections.Generic.KeyValuePair<string, string?>(kvp.Key, kvp.Value)));
});
});
}
Expand All @@ -51,7 +75,8 @@ public AuthIntegrationTests(WebApplicationFactory<Program> factory)
public async Task FullAuthFlow_RegisterLoginRefreshLogout_Succeeds()
{
var client = _factory.CreateClient();
var registerRequest = new RegisterRequest { FirstName = "Int", LastName = "Tester", Email = "inttest@example.com", Password = "IntPass!234", ConfirmPassword = "IntPass!234" };
var intPass = "IntPass!234";
var registerRequest = new RegisterRequest { FirstName = "Int", LastName = "Tester", Email = "inttest@example.com", Password = intPass, ConfirmPassword = intPass };
var registerResp = await client.PostAsJsonAsync("/api/auth/register", registerRequest);
registerResp.EnsureSuccessStatusCode();
var registerContent = await registerResp.Content.ReadFromJsonAsync<AuthResponse>();
Expand All @@ -61,18 +86,18 @@ public async Task FullAuthFlow_RegisterLoginRefreshLogout_Succeeds()
loginResp.EnsureSuccessStatusCode();
var loginContent = await loginResp.Content.ReadFromJsonAsync<AuthResponse>();

var refreshReq = new RefreshTokenRequest { RefreshToken = loginContent.RefreshToken };
var refreshReq = new RefreshTokenRequest { RefreshToken = loginContent!.RefreshToken! };
var refreshResp = await client.PostAsJsonAsync("/api/auth/refresh", refreshReq);
refreshResp.EnsureSuccessStatusCode();
var refreshContent = await refreshResp.Content.ReadFromJsonAsync<AuthResponse>();

// Logout (endpoint requires a valid access token)
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshContent.Token);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshContent!.Token!);
var logoutResp = await client.PostAsJsonAsync("/api/auth/logout", new RefreshTokenRequest { RefreshToken = refreshContent.RefreshToken });
if (logoutResp.StatusCode != System.Net.HttpStatusCode.NoContent)
{
var body = await logoutResp.Content.ReadAsStringAsync();
Assert.True(false, $"Logout failed with {(int)logoutResp.StatusCode}: {body}");
throw new Xunit.Sdk.XunitException($"Logout failed with {(int)logoutResp.StatusCode}: {body}");
}

// Trying to refresh with the same token should fail
Expand Down
22 changes: 14 additions & 8 deletions SmartHub.Tests/AuthServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ private SmartHubDbContext CreateInMemoryDb(string dbName)

private IConfiguration CreateJwtConfig()
{
var dict = new System.Collections.Generic.Dictionary<string, string?>
// Generate a runtime JWT key for tests to avoid hardcoding secrets in source
var rnd = System.Security.Cryptography.RandomNumberGenerator.GetBytes(32);
var runtimeKey = Convert.ToBase64String(rnd);
var dict = new System.Collections.Generic.Dictionary<string, string>
{
// Use a 32-byte key for HS256
{ "Jwt:Key", "01234567890123456789012345678901" },
{ "Jwt:Key", runtimeKey },
{ "Jwt:Issuer", "SmartHub" },
{ "Jwt:Audience", "SmartHubClient" },
{ "Jwt:ExpireMinutes", "60" }
};
return new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
return new ConfigurationBuilder().AddInMemoryCollection(dict.Select(kvp => new System.Collections.Generic.KeyValuePair<string, string?>(kvp.Key, kvp.Value))).Build();
}

[Fact]
Expand All @@ -40,7 +42,8 @@ public async Task Register_ShouldHashRefreshTokenAndStoreHashedValue()
using var db = CreateInMemoryDb("RegisterTestDb");
var config = CreateJwtConfig();
var service = new AuthService(db, config);
var request = new RegisterRequest { FirstName = "T", LastName = "User", Email = "test@example.com", Password = "Pass123!", ConfirmPassword = "Pass123!" };
var pwd = "Pass123!";
var request = new RegisterRequest { FirstName = "T", LastName = "User", Email = "test@example.com", Password = pwd, ConfirmPassword = pwd };

var response = await service.RegisterAsync(request);

Expand All @@ -58,7 +61,8 @@ public async Task Login_ShouldVerifyPasswordAndStoreHashedRefreshToken()
using var db = CreateInMemoryDb("LoginTestDb");
var config = CreateJwtConfig();
var service = new AuthService(db, config);
var register = new RegisterRequest { FirstName = "L", LastName = "User", Email = "login@example.com", Password = "Login!234", ConfirmPassword = "Login!234" };
var loginPwd = "Login!234";
var register = new RegisterRequest { FirstName = "L", LastName = "User", Email = "login@example.com", Password = loginPwd, ConfirmPassword = loginPwd };
var regResponse = await service.RegisterAsync(register);

var loginRequest = new LoginRequest { Email = register.Email, Password = register.Password };
Expand All @@ -76,7 +80,8 @@ public async Task RefreshToken_ShouldFailForInvalidToken()
using var db = CreateInMemoryDb("RefreshInvalidTestDb");
var config = CreateJwtConfig();
var service = new AuthService(db, config);
var register = new RegisterRequest { FirstName = "R", LastName = "User", Email = "refresh@example.com", Password = "Refresh!234", ConfirmPassword = "Refresh!234" };
var refreshPwd = "Refresh!234";
var register = new RegisterRequest { FirstName = "R", LastName = "User", Email = "refresh@example.com", Password = refreshPwd, ConfirmPassword = refreshPwd };
var regResponse = await service.RegisterAsync(register);

await Assert.ThrowsAsync<InvalidOperationException>(async () => await service.RefreshTokenAsync("invalidtoken"));
Expand All @@ -88,7 +93,8 @@ public async Task Logout_ShouldRevokeRefreshToken()
using var db = CreateInMemoryDb("LogoutTestDb");
var config = CreateJwtConfig();
var service = new AuthService(db, config);
var register = new RegisterRequest { FirstName = "O", LastName = "User", Email = "logout@example.com", Password = "Logout!234", ConfirmPassword = "Logout!234" };
var logoutPwd = "Logout!234";
var register = new RegisterRequest { FirstName = "O", LastName = "User", Email = "logout@example.com", Password = logoutPwd, ConfirmPassword = logoutPwd };
var regResponse = await service.RegisterAsync(register);

// ensure logout removes refresh token
Expand Down
Loading