Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,29 @@
|
||||
using System.Net;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
/// <summary>Regression coverage for a real bug found via E2E testing: two concurrent
|
||||
/// logins for the same account raced on ASP.NET Core Identity's optimistic-concurrency
|
||||
/// token while recording the last-login timestamp, turning a successful authentication
|
||||
/// into a 500 for whichever request lost the race.</summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class ConcurrentLoginTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Concurrent_logins_for_the_same_account_never_fail_with_a_server_error()
|
||||
{
|
||||
using var clientA = Factory.CreateClient();
|
||||
using var clientB = Factory.CreateClient();
|
||||
|
||||
var loginA = SetupHelper.LoginAsync(clientA, OwnerEmail, OwnerPassword);
|
||||
var loginB = SetupHelper.LoginAsync(clientB, OwnerEmail, OwnerPassword);
|
||||
|
||||
var results = await Task.WhenAll(loginA, loginB);
|
||||
|
||||
foreach (var response in results)
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class CsrfTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Cookie_authenticated_mutation_without_a_csrf_header_is_rejected()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
// Deliberately bypasses SetupHelper.PostWithCsrfAsync to send the mutation
|
||||
// without ever having fetched/attached a CSRF token.
|
||||
using var response = await client.PostAsJsonAsync("/api/v1/auth/logout", new { });
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cookie_authenticated_mutation_with_a_valid_csrf_header_succeeds()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var response = await SetupHelper.PostWithCsrfAsync<object?>(client, "/api/v1/auth/logout", null);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_csrf_token_obtained_by_one_session_does_not_validate_for_another()
|
||||
{
|
||||
using var attackerClient = Factory.CreateClient();
|
||||
var stolenToken = await SetupHelper.GetCsrfTokenAsync(attackerClient);
|
||||
|
||||
using var victimClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(victimClient, OwnerEmail, OwnerPassword);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/auth/logout");
|
||||
request.Headers.Add(stolenToken.HeaderName, stolenToken.Value);
|
||||
|
||||
using var response = await victimClient.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Auth;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class LoginLogoutLockoutTests(PostgresContainerFixture postgres)
|
||||
: OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Login_with_correct_credentials_establishes_an_authenticated_session()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var session = await response.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
Assert.NotNull(session);
|
||||
Assert.True(session.Authenticated);
|
||||
Assert.Equal(OwnerEmail, session.Email);
|
||||
Assert.Equal("Administrator", session.Role);
|
||||
|
||||
// The same client (with its session cookie) now sees itself as authenticated
|
||||
// on a fresh request too, not only in the login response body.
|
||||
var sessionAfter = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(sessionAfter);
|
||||
Assert.True(sessionAfter.Authenticated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_with_wrong_password_is_rejected_generically()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, OwnerEmail, "totally-wrong-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.DoesNotContain(OwnerPassword, body, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_for_an_unknown_account_returns_the_same_response_as_a_wrong_password()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, "does-not-exist@itworx.test", "whatever-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_rate_limit_is_per_remote_ip_and_ignores_untrusted_forwarded_headers()
|
||||
{
|
||||
using var clientA = Factory.CreateClient();
|
||||
clientA.DefaultRequestHeaders.Add("X-Test-Remote-Ip", "192.0.2.10");
|
||||
|
||||
// Without a configured trusted proxy, these caller-supplied values must not
|
||||
// create fresh rate-limit partitions. All attempts originate from client A's
|
||||
// effective remote address.
|
||||
for (var attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", $"203.0.113.{attempt + 1}");
|
||||
|
||||
var response = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.250");
|
||||
var exhaustedClientA = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, exhaustedClientA.StatusCode);
|
||||
|
||||
using var clientB = Factory.CreateClient();
|
||||
clientB.DefaultRequestHeaders.Add("X-Test-Remote-Ip", "192.0.2.11");
|
||||
clientB.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.251");
|
||||
var clientBLogin = await SetupHelper.LoginAsync(clientB, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.OK, clientBLogin.StatusCode);
|
||||
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.252");
|
||||
var stillExhaustedClientA = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, stillExhaustedClientA.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repeated_failed_logins_lock_the_account()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
// Configured lockout threshold is 5 failed attempts.
|
||||
HttpResponseMessage last = null!;
|
||||
for (var attempt = 0; attempt < 6; attempt++)
|
||||
{
|
||||
last = await SetupHelper.LoginAsync(client, OwnerEmail, "wrong-password-attempt");
|
||||
}
|
||||
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, last.StatusCode);
|
||||
|
||||
// Even the correct password is now rejected while locked out.
|
||||
var withCorrectPassword = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, withCorrectPassword.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Logout_ends_the_session()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var logoutResponse = await SetupHelper.PostWithCsrfAsync<object?>(client, "/api/v1/auth/logout", null);
|
||||
Assert.Equal(HttpStatusCode.OK, logoutResponse.StatusCode);
|
||||
|
||||
var session = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(session);
|
||||
Assert.False(session.Authenticated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Admin;
|
||||
using ItWorx.KnowledgeHub.Contracts.Auth;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class RecoveryTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Administrator_issued_token_lets_the_user_set_a_new_password_and_old_sessions_stop_working()
|
||||
{
|
||||
using var adminClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(adminClient, OwnerEmail, OwnerPassword);
|
||||
|
||||
const string email = "vergeten@itworx.test";
|
||||
const string originalPassword = "original-strong-password";
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
adminClient,
|
||||
"/api/v1/admin/users",
|
||||
new CreateUserRequest("Vergeetachtige Gebruiker", email, originalPassword, "Editor"));
|
||||
var user = await createResponse.Content.ReadFromJsonAsync<UserSummaryResponse>();
|
||||
Assert.NotNull(user);
|
||||
|
||||
// The user logs in once with the original password to prove that session gets
|
||||
// invalidated once the password is reset via recovery.
|
||||
using var userClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(userClient, email, originalPassword);
|
||||
var beforeReset = await userClient.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(beforeReset);
|
||||
Assert.True(beforeReset.Authenticated);
|
||||
|
||||
var issueResponse = await SetupHelper.PostWithCsrfAsync<object?>(
|
||||
adminClient,
|
||||
$"/api/v1/admin/users/{user.Id}/recovery-tokens",
|
||||
null);
|
||||
Assert.Equal(HttpStatusCode.OK, issueResponse.StatusCode);
|
||||
|
||||
var issued = await issueResponse.Content.ReadFromJsonAsync<IssueRecoveryTokenResponse>();
|
||||
Assert.NotNull(issued);
|
||||
Assert.False(string.IsNullOrWhiteSpace(issued.Token));
|
||||
|
||||
const string newPassword = "brand-new-strong-password";
|
||||
using var redeemClient = Factory.CreateClient();
|
||||
var redeemResponse = await redeemClient.PostAsJsonAsync(
|
||||
"/api/v1/auth/recovery/redeem",
|
||||
new RecoveryRedeemRequest(issued.Token, newPassword));
|
||||
Assert.Equal(HttpStatusCode.OK, redeemResponse.StatusCode);
|
||||
|
||||
// The pre-reset session must be dead now (security stamp rotated).
|
||||
var afterReset = await userClient.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(afterReset);
|
||||
Assert.False(afterReset.Authenticated);
|
||||
|
||||
// The old password no longer works...
|
||||
using var oldPasswordClient = Factory.CreateClient();
|
||||
var oldPasswordAttempt = await SetupHelper.LoginAsync(oldPasswordClient, email, originalPassword);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, oldPasswordAttempt.StatusCode);
|
||||
|
||||
// ...but the new one does.
|
||||
using var newPasswordClient = Factory.CreateClient();
|
||||
var newPasswordAttempt = await SetupHelper.LoginAsync(newPasswordClient, email, newPassword);
|
||||
Assert.Equal(HttpStatusCode.OK, newPasswordAttempt.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Redeeming_the_same_recovery_token_twice_only_works_once()
|
||||
{
|
||||
using var adminClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(adminClient, OwnerEmail, OwnerPassword);
|
||||
|
||||
const string email = "eenmalig@itworx.test";
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
adminClient,
|
||||
"/api/v1/admin/users",
|
||||
new CreateUserRequest("Eenmalige Gebruiker", email, "starting-strong-password", "Reader"));
|
||||
var user = await createResponse.Content.ReadFromJsonAsync<UserSummaryResponse>();
|
||||
Assert.NotNull(user);
|
||||
|
||||
var issueResponse = await SetupHelper.PostWithCsrfAsync<object?>(
|
||||
adminClient,
|
||||
$"/api/v1/admin/users/{user.Id}/recovery-tokens",
|
||||
null);
|
||||
var issued = await issueResponse.Content.ReadFromJsonAsync<IssueRecoveryTokenResponse>();
|
||||
Assert.NotNull(issued);
|
||||
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var first = await client.PostAsJsonAsync(
|
||||
"/api/v1/auth/recovery/redeem",
|
||||
new RecoveryRedeemRequest(issued.Token, "first-new-strong-password"));
|
||||
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
|
||||
|
||||
var second = await client.PostAsJsonAsync(
|
||||
"/api/v1/auth/recovery/redeem",
|
||||
new RecoveryRedeemRequest(issued.Token, "second-new-strong-password"));
|
||||
Assert.Equal(HttpStatusCode.BadRequest, second.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Redeeming_an_unknown_token_fails_without_revealing_anything()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/api/v1/auth/recovery/redeem",
|
||||
new RecoveryRedeemRequest("ikhrec_not-a-real-token", "whatever-strong-password"));
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
// V2-M7: closes the recovery-token admin UI (issuance) — this authorization
|
||||
// boundary (matching TrashPurgeTests.Purge_is_administrator_only's exact pattern)
|
||||
// had no test at all before, even though the endpoint itself already correctly
|
||||
// requires Administrator via AdminEndpoints' group-level policy.
|
||||
[Fact]
|
||||
public async Task Issuing_a_recovery_token_is_administrator_only()
|
||||
{
|
||||
using var adminClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(adminClient, OwnerEmail, OwnerPassword);
|
||||
|
||||
var targetEmail = "doelgebruiker-recovery@itworx.test";
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
adminClient,
|
||||
"/api/v1/admin/users",
|
||||
new CreateUserRequest("Doelgebruiker", targetEmail, "starting-strong-password", "Reader"));
|
||||
var target = await createResponse.Content.ReadFromJsonAsync<UserSummaryResponse>();
|
||||
Assert.NotNull(target);
|
||||
|
||||
const string editorEmail = "redacteur-recovery@itworx.test";
|
||||
const string editorPassword = "correct-horse-battery-staple";
|
||||
await SetupHelper.PostWithCsrfAsync(
|
||||
adminClient,
|
||||
"/api/v1/admin/users",
|
||||
new CreateUserRequest("Redacteur", editorEmail, editorPassword, "Editor"));
|
||||
|
||||
using var editorClient = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(editorClient, editorEmail, editorPassword);
|
||||
|
||||
var response = await SetupHelper.PostWithCsrfAsync<object?>(
|
||||
editorClient, $"/api/v1/admin/users/{target.Id}/recovery-tokens", null);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Auth;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Configures a deliberately short, deterministic cookie lifetime for this test class
|
||||
/// only (see docs/22-testing-strategy.md — avoid sleeps except for a documented,
|
||||
/// bounded, real timing behavior; session expiry inherently requires elapsed time).
|
||||
/// </summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class SessionExpiryTests : OwnerSetupTestBase
|
||||
{
|
||||
public SessionExpiryTests(PostgresContainerFixture postgres)
|
||||
: base(postgres, new Dictionary<string, string?> { ["KnowledgeHub:Auth:CookieExpireSeconds"] = "2" })
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Session_cookie_expires_after_the_configured_lifetime()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var immediately = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(immediately);
|
||||
Assert.True(immediately.Authenticated);
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(3));
|
||||
|
||||
var afterExpiry = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(afterExpiry);
|
||||
Assert.False(afterExpiry.Authenticated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Admin;
|
||||
using ItWorx.KnowledgeHub.Contracts.Identity;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Backs the accessible owner picker (docs/adr/ADR-029-v2-r5-final-hardening.md, brief
|
||||
/// Phase 7), replacing manual `OwnerId`/GUID entry. Any Reader may search, results are
|
||||
/// scoped to the current workspace's enabled members only, and a disabled account is
|
||||
/// never disclosed.
|
||||
/// </summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class WorkspaceMemberSearchTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task A_reader_can_search_workspace_members_by_name_or_email()
|
||||
{
|
||||
using var owner = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(owner, OwnerEmail, OwnerPassword);
|
||||
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
owner, "/api/v1/admin/users",
|
||||
new CreateUserRequest("Zoekbare Collega", "zoekbare-collega@itworx.test", "reader-strong-password", "Reader"));
|
||||
Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
|
||||
|
||||
using var reader = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(reader, "zoekbare-collega@itworx.test", "reader-strong-password");
|
||||
|
||||
var byName = await reader.GetFromJsonAsync<WorkspaceMemberListResponse>("/api/v1/workspace/members?q=Zoekbare");
|
||||
Assert.NotNull(byName);
|
||||
Assert.Contains(byName.Items, item => item.Email == "zoekbare-collega@itworx.test");
|
||||
|
||||
var byEmail = await reader.GetFromJsonAsync<WorkspaceMemberListResponse>(
|
||||
"/api/v1/workspace/members?q=zoekbare-collega@itworx.test");
|
||||
Assert.NotNull(byEmail);
|
||||
Assert.Contains(byEmail.Items, item => item.DisplayName == "Zoekbare Collega");
|
||||
|
||||
var noMatch = await reader.GetFromJsonAsync<WorkspaceMemberListResponse>(
|
||||
"/api/v1/workspace/members?q=geen-enkele-overeenkomst-xyz");
|
||||
Assert.NotNull(noMatch);
|
||||
Assert.Empty(noMatch.Items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task An_empty_query_returns_an_initial_list_rather_than_an_error()
|
||||
{
|
||||
using var owner = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(owner, OwnerEmail, OwnerPassword);
|
||||
|
||||
var response = await owner.GetFromJsonAsync<WorkspaceMemberListResponse>("/api/v1/workspace/members");
|
||||
Assert.NotNull(response);
|
||||
Assert.Contains(response.Items, item => item.Email == OwnerEmail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_disabled_account_is_never_returned()
|
||||
{
|
||||
using var owner = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(owner, OwnerEmail, OwnerPassword);
|
||||
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
owner, "/api/v1/admin/users",
|
||||
new CreateUserRequest("Uitgeschakelde Collega", "uitgeschakeld@itworx.test", "reader-strong-password", "Reader"));
|
||||
Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
|
||||
var created = await createResponse.Content.ReadFromJsonAsync<UserSummaryResponse>();
|
||||
Assert.NotNull(created);
|
||||
|
||||
var disableResponse = await SetupHelper.PatchWithCsrfAsync(
|
||||
owner, $"/api/v1/admin/users/{created.Id}", new { enabled = false });
|
||||
Assert.Equal(HttpStatusCode.OK, disableResponse.StatusCode);
|
||||
|
||||
var result = await owner.GetFromJsonAsync<WorkspaceMemberListResponse>("/api/v1/workspace/members?q=Uitgeschakelde");
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task An_unauthenticated_caller_is_rejected()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
var response = await client.GetAsync("/api/v1/workspace/members");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user