-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathTestHelper.cs
More file actions
175 lines (158 loc) · 6.42 KB
/
TestHelper.cs
File metadata and controls
175 lines (158 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using System.Collections.Concurrent;
using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using OpenShock.Common.Constants;
using OpenShock.Common.OpenShockDb;
using OpenShock.Common.Services.Session;
using OpenShock.Common.Utils;
namespace OpenShock.API.IntegrationTests.Helpers;
public static class TestHelper
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
/// <summary>
/// Cache BCrypt hashes to avoid repeated expensive hashing across tests.
/// BCrypt is synchronous and CPU-bound; hashing in every test causes thread pool
/// starvation on CI runners with fewer cores, leading to test server timeouts.
/// </summary>
private static readonly ConcurrentDictionary<string, string> PasswordHashCache = new();
/// <summary>
/// Creates a user directly in DB, creates a session via ISessionService, returns auth info.
/// This bypasses signup/login endpoints entirely to avoid rate limiting.
/// </summary>
public static async Task<AuthenticatedUser> CreateAndLoginUser(
WebApplicationFactory factory,
string username,
string email,
string password)
{
// 1. Create user directly in DB
var userId = await CreateUserInDb(factory, username, email, password);
// 2. Create session via ISessionService (stored in Redis)
await using var scope = factory.Services.CreateAsyncScope();
var sessionService = scope.ServiceProvider.GetRequiredService<ISessionService>();
var session = await sessionService.CreateSessionAsync(userId, "IntegrationTest", "127.0.0.1");
return new AuthenticatedUser(userId, username, email, session.Token);
}
/// <summary>
/// Creates an HttpClient that sends the session cookie for authentication.
/// </summary>
public static HttpClient CreateAuthenticatedClient(WebApplicationFactory factory, string sessionToken)
{
var client = factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
HandleCookies = false
});
client.DefaultRequestHeaders.Add("Cookie", $"{AuthConstants.UserSessionCookieName}={sessionToken}");
return client;
}
/// <summary>
/// Creates an HttpClient that sends an API token header for authentication.
/// </summary>
public static HttpClient CreateApiTokenClient(WebApplicationFactory factory, string apiToken)
{
var client = factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
HandleCookies = false
});
client.DefaultRequestHeaders.Add(AuthConstants.ApiTokenHeaderName, apiToken);
return client;
}
/// <summary>
/// Creates an HttpClient that sends a hub/device token header for authentication.
/// </summary>
public static HttpClient CreateHubTokenClient(WebApplicationFactory factory, string hubToken)
{
var client = factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
HandleCookies = false
});
client.DefaultRequestHeaders.Add(AuthConstants.HubTokenHeaderName, hubToken);
return client;
}
/// <summary>
/// Creates a user directly in the DB (bypasses signup endpoint).
/// </summary>
public static async Task<Guid> CreateUserInDb(
WebApplicationFactory factory,
string username,
string email,
string password,
bool activated = true)
{
await using var scope = factory.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<OpenShockContext>();
var userId = Guid.CreateVersion7();
var hash = PasswordHashCache.GetOrAdd(password, HashingUtils.HashPassword);
db.Users.Add(new User
{
Id = userId,
Name = username,
Email = email,
PasswordHash = hash,
ActivatedAt = activated ? DateTime.UtcNow : null
});
await db.SaveChangesAsync();
return userId;
}
/// <summary>
/// Creates a device in the DB for a given user. Returns (deviceId, deviceToken).
/// </summary>
public static async Task<(Guid DeviceId, string Token)> CreateDeviceInDb(
WebApplicationFactory factory,
Guid ownerId,
string name = "TestDevice")
{
await using var scope = factory.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<OpenShockContext>();
var deviceId = Guid.CreateVersion7();
var token = CryptoUtils.RandomAlphaNumericString(256);
db.Devices.Add(new Device
{
Id = deviceId,
Name = name,
OwnerId = ownerId,
Token = token,
CreatedAt = DateTime.UtcNow
});
await db.SaveChangesAsync();
return (deviceId, token);
}
/// <summary>
/// Creates an API token in the DB for a given user. Returns the raw token string.
/// </summary>
public static async Task<(Guid TokenId, string RawToken)> CreateApiTokenInDb(
WebApplicationFactory factory,
Guid userId,
string name = "TestToken",
List<Common.Models.PermissionType>? permissions = null)
{
await using var scope = factory.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<OpenShockContext>();
var rawToken = CryptoUtils.RandomAlphaNumericString(AuthConstants.ApiTokenLength);
var tokenId = Guid.CreateVersion7();
db.ApiTokens.Add(new ApiToken
{
Id = tokenId,
UserId = userId,
Name = name,
TokenHash = HashingUtils.HashToken(rawToken),
CreatedByIp = IPAddress.Loopback,
Permissions = permissions ?? [Common.Models.PermissionType.Shockers_Use]
});
await db.SaveChangesAsync();
return (tokenId, rawToken);
}
public static StringContent JsonContent(object obj)
{
return new StringContent(JsonSerializer.Serialize(obj, JsonOptions), Encoding.UTF8, "application/json");
}
}
public sealed record AuthenticatedUser(Guid Id, string Username, string Email, string SessionToken);