Add validation to RealmCreate
This commit is contained in:
parent
09480eb1e4
commit
ddbb1f42d7
16 changed files with 326 additions and 23 deletions
61
IdentityShroud.Api.Tests/Apis/RealmApisTests.cs
Normal file
61
IdentityShroud.Api.Tests/Apis/RealmApisTests.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using FluentResults;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.Core.Tests.Fixtures;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSubstitute.ClearExtensions;
|
||||
|
||||
namespace IdentityShroud.Api.Tests.Apis;
|
||||
|
||||
public class RealmApisTests(ApplicationFactory factory) : IClassFixture<ApplicationFactory>
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, null, null, false, "Name")]
|
||||
[InlineData(null, null, "Foo", true, "")]
|
||||
[InlineData(null, null, "", false, "Name")]
|
||||
[InlineData(null, "foo", "Foo", true, "")]
|
||||
[InlineData(null, "f/oo", "Foo", false, "Slug")]
|
||||
[InlineData(null, "", "Foo", false, "Slug")]
|
||||
[InlineData("0814934a-efe2-4784-ba84-a184c0b9de9e", "foo", "Foo", true, "")]
|
||||
[InlineData("00000000-0000-0000-0000-000000000000", "foo", "Foo", false, "Id")]
|
||||
public async Task Create(string? id, string? slug, string? name, bool succeeds, string fieldName)
|
||||
{
|
||||
var client = factory.CreateClient();
|
||||
|
||||
factory.RealmService.ClearSubstitute();
|
||||
factory.RealmService.Create(Arg.Any<RealmCreateRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Result.Ok(new RealmCreateResponse(Guid.NewGuid(), "foo", "Foo")));
|
||||
|
||||
Guid? inputId = id is null ? (Guid?)null : new Guid(id);
|
||||
var response = await client.PostAsync("/realms", JsonContent.Create(new
|
||||
{
|
||||
Id = inputId,
|
||||
Slug = slug,
|
||||
Name = name,
|
||||
}),
|
||||
TestContext.Current.CancellationToken);
|
||||
#if DEBUG
|
||||
string contents = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||
#endif
|
||||
|
||||
if (succeeds)
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
|
||||
await factory.RealmService.Received(1).Create(
|
||||
Arg.Is<RealmCreateRequest>(r => r.Id == inputId && r.Slug == slug && r.Name == name),
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var problemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Contains(problemDetails!.Errors, e => e.Key == fieldName);
|
||||
await factory.RealmService.DidNotReceive().Create(
|
||||
Arg.Any<RealmCreateRequest>(),
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
}
|
||||
24
IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs
Normal file
24
IdentityShroud.Api.Tests/Fixtures/ApplicationFactory.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using IdentityShroud.Core.Services;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.TestPlatform.TestHost;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Fixtures;
|
||||
|
||||
public class ApplicationFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
public IRealmService RealmService { get; } = Substitute.For<IRealmService>();
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
base.ConfigureWebHost(builder);
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddScoped<IRealmService>(c => RealmService);
|
||||
});
|
||||
|
||||
builder.UseEnvironment("Development");
|
||||
}
|
||||
}
|
||||
32
IdentityShroud.Api.Tests/IdentityShroud.Api.Tests.csproj
Normal file
32
IdentityShroud.Api.Tests/IdentityShroud.Api.Tests.csproj
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Testcontainers" Version="4.10.0" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.10.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
|
||||
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
<Using Include="NSubstitute"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.Api\IdentityShroud.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using FluentResults;
|
||||
using IdentityShroud.Api.Validation;
|
||||
using IdentityShroud.Core.Messages;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
|
@ -6,23 +8,36 @@ using Microsoft.AspNetCore.Mvc;
|
|||
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
public static class RealmController
|
||||
public static class RealmApi
|
||||
{
|
||||
public static void MapRealmEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var realm = app.MapGroup("/realms/{slug}");
|
||||
var realmsGroup = app.MapGroup("/realms");
|
||||
realmsGroup.MapPost("", RealmCreate)
|
||||
.Validate<RealmCreateRequest>()
|
||||
.WithName("Create Realm")
|
||||
.Produces(StatusCodes.Status201Created);
|
||||
|
||||
realm.MapGet("", GetRoot);
|
||||
realm.MapPost("", (RealmCreateRequest request, [FromServices] RealmService service) =>
|
||||
service.Create(request))
|
||||
.WithName("Create Realm");
|
||||
realm.MapGet(".well-known/openid-configuration", GetOpenIdConfiguration);
|
||||
var realmSlugGroup = app.MapGroup("{slug}");
|
||||
realmSlugGroup.MapGet("", GetRealmInfo);
|
||||
realmSlugGroup.MapGet(".well-known/openid-configuration", GetOpenIdConfiguration);
|
||||
|
||||
var openidConnect = realm.MapGroup("openid-connect");
|
||||
var openidConnect = realmSlugGroup.MapGroup("openid-connect");
|
||||
openidConnect.MapPost("auth", OpenIdConnectAuth);
|
||||
openidConnect.MapPost("token", OpenIdConnectToken);
|
||||
openidConnect.MapGet("jwks", OpenIdConnectJwks);
|
||||
}
|
||||
|
||||
private static async Task<Results<Created<RealmCreateResponse>, InternalServerError>>
|
||||
RealmCreate(RealmCreateRequest request, [FromServices] IRealmService service)
|
||||
{
|
||||
var response = await service.Create(request);
|
||||
if (response.IsSuccess)
|
||||
return TypedResults.Created($"/realms/{response.Value.Slug}", response.Value);
|
||||
|
||||
// TODO make helper to convert failure response to a proper HTTP result.
|
||||
return TypedResults.InternalServerError();
|
||||
}
|
||||
|
||||
private static Task OpenIdConnectJwks(HttpContext context)
|
||||
{
|
||||
|
|
@ -57,7 +72,7 @@ public static class RealmController
|
|||
}, AppJsonSerializerContext.Default.OpenIdConfiguration);
|
||||
}
|
||||
|
||||
private static string GetRoot()
|
||||
private static string GetRealmInfo()
|
||||
{
|
||||
return "Hello World!";
|
||||
|
||||
|
|
@ -10,7 +10,12 @@
|
|||
<UserSecretsId>6b8ef434-0577-4a3c-8749-6b547d7787c5</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
using FluentValidation;
|
||||
using IdentityShroud.Api;
|
||||
using IdentityShroud.Api.Validation;
|
||||
using IdentityShroud.Core;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Security;
|
||||
using Serilog;
|
||||
using Serilog.Formatting.Json;
|
||||
|
||||
|
||||
// Initial logging until we can set it up from Configuration
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
|
|
@ -34,6 +37,8 @@ void ConfigureBuilder(WebApplicationBuilder builder)
|
|||
services.AddOptions<DbConfiguration>().Bind(configuration.GetSection("db"));
|
||||
services.AddSingleton<ISecretProvider, ConfigurationSecretProvider>();
|
||||
|
||||
services.AddValidatorsFromAssemblyContaining<RealmCreateRequestValidator>();
|
||||
|
||||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||||
.Enrich.FromLogContext()
|
||||
//.Enrich.With<UserEnricher>()
|
||||
|
|
@ -51,3 +56,5 @@ void ConfigureApplication(WebApplication app)
|
|||
// app.UseRouting();
|
||||
// app.MapControllers();
|
||||
}
|
||||
|
||||
public partial class Program { }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace IdentityShroud.Api.Validation;
|
||||
|
||||
public static class EndpointRouteBuilderExtensions
|
||||
{
|
||||
public static RouteHandlerBuilder Validate<TDto>(this RouteHandlerBuilder builder) where TDto : class
|
||||
=> builder.AddEndpointFilter<ValidateFilter<TDto>>();
|
||||
}
|
||||
19
IdentityShroud.Api/Validation/RealmCreateRequestValidator.cs
Normal file
19
IdentityShroud.Api/Validation/RealmCreateRequestValidator.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using FluentValidation;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
namespace IdentityShroud.Api.Validation;
|
||||
|
||||
public class RealmCreateRequestValidator : AbstractValidator<RealmCreateRequest>
|
||||
{
|
||||
private const string SlugPattern = @"^(?=.{1,40}$)[a-z0-9]+(?:-[a-z0-9]+)*$";
|
||||
|
||||
public RealmCreateRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.NotEqual(Guid.Empty).When(x => x.Id.HasValue);
|
||||
RuleFor(x => x.Slug)
|
||||
.Matches(SlugPattern).Unless(x => x.Slug is null);
|
||||
RuleFor(x => x.Name)
|
||||
.NotNull().Length(1, 255);
|
||||
}
|
||||
}
|
||||
33
IdentityShroud.Api/Validation/ValidateFilter.cs
Normal file
33
IdentityShroud.Api/Validation/ValidateFilter.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace IdentityShroud.Api.Validation;
|
||||
|
||||
public class ValidateFilter<T> : IEndpointFilter where T : class
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(
|
||||
EndpointFilterInvocationContext context,
|
||||
EndpointFilterDelegate next)
|
||||
{
|
||||
// Grab the deserialized argument (the DTO) from the context.
|
||||
// The order of arguments matches the order of parameters in the endpoint delegate.
|
||||
var dto = context.Arguments
|
||||
.FirstOrDefault(arg => arg is T) as T;
|
||||
|
||||
if (dto == null)
|
||||
return Results.BadRequest("Unable to read request body.");
|
||||
|
||||
// Resolve the matching validator from DI.
|
||||
var validator = context.HttpContext.RequestServices
|
||||
.GetService<IValidator<T>>();
|
||||
|
||||
if (validator != null)
|
||||
{
|
||||
var validationResult = await validator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
return Results.ValidationProblem(validationResult.ToDictionary());
|
||||
}
|
||||
|
||||
// Validation passed – continue to the actual handler.
|
||||
return await next(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -41,12 +41,13 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
RealmCreateResponse val = ResultAssert.Success(response);
|
||||
if (realmId.HasValue)
|
||||
Assert.Equal(realmId, val.Realm.Id);
|
||||
Assert.Equal(realmId, val.Id);
|
||||
else
|
||||
Assert.NotEqual(Guid.Empty, val.Realm.Id);
|
||||
Assert.NotEqual(Guid.Empty, val.Id);
|
||||
|
||||
Assert.Equal("slug", val.Realm.Slug);
|
||||
Assert.Equal("New realm", val.Realm.Name);
|
||||
Assert.NotEmpty(val.Realm.PrivateKeyEncrypted);
|
||||
Assert.Equal("slug", val.Slug);
|
||||
Assert.Equal("New realm", val.Name);
|
||||
|
||||
// TODO verify data has been stored!
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
namespace IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
public record RealmCreateRequest(Guid? Id, string Slug, string Description);
|
||||
public record RealmCreateRequest(Guid? Id, string? Slug, string Name);
|
||||
85
IdentityShroud.Core/Helpers/SlugHelper.cs
Normal file
85
IdentityShroud.Core/Helpers/SlugHelper.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace IdentityShroud.Core.Helpers;
|
||||
|
||||
public static class SlugHelper
|
||||
{
|
||||
public static string GenerateSlug(string text, int maxLength = 40)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return string.Empty;
|
||||
|
||||
// Normalize to decomposed form (separates accents from letters)
|
||||
string normalized = text.Normalize(NormalizationForm.FormD);
|
||||
|
||||
StringBuilder sb = new StringBuilder(normalized.Length);
|
||||
bool lastWasHyphen = false;
|
||||
|
||||
foreach (char c in normalized)
|
||||
{
|
||||
// Skip diacritics (accents)
|
||||
if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
|
||||
continue;
|
||||
|
||||
char lower = char.ToLowerInvariant(c);
|
||||
|
||||
// Convert valid characters
|
||||
if ((lower >= 'a' && lower <= 'z') || (lower >= '0' && lower <= '9'))
|
||||
{
|
||||
sb.Append(lower);
|
||||
lastWasHyphen = false;
|
||||
}
|
||||
// Convert spaces, underscores, and other separators to hyphen
|
||||
else if (char.IsWhiteSpace(c) || c == '_' || c == '-')
|
||||
{
|
||||
if (!lastWasHyphen && sb.Length > 0)
|
||||
{
|
||||
sb.Append('-');
|
||||
lastWasHyphen = true;
|
||||
}
|
||||
}
|
||||
// Skip all other characters
|
||||
}
|
||||
|
||||
// Trim trailing hyphen if any
|
||||
if (sb.Length > 0 && sb[sb.Length - 1] == '-')
|
||||
sb.Length--;
|
||||
|
||||
string slug = sb.ToString();
|
||||
|
||||
// Handle truncation with hash suffix for long strings
|
||||
if (slug.Length > maxLength)
|
||||
{
|
||||
// Generate hash of original text
|
||||
string hashSuffix = GenerateHashSuffix(text);
|
||||
int contentLength = maxLength - hashSuffix.Length;
|
||||
|
||||
// Truncate at word boundary if possible
|
||||
int cutPoint = contentLength;
|
||||
int lastHyphen = slug.LastIndexOf('-', contentLength - 1);
|
||||
|
||||
if (lastHyphen > contentLength / 2)
|
||||
cutPoint = lastHyphen;
|
||||
|
||||
slug = slug.Substring(0, cutPoint).TrimEnd('-') + hashSuffix;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
private static string GenerateHashSuffix(string text)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
|
||||
|
||||
// Take first 4 bytes (will become ~5-6 base64url chars)
|
||||
string base64Url = WebEncoders.Base64UrlEncode(hash, 0, 4);
|
||||
return "-" + base64Url;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
IdentityShroud.Core/Services/IRealmService.cs
Normal file
8
IdentityShroud.Core/Services/IRealmService.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
namespace IdentityShroud.Core.Services;
|
||||
|
||||
public interface IRealmService
|
||||
{
|
||||
Task<Result<RealmCreateResponse>> Create(RealmCreateRequest request, CancellationToken ct = default);
|
||||
}
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Helpers;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
||||
namespace IdentityShroud.Core.Services;
|
||||
|
||||
public record RealmCreateResponse(Realm Realm);
|
||||
public record RealmCreateResponse(Guid Id, string Slug, string Name);
|
||||
|
||||
public class RealmService(
|
||||
Db db,
|
||||
IEncryptionService encryptionService)
|
||||
IEncryptionService encryptionService) : IRealmService
|
||||
{
|
||||
public async Task<Result<RealmCreateResponse>> Create(RealmCreateRequest request, CancellationToken ct = default)
|
||||
{
|
||||
Realm realm = new()
|
||||
{
|
||||
Id = request.Id ?? Guid.CreateVersion7(),
|
||||
Slug = request.Slug,
|
||||
Name = request.Description,
|
||||
Slug = request.Slug ?? SlugHelper.GenerateSlug(request.Name),
|
||||
Name = request.Name,
|
||||
};
|
||||
|
||||
using RSA rsa = RSA.Create(2048);
|
||||
|
|
@ -26,6 +27,7 @@ public class RealmService(
|
|||
db.Add(realm);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return new RealmCreateResponse(realm);
|
||||
return new RealmCreateResponse(
|
||||
realm.Id, realm.Slug, realm.Name);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,11 @@ EndProject
|
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.Core.Tests", "IdentityShroud.Core.Tests\IdentityShroud.Core.Tests.csproj", "{DC887623-8680-4D3B-B23A-D54F7DA91891}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{576359FF-C672-4CC3-A683-3BB9D647E75D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
compose.yaml = compose.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.Migrations", "IdentityShroud.Migrations\IdentityShroud.Migrations.csproj", "{DEECABE3-8934-4696-B0E1-48738DD0CEC4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.Api.Tests", "IdentityShroud.Api.Tests\IdentityShroud.Api.Tests.csproj", "{4758FE2E-A437-44F0-B58E-09E52D67D288}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -35,5 +34,9 @@ Global
|
|||
{DEECABE3-8934-4696-B0E1-48738DD0CEC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DEECABE3-8934-4696-B0E1-48738DD0CEC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DEECABE3-8934-4696-B0E1-48738DD0CEC4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4758FE2E-A437-44F0-B58E-09E52D67D288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4758FE2E-A437-44F0-B58E-09E52D67D288}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4758FE2E-A437-44F0-B58E-09E52D67D288}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4758FE2E-A437-44F0-B58E-09E52D67D288}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOkOfT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe2a19de442f561af862af2dcad0852b7e62707a5cf194d266d1656f92bbb6d2_003FOkOfT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APostgreSqlBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fcdd0beaf7beaf8366c0862f34fe40da30911084d957625ab31577851ee8cae7_003FPostgreSqlBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATypedResults_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fcea118513a410f660e578fe32bed95cf86457dd135e4b4632ca91eb4f7b_003FTypedResults_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/dotCover/Editor/HighlightingSourceSnapshotLocation/@EntryValue">/home/eelke/.cache/JetBrains/Rider2025.3/resharper-host/temp/Rider/vAny/CoverageData/_IdentityShroud.-1277985570/Snapshot/snapshot.utdcvr</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">/home/eelke/.dotnet/dotnet</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/home/eelke/.dotnet/sdk/10.0.102/MSBuild.dll</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7d190ab0_002D4f9d_002D4f9f_002Dad83_002Da57b539f3bbd/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue