mirror of
https://github.com/bitwarden/server
synced 2025-12-14 07:13:39 +00:00
Fix tests
This commit is contained in:
41
test/SeederApi.IntegrationTest/HttpClientExtensions.cs
Normal file
41
test/SeederApi.IntegrationTest/HttpClientExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|
||||||
|
public static class HttpClientExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a POST request with JSON content and attaches the x-play-id header.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">The type of the value to serialize.</typeparam>
|
||||||
|
/// <param name="client">The HTTP client.</param>
|
||||||
|
/// <param name="requestUri">The URI the request is sent to.</param>
|
||||||
|
/// <param name="value">The value to serialize.</param>
|
||||||
|
/// <param name="playId">The play ID to attach as x-play-id header.</param>
|
||||||
|
/// <param name="options">Options to control the behavior during serialization.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||||
|
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||||
|
public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(
|
||||||
|
this HttpClient client,
|
||||||
|
[StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri,
|
||||||
|
TValue value,
|
||||||
|
string playId,
|
||||||
|
JsonSerializerOptions? options = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(client);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(playId))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Play ID cannot be null or whitespace.", nameof(playId));
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = JsonContent.Create(value, mediaType: null, options);
|
||||||
|
content.Headers.Remove("x-play-id");
|
||||||
|
content.Headers.Add("x-play-id", playId);
|
||||||
|
|
||||||
|
return client.PostAsync(requestUri, content, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Bit.SeederApi.IntegrationTest;
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|
||||||
public class EmergencyAccessInviteQueryTests : IClassFixture<SeederApiApplicationFactory>, IAsyncLifetime
|
public class QueryControllerTests : IClassFixture<SeederApiApplicationFactory>, IAsyncLifetime
|
||||||
{
|
{
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private readonly SeederApiApplicationFactory _factory;
|
private readonly SeederApiApplicationFactory _factory;
|
||||||
|
|
||||||
public EmergencyAccessInviteQueryTests(SeederApiApplicationFactory factory)
|
public QueryControllerTests(SeederApiApplicationFactory factory)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_client = _factory.CreateClient();
|
_client = _factory.CreateClient();
|
||||||
@@ -38,16 +38,11 @@ public class EmergencyAccessInviteQueryTests : IClassFixture<SeederApiApplicatio
|
|||||||
});
|
});
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var result = await response.Content.ReadFromJsonAsync<QueryResponse>();
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotNull(result.Result);
|
|
||||||
|
|
||||||
// The result should be a JSON array (even if empty for non-existent email)
|
var urls = System.Text.Json.JsonSerializer.Deserialize<List<string>>(result);
|
||||||
var resultElement = result.Result as System.Text.Json.JsonElement?;
|
|
||||||
Assert.NotNull(resultElement);
|
|
||||||
|
|
||||||
var urls = System.Text.Json.JsonSerializer.Deserialize<List<string>>(resultElement.Value.GetRawText());
|
|
||||||
Assert.NotNull(urls);
|
Assert.NotNull(urls);
|
||||||
// For a non-existent email, we expect an empty list
|
// For a non-existent email, we expect an empty list
|
||||||
Assert.Empty(urls);
|
Assert.Empty(urls);
|
||||||
@@ -90,19 +85,9 @@ public class EmergencyAccessInviteQueryTests : IClassFixture<SeederApiApplicatio
|
|||||||
});
|
});
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var result = await response.Content.ReadFromJsonAsync<QueryResponse>();
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.Equal("[]", result);
|
||||||
|
|
||||||
// Verify the response only has Result field, not SeedId
|
|
||||||
// (Queries are read-only and don't track entities)
|
|
||||||
var jsonString = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.DoesNotContain("seedId", jsonString, StringComparison.OrdinalIgnoreCase);
|
|
||||||
Assert.Contains("result", jsonString, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class QueryResponse
|
|
||||||
{
|
|
||||||
public object? Result { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,21 +29,21 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SeedEndpoint_WithValidScene_ReturnsOkWithSeedId()
|
public async Task SeedEndpoint_WithValidScene_ReturnsOk()
|
||||||
{
|
{
|
||||||
var testEmail = $"seed-test-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"seed-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var playId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, playId);
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var result = await response.Content.ReadFromJsonAsync<SceneResponseModel>();
|
var result = await response.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotEqual(Guid.Empty, result.SeedId);
|
|
||||||
Assert.NotNull(result.MangleMap);
|
Assert.NotNull(result.MangleMap);
|
||||||
Assert.Null(result.Result);
|
Assert.Null(result.Result);
|
||||||
}
|
}
|
||||||
@@ -74,60 +74,63 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DeleteEndpoint_WithValidSeedId_ReturnsOk()
|
public async Task DeleteEndpoint_WithValidPlayId_ReturnsOk()
|
||||||
{
|
{
|
||||||
var testEmail = $"delete-test-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"delete-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var playId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, playId);
|
||||||
|
|
||||||
seedResponse.EnsureSuccessStatusCode();
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||||
Assert.NotNull(seedResult);
|
Assert.NotNull(seedResult);
|
||||||
|
|
||||||
var deleteResponse = await _client.DeleteAsync($"/seed/{seedResult.SeedId}");
|
var deleteResponse = await _client.DeleteAsync($"/seed/{playId}");
|
||||||
deleteResponse.EnsureSuccessStatusCode();
|
deleteResponse.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DeleteEndpoint_WithInvalidSeedId_ReturnsOkWithNull()
|
public async Task DeleteEndpoint_WithInvalidPlayId_ReturnsOk()
|
||||||
{
|
{
|
||||||
// DestroyRecipe is idempotent - returns null for non-existent seeds
|
// DestroyRecipe is idempotent - returns null for non-existent play IDs
|
||||||
var nonExistentSeedId = Guid.NewGuid();
|
var nonExistentPlayId = Guid.NewGuid().ToString();
|
||||||
var response = await _client.DeleteAsync($"/seed/{nonExistentSeedId}");
|
var response = await _client.DeleteAsync($"/seed/{nonExistentPlayId}");
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal("null", content);
|
Assert.Equal($$"""{"playId":"{{nonExistentPlayId}}"}""", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DeleteBatchEndpoint_WithValidSeedIds_ReturnsOk()
|
public async Task DeleteBatchEndpoint_WithValidPlayIds_ReturnsOk()
|
||||||
{
|
{
|
||||||
// Create multiple seeds
|
// Create multiple seeds with different play IDs
|
||||||
var seedIds = new List<Guid>();
|
var playIds = new List<string>();
|
||||||
for (var i = 0; i < 3; i++)
|
for (var i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
|
var playId = Guid.NewGuid().ToString();
|
||||||
|
playIds.Add(playId);
|
||||||
|
|
||||||
var testEmail = $"batch-test-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"batch-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, playId);
|
||||||
|
|
||||||
seedResponse.EnsureSuccessStatusCode();
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||||
Assert.NotNull(seedResult);
|
Assert.NotNull(seedResult);
|
||||||
Assert.NotNull(seedResult.SeedId);
|
|
||||||
seedIds.Add(seedResult.SeedId.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete them in batch
|
// Delete them in batch
|
||||||
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
||||||
{
|
{
|
||||||
Content = JsonContent.Create(seedIds)
|
Content = JsonContent.Create(playIds)
|
||||||
};
|
};
|
||||||
var deleteResponse = await _client.SendAsync(request);
|
var deleteResponse = await _client.SendAsync(request);
|
||||||
deleteResponse.EnsureSuccessStatusCode();
|
deleteResponse.EnsureSuccessStatusCode();
|
||||||
@@ -141,24 +144,25 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
|||||||
public async Task DeleteBatchEndpoint_WithSomeInvalidIds_ReturnsOk()
|
public async Task DeleteBatchEndpoint_WithSomeInvalidIds_ReturnsOk()
|
||||||
{
|
{
|
||||||
// DestroyRecipe is idempotent - batch delete succeeds even with non-existent IDs
|
// DestroyRecipe is idempotent - batch delete succeeds even with non-existent IDs
|
||||||
// Create one valid seed
|
// Create one valid seed with a play ID
|
||||||
|
var validPlayId = Guid.NewGuid().ToString();
|
||||||
var testEmail = $"batch-partial-test-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"batch-partial-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, validPlayId);
|
||||||
|
|
||||||
seedResponse.EnsureSuccessStatusCode();
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||||
Assert.NotNull(seedResult);
|
Assert.NotNull(seedResult);
|
||||||
|
|
||||||
// Try to delete with mix of valid and invalid IDs
|
// Try to delete with mix of valid and invalid IDs
|
||||||
Assert.NotNull(seedResult.SeedId);
|
var playIds = new List<string> { validPlayId, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() };
|
||||||
var seedIds = new List<Guid> { seedResult.SeedId.Value, Guid.NewGuid(), Guid.NewGuid() };
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
||||||
{
|
{
|
||||||
Content = JsonContent.Create(seedIds)
|
Content = JsonContent.Create(playIds)
|
||||||
};
|
};
|
||||||
var deleteResponse = await _client.SendAsync(request);
|
var deleteResponse = await _client.SendAsync(request);
|
||||||
|
|
||||||
@@ -174,13 +178,17 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
|||||||
// Create multiple seeds
|
// Create multiple seeds
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
|
var playId = Guid.NewGuid().ToString();
|
||||||
var testEmail = $"deleteall-test-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"deleteall-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, playId);
|
||||||
|
|
||||||
|
var body = await seedResponse.Content.ReadAsStringAsync();
|
||||||
|
Console.WriteLine(body);
|
||||||
seedResponse.EnsureSuccessStatusCode();
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,21 +198,22 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SeedEndpoint_VerifyResponseContainsSeedIdAndResult()
|
public async Task SeedEndpoint_VerifyResponseContainsMangleMapAndResult()
|
||||||
{
|
{
|
||||||
var testEmail = $"verify-response-{Guid.NewGuid()}@bitwarden.com";
|
var testEmail = $"verify-response-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var playId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
{
|
{
|
||||||
Template = "SingleUserScene",
|
Template = "SingleUserScene",
|
||||||
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
});
|
}, playId);
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
var jsonString = await response.Content.ReadAsStringAsync();
|
var jsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// Verify the response contains both SeedId and Result fields
|
// Verify the response contains MangleMap and Result fields
|
||||||
Assert.Contains("seedId", jsonString, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("mangleMap", jsonString, StringComparison.OrdinalIgnoreCase);
|
||||||
Assert.Contains("result", jsonString, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("result", jsonString, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ public class QueryController(ILogger<QueryController> logger, IQueryService quer
|
|||||||
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
catch (SceneNotFoundException ex)
|
catch (QueryNotFoundException ex)
|
||||||
{
|
{
|
||||||
return NotFound(new { Error = ex.Message });
|
return NotFound(new { Error = ex.Message });
|
||||||
}
|
}
|
||||||
catch (SceneExecutionException ex)
|
catch (QueryExecutionException ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||||
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ builder.Services.AddScoped<Microsoft.AspNetCore.Identity.IPasswordHasher<Bit.Cor
|
|||||||
builder.Services.AddSingleton<Bit.RustSDK.RustSdkService>();
|
builder.Services.AddSingleton<Bit.RustSDK.RustSdkService>();
|
||||||
builder.Services.AddScoped<Bit.Seeder.Factories.UserSeeder>();
|
builder.Services.AddScoped<Bit.Seeder.Factories.UserSeeder>();
|
||||||
builder.Services.AddScoped<ISceneService, SceneService>();
|
builder.Services.AddScoped<ISceneService, SceneService>();
|
||||||
|
builder.Services.AddScoped<IQueryService, QueryService>();
|
||||||
builder.Services.AddScoped<MangleId>(_ => new MangleId());
|
builder.Services.AddScoped<MangleId>(_ => new MangleId());
|
||||||
builder.Services.AddScenes();
|
builder.Services.AddScenes();
|
||||||
builder.Services.AddQueries();
|
builder.Services.AddQueries();
|
||||||
@@ -41,3 +42,6 @@ app.UseRouting();
|
|||||||
app.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}");
|
app.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// Make Program class accessible for integration tests
|
||||||
|
public partial class Program { }
|
||||||
|
|||||||
@@ -43,12 +43,13 @@ public class SceneService(
|
|||||||
var userIds = playData.Select(pd => pd.UserId).Distinct().ToList();
|
var userIds = playData.Select(pd => pd.UserId).Distinct().ToList();
|
||||||
var organizationIds = playData.Select(pd => pd.OrganizationId).Distinct().ToList();
|
var organizationIds = playData.Select(pd => pd.OrganizationId).Distinct().ToList();
|
||||||
|
|
||||||
// Delete Users before Oraganizations to respect foreign key constraints
|
// Delete Users before Organizations to respect foreign key constraints
|
||||||
if (userIds.Count > 0)
|
if (userIds.Count > 0)
|
||||||
{
|
{
|
||||||
var users = databaseContext.Users.Where(u => userIds.Contains(u.Id));
|
var users = databaseContext.Users.Where(u => userIds.Contains(u.Id));
|
||||||
await userRepository.DeleteManyAsync(users);
|
await userRepository.DeleteManyAsync(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organizationIds.Count > 0)
|
if (organizationIds.Count > 0)
|
||||||
{
|
{
|
||||||
var organizations = databaseContext.Organizations.Where(o => organizationIds.Contains(o.Id));
|
var organizations = databaseContext.Organizations.Where(o => organizationIds.Contains(o.Id));
|
||||||
|
|||||||
Reference in New Issue
Block a user