mirror of
https://github.com/bitwarden/server
synced 2025-12-17 16:53:23 +00:00
Split Scene and Query
This commit is contained in:
@@ -134,10 +134,13 @@ EndProject
|
|||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\RustSdk.csproj", "{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\RustSdk.csproj", "{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}"
|
||||||
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedWeb.Test", "test\SharedWeb.Test\SharedWeb.Test.csproj", "{AD59537D-5259-4B7A-948F-0CF58E80B359}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedWeb.Test", "test\SharedWeb.Test\SharedWeb.Test.csproj", "{AD59537D-5259-4B7A-948F-0CF58E80B359}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi\SeederApi.csproj", "{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi\SeederApi.csproj", "{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi.IntegrationTest", "test\SeederApi.IntegrationTest\SeederApi.IntegrationTest.csproj", "{A2E067EF-609C-4D13-895A-E054C61D48BB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -354,6 +357,10 @@ Global
|
|||||||
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A2E067EF-609C-4D13-895A-E054C61D48BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A2E067EF-609C-4D13-895A-E054C61D48BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A2E067EF-609C-4D13-895A-E054C61D48BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A2E067EF-609C-4D13-895A-E054C61D48BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -411,6 +418,7 @@ Global
|
|||||||
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
||||||
{AD59537D-5259-4B7A-948F-0CF58E80B359} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
{AD59537D-5259-4B7A-948F-0CF58E80B359} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
||||||
|
{A2E067EF-609C-4D13-895A-E054C61D48BB} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
||||||
|
|||||||
108
test/SeederApi.IntegrationTest/QueryControllerTest.cs
Normal file
108
test/SeederApi.IntegrationTest/QueryControllerTest.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Bit.SeederApi.Models.Requests;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|
||||||
|
public class EmergencyAccessInviteQueryTests : IClassFixture<SeederApiApplicationFactory>, IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly SeederApiApplicationFactory _factory;
|
||||||
|
|
||||||
|
public EmergencyAccessInviteQueryTests(SeederApiApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_client = _factory.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DisposeAsync()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task QueryEndpoint_WithValidQueryAndArguments_ReturnsOk()
|
||||||
|
{
|
||||||
|
var testEmail = $"emergency-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
|
var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel
|
||||||
|
{
|
||||||
|
Template = "EmergencyAccessInviteQuery",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<QueryResponse>();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotNull(result.Result);
|
||||||
|
|
||||||
|
// The result should be a JSON array (even if empty for non-existent email)
|
||||||
|
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);
|
||||||
|
// For a non-existent email, we expect an empty list
|
||||||
|
Assert.Empty(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task QueryEndpoint_WithInvalidQueryName_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel
|
||||||
|
{
|
||||||
|
Template = "NonExistentQuery",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = "test@example.com" })
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task QueryEndpoint_WithMissingRequiredField_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
// EmergencyAccessInviteQuery requires 'email' field
|
||||||
|
var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel
|
||||||
|
{
|
||||||
|
Template = "EmergencyAccessInviteQuery",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { wrongField = "value" })
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task QueryEndpoint_VerifyQueryDoesNotCreateSeedId()
|
||||||
|
{
|
||||||
|
var testEmail = $"test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
|
var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel
|
||||||
|
{
|
||||||
|
Template = "EmergencyAccessInviteQuery",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<QueryResponse>();
|
||||||
|
|
||||||
|
Assert.NotNull(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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
214
test/SeederApi.IntegrationTest/SeedControllerTest.cs
Normal file
214
test/SeederApi.IntegrationTest/SeedControllerTest.cs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Bit.SeederApi.Models.Requests;
|
||||||
|
using Bit.SeederApi.Models.Response;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|
||||||
|
public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly SeederApiApplicationFactory _factory;
|
||||||
|
|
||||||
|
public SeedControllerTests(SeederApiApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_client = _factory.CreateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
// Clean up any seeded data after each test
|
||||||
|
await _client.DeleteAsync("/seed");
|
||||||
|
_client.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SeedEndpoint_WithValidScene_ReturnsOkWithSeedId()
|
||||||
|
{
|
||||||
|
var testEmail = $"seed-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEqual(Guid.Empty, result.SeedId);
|
||||||
|
Assert.NotNull(result.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SeedEndpoint_WithInvalidSceneName_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "NonExistentScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = "test@example.com" })
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SeedEndpoint_WithMissingRequiredField_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
// SingleUserScene requires 'email' field
|
||||||
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { wrongField = "value" })
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEndpoint_WithValidSeedId_ReturnsOk()
|
||||||
|
{
|
||||||
|
var testEmail = $"delete-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||||
|
Assert.NotNull(seedResult);
|
||||||
|
|
||||||
|
var deleteResponse = await _client.DeleteAsync($"/seed/{seedResult.SeedId}");
|
||||||
|
deleteResponse.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEndpoint_WithInvalidSeedId_ReturnsOkWithNull()
|
||||||
|
{
|
||||||
|
// DestroyRecipe is idempotent - returns null for non-existent seeds
|
||||||
|
var nonExistentSeedId = Guid.NewGuid();
|
||||||
|
var response = await _client.DeleteAsync($"/seed/{nonExistentSeedId}");
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("null", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteBatchEndpoint_WithValidSeedIds_ReturnsOk()
|
||||||
|
{
|
||||||
|
// Create multiple seeds
|
||||||
|
var seedIds = new List<Guid>();
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var testEmail = $"batch-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||||
|
Assert.NotNull(seedResult);
|
||||||
|
Assert.NotNull(seedResult.SeedId);
|
||||||
|
seedIds.Add(seedResult.SeedId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete them in batch
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(seedIds)
|
||||||
|
};
|
||||||
|
var deleteResponse = await _client.SendAsync(request);
|
||||||
|
deleteResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var result = await deleteResponse.Content.ReadFromJsonAsync<BatchDeleteResponse>();
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Batch delete completed successfully", result.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteBatchEndpoint_WithSomeInvalidIds_ReturnsOk()
|
||||||
|
{
|
||||||
|
// DestroyRecipe is idempotent - batch delete succeeds even with non-existent IDs
|
||||||
|
// Create one valid seed
|
||||||
|
var testEmail = $"batch-partial-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
|
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||||
|
Assert.NotNull(seedResult);
|
||||||
|
|
||||||
|
// Try to delete with mix of valid and invalid IDs
|
||||||
|
Assert.NotNull(seedResult.SeedId);
|
||||||
|
var seedIds = new List<Guid> { seedResult.SeedId.Value, Guid.NewGuid(), Guid.NewGuid() };
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch")
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(seedIds)
|
||||||
|
};
|
||||||
|
var deleteResponse = await _client.SendAsync(request);
|
||||||
|
|
||||||
|
deleteResponse.EnsureSuccessStatusCode();
|
||||||
|
var result = await deleteResponse.Content.ReadFromJsonAsync<BatchDeleteResponse>();
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Batch delete completed successfully", result.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAllEndpoint_DeletesAllSeededData()
|
||||||
|
{
|
||||||
|
// Create multiple seeds
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var testEmail = $"deleteall-test-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
seedResponse.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all
|
||||||
|
var deleteResponse = await _client.DeleteAsync("/seed");
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SeedEndpoint_VerifyResponseContainsSeedIdAndResult()
|
||||||
|
{
|
||||||
|
var testEmail = $"verify-response-{Guid.NewGuid()}@bitwarden.com";
|
||||||
|
|
||||||
|
var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel
|
||||||
|
{
|
||||||
|
Template = "SingleUserScene",
|
||||||
|
Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail })
|
||||||
|
});
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var jsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// Verify the response contains both SeedId and Result fields
|
||||||
|
Assert.Contains("seedId", jsonString, StringComparison.OrdinalIgnoreCase);
|
||||||
|
Assert.Contains("result", jsonString, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BatchDeleteResponse
|
||||||
|
{
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||||
|
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\util\SeederApi\SeederApi.csproj" />
|
||||||
|
<ProjectReference Include="..\..\util\Seeder\Seeder.csproj" />
|
||||||
|
<ProjectReference Include="..\IntegrationTestCommon\IntegrationTestCommon.csproj" />
|
||||||
|
|
||||||
|
<Content Include="..\..\util\SeederApi\appsettings.*.json">
|
||||||
|
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using Bit.IntegrationTestCommon.Factories;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|
||||||
|
public class SeederApiApplicationFactory : WebApplicationFactoryBase<Program>
|
||||||
|
{
|
||||||
|
}
|
||||||
37
util/SeederApi/Controllers/QueryController.cs
Normal file
37
util/SeederApi/Controllers/QueryController.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Bit.SeederApi.Models.Requests;
|
||||||
|
using Bit.SeederApi.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.Controllers
|
||||||
|
{
|
||||||
|
[Route("query")]
|
||||||
|
public class QueryController(ILogger<QueryController> logger, IRecipeService recipeService)
|
||||||
|
: Controller
|
||||||
|
{
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Query([FromBody] QueryRequestModel request)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Executing query: {Query}", request.Template);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = recipeService.ExecuteQuery(request.Template, request.Arguments);
|
||||||
|
|
||||||
|
return Json(new { Result = result });
|
||||||
|
}
|
||||||
|
catch (RecipeNotFoundException ex)
|
||||||
|
{
|
||||||
|
return NotFound(new { Error = ex.Message });
|
||||||
|
}
|
||||||
|
catch (RecipeExecutionException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||||
|
return BadRequest(new
|
||||||
|
{
|
||||||
|
Error = ex.Message,
|
||||||
|
Details = ex.InnerException?.Message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,37 +5,11 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Bit.SeederApi.Controllers
|
namespace Bit.SeederApi.Controllers
|
||||||
{
|
{
|
||||||
[Route("")]
|
[Route("seed")]
|
||||||
public class SeedController(ILogger<SeedController> logger, IRecipeService recipeService)
|
public class SeedController(ILogger<SeedController> logger, IRecipeService recipeService)
|
||||||
: Controller
|
: Controller
|
||||||
{
|
{
|
||||||
[HttpPost("/query")]
|
[HttpPost]
|
||||||
public IActionResult Query([FromBody] SeedRequestModel request)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Executing query: {Query}", request.Template);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = recipeService.ExecuteQuery(request.Template, request.Arguments);
|
|
||||||
|
|
||||||
return Json(new { Result = result });
|
|
||||||
}
|
|
||||||
catch (RecipeNotFoundException ex)
|
|
||||||
{
|
|
||||||
return NotFound(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
catch (RecipeExecutionException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
|
||||||
return BadRequest(new
|
|
||||||
{
|
|
||||||
Error = ex.Message,
|
|
||||||
Details = ex.InnerException?.Message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("/seed")]
|
|
||||||
public IActionResult Seed([FromBody] SeedRequestModel request)
|
public IActionResult Seed([FromBody] SeedRequestModel request)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||||
@@ -65,7 +39,7 @@ namespace Bit.SeederApi.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("/seed/batch")]
|
[HttpDelete("batch")]
|
||||||
public async Task<IActionResult> DeleteBatch([FromBody] List<Guid> seedIds)
|
public async Task<IActionResult> DeleteBatch([FromBody] List<Guid> seedIds)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Deleting batch of seeded data with IDs: {SeedIds}", string.Join(", ", seedIds));
|
logger.LogInformation("Deleting batch of seeded data with IDs: {SeedIds}", string.Join(", ", seedIds));
|
||||||
@@ -102,7 +76,7 @@ namespace Bit.SeederApi.Controllers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("/seed/{seedId}")]
|
[HttpDelete("{seedId}")]
|
||||||
public async Task<IActionResult> Delete([FromRoute] Guid seedId)
|
public async Task<IActionResult> Delete([FromRoute] Guid seedId)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Deleting seeded data with ID: {SeedId}", seedId);
|
logger.LogInformation("Deleting seeded data with ID: {SeedId}", seedId);
|
||||||
@@ -125,7 +99,7 @@ namespace Bit.SeederApi.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpDelete("/seed")]
|
[HttpDelete]
|
||||||
public async Task<IActionResult> DeleteAll()
|
public async Task<IActionResult> DeleteAll()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Deleting all seeded data");
|
logger.LogInformation("Deleting all seeded data");
|
||||||
|
|||||||
11
util/SeederApi/Models/Request/QueryRequestModel.cs
Normal file
11
util/SeederApi/Models/Request/QueryRequestModel.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Bit.SeederApi.Models.Requests;
|
||||||
|
|
||||||
|
public class QueryRequestModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public required string Template { get; set; }
|
||||||
|
public JsonElement? Arguments { get; set; }
|
||||||
|
}
|
||||||
@@ -37,3 +37,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 { }
|
||||||
|
|||||||
Reference in New Issue
Block a user