diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bfe16be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +* +!src +!tests +**/bin +**/obj +!AspNetCore.RestFramework.sln +!scripts/*.sh diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..0d91f8d --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,40 @@ +name: Validate PR + +on: + pull_request: + branches: + - main + paths: + - 'src/**' + - 'tests/**' + - '*.sln' + - 'Dockerfile' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 0 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8' + - name: Check if the project is well formatted + run: | + ./scripts/start-check-formatting.sh + - name: Install dotnet-sonarscanner + run: | + dotnet tool install --global dotnet-sonarscanner + - name: Build the project, run all tests, and publish the test results + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + PR_SOURCE_BRANCH: ${{ github.head_ref }} + PR_TARGET_BRANCH: ${{ github.base_ref }} + GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} + run: | + set -e + docker compose up -d db + ./scripts/start-sonarcloud.sh diff --git a/.github/workflows/publish-coverage.yml b/.github/workflows/publish-coverage.yml new file mode 100644 index 0000000..d366daf --- /dev/null +++ b/.github/workflows/publish-coverage.yml @@ -0,0 +1,33 @@ +name: Publish package and coverage report + +on: + push: + branches: + - main + paths: + - 'src/**' + - 'tests/**' + - '*.sln' + - 'Directory.Build.props' + +jobs: + publish-coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 0 + - name: Install dotnet-sonarscanner + run: | + dotnet tool install --global dotnet-sonarscanner + - name: Build the project, run all tests and publish to SonarCloud + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + PROJECT_VERSION: ${{ github.sha }} + SOURCE_BRANCH_NAME: ${{ github.ref_name }} + run: | + set -e + docker compose up -d db + ./scripts/start-sonarcloud.sh diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml new file mode 100644 index 0000000..5c246db --- /dev/null +++ b/.github/workflows/publish-package.yml @@ -0,0 +1,29 @@ +name: Publish coverage report + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+*' + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish-coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Generate package + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + dotnet pack -o out -p:PackageVersion=$TAG_NAME + - name: Push package + run: | + dotnet nuget push out/*.nupkg \ + --api-key ${{ secrets.PUBLIC_NUGET_API_KEY }} \ + --source "https://api.nuget.org/v3/index.json" \ + --skip-duplicate diff --git a/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj b/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj deleted file mode 100644 index 22b90db..0000000 --- a/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net6.0 - 0.0.1 - 0.0.1 - - - - - - - - - - diff --git a/AspNetCore.RestFramework.Sample/AspNetCore.RestFramework.Sample.csproj b/AspNetCore.RestFramework.Sample/AspNetCore.RestFramework.Sample.csproj deleted file mode 100644 index 6ccb3ba..0000000 --- a/AspNetCore.RestFramework.Sample/AspNetCore.RestFramework.Sample.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - net6.0 - AspNetRestFramework.Sample - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - diff --git a/AspNetCore.RestFramework.Sample/Context/ApplicationDbContext.cs b/AspNetCore.RestFramework.Sample/Context/ApplicationDbContext.cs deleted file mode 100644 index c582fcf..0000000 --- a/AspNetCore.RestFramework.Sample/Context/ApplicationDbContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AspNetRestFramework.Sample.Mappings; -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; - -namespace AspNetRestFramework.Sample.Context -{ - public class ApplicationDbContext : DbContext - { - public ApplicationDbContext(DbContextOptions options) : base(options) - { - } - - - public DbSet Customer { get; set; } - public DbSet Seller { get; set; } - public DbSet CustomerDocument { get; set; } - public DbSet IntAsIdEntities { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.ApplyConfiguration(new CustomerConfig()); - modelBuilder.ApplyConfiguration(new SellerConfig()); - modelBuilder.ApplyConfiguration(new CustomerDocumentConfig()); - modelBuilder.ApplyConfiguration(new IntAsIdEntityConfig()); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Controllers/CustomerDocumentsController.cs b/AspNetCore.RestFramework.Sample/Controllers/CustomerDocumentsController.cs deleted file mode 100644 index ecb8236..0000000 --- a/AspNetCore.RestFramework.Sample/Controllers/CustomerDocumentsController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Filters; -using AspNetCore.RestFramework.Core.Serializer; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Filters; -using AspNetRestFramework.Sample.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace AspNetRestFramework.Sample.Controllers; - -[Route("api/[controller]")] -[ApiController] -public class CustomerDocumentsController : BaseController -{ - public CustomerDocumentsController(Serializer serializer, ApplicationDbContext context, ILogger logger) : base(serializer, context,logger) - { - AllowedFields = new[] { - nameof(CustomerDocument.Document), - nameof(CustomerDocument.DocumentType), - nameof(CustomerDocument.CustomerId) - }; - - Filters.Add(new CustomerFilter()); - Filters.Add(new QueryStringFilter(AllowedFields)); - Filters.Add(new QueryStringSearchFilter(AllowedFields)); - Filters.Add(new QueryStringIdRangeFilter()); - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/Controllers/CustomersController.cs b/AspNetCore.RestFramework.Sample/Controllers/CustomersController.cs deleted file mode 100644 index 5943fd3..0000000 --- a/AspNetCore.RestFramework.Sample/Controllers/CustomersController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Filters; -using Microsoft.AspNetCore.Mvc; -using System; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.CustomSerializers; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Filters; -using AspNetRestFramework.Sample.Models; -using Microsoft.Extensions.Logging; - -namespace AspNetRestFramework.Sample.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class CustomersController : BaseController - { - public CustomersController( - CustomerSerializer serializer, - ApplicationDbContext dbContext, - ILogger logger) - : base( - serializer, - dbContext, - logger) - { - AllowedFields = new[] { - nameof(Customer.Id), - nameof(Customer.Name), - nameof(Customer.CNPJ), - nameof(Customer.Age), - }; - - Filters.Add(new QueryStringFilter(AllowedFields)); - Filters.Add(new QueryStringSearchFilter(AllowedFields)); - Filters.Add(new QueryStringIdRangeFilter()); - Filters.Add(new DocumentFilter()); - Filters.Add(new CustomerDocumentIncludeFilter()); - } - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/Controllers/IntAsIdEntitiesController.cs b/AspNetCore.RestFramework.Sample/Controllers/IntAsIdEntitiesController.cs deleted file mode 100644 index def33e2..0000000 --- a/AspNetCore.RestFramework.Sample/Controllers/IntAsIdEntitiesController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Filters; -using AspNetCore.RestFramework.Core.Serializer; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace AspNetRestFramework.Sample.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class IntAsIdEntitiesController : BaseController - { - public IntAsIdEntitiesController( - Serializer serializer, - ApplicationDbContext context, - ILogger logger) - : base( - serializer, - context, - new ActionOptions() { AllowPatch = false, AllowPut = false }, - logger) - { - AllowedFields = new[] { - nameof(IntAsIdEntity.Id), - nameof(IntAsIdEntity.Name), - }; - - Filters.Add(new QueryStringFilter(AllowedFields)); - Filters.Add(new QueryStringSearchFilter(AllowedFields)); - Filters.Add(new QueryStringIdRangeFilter()); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Controllers/SellersController.cs b/AspNetCore.RestFramework.Sample/Controllers/SellersController.cs deleted file mode 100644 index 5f1d923..0000000 --- a/AspNetCore.RestFramework.Sample/Controllers/SellersController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Serializer; -using Microsoft.AspNetCore.Mvc; -using System; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using Microsoft.Extensions.Logging; -using AspNetCore.RestFramework.Core.Filters; - -namespace AspNetRestFramework.Sample.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class SellersController : BaseController - { - public SellersController( - Serializer serializer, - ApplicationDbContext dbContext, - ILogger logger) - : base( - serializer, - dbContext, - logger) - { - AllowedFields = new[] { - nameof(Seller.Name) - }; - - Filters.Add(new QueryStringFilter(AllowedFields)); - Filters.Add(new QueryStringSearchFilter(AllowedFields)); - Filters.Add(new QueryStringIdRangeFilter()); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/CustomSerializers/CustomerSerializer.cs b/AspNetCore.RestFramework.Sample/CustomSerializers/CustomerSerializer.cs deleted file mode 100644 index 9fd7c3f..0000000 --- a/AspNetCore.RestFramework.Sample/CustomSerializers/CustomerSerializer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Serializer; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; - -namespace AspNetRestFramework.Sample.CustomSerializers -{ - public class CustomerSerializer : Serializer - { - public CustomerSerializer(ApplicationDbContext applicationDbContext) : base(applicationDbContext) - { - } - } -} diff --git a/AspNetCore.RestFramework.Sample/DTO/CustomerDocumentDto.cs b/AspNetCore.RestFramework.Sample/DTO/CustomerDocumentDto.cs deleted file mode 100644 index 5bc821c..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/CustomerDocumentDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Base; - -namespace AspNetRestFramework.Sample.DTO; - -public class CustomerDocumentDto : BaseDto -{ - public string Document { get; set; } - public string DocumentType { get; set; } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/DTO/CustomerDto.cs b/AspNetCore.RestFramework.Sample/DTO/CustomerDto.cs deleted file mode 100644 index 2a972b2..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/CustomerDto.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Base; -using System.Collections.Generic; - -namespace AspNetRestFramework.Sample.DTO -{ - public class CustomerDto : BaseDto - { - public CustomerDto() - { - } - - public string Name { get; set; } - public string CNPJ { get; set; } - - public ICollection CustomerDocuments { get; set; } - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/DTO/IntAsIdEntityDto.cs b/AspNetCore.RestFramework.Sample/DTO/IntAsIdEntityDto.cs deleted file mode 100644 index ca9377a..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/IntAsIdEntityDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; - -namespace AspNetRestFramework.Sample.DTO -{ - public class IntAsIdEntityDto : BaseDto - { - public string Name { get; set; } - } -} diff --git a/AspNetCore.RestFramework.Sample/DTO/SellerDto.cs b/AspNetCore.RestFramework.Sample/DTO/SellerDto.cs deleted file mode 100644 index 22d3ebf..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/SellerDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Base; - -namespace AspNetRestFramework.Sample.DTO -{ - public class SellerDto : BaseDto - { - public string Name { get; set; } - } -} diff --git a/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDocumentDtoValidator.cs b/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDocumentDtoValidator.cs deleted file mode 100644 index 66b9fff..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDocumentDtoValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; - -namespace AspNetRestFramework.Sample.DTO.Validators -{ - public class CustomerDocumentDtoValidator : AbstractValidator - { - public CustomerDocumentDtoValidator() - { - RuleFor(m => m.Document) - .MinimumLength(3) - .WithMessage("Name should have at least 3 characters"); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDtoValidator.cs b/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDtoValidator.cs deleted file mode 100644 index 97b364d..0000000 --- a/AspNetCore.RestFramework.Sample/DTO/Validators/CustomerDtoValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FluentValidation; -using Microsoft.AspNetCore.Http; - -namespace AspNetRestFramework.Sample.DTO.Validators -{ - public class CustomerDtoValidator : AbstractValidator - { - public CustomerDtoValidator(IHttpContextAccessor context) - { - RuleFor(m => m.Name) - .MinimumLength(3) - .WithMessage("Name should have at least 3 characters"); - - if (context.HttpContext.Request.Method == HttpMethods.Post) - RuleFor(m => m.CNPJ) - .NotEqual("567") - .WithMessage("CNPJ cannot be 567"); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/FakeData/FakeDataGenerator.cs b/AspNetCore.RestFramework.Sample/FakeData/FakeDataGenerator.cs deleted file mode 100644 index b53992b..0000000 --- a/AspNetCore.RestFramework.Sample/FakeData/FakeDataGenerator.cs +++ /dev/null @@ -1,75 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using Bogus; -using Bogus.Extensions.Brazil; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace AspNetRestFramework.Sample.FakeData -{ - public static class FakeDataGenerator - { - private const int SELLERS_TO_GENERATE = 30; - private const int CUSTOMERS_TO_GENERATE = 500; - private const int DOCUMENTS_TO_GENERATE_MIN = 1; - private const int DOCUMENTS_TO_GENERATE_MAX = 5; - - public static (IList, IList, IList) GenerateFakeData() - { - var sellers = Enumerable - .Range(0, SELLERS_TO_GENERATE) - .Select(_ => { - var sellerFaker = new Faker() - .RuleFor(m => m.Id, m => Guid.NewGuid()) - .RuleFor(m => m.Name, m => m.Company.CompanyName()); - - return sellerFaker.Generate(); - }) - .ToList(); - - var customerDocuments = new List(); - - var customers = Enumerable - .Range(0, CUSTOMERS_TO_GENERATE) - .Select(_ => { - var customerFaker = new Faker() - .RuleFor(m => m.Id, m => Guid.NewGuid()) - .RuleFor(m => m.Name, m => m.Company.CompanyName()) - .RuleFor(m => m.Age, m => m.Random.Number(18, 99)) - .RuleFor(m => m.CNPJ, m => m.Company.Cnpj()); - - var customer = customerFaker.Generate(); - - var documentsToGenerate = new Faker().Random.Number(DOCUMENTS_TO_GENERATE_MIN, DOCUMENTS_TO_GENERATE_MAX); - customerDocuments.AddRange( - Enumerable.Range(0, documentsToGenerate) - .Select(_ => - { - var docFaker = new Faker() - .RuleFor(m => m.Id, m => Guid.NewGuid()) - .RuleFor(m => m.CustomerId, customer.Id) - .RuleFor(m => m.DocumentType, m => m.Random.ArrayElement(new[] { "cpf", "cnpj", "xpto", "others" })) - .RuleFor(m => m.Document, (f, d) => { - return d.DocumentType switch - { - "cpf" => f.Person.Cpf(), - "cnpj" => f.Company.Cnpj(), - "xpto" => $"xpto_{f.Random.Number(10000, 99999)}_{f.Random.Word()}", - "others" => f.Address.Country(), - _ => "invalid", - }; - }); - - return docFaker.Generate(); - }) - .ToList() - ); - - return customer; - }) - .ToList(); - - return (sellers, customers, customerDocuments); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Filters/CustomerDocumentIncludeFilter.cs b/AspNetCore.RestFramework.Sample/Filters/CustomerDocumentIncludeFilter.cs deleted file mode 100644 index 33e7cde..0000000 --- a/AspNetCore.RestFramework.Sample/Filters/CustomerDocumentIncludeFilter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Linq; -using AspNetCore.RestFramework.Core.Filters; -using AspNetRestFramework.Sample.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; - -namespace AspNetRestFramework.Sample.Filters; - -public class CustomerDocumentIncludeFilter : Filter -{ - public override IQueryable AddFilter(IQueryable query, HttpRequest request) - { - return query.Include(x => x.CustomerDocument); - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/Filters/CustomerFilter.cs b/AspNetCore.RestFramework.Sample/Filters/CustomerFilter.cs deleted file mode 100644 index 182634c..0000000 --- a/AspNetCore.RestFramework.Sample/Filters/CustomerFilter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Linq; -using AspNetCore.RestFramework.Core.Filters; -using AspNetRestFramework.Sample.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; - -namespace AspNetRestFramework.Sample.Filters; - -public class CustomerFilter : Filter -{ - public override IQueryable AddFilter(IQueryable query, HttpRequest request) - { - return query.Include(x => x.Customer); - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Sample/Filters/DocumentFilter.cs b/AspNetCore.RestFramework.Sample/Filters/DocumentFilter.cs deleted file mode 100644 index 5405aef..0000000 --- a/AspNetCore.RestFramework.Sample/Filters/DocumentFilter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AspNetCore.RestFramework.Core.Filters; -using Microsoft.AspNetCore.Http; -using System.Linq; -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; - -namespace AspNetRestFramework.Sample.Filters -{ - public class DocumentFilter : Filter - { - public override IQueryable AddFilter(IQueryable query, HttpRequest request) - { - var queryString = request.Query.Select(x => new { x.Key, x.Value}).ToList(); - - var documentType = ""; - - if (queryString.Any(x => x.Key == "cpf")) - documentType = "cpf"; - - if (queryString.Any(x => x.Key == "cnpj")) - documentType = "cnpj"; - - var document = queryString.FirstOrDefault(x => x.Key == documentType)?.Value; - if (string.IsNullOrWhiteSpace(document)) - return query; - - - return query.Where(x => x.CustomerDocument.Any(x => x.DocumentType == documentType && x.Document == document.Value.ToString())); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Mappings/CustomerConfig.cs b/AspNetCore.RestFramework.Sample/Mappings/CustomerConfig.cs deleted file mode 100644 index 753060a..0000000 --- a/AspNetCore.RestFramework.Sample/Mappings/CustomerConfig.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace AspNetRestFramework.Sample.Mappings -{ - public class CustomerConfig : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(b => b.Id); - - //builder.Navigation(e => e.CustomerDocuments).AutoInclude(); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Mappings/CustomerDocumentConfig.cs b/AspNetCore.RestFramework.Sample/Mappings/CustomerDocumentConfig.cs deleted file mode 100644 index b54cff3..0000000 --- a/AspNetCore.RestFramework.Sample/Mappings/CustomerDocumentConfig.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace AspNetRestFramework.Sample.Mappings -{ - public class CustomerDocumentConfig : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasOne(b => b.Customer) - .WithMany(b => b.CustomerDocument) - .HasForeignKey(b => b.CustomerId) - .IsRequired(); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Mappings/IntAsIdEntityConfig.cs b/AspNetCore.RestFramework.Sample/Mappings/IntAsIdEntityConfig.cs deleted file mode 100644 index d5ca99c..0000000 --- a/AspNetCore.RestFramework.Sample/Mappings/IntAsIdEntityConfig.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace AspNetRestFramework.Sample.Mappings -{ - public class IntAsIdEntityConfig : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(b => b.Id); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Mappings/SellerConfig.cs b/AspNetCore.RestFramework.Sample/Mappings/SellerConfig.cs deleted file mode 100644 index e6a0426..0000000 --- a/AspNetCore.RestFramework.Sample/Mappings/SellerConfig.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace AspNetRestFramework.Sample.Mappings -{ - public class SellerConfig : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(b => b.Id); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.Designer.cs b/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.Designer.cs deleted file mode 100644 index 0d0256d..0000000 --- a/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.Designer.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -using System; -using AspNetRestFramework.Sample.Context; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace AspNetRestFramework.Sample.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20220504131959_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Age") - .HasColumnType("int"); - - b.Property("CNPJ") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CustomerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Document") - .HasColumnType("nvarchar(max)"); - - b.Property("DocumentType") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("CustomerDocument"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Seller", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Seller"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.HasOne("AspNetRestFramework.Sample.Models.Customer", "Customer") - .WithMany("CustomerDocuments") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Navigation("CustomerDocuments"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.cs b/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.cs deleted file mode 100644 index 28da324..0000000 --- a/AspNetCore.RestFramework.Sample/Migrations/20220504131959_InitialCreate.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace AspNetRestFramework.Sample.Migrations -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Customer", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true), - CNPJ = table.Column(type: "nvarchar(max)", nullable: true), - Age = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Customer", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Seller", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Seller", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CustomerDocument", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Document = table.Column(type: "nvarchar(max)", nullable: true), - DocumentType = table.Column(type: "nvarchar(max)", nullable: true), - CustomerId = table.Column(type: "uniqueidentifier", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CustomerDocument", x => x.Id); - table.ForeignKey( - name: "FK_CustomerDocument_Customer_CustomerId", - column: x => x.CustomerId, - principalTable: "Customer", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_CustomerDocument_CustomerId", - table: "CustomerDocument", - column: "CustomerId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "CustomerDocument"); - - migrationBuilder.DropTable( - name: "Seller"); - - migrationBuilder.DropTable( - name: "Customer"); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.Designer.cs b/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.Designer.cs deleted file mode 100644 index 1e6dd64..0000000 --- a/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.Designer.cs +++ /dev/null @@ -1,117 +0,0 @@ -// -using System; -using AspNetRestFramework.Sample.Context; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace AspNetRestFramework.Sample.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20220809142812_IntAsIdEntity")] - partial class IntAsIdEntity - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Age") - .HasColumnType("int"); - - b.Property("CNPJ") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CustomerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Document") - .HasColumnType("nvarchar(max)"); - - b.Property("DocumentType") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("CustomerDocument"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.IntAsIdEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("IntAsIdEntities"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Seller", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Seller"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.HasOne("AspNetRestFramework.Sample.Models.Customer", "Customer") - .WithMany("CustomerDocument") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Navigation("CustomerDocument"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.cs b/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.cs deleted file mode 100644 index 0041469..0000000 --- a/AspNetCore.RestFramework.Sample/Migrations/20220809142812_IntAsIdEntity.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace AspNetRestFramework.Sample.Migrations -{ - public partial class IntAsIdEntity : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "IntAsIdEntities", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_IntAsIdEntities", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "IntAsIdEntities"); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Migrations/ApplicationDbContextModelSnapshot.cs b/AspNetCore.RestFramework.Sample/Migrations/ApplicationDbContextModelSnapshot.cs deleted file mode 100644 index a29aaa2..0000000 --- a/AspNetCore.RestFramework.Sample/Migrations/ApplicationDbContextModelSnapshot.cs +++ /dev/null @@ -1,115 +0,0 @@ -// -using System; -using AspNetRestFramework.Sample.Context; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace AspNetRestFramework.Sample.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Age") - .HasColumnType("int"); - - b.Property("CNPJ") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CustomerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Document") - .HasColumnType("nvarchar(max)"); - - b.Property("DocumentType") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("CustomerDocument"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.IntAsIdEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("IntAsIdEntities"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Seller", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Seller"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.CustomerDocument", b => - { - b.HasOne("AspNetRestFramework.Sample.Models.Customer", "Customer") - .WithMany("CustomerDocument") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Customer"); - }); - - modelBuilder.Entity("AspNetRestFramework.Sample.Models.Customer", b => - { - b.Navigation("CustomerDocument"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Models/Customer.cs b/AspNetCore.RestFramework.Sample/Models/Customer.cs deleted file mode 100644 index 6ff45fa..0000000 --- a/AspNetCore.RestFramework.Sample/Models/Customer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using System; -using System.Collections.Generic; - -namespace AspNetRestFramework.Sample.Models -{ - public class Customer : BaseModel - { - public string Name { get; set; } - public string CNPJ { get; set; } - public int Age { get; set; } - - public ICollection CustomerDocument { get; set; } - - public override string[] GetFields() - { - return new[] { "Name", "CNPJ", "Age", "Id", "CustomerDocument", "CustomerDocument:DocumentType", "CustomerDocument:Document" }; - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Models/CustomerDocument.cs b/AspNetCore.RestFramework.Sample/Models/CustomerDocument.cs deleted file mode 100644 index 80bb88e..0000000 --- a/AspNetCore.RestFramework.Sample/Models/CustomerDocument.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using System; - -namespace AspNetRestFramework.Sample.Models -{ - public class CustomerDocument : BaseModel - { - public string Document { get; set; } - public string DocumentType { get; set; } - public Guid CustomerId { get; set; } - public Customer Customer { get; set; } - public override string[] GetFields() - { - return new[] { "Id", "Document", "DocumentType", "CustomerId", "Customer", "Customer:CNPJ", "Customer:Age" }; - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Models/IntAsIdEntity.cs b/AspNetCore.RestFramework.Sample/Models/IntAsIdEntity.cs deleted file mode 100644 index a8b32de..0000000 --- a/AspNetCore.RestFramework.Sample/Models/IntAsIdEntity.cs +++ /dev/null @@ -1,12 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; - -namespace AspNetRestFramework.Sample.Models -{ - public class IntAsIdEntity : BaseModel - { - public string Name { get; set; } - - public override string[] GetFields() - => new[] { nameof(Id), nameof(Name) }; - } -} diff --git a/AspNetCore.RestFramework.Sample/Models/Seller.cs b/AspNetCore.RestFramework.Sample/Models/Seller.cs deleted file mode 100644 index d83c848..0000000 --- a/AspNetCore.RestFramework.Sample/Models/Seller.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using System; - -namespace AspNetRestFramework.Sample.Models -{ - public class Seller : BaseModel - { - public string Name { get; set; } - public override string[] GetFields() => throw new NotImplementedException(); - } -} diff --git a/AspNetCore.RestFramework.Sample/Program.cs b/AspNetCore.RestFramework.Sample/Program.cs deleted file mode 100644 index 1252170..0000000 --- a/AspNetCore.RestFramework.Sample/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.FakeData; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; - -namespace AspNetRestFramework.Sample -{ - public class Program - { - public static void Main(string[] args) - { - var app = CreateHostBuilder(args).Build(); - - InitializeDatabase(app); - - app.Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - - private static void InitializeDatabase(IHost app) - { - using (var scope = app.Services.CreateScope()) - using (var dbContext = scope.ServiceProvider.GetRequiredService()) - { - dbContext.Database.EnsureDeleted(); - dbContext.Database.Migrate(); - - var (sellers, customers, customerDocuments) = FakeDataGenerator.GenerateFakeData(); - - dbContext.Seller.AddRange(sellers); - dbContext.Customer.AddRange(customers); - dbContext.CustomerDocument.AddRange(customerDocuments); - - dbContext.SaveChanges(); - } - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Properties/launchSettings.json b/AspNetCore.RestFramework.Sample/Properties/launchSettings.json deleted file mode 100644 index 90f1d4b..0000000 --- a/AspNetCore.RestFramework.Sample/Properties/launchSettings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:35185", - "sslPort": 44352 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "AspNetCore.RestFramework.Sample": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/AspNetCore.RestFramework.Sample/Startup.cs b/AspNetCore.RestFramework.Sample/Startup.cs deleted file mode 100644 index 27b204f..0000000 --- a/AspNetCore.RestFramework.Sample/Startup.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using AspNetCore.RestFramework.Core.Extensions; -using AspNetCore.RestFramework.Core.Serializer; -using AspNetRestFramework.Sample.Context; -using AspNetRestFramework.Sample.CustomSerializers; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.DTO.Validators; -using AspNetRestFramework.Sample.Models; -using FluentValidation; -using FluentValidation.AspNetCore; -using JSM.FluentValidation.AspNet.AsyncFilter; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; -using Newtonsoft.Json; - -namespace AspNetRestFramework.Sample -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddControllers() - .AddNewtonsoftJson(config => - { - config.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - config.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - }) - .AddModelValidationAsyncActionFilter(options => - { - options.OnlyApiController = true; - }) - .ConfigureValidationResponseFormat(); - - services.AddHttpContextAccessor(); - - services.AddFluentValidationAutoValidation(); - services.AddValidatorsFromAssemblyContaining(); - - var connectionString = Configuration.GetConnectionString("DefaultConnectionString"); - services.AddDbContext(opt => opt.UseSqlServer(connectionString)); - - services.AddScoped(); - services.AddScoped>(); - services.AddScoped>(); - services.AddScoped>(); - - services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "AspNetCore.RestFramework.Sample", Version = "v1" }); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - app.UseDeveloperExceptionPage(); - - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCore.RestFramework.Sample v1")); - - app.UseCors(c => - { - c.AllowAnyHeader(); - c.AllowAnyMethod(); - c.AllowAnyOrigin(); - }); - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} diff --git a/AspNetCore.RestFramework.Sample/appsettings.json b/AspNetCore.RestFramework.Sample/appsettings.json deleted file mode 100644 index a225839..0000000 --- a/AspNetCore.RestFramework.Sample/appsettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnectionString": "Data Source=localhost,1433;Initial Catalog=RestFramework;User Id=sa;Password=Password1" - } -} \ No newline at end of file diff --git a/AspNetCore.RestFramework.Test/AspNetCore.RestFramework.Test.csproj b/AspNetCore.RestFramework.Test/AspNetCore.RestFramework.Test.csproj deleted file mode 100644 index d09cefc..0000000 --- a/AspNetCore.RestFramework.Test/AspNetCore.RestFramework.Test.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net6.0 - - false - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Delete.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Delete.cs deleted file mode 100644 index 98ed2b0..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Delete.cs +++ /dev/null @@ -1,58 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task Delete_WithObject_ShouldDeleteEntityFromDatabaseAndReturnOk() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - dbSet.Add(customer); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.DeleteAsync($"api/Customers/{customer.Id}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var updatedCustomer = dbSet.AsNoTracking().FirstOrDefault(x => x.Id == customer.Id); - updatedCustomer.Should().BeNull(); - } - - [Fact] - public async Task Delete_WhenEntityDoesntExist_ReturnsNotFound() - { - // Act - var response = await Client.DeleteAsync($"api/Customers/{Guid.NewGuid()}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); - } - - [Fact] - public async Task Delete_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() - { - // Act - var response = await Client.DeleteAsync($"api/Sellers/{Guid.NewGuid()}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.DeleteMany.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.DeleteMany.cs deleted file mode 100644 index a5d3ce4..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.DeleteMany.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task DeleteMany_ShouldDeleteManyEntities_AndReturnTheirIds() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { Id = Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { Id = Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - var expectedGuids = new[] { - Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), - Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), - }; - - // Act - var response = await Client.DeleteAsync($"api/Customers?ids=6bdc2b9e-3710-40b9-93dd-c7558b446e21&ids=22ee1df9-c543-4509-a755-e7cd5dc0045e"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var deletedIds = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - deletedIds.Should().BeEquivalentTo(expectedGuids); - - var entitiesWithDeletedIds = dbSet.Where(m => expectedGuids.Contains(m.Id)).AsNoTracking(); - entitiesWithDeletedIds.Should().BeEmpty(); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.GetSingle.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.GetSingle.cs deleted file mode 100644 index 11c6419..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.GetSingle.cs +++ /dev/null @@ -1,77 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Newtonsoft.Json; -using System; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task GetSingle_WithValidParameter_ShouldReturn1Record() - { - // Arrange - var dbSet = Context.Set(); - var customer1 = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - var customer2 = new Customer() { Id = Guid.NewGuid(), CNPJ = "456", Name = "def" }; - var customer3 = new Customer() { Id = Guid.NewGuid(), CNPJ = "789", Name = "ghi" }; - - - dbSet.AddRange(customer1, customer2, customer3); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync($"api/Customers/{customer1.Id}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customer = JsonConvert.DeserializeObject(responseData); - customer.Should().NotBeNull(); - customer.Id.Should().Be(customer1.Id); - } - - [Fact] - public async Task GetSingle_WithInValidParameter_ShouldReturn404() - { - // Arrange - var dbSet = Context.Set(); - var customer1 = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - var customer2 = new Customer() { Id = Guid.NewGuid(), CNPJ = "456", Name = "def" }; - var customer3 = new Customer() { Id = Guid.NewGuid(), CNPJ = "789", Name = "ghi" }; - - dbSet.AddRange(customer1, customer2, customer3); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync($"api/Customers/{Guid.NewGuid()}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); - } - - [Fact] - public async Task GetSingle_WithoutFields_ShouldReturnBadRequest() - { - // Arrange - var dbSet = Context.Set(); - var seller1 = new Seller() { Id = Guid.NewGuid(), Name = "Test" }; - - dbSet.Add(seller1); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync($"api/Sellers/{seller1.Id}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var msg = JsonConvert.DeserializeObject(responseData); - msg.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.ListPaged.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.ListPaged.cs deleted file mode 100644 index f647bd0..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.ListPaged.cs +++ /dev/null @@ -1,493 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Web; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task ListPaged_ShouldReturn200OK() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - - customers.Data.Count.Should().Be(3); - } - - [Fact] - public async Task ListPaged_WithQueryString_ShouldReturnSingleRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?Name=ghi&CNPJ=789"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(1); - customers.Data.First().Name.Should().Be("ghi"); - } - - [Fact] - public async Task ListPaged_WithIdQueryStringFilter_ShouldReturnSingleRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?id=35d948bd-ab3d-4446-912b-2d20c57c4935"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(1); - customers.Data.First().Name.Should().Be("abc"); - } - - [Fact] - public async Task ListPaged_WithIdRangeQueryStringFilter_ShouldReturnTwoRecords() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { Id = Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { Id = Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?ids=6bdc2b9e-3710-40b9-93dd-c7558b446e21&ids=22ee1df9-c543-4509-a755-e7cd5dc0045e"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(2); - customers.Data.ElementAt(0).Name.Should().Be("def"); - customers.Data.ElementAt(1).Name.Should().Be("ghi"); - } - - [Fact] - public async Task ListPaged_WithIdRangeQueryStringFilterAndIdIsNotGuid_ShouldReturnTwoRecords() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new IntAsIdEntity() { Name = "abc" }); - dbSet.Add(new IntAsIdEntity() { Name = "def" }); - dbSet.Add(new IntAsIdEntity() { Name = "ghi" }); - dbSet.Add(new IntAsIdEntity() { Name = "jkl" }); - await Context.SaveChangesAsync(); - - var entities = dbSet.Where(m => new[] { "def", "ghi" }.Contains(m.Name)).AsNoTracking().ToList(); - - // Act - var response = await Client.GetAsync($"api/IntAsIdEntities?ids={entities[0].Id}&ids={entities[1].Id}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(2); - customers.Data.ElementAt(0).Name.Should().Be("def"); - customers.Data.ElementAt(1).Name.Should().Be("ghi"); - } - - [Fact] - public async Task ListPaged_WithIntegerQueryString_ShouldReturnTwoRecords() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?Age=20"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(2); - } - - [Fact] - public async Task ListPaged_WithIntegerQueryStringAndSortDescParameter_ShouldReturnTwoRecordsSortedDesc() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "124", Name = "abc", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?Age=20&SortDesc=Name,CNPJ"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(3); - - var first = customers.Data.First(); - var second = customers.Data.Skip(1).First(); - var third = customers.Data.Skip(2).First(); - - first.Name.Should().Be("def"); - first.CNPJ.Should().Be("456"); - second.Name.Should().Be("abc"); - second.CNPJ.Should().Be("124"); - third.Name.Should().Be("abc"); - third.CNPJ.Should().Be("123"); - } - - [Fact] - public async Task ListPaged_WithIntegerQueryStringAndSortAscParameter_ShouldReturnTwoRecordsSortedAsc() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "124", Name = "abc", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?Age=20&Sort=Name,CNPJ"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(3); - - var first = customers.Data.First(); - var second = customers.Data.Skip(1).First(); - var third = customers.Data.Skip(2).First(); - - first.Name.Should().Be("abc"); - first.CNPJ.Should().Be("123"); - second.Name.Should().Be("abc"); - second.CNPJ.Should().Be("124"); - third.Name.Should().Be("def"); - third.CNPJ.Should().Be("456"); - } - - [Fact] - public async Task ListPaged_WithQueryStringDocumentParameter_ShouldReturnSingleRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() - { - CNPJ = "123", - Name = "abc", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "XYZ" - }, new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cpf", - Document = "1234" - }} - - }); - dbSet.Add(new Customer() - { - CNPJ = "456", - Name = "def", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "LHA" - }} - }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?cpf=1234"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(1); - customers.Data.First().Name.Should().Be("abc"); - } - - [Fact] - public async Task ListPaged_WithQueryStringDocumentParameterAndName_ShouldReturnSingleRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() - { - CNPJ = "123", - Name = "abc", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "XYZ" - }, new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cpf", - Document = "1234" - }} - - }); - dbSet.Add(new Customer() - { - CNPJ = "456", - Name = "def", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "LHA" - }} - }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?cpf=1234&Name=abc"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(1); - customers.Data.First().Name.Should().Be("abc"); - } - - [Fact] - public async Task ListPaged_WithQueryStringDocumentParameterAndName_ShouldReturnNoRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() - { - CNPJ = "123", - Name = "abc", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "XYZ" - }, new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cpf", - Document = "1234" - }} - - }); - dbSet.Add(new Customer() - { - CNPJ = "456", - Name = "def", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "LHA" - }} - }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?cpf=1234&Name=ghi"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Should().BeEmpty(); - } - [Fact] - public async Task ListPaged_WithQueryStringCustomerParameter_ShouldReturnNoRecord() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() - { - CNPJ = "123", - Name = "abc", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "XYZ" - }, new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cpf", - Document = "1234" - }} - - }); - dbSet.Add(new Customer() - { - CNPJ = "456", - Name = "def", - CustomerDocument = new List() { new CustomerDocument - { - Id = Guid.NewGuid(), - DocumentType = "cnpj", - Document = "LHA" - }} - }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?cpf=5557"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Should().BeEmpty(); - } - - [Fact] - public async Task ListPaged_WithPageSize1AndPageSize3_ShouldReturn1RecordAnd3Pages() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?pageSize=3&page=1"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(3); - customers.Total.Should().Be(3); - } - - [Fact] - public async Task ListPaged_WithPageSize1AndPageSize3_ShouldReturn1RecordAnd1Pages() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync("api/Customers?pageSize=1&page=3"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(1); - customers.Total.Should().Be(3); - customers.Data.First().Name.Should().Be("ghi"); - } - - [Theory] - [InlineData("", 5)] - [InlineData(" ", 5)] - [InlineData("1a91f9ec-920b-4c92-83b0-6bf40d0209c2", 1)] - [InlineData("10", 2)] - [InlineData("12", 1)] - [InlineData("%0001%", 5)] - [InlineData("5%", 2)] - [InlineData("%7", 2)] - [InlineData("Agua Alta", 1)] - [InlineData("Agua%", 2)] - [InlineData("% Inc", 2)] - [InlineData("aaa", 0)] - public async Task ListPaged_WithSearchTerm_ReturnsExpectedCount(string term, int expectedCount) - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { Id = Guid.Parse("1a91f9ec-920b-4c92-83b0-6bf40d0209c2"), Age = 10, CNPJ = "76.637.568/0001-80", Name = "Agua Alta" }); - dbSet.Add(new Customer() { Id = Guid.Parse("a71bf8fa-0714-4281-8c51-23e763442919"), Age = 12, CNPJ = "24.451.215/0001-97", Name = "Agua Baixa" }); - dbSet.Add(new Customer() { Id = Guid.Parse("555b437e-3cd8-493c-b502-94cb9ba69a6b"), Age = 10, CNPJ = "81.517.224/0001-77", Name = "Bailão 12 Inc" }); - dbSet.Add(new Customer() { Id = Guid.Parse("f10ca31e-f60b-4d4e-8ca3-a754c4fda6bc"), Age = 25, CNPJ = "59.732.451/0001-66", Name = "Xablau Inc" }); - dbSet.Add(new Customer() { Id = Guid.Parse("c2710e39-f17a-469e-8994-28fd621819b4"), Age = 28, CNPJ = "55.387.453/0001-04", Name = "Problem Solver" }); - await Context.SaveChangesAsync(); - - // Act - var response = await Client.GetAsync($"api/Customers?search={HttpUtility.UrlEncode(term)}"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var responseData = await response.Content.ReadAsStringAsync(); - var customers = JsonConvert.DeserializeObject>>(responseData); - customers.Data.Count.Should().Be(expectedCount); - customers.Total.Should().Be(expectedCount); - } - - [Fact] - public async Task ListPaged_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() - { - // Act - var response = await Client.GetAsync("api/Sellers"); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Patch.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Patch.cs deleted file mode 100644 index c7f2ca2..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Patch.cs +++ /dev/null @@ -1,143 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task Patch_WithFullObject_ShouldUpdateFullObject() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - dbSet.Add(customer); - await Context.SaveChangesAsync(); - - var customerToUpdate = new - { - Id = customer.Id, - CNPJ = "aaaa", - Name = "eee" - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PatchAsync($"api/Customers/{customer.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); - updatedCustomer.Name.Should().Be(customerToUpdate.Name); - updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); - } - - [Fact] - public async Task Patch_WithPartialObject_ShouldUpdatePartialObject() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - dbSet.Add(customer); - await Context.SaveChangesAsync(); - - var customerToUpdate = new - { - Id = customer.Id, - CNPJ = "aaaa", - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PatchAsync($"api/Customers/{customer.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); - updatedCustomer.Name.Should().Be(customer.Name); - updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); - } - - [Fact] - public async Task Patch_WhenEntityDoesntExist_ReturnsNotFound() - { - // Arrange - var customerToUpdate = new - { - CNPJ = "aaaa", - Name = "eee" - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PatchAsync($"api/Customers/{Guid.NewGuid()}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); - } - - [Fact] - public async Task Patch_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() - { - // Arrange - var seller = new SellerDto() - { - Id = Guid.NewGuid(), - Name = "Seller", - }; - - var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PatchAsync($"api/Sellers/{seller.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - } - - [Fact] - public async Task Patch_WhenPatchIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() - { - // Arrange - var dbSet = Context.Set(); - var entity = new IntAsIdEntity() { Name = "abc" }; - dbSet.Add(entity); - await Context.SaveChangesAsync(); - - var entityToUpdate = new IntAsIdEntityDto() - { - Id = entity.Id, - Name = "aaaa", - }; - - var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PatchAsync($"api/IntAsIdEntities/{entity.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.MethodNotAllowed); - - var notUpdatedEntity = dbSet.AsNoTracking().First(x => x.Id == entity.Id); - notUpdatedEntity.Name.Should().Be(entity.Name); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Post.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Post.cs deleted file mode 100644 index 289ff92..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Post.cs +++ /dev/null @@ -1,89 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task Post_WithValidData_ShouldInsertObject() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - - var content = new StringContent(JsonConvert.SerializeObject(customer), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PostAsync("api/Customers", content); - - // Assert - var addedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); - response.StatusCode.Should().Be(System.Net.HttpStatusCode.Created); - addedCustomer.Name.Should().Be(customer.Name); - addedCustomer.CNPJ.Should().Be(customer.CNPJ); - } - - [Fact] - public async Task Post_WithInvalidData_ShouldNotInsertObjectAndReturn400Error() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "567", Name = "ac" }; - - var content = new StringContent(JsonConvert.SerializeObject(customer), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PostAsync("api/Customers", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["Name"].Should().Contain("Name should have at least 3 characters"); - responseMessages.Error["CNPJ"].Should().Contain("CNPJ cannot be 567"); - - var customers = dbSet.AsNoTracking().ToList(); - customers.Should().BeEmpty(); - } - - [Fact] - public async Task Post_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() - { - // Arrange - var dbSet = Context.Set(); - - var seller = new SellerDto() - { - Name = "Seller", - }; - - var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PostAsync("api/Sellers", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - - var sellers = dbSet.AsNoTracking().ToList(); - sellers.Should().BeEmpty(); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Put.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Put.cs deleted file mode 100644 index 9b1e8c7..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.Put.cs +++ /dev/null @@ -1,116 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using AspNetCore.RestFramework.Core.Errors; -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task Put_WithFullObject_ShouldUpdateFullObject() - { - // Arrange - var dbSet = Context.Set(); - var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; - dbSet.Add(customer); - await Context.SaveChangesAsync(); - - var customerToUpdate = new - { - Id = customer.Id, - CNPJ = "aaaa", - Name = "eee" - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/Customers/{customer.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); - updatedCustomer.Name.Should().Be(customerToUpdate.Name); - updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); - } - - [Fact] - public async Task Put_WhenEntityDoesntExist_ReturnsNotFound() - { - // Arrange - var customerToUpdate = new - { - CNPJ = "aaaa", - Name = "eee" - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/Customers/{Guid.NewGuid()}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); - } - - [Fact] - public async Task Put_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() - { - // Arrange - var seller = new SellerDto() - { - Id = Guid.NewGuid(), - Name = "Seller", - }; - - var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/Sellers/{seller.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); - var responseData = await response.Content.ReadAsStringAsync(); - var responseMessages = JsonConvert.DeserializeObject(responseData); - - responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); - } - - [Fact] - public async Task Put_WhenPutIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() - { - // Arrange - var dbSet = Context.Set(); - var entity = new IntAsIdEntity() { Name = "abc" }; - dbSet.Add(entity); - Context.SaveChanges(); - - var entityToUpdate = new IntAsIdEntityDto() - { - Id = entity.Id, - Name = "aaaa", - }; - - var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/IntAsIdEntities/{entity.Id}", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.MethodNotAllowed); - - var notUpdatedEntity = dbSet.AsNoTracking().First(x => x.Id == entity.Id); - notUpdatedEntity.Name.Should().Be(entity.Name); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.PutMany.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.PutMany.cs deleted file mode 100644 index 00fed5f..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.PutMany.cs +++ /dev/null @@ -1,79 +0,0 @@ -using AspNetRestFramework.Sample.DTO; -using AspNetRestFramework.Sample.Models; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests - { - [Fact] - public async Task PutMany_ShouldUpdateManyEntities_AndReturnTheirIds() - { - // Arrange - var dbSet = Context.Set(); - dbSet.Add(new Customer() { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); - dbSet.Add(new Customer() { Id = Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), CNPJ = "456", Name = "def" }); - dbSet.Add(new Customer() { Id = Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), CNPJ = "789", Name = "ghi" }); - await Context.SaveChangesAsync(); - - var customerData = new CustomerDto - { - CNPJ = "aaaa", - Name = "eee" - }; - - var expectedGuids = new[] { - Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), - Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), - }; - - var content = new StringContent(JsonConvert.SerializeObject(customerData), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/Customers?ids=6bdc2b9e-3710-40b9-93dd-c7558b446e21&ids=22ee1df9-c543-4509-a755-e7cd5dc0045e", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - var updatedIds = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - updatedIds.Should().BeEquivalentTo(expectedGuids); - - var updatedEntities = dbSet.Where(m => expectedGuids.Contains(m.Id)).AsNoTracking(); - updatedEntities.Should().BeEquivalentTo( - new[] { - new Customer() { CNPJ = "aaaa", Name = "eee" }, - new Customer() { CNPJ = "aaaa", Name = "eee" }, - }, - config => config - .Including(m => m.CNPJ) - .Including(m => m.Name) - ); - } - - [Fact] - public async Task PutMany_WhenPutIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() - { - // Arrange - var entityToUpdate = new IntAsIdEntityDto() - { - Name = "aaaa", - }; - - var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, "application/json-patch+json"); - - // Act - var response = await Client.PutAsync($"api/IntAsIdEntities?ids=1&ids=2", content); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.MethodNotAllowed); - } - } -} diff --git a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.cs b/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.cs deleted file mode 100644 index ffcb5b8..0000000 --- a/AspNetCore.RestFramework.Test/Core/BaseController/BaseControllerTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AspNetCore.RestFramework.Core.Base; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using AspNetRestFramework.Sample.Models; -using Xunit; -using AspNetCore.RestFramework.Core.Errors; - -namespace AspNetCore.RestFramework.Test.Core.BaseController -{ - public partial class BaseControllerTests : IntegrationTestBase - { - [Fact] - public async Task SwaggerJson_ShouldReturnStatus200() - { - // Arrange - const string urlSwagger = "/swagger/v1/swagger.json"; - - // Act - var response = await Client.GetAsync(urlSwagger); - - // Assert - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); - } - } -} diff --git a/AspNetCore.RestFramework.Test/IntegrationTestBase.cs b/AspNetCore.RestFramework.Test/IntegrationTestBase.cs deleted file mode 100644 index b572aac..0000000 --- a/AspNetCore.RestFramework.Test/IntegrationTestBase.cs +++ /dev/null @@ -1,52 +0,0 @@ -using AspNetRestFramework.Sample; -using AspNetRestFramework.Sample.Context; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using System; -using System.IO; -using System.Net.Http; -using Xunit; - -namespace AspNetCore.RestFramework.Test -{ - [Collection("Database Collection")] - public abstract class IntegrationTestBase - { - protected IntegrationTestBase() - { - var applicationPath = Directory.GetCurrentDirectory(); - - Server = new TestServer(new WebHostBuilder() - .UseEnvironment("Testing") - .UseContentRoot(applicationPath) - .UseConfiguration(new ConfigurationBuilder() - .SetBasePath(applicationPath) - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build() - ) - .UseStartup()); - - Context = (ApplicationDbContext)Server.Services.GetService(typeof(ApplicationDbContext)); - - Client = Server.CreateClient(); - - ClearDatabase(); - } - - protected TestServer Server { get; } - protected ApplicationDbContext Context { get; } - protected HttpClient Client { get; } - - protected void ClearDatabase() - { - Context.Database.ExecuteSqlRaw(@" - EXEC sp_msforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT all'; - EXEC sp_msforeachtable 'DELETE FROM ?'; - EXEC sp_msforeachtable 'ALTER TABLE ? CHECK CONSTRAINT all'; - "); - } - } -} diff --git a/AspNetCoreRestFramework.sln b/AspNetCore.RestFramework.sln similarity index 78% rename from AspNetCoreRestFramework.sln rename to AspNetCore.RestFramework.sln index 4d1ab94..0f8031a 100644 --- a/AspNetCoreRestFramework.sln +++ b/AspNetCore.RestFramework.sln @@ -3,11 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31424.327 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.RestFramework.Sample", "AspNetCore.RestFramework.Sample\AspNetCore.RestFramework.Sample.csproj", "{1BD03AAE-F48D-4153-8DAD-964E4CD81CD4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.RestFramework.Core.Test", "tests\AspNetCore.RestFramework.Core.Test\AspNetCore.RestFramework.Core.Test.csproj", "{5C337F78-3254-477E-9937-42FD595F9733}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.RestFramework.Test", "AspNetCore.RestFramework.Test\AspNetCore.RestFramework.Test.csproj", "{5C337F78-3254-477E-9937-42FD595F9733}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.RestFramework.Core", "AspNetCore.RestFramework.Core\AspNetCore.RestFramework.Core.csproj", "{CA09285F-319F-459A-844D-9AF468C0ABF5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.RestFramework.Core", "src\AspNetCore.RestFramework.Core\AspNetCore.RestFramework.Core.csproj", "{CA09285F-319F-459A-844D-9AF468C0ABF5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..f46ab5b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/Dockerfile b/Dockerfile index bb58bee..b05377d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,22 @@ -FROM juntossomosmais/dotnet-sonar:6.0 +ARG DOTNET_VERSION=8.0 -# Install EF Core tools -RUN dotnet tool install --global dotnet-ef --version 6.0.2 +FROM mcr.microsoft.com/dotnet/sdk:$DOTNET_VERSION WORKDIR /app -COPY . ./ \ No newline at end of file + +# https://github.com/dotnet/dotnet-docker/issues/520 +ENV PATH="${PATH}:/root/.dotnet/tools" + +# Tools used during development +RUN dotnet tool install --global dotnet-ef +RUN dotnet tool install --global dotnet-format + +# Restores (downloads) all NuGet packages from all projects of the solution on a separate layer +COPY src/AspNetCore.RestFramework.Core/*.csproj ./src/AspNetCore.RestFramework.Core/ +COPY tests/AspNetCore.RestFramework.Core.Test/*.csproj ./tests/AspNetCore.RestFramework.Core.Test/ + +RUN dotnet restore --locked-mode src/AspNetCore.RestFramework.Core +RUN dotnet restore --locked-mode tests/AspNetCore.RestFramework.Core.Test + +COPY . ./ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f311848 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Juntos Somos Mais + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2bf6d2b..8d3557c 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,34 @@ -# csharp-rest-framework +# AspNetCore.RestFramework -O [csharp-rest-framework](https://github.com/juntossomosmais/csharp-rest-framework), ou `AspNetCore.RestFramework.Core`, é um framework para ASP.NET desenvolvido pela Juntos Somos Mais para tornar mais prática e uniforme a criação de APIs REST. +AspNetCore REST Framework makes you focus on business, not on boilerplate code. It's designed to follow the famous Django's slogan "The web framework for perfectionists with deadlines." 🤺 -## Sumário +This is a copy of the convention established by [Django REST framework](https://github.com/encode/django-rest-framework), though translated to C# and adapted to the .NET Core framework. -- [csharp-rest-framework](#csharp-rest-framework) - - [Sumário](#sumário) - - [Ordenação](#ordenação) - - [Filtros](#filtros) - - [QueryStringFilter](#querystringfilter) - - [QueryStringIdRangeFilter](#querystringidrangefilter) - - [QueryStringSearchFilter](#querystringsearchfilter) - - [Implementando um filtro](#implementando-um-filtro) - - [Erros](#erros) - - [Validação](#validação) - - [Serializer](#serializer) - - [Exemplo de uso](#exemplo-de-uso) - - [Entidade](#entidade) - - [Entity Framework](#entity-framework) - - [DTO](#dto) - - [Validação](#validação-1) - - [Include de entidades filhas/pais](#include-de-entidades-filhaspais) - - [Controller](#controller) - - [Dicionário](#dicionário) +## Sorting -## Ordenação +In the `ListPaged` method, we use the query parameters `sort` or `sortDesc` to sort by a field. If not specified, we will always use the entity's `Id` field for ascending sorting. -No método `ListPaged`, usamos os _query parameters_ `sort` ou `sortDesc` para fazer a ordenação por um campo. Se não for informado, usaremos sempre o campo `Id` da entidade para ordenação crescente. +## Filters -## Filtros - -Filtros são mecanismos aplicados sempre que tentamos obter os dados de uma entidade nos métodos `GetSingle` e `ListPaged`. +Filters are mechanisms applied whenever we try to retrieve entity data in the `GetSingle` and `ListPaged` methods. ### QueryStringFilter -O `QueryStringFilter`, talvez o mais relevante, é um filtro que faz _match_ dos campos passados nos _query parameters_ com os campos da entidade cujo filtro é permitido. Todos os filtros são criados usando como operador o _equals_ (`==`). +The `QueryStringFilter`, perhaps the most relevant, is a filter that matches the fields passed in the query parameters with the fields of the entity whose filter is allowed. All filters are created using the equals (`==`) operator. ### QueryStringIdRangeFilter -O `QueryStringIdRangeFilter` é um filtro cujo objetivo principal é atender o método `getMany` do React Admin, um framework utilizado em um dos nossos produtos, o Portal Admin (novo). Seu objetivo é, caso o parâmetro `ids` seja passado, filtrar as entidades pelo `Id` baseado em todos os `ids` informados nos _query parameters_. +The `QueryStringIdRangeFilter` goal is to filter the entities by `Id` based on all the `ids` provided in the query parameters. ### QueryStringSearchFilter -O `QueryStringSearchFilter` é um filtro que permite que seja informado um parâmetro `search` nos _query parameters_ para fazer uma busca, através de um único input, em vários campos da entidade, inclusive fazendo `LIKE` em strings. +The `QueryStringSearchFilter` is a filter that allows a `search` parameter to be provided in the query parameters to search, through a single input, in several fields of the entity, even performing `LIKE` on strings. -### Implementando um filtro +### Implementing a filter -Dado um `IQueryable` e um `HttpRequest`, você pode implementar o filtro da maneira como preferir. Basta herdar da classe base e adicionar ao seu _controller_: +Given an `IQueryable` and an `HttpRequest`, you can implement the filter as you prefer. Just inherit from the base class and add it to your controller: -```cs +```csharp public class MyFilter : AspNetCore.RestFramework.Core.Filters.Filter { private readonly string _forbiddenName; @@ -65,65 +45,57 @@ public class MyFilter : AspNetCore.RestFramework.Core.Filters.Filter } ``` -```cs +```csharp public class SellerController { public SellerController(...) : base(...) { - Filters.Add(new MyFilter("Exemplo")); + Filters.Add(new MyFilter("Example")); } } ``` -## Erros - -Utilizamos como padrão de erros [este aqui](https://votorantimindustrial.sharepoint.com/:w:/r/sites/juntos_somos_mais/_layouts/15/Doc.aspx?sourcedoc=%7BE8895835-9397-4E19-9046-26D7168A931A%7D&file=Padr%C3%A3o%20de%20retorno%20das%20APIs.docx&action=default&mobileredirect=true), que é o padrão indicado no nosso [playbook de C#](https://github.com/juntossomosmais/playbook/blob/master/backend/csharp.md). +## Errors -No framework, as classes `ValidationErrors` e `UnexpectedError` já implementam esse contrato, sendo retornadas no `BaseController` em caso de erros de validação ou outra exception. +The `ValidationErrors` and `UnexpectedError` might be returned in the `BaseController` in case of validation errors or other exceptions. -## Validação +## Validation -Para validação, também conforme o playbook, devemos utilizar **FluentValidation**. Basta implementar os _validators_ dos DTOs e configurar sua aplicação com a extensão (2) `ModelStateValidationExtensions.ConfigureValidationResponseFormat` para garantir que, caso o `ModelState` seja inválido, um `ValidationErrors` seja retornado com um `400 Not Found`. +Implement validators for the DTOs and configure your application with the extension `ModelStateValidationExtensions.ConfigureValidationResponseFormat` to ensure that in case of the `ModelState` being invalid, a `ValidationErrors` is returned. It might be necessary to add the `HttpContext` accessor to the services. Check the example below: -Além disso -- (1) Devemos usar o `JSM.FluentValidations.AspNet.AsyncFilter`; -- (3) **Talvez** se faça necessário acessar o `HttpContext` nos _validators_, para isso basta adicionar o acessor aos serviços. - -```cs +```csharp services.AddControllers() - ... - // no final do AddControllers, adicione o seguinte: - // 1 - JSM.FluentValidations.AspNet.AsyncFilter + // ... + // At the end of AddControllers, add the following: .AddModelValidationAsyncActionFilter(options => { options.OnlyApiController = true; }) - // 2 - ModelStateValidationExtensions + // ModelStateValidationExtensions .ConfigureValidationResponseFormat(); - -// 3 - acessor do HttpContext +// ... services.AddHttpContextAccessor(); ``` ## Serializer -O `Serializer` é um mecanismo utilizado pelo `BaseController` como se fosse um _service_. Nele, estão as lógicas utilizadas pelo _controller_, e este _serializer_ é injetado em cada um. Ele pode ter seus métodos sobrescritos, por exemplo, para lógicas adicionais ou diferentes em entidades específicas. +`Serializer` is a mechanism used by the `BaseController`. Each controller has its own serializer. The serializer's methods can be overridden to add additional or different logic for specific entities. It works more or less similar to the [Django REST framework's serializers](https://www.django-rest-framework.org/api-guide/serializers/). -## Exemplo de uso +## Quickstart with an example -Vamos, como exemplo, criar uma API para uma suposta entidade `Customer`. +Let's create a CRUD API for a `Customer` entity with a `CustomerDocument` child entity. -### Entidade +### Entity -Algumas características das entidades: +Some characteristics of the entities: -- Devemos herdar de `BaseModel`. - - O `TPrimaryKey` é o tipo da chave primária, no marketplace costumamos usar `Guid`. -- O `GetFields` é um método cuja implementação é obrigatória. Ele informa quais campos de uma entidade serão serializados na _response_ da API. - - Para campos de entidades filhas ou pais, podemos usar o `:` para indicá-las para que também sejam serializadas. Nesse caso, é necessário fazer o `Include` com um filtro. +- We should inherit from `BaseModel`. + - The `TPrimaryKey` is the type of the primary key. In this case, we are using `Guid`. +- The `GetFields` method is mandatory. It informs which fields of the entity will be serialized in the API response. + - For fields of child or parent entities, we can use `:` to indicate them to be serialized as well. In this case, it is necessary to perform the `Include` with a filter. -```cs +```csharp public class Customer : BaseModel { public string Name { get; set; } @@ -141,9 +113,9 @@ public class Customer : BaseModel ### Entity Framework -Basta adicionar a collection ao `DbContext` da aplicação: +Add the collection to the application's `DbContext`: -```cs +```csharp public class ApplicationDbContext : DbContext { public DbSet Customer { get; set; } @@ -152,27 +124,25 @@ public class ApplicationDbContext : DbContext ### DTO -No DTO, precisamos apenas herdar de `BaseDto`, parecido com a entidade, e colocar lá. +The DTO is required to inherit from `BaseDto`, like the entity. -```cs +```csharp public class CustomerDto : BaseDto { - public CustomerDto() - { - } + public CustomerDto() { } public string Name { get; set; } public string CNPJ { get; set; } - + public ICollection CustomerDocuments { get; set; } } ``` -### Validação +### Validation -Com o DTO criado, se necessário, devemos criar o _validator_ para ele. Abaixo, temos um exemplo bem simples dessa implementação: +A validation is not mandatory, but it is recommended to ensure that the data is correct. The validation is done using the `FluentValidation` library. -```cs +```csharp public class CustomerDtoValidator : AbstractValidator { public CustomerDtoValidator(IHttpContextAccessor context) @@ -189,12 +159,11 @@ public class CustomerDtoValidator : AbstractValidator } ``` +### Include child/parent entities -### Include de entidades filhas/pais +Previously, we included the `CustomerDocument` entity in the `Customer` entity. Check out the `GetFields` method in the `Customer` entity. -Precisamos criar um filtro para dar o `Include` na entidade filha `CustomerDocument`. Simples assim: - -```cs +```csharp public class CustomerDocumentIncludeFilter : Filter { public override IQueryable AddFilter(IQueryable query, HttpRequest request) @@ -206,11 +175,9 @@ public class CustomerDocumentIncludeFilter : Filter ### Controller -Agora, basta criar o _controller_ da entidade para criar a API. - -Perceba que o `AllowedFields` é usado para configurar a maioria dos filtros de _query string_. +The CRUD API is created by inheriting from the `BaseController` and passing the necessary parameters. Note how `AllowedFields` and `Filters` are set. -```cs +```csharp [Route("api/[controller]")] [ApiController] public class CustomersController : BaseController @@ -239,16 +206,20 @@ public class CustomersController : BaseController&2; fi } - -usage() -{ - cat << USAGE >&2 -Usage: - $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] - -h HOST | --host=HOST Host or IP under test - -p PORT | --port=PORT TCP port under test - Alternatively, you specify the host and port as host:port - -s | --strict Only execute subcommand if the test succeeds - -q | --quiet Don't output any status messages - -t TIMEOUT | --timeout=TIMEOUT - Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes -USAGE - exit 1 -} - -wait_for() -{ - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - else - echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" - fi - WAITFORIT_start_ts=$(date +%s) - while : - do - if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then - nc -z $WAITFORIT_HOST $WAITFORIT_PORT - WAITFORIT_result=$? - else - (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 - WAITFORIT_result=$? - fi - if [[ $WAITFORIT_result -eq 0 ]]; then - WAITFORIT_end_ts=$(date +%s) - echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" - break - fi - sleep 1 - done - return $WAITFORIT_result -} - -wait_for_wrapper() -{ - # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 - if [[ $WAITFORIT_QUIET -eq 1 ]]; then - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - else - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - fi - WAITFORIT_PID=$! - trap "kill -INT -$WAITFORIT_PID" INT - wait $WAITFORIT_PID - WAITFORIT_RESULT=$? - if [[ $WAITFORIT_RESULT -ne 0 ]]; then - echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - fi - return $WAITFORIT_RESULT -} - -# process arguments -while [[ $# -gt 0 ]] -do - case "$1" in - *:* ) - WAITFORIT_hostport=(${1//:/ }) - WAITFORIT_HOST=${WAITFORIT_hostport[0]} - WAITFORIT_PORT=${WAITFORIT_hostport[1]} - shift 1 - ;; - --child) - WAITFORIT_CHILD=1 - shift 1 - ;; - -q | --quiet) - WAITFORIT_QUIET=1 - shift 1 - ;; - -s | --strict) - WAITFORIT_STRICT=1 - shift 1 - ;; - -h) - WAITFORIT_HOST="$2" - if [[ $WAITFORIT_HOST == "" ]]; then break; fi - shift 2 - ;; - --host=*) - WAITFORIT_HOST="${1#*=}" - shift 1 - ;; - -p) - WAITFORIT_PORT="$2" - if [[ $WAITFORIT_PORT == "" ]]; then break; fi - shift 2 - ;; - --port=*) - WAITFORIT_PORT="${1#*=}" - shift 1 - ;; - -t) - WAITFORIT_TIMEOUT="$2" - if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi - shift 2 - ;; - --timeout=*) - WAITFORIT_TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - WAITFORIT_CLI=("$@") - break - ;; - --help) - usage - ;; - *) - echoerr "Unknown argument: $1" - usage - ;; - esac -done - -if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then - echoerr "Error: you need to provide a host and port to test." - usage -fi - -WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} -WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} -WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} -WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} - -# check to see if timeout is from busybox? -WAITFORIT_TIMEOUT_PATH=$(type -p timeout) -WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) -if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then - WAITFORIT_ISBUSY=1 - WAITFORIT_BUSYTIMEFLAG="-t" - -else - WAITFORIT_ISBUSY=0 - WAITFORIT_BUSYTIMEFLAG="" -fi - -if [[ $WAITFORIT_CHILD -gt 0 ]]; then - wait_for - WAITFORIT_RESULT=$? - exit $WAITFORIT_RESULT -else - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - wait_for_wrapper - WAITFORIT_RESULT=$? - else - wait_for - WAITFORIT_RESULT=$? - fi -fi - -if [[ $WAITFORIT_CLI != "" ]]; then - if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then - echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" - exit $WAITFORIT_RESULT - fi - exec "${WAITFORIT_CLI[@]}" -else - exit -fi diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 5f0ca6c..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,25 +0,0 @@ -trigger: - - main - -pr: - - main - -variables: - - group: Main variables for all pipelines - - group: sonar-cloud-config - -resources: - repositories: - - repository: templates - type: github - name: juntossomosmais/azure-pipelines-templates - endpoint: github.com - ref: refactor/change-folder-structure - -extends: - template: dotnet/nuget_deploy_artifacts.yaml@templates - parameters: - SonarProject: "juntossomosmais_csharp-rest-framework" - AzureFeedId: "7a82b4a3-6db8-4249-a7ec-9968996b9406/0d3df630-9f80-4d07-b31c-822063ea1dd0" - AzurePackageId: "46a8a833-03d0-4d89-97f4-4ef2fc850e13" - DotnetVersion: "6.0.x" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 28e142a..5ecb602 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,42 @@ version: "3.6" services: - - sqlserver: - image: mcr.microsoft.com/mssql/server:latest + db: + image: "mcr.microsoft.com/mssql/server:2022-latest" + ports: + - "1433:1433" environment: - SA_PASSWORD: Password1 + SA_PASSWORD: "Password1" ACCEPT_EULA: "Y" - ports: - - 1433:1433 + # Developer / Express / Standard / Enterprise / EnterpriseCore + MSSQL_PID: Developer + MSSQL_TCP_PORT: 1433 + healthcheck: + test: /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P "$$SA_PASSWORD" -Q "SELECT 1" -b -o /dev/null + interval: 10s + timeout: 3s + retries: 10 + start_period: 10s integration-tests: - container_name: csharp-rest-framework-integration-tests build: context: . dockerfile: Dockerfile depends_on: - - sqlserver + db: + condition: service_healthy + volumes: + - .:/app environment: - - "ConnectionStrings__DefaultConnectionString=Data Source=sqlserver,1433;Initial Catalog=RestFramework;User Id=sa;Password=Password1" - command: - [ - "./Scripts/start-tests.sh" - ] \ No newline at end of file + - "ConnectionStrings__AppDbContext=Data Source=db,1433;Initial Catalog=REPLACE_ME_PROGRAMATICALLY;User Id=sa;Password=Password1;TrustServerCertificate=True" + command: [ "./scripts/start-tests.sh" ] + + lint-formatter: + build: + context: . + dockerfile: Dockerfile + volumes: + - .:/app + # Uncomment the following line to run the checker + # command: [ "./scripts/start-check-formatting.sh" ] + # Uncomment the following line to run the formatter + command: [ "./scripts/start-formatter.sh" ] diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 9311ef2..0000000 --- a/nuget.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - diff --git a/scripts/start-check-formatting.sh b/scripts/start-check-formatting.sh new file mode 100755 index 0000000..a40019b --- /dev/null +++ b/scripts/start-check-formatting.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +dotnet format --verify-no-changes --verbosity d diff --git a/scripts/start-formatter.sh b/scripts/start-formatter.sh new file mode 100755 index 0000000..94acc03 --- /dev/null +++ b/scripts/start-formatter.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +dotnet format --verbosity d diff --git a/scripts/start-sonarcloud.sh b/scripts/start-sonarcloud.sh new file mode 100755 index 0000000..2f4f713 --- /dev/null +++ b/scripts/start-sonarcloud.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +# You should start the scanner prior building your project and running your tests +if [ -n "${PR_SOURCE_BRANCH:-}" ]; then + dotnet sonarscanner begin \ + /d:sonar.login="$SONAR_TOKEN" \ + /k:"juntossomosmais_AspNetCore.RestFramework" \ + /o:"juntossomosmais" \ + /d:sonar.host.url="https://sonarcloud.io" \ + /d:sonar.cs.opencover.reportsPaths="**/*/coverage.opencover.xml" \ + /d:sonar.cs.vstest.reportsPaths="**/*/*.trx" \ + /d:sonar.pullrequest.base="$PR_TARGET_BRANCH" \ + /d:sonar.pullrequest.branch="$PR_SOURCE_BRANCH" \ + /d:sonar.pullrequest.key="$GITHUB_PR_NUMBER" +else + dotnet sonarscanner begin \ + /d:sonar.login="$SONAR_TOKEN" \ + /v:"$PROJECT_VERSION" \ + /k:"juntossomosmais_AspNetCore.RestFramework" \ + /o:"juntossomosmais" \ + /d:sonar.host.url="https://sonarcloud.io" \ + /d:sonar.cs.opencover.reportsPaths="**/*/coverage.opencover.xml" \ + /d:sonar.cs.vstest.reportsPaths="**/*/*.trx" \ + /d:sonar.branch.name="$SOURCE_BRANCH_NAME" +fi + +# Now we can run our tests as usual +./scripts/start-tests.sh + +# Now we can collect the results 👍 +dotnet sonarscanner end /d:sonar.login="$SONAR_TOKEN" diff --git a/scripts/start-tests.sh b/scripts/start-tests.sh new file mode 100755 index 0000000..f43c81d --- /dev/null +++ b/scripts/start-tests.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +dotnet test tests/AspNetCore.RestFramework.Core.Test \ +--configuration Release \ +--logger trx \ +--logger "console;verbosity=normal" \ +--settings "./runsettings.xml" diff --git a/sonar-project.xml b/sonar-project.xml index ba0c9cc..58bd90f 100644 --- a/sonar-project.xml +++ b/sonar-project.xml @@ -1,10 +1,9 @@ - + + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="http://www.sonarsource.com/msbuild/integration/2015/1"> http://localhost:9000 **/*/coverage.opencover.xml **/*/*.trx - **/Migrations/*.cs,**/Swagger/*.cs - AspNetCore.RestFramework.Sample/**/*.cs - \ No newline at end of file + **/Migrations/*.cs + diff --git a/src/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj b/src/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj new file mode 100644 index 0000000..8df3dfb --- /dev/null +++ b/src/AspNetCore.RestFramework.Core/AspNetCore.RestFramework.Core.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + + AspNetCore.RestFramework + andresantarosa,fabio_almeida100,phmonte,ricardochaves,willianantunes + Don't code a bunch of code to create CRUD applications. Use AspNetCore REST Framework, inspired by Django REST framework! It aims to provide a robust and flexible foundation for building RESTful APIs using ASP.NET Core. + https://github.com/juntossomosmais/AspNetCore.RestFramework/blob/master/LICENSE + https://github.com/juntossomosmais/AspNetCore.RestFramework + api rest drf django pagination crud + true + snupkg + + + + + + + + + diff --git a/AspNetCore.RestFramework.Core/Base/ActionOptions.cs b/src/AspNetCore.RestFramework.Core/Base/ActionOptions.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/ActionOptions.cs rename to src/AspNetCore.RestFramework.Core/Base/ActionOptions.cs diff --git a/AspNetCore.RestFramework.Core/Base/BaseController.cs b/src/AspNetCore.RestFramework.Core/Base/BaseController.cs similarity index 99% rename from AspNetCore.RestFramework.Core/Base/BaseController.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseController.cs index ae1c884..5f71a7f 100644 --- a/AspNetCore.RestFramework.Core/Base/BaseController.cs +++ b/src/AspNetCore.RestFramework.Core/Base/BaseController.cs @@ -1,5 +1,4 @@ -using JSM.PartialJsonObject; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System; @@ -12,6 +11,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using AspNetCore.RestFramework.Core.Errors; +using AspNetCore.RestFramework.Core.Helpers; namespace AspNetCore.RestFramework.Core.Base { @@ -161,7 +161,7 @@ public virtual async Task Patch( return BadRequest(new UnexpectedError(BaseMessages.ERROR_GET_FIELDS)); var data = await _serializer.PatchAsync(entity, id); - + if (data == null) return NotFound(); @@ -320,4 +320,4 @@ public virtual IQueryable GetQuerySet() #endregion } -} \ No newline at end of file +} diff --git a/AspNetCore.RestFramework.Core/Base/BaseDto.cs b/src/AspNetCore.RestFramework.Core/Base/BaseDto.cs similarity index 76% rename from AspNetCore.RestFramework.Core/Base/BaseDto.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseDto.cs index 48af794..1e55249 100644 --- a/AspNetCore.RestFramework.Core/Base/BaseDto.cs +++ b/src/AspNetCore.RestFramework.Core/Base/BaseDto.cs @@ -1,6 +1,6 @@ namespace AspNetCore.RestFramework.Core.Base { - public abstract class BaseDto + public abstract class BaseDto { [System.Text.Json.Serialization.JsonIgnore] public TPrimaryKey Id { get; set; } diff --git a/AspNetCore.RestFramework.Core/Base/BaseErrorResponse.cs b/src/AspNetCore.RestFramework.Core/Base/BaseErrorResponse.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/BaseErrorResponse.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseErrorResponse.cs diff --git a/AspNetCore.RestFramework.Core/Base/BaseFilter.cs b/src/AspNetCore.RestFramework.Core/Base/BaseFilter.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/BaseFilter.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseFilter.cs diff --git a/AspNetCore.RestFramework.Core/Base/BaseMessages.cs b/src/AspNetCore.RestFramework.Core/Base/BaseMessages.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/BaseMessages.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseMessages.cs diff --git a/AspNetCore.RestFramework.Core/Base/BaseModel.cs b/src/AspNetCore.RestFramework.Core/Base/BaseModel.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/BaseModel.cs rename to src/AspNetCore.RestFramework.Core/Base/BaseModel.cs diff --git a/AspNetCore.RestFramework.Core/Base/IBaseModel.cs b/src/AspNetCore.RestFramework.Core/Base/IBaseModel.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/IBaseModel.cs rename to src/AspNetCore.RestFramework.Core/Base/IBaseModel.cs diff --git a/AspNetCore.RestFramework.Core/Base/PagedBaseResponse.cs b/src/AspNetCore.RestFramework.Core/Base/PagedBaseResponse.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Base/PagedBaseResponse.cs rename to src/AspNetCore.RestFramework.Core/Base/PagedBaseResponse.cs diff --git a/AspNetCore.RestFramework.Core/Errors/UnexpectedError.cs b/src/AspNetCore.RestFramework.Core/Errors/UnexpectedError.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Errors/UnexpectedError.cs rename to src/AspNetCore.RestFramework.Core/Errors/UnexpectedError.cs diff --git a/AspNetCore.RestFramework.Core/Errors/ValidationErrors.cs b/src/AspNetCore.RestFramework.Core/Errors/ValidationErrors.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Errors/ValidationErrors.cs rename to src/AspNetCore.RestFramework.Core/Errors/ValidationErrors.cs diff --git a/AspNetCore.RestFramework.Core/Extensions/ModelStateValidationExtensions.cs b/src/AspNetCore.RestFramework.Core/Extensions/ModelStateValidationExtensions.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Extensions/ModelStateValidationExtensions.cs rename to src/AspNetCore.RestFramework.Core/Extensions/ModelStateValidationExtensions.cs diff --git a/AspNetCore.RestFramework.Core/Filters/Filter.cs b/src/AspNetCore.RestFramework.Core/Filters/Filter.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Filters/Filter.cs rename to src/AspNetCore.RestFramework.Core/Filters/Filter.cs diff --git a/AspNetCore.RestFramework.Core/Filters/FilterBuilder.cs b/src/AspNetCore.RestFramework.Core/Filters/FilterBuilder.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Filters/FilterBuilder.cs rename to src/AspNetCore.RestFramework.Core/Filters/FilterBuilder.cs diff --git a/AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs b/src/AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs similarity index 99% rename from AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs rename to src/AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs index 7f2f151..fbbe55f 100644 --- a/AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs +++ b/src/AspNetCore.RestFramework.Core/Filters/QueryStringFilter.cs @@ -47,7 +47,7 @@ private static Expression> GetColumnEquality(string property * */ var obj = Expression.Parameter(typeof(TEntity), "obj"); - + var objProperty = Expression.PropertyOrField(obj, property); object convertedValue; diff --git a/AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs b/src/AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs similarity index 97% rename from AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs rename to src/AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs index 4d3228f..6e7e22d 100644 --- a/AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs +++ b/src/AspNetCore.RestFramework.Core/Filters/QueryStringIdRangeFilter.cs @@ -13,12 +13,12 @@ public override IQueryable AddFilter(IQueryable query, HttpReq var idsFilter = request.Query.FirstOrDefault(m => m.Key.Equals("ids", StringComparison.OrdinalIgnoreCase)); var ids = idsFilter.Value.Select(ConvertToPrimaryKeyType).ToList(); - if (ids.Count> 0) + if (ids.Count > 0) query = query.Where(m => ids.Contains(m.Id)); return query; } - + private static TPrimaryKey ConvertToPrimaryKeyType(string value) { try diff --git a/AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs b/src/AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs similarity index 99% rename from AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs rename to src/AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs index e3f726f..ac42860 100644 --- a/AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs +++ b/src/AspNetCore.RestFramework.Core/Filters/QueryStringSearchFilter.cs @@ -26,7 +26,7 @@ public override IQueryable AddFilter(IQueryable query, HttpReq var parameter = Expression.Parameter(typeof(TEntity), "obj"); var expressions = new List(); - + foreach (var field in _allowedFields) { if (TryGetColumnFilter(field, searchFilter.Value, parameter, out Expression expression)) diff --git a/AspNetCore.RestFramework.Core/Filters/SortFilter.cs b/src/AspNetCore.RestFramework.Core/Filters/SortFilter.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Filters/SortFilter.cs rename to src/AspNetCore.RestFramework.Core/Filters/SortFilter.cs diff --git a/src/AspNetCore.RestFramework.Core/Helpers/PartialJsonObject.cs b/src/AspNetCore.RestFramework.Core/Helpers/PartialJsonObject.cs new file mode 100644 index 0000000..c3ece53 --- /dev/null +++ b/src/AspNetCore.RestFramework.Core/Helpers/PartialJsonObject.cs @@ -0,0 +1,278 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace AspNetCore.RestFramework.Core.Helpers +{ + [JsonConverter(typeof(PartialJsonObjectConverter))] + public class PartialJsonObject : PartialJsonObject where T : class + { + [JsonIgnore] + private T _instance; + [JsonIgnore] + public T Instance => _instance ?? (_instance = ToObject()); + + [JsonIgnore] + private Dictionary _cache = new Dictionary(); + + public PartialJsonObject(JObject jsonObject) + { + JsonObject = jsonObject; + } + + public PartialJsonObject(string json) + { + JsonObject = JObject.Parse(json); + } + + public bool IsSet(Expression> expPath) + { + var paths = ExpressionToPathParser.ParseExpressionToPaths(expPath.Body).ToArray(); + + return IsSet(paths); + } + + public override bool IsSet(string path) + { + return IsSet(path.Split('.')); + } + + public override bool IsSet(params string[] paths) + { + var path = string.Join(".", paths).ToLower(); + + if (_cache.TryGetValue(path, out var isSet)) + { + return isSet; + } + + isSet = TryGetFullPath(JsonObject, paths, 0, out var _); + + _cache[path] = isSet; + + if (isSet && paths.Length > 1) + { + for (var i = 1; i < paths.Length; i++) + { + path = string.Join(".", paths.Take(i)).ToLower(); + + _cache[path] = true; + } + } + + return isSet; + } + + /// + /// Get the value if set, if not get the default value + /// + /// Type of the return value + /// Value path + /// Default value. This is returned in case if the searched field was not found + /// If the field has a populated value, it returns the field and if not, returns the default value + public R GetIfSet(Expression> path, R defaultValue = default) + { + return IsSet(path) ? path.Compile()(Instance) : defaultValue; + } + + public PartialJsonObject Add(Expression> expPath) + { + var path = GetMemberPath(expPath).ToLower(); + + _cache.Remove(path); + _cache.Add(path, true); + + return this; + } + + public PartialJsonObject Remove(Expression> expPath) + { + var path = GetMemberPath(expPath).ToLower(); + + _cache.Remove(path); + _cache.Add(path, false); + + return this; + } + + public string GetJSONPath(Expression> expPath) + { + var paths = ExpressionToPathParser.ParseExpressionToPaths(expPath.Body).ToArray(); + + return GetJSONPath(paths); + } + + public string GetJSONPath(params string[] paths) + { + if (TryGetFullPath(JsonObject, paths, 0, out var fullPath)) + { + return fullPath; + } + + return string.Join(".", paths); + } + + public void ClearCache() => _cache.Clear(); + + public void CopyTo(T obj) + { + JsonConvert.PopulateObject(JsonObject.ToString(), obj); + } + + public T ToObject() + { + return JsonObject.ToObject(); + } + + public static implicit operator T(PartialJsonObject partialJsonObject) + => partialJsonObject.Instance; + + public static string GetMemberPath(Expression> expPath) + { + return string.Join(".", ExpressionToPathParser.ParseExpressionToPaths(expPath.Body)); + } + + private static bool TryGetFullPath(JToken token, string[] paths, int pathIndex, out string fullPath) + { + var path = paths[pathIndex]; + if (token is JArray array) + { + int arrayIndex; + + if (path == "$last") + arrayIndex = array.Count - 1; + else if (!int.TryParse(path, out arrayIndex)) + arrayIndex = -1; + + if (arrayIndex >= 0 && + arrayIndex < array.Count) + { + pathIndex++; + if (pathIndex < paths.Length) + { + return TryGetFullPath(array[arrayIndex], paths, pathIndex, out fullPath); + } + + fullPath = $"{token.Path}[{arrayIndex}]"; + return true; + } + } + else if (token is JObject obj) + { + var val = obj.GetValue(path, StringComparison.OrdinalIgnoreCase); + + if (val != null) + { + pathIndex++; + if (pathIndex < paths.Length) + { + return TryGetFullPath(val, paths, pathIndex, out fullPath); + } + + fullPath = val.Path; + return true; + } + } + + fullPath = null; + return false; + } + } + + public abstract class PartialJsonObject + { + [JsonIgnore] + public JObject JsonObject { get; internal set; } + + public abstract bool IsSet(string path); + + public abstract bool IsSet(params string[] paths); + } + + internal class ExpressionToPathParser + { + internal static IEnumerable ParseExpressionToPaths(Expression expressionBody) + { + if (expressionBody is MethodCallExpression methodExpr) + { + foreach (var p in ParseMethodCallExpressionToPaths(methodExpr)) + yield return p; + } + else if (expressionBody is BinaryExpression binaryExpr && + binaryExpr.NodeType == ExpressionType.ArrayIndex) + { + foreach (var p in ParseExpressionToPaths(binaryExpr.Left)) + yield return p; + + yield return GetExpressionCompiledValue(binaryExpr.Right).ToString(); + } + else if (expressionBody is MemberExpression propExpr) + { + foreach (var p in ParseExpressionToPaths(propExpr.Expression)) + yield return p; + + yield return propExpr.Member.Name; + } + } + + internal static IEnumerable ParseMethodCallExpressionToPaths(MethodCallExpression expression) + { + if (expression.Method.DeclaringType == typeof(Enumerable) && + (expression.Method.Name == nameof(Enumerable.ElementAt) || + expression.Method.Name == nameof(Enumerable.ElementAtOrDefault) || + (expression.Method.Name == nameof(Enumerable.First) && expression.Arguments.Count == 1) || + (expression.Method.Name == nameof(Enumerable.FirstOrDefault) && expression.Arguments.Count == 1) || + (expression.Method.Name == nameof(Enumerable.Last) && expression.Arguments.Count == 1) || + (expression.Method.Name == nameof(Enumerable.LastOrDefault) && expression.Arguments.Count == 1))) + { + foreach (var p in ParseExpressionToPaths(expression.Arguments[0])) + yield return p; + + if (expression.Method.Name == nameof(Enumerable.First) || + expression.Method.Name == nameof(Enumerable.FirstOrDefault)) + yield return "0"; + else if (expression.Method.Name == nameof(Enumerable.Last) || + expression.Method.Name == nameof(Enumerable.LastOrDefault)) + yield return "$last"; + else + yield return GetExpressionCompiledValue(expression.Arguments[1]).ToString(); + } + else if (expression.Method.DeclaringType.IsGenericType && + expression.Method.DeclaringType == typeof(List<>).MakeGenericType(expression.Method.DeclaringType.GenericTypeArguments[0]) && + expression.Method.Name == "get_Item") + { + foreach (var p in ParseExpressionToPaths(expression.Object)) + yield return p; + + yield return GetExpressionCompiledValue(expression.Arguments[0]).ToString(); + } + } + + private static object GetExpressionCompiledValue(Expression expression) + { + if (expression is ConstantExpression constExpr) + return constExpr.Value; + else + return Expression.Lambda(expression).Compile().DynamicInvoke(); + } + } + + internal class PartialJsonObjectConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => typeof(PartialJsonObject).IsAssignableFrom(objectType); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + + return Activator.CreateInstance(objectType, obj); + } + + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); + } +} diff --git a/AspNetCore.RestFramework.Core/Serializer/JsonTransform.cs b/src/AspNetCore.RestFramework.Core/Serializer/JsonTransform.cs similarity index 100% rename from AspNetCore.RestFramework.Core/Serializer/JsonTransform.cs rename to src/AspNetCore.RestFramework.Core/Serializer/JsonTransform.cs diff --git a/AspNetCore.RestFramework.Core/Serializer/Serializer.cs b/src/AspNetCore.RestFramework.Core/Serializer/Serializer.cs similarity index 97% rename from AspNetCore.RestFramework.Core/Serializer/Serializer.cs rename to src/AspNetCore.RestFramework.Core/Serializer/Serializer.cs index c33c26c..83c2838 100644 --- a/AspNetCore.RestFramework.Core/Serializer/Serializer.cs +++ b/src/AspNetCore.RestFramework.Core/Serializer/Serializer.cs @@ -1,11 +1,11 @@ -using JSM.PartialJsonObject; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AspNetCore.RestFramework.Core.Base; +using AspNetCore.RestFramework.Core.Helpers; namespace AspNetCore.RestFramework.Core.Serializer { @@ -35,7 +35,7 @@ public Serializer(TContext applicationDbContext) query = query.Skip(skip * pageSize).Take(pageSize); var data = await query.ToListAsync(); - + return (total, data); } @@ -84,10 +84,10 @@ public virtual async Task PutAsync(TOrigin origin, TPrimaryKey ent return null; var stringDeserialized = JsonConvert.SerializeObject(origin); - + dynamic stringDeserializedDynamic = JsonConvert.DeserializeObject(stringDeserialized); stringDeserializedDynamic.Id = entityId; - + JsonConvert.PopulateObject(stringDeserializedDynamic.ToString(), destinationObject); _dbContext.Update(destinationObject); await _dbContext.SaveChangesAsync(); @@ -117,7 +117,7 @@ public virtual async Task> PutManyAsync(TOrigin origin, IList public virtual async Task DeleteAsync(TPrimaryKey entityId) { var data = await GetFromDB(entityId); - + if (data == null) return null; @@ -136,7 +136,7 @@ public virtual async Task> DeleteManyAsync(IList return deletedObjects.Select(m => m.Id).ToList(); } - + public async Task GetFromDB(TPrimaryKey id, IQueryable query) { var key = id.ToString(); diff --git a/src/AspNetCore.RestFramework.Core/packages.lock.json b/src/AspNetCore.RestFramework.Core/packages.lock.json new file mode 100644 index 0000000..203b468 --- /dev/null +++ b/src/AspNetCore.RestFramework.Core/packages.lock.json @@ -0,0 +1,819 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.2.0", + "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.AspNetCore.Mvc.Core": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.2.5", + "contentHash": "/8sr8ixIUD57UFwUntha9bOwex7/AkZfdk1f9oNJG1Ek7p/uuKVa7fuHmYZpQOf35Oxrt+2Ku4WPwMSbNxOuWg==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Core": "2.2.0", + "Microsoft.AspNetCore.Authorization.Policy": "2.2.0", + "Microsoft.AspNetCore.Hosting.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Mvc.Abstractions": "2.2.0", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Routing": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.DependencyInjection": "2.2.0", + "Microsoft.Extensions.DependencyModel": "2.1.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "System.Diagnostics.DiagnosticSource": "4.5.0", + "System.Threading.Tasks.Extensions": "4.5.1" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "Direct", + "requested": "[8.*, )", + "resolved": "8.0.8", + "contentHash": "iK+jrJzkfbIxutB7or808BPmJtjUEi5O+eSM7cLDwsyde6+3iOujCSfWnrHrLxY3u+EQrJD+aD8DJ6ogPA2Rtw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "8.0.8", + "Microsoft.EntityFrameworkCore.Analyzers": "8.0.8", + "Microsoft.Extensions.Caching.Memory": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0" + } + }, + "Microsoft.AspNetCore.Authentication.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "VloMLDJMf3n/9ic5lCBOa42IBYJgyB1JhzLsL68Zqg+2bEPWfGBj/xCJy/LrKTArN0coOcZp3wyVTZlx0y9pHQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authentication.Core": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "XlVJzJ5wPOYW+Y0J6Q/LVTEyfS4ssLXmt60T0SPP+D8abVhBTl+cgw2gDHlyKYIkcJg7btMVh383NDkMVqD/fg==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Extensions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authorization": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "/L0W8H3jMYWyaeA9gBJqS/tSWBegP9aaTM0mjRhxTttBY9z4RVDRYJ2CwPAmAXIuPr3r1sOw+CS8jFVRGHRezQ==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authorization.Policy": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "aJCo6niDRKuNg2uS2WMEmhJTooQUGARhV2ENQ2tO5443zVHUo19MSgrgGo9FIrfD+4yKPF8Q+FF33WkWfPbyKw==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Authorization": "2.2.0" + } + }, + "Microsoft.AspNetCore.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ubycklv+ZY7Kutdwuy1W4upWcZ6VFR8WUXU7l7B2+mvbDBBPAcfpi+E+Y5GFe+Q157YfA3C49D2GCjAZc7Mobw==", + "dependencies": { + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Hosting.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "1PMijw8RMtuQF60SsD/JlKtVfvh4NORAhF4wjysdABhlhTrYmtgssqyncR0Stq5vqtjplZcj6kbT4LRTglt9IQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Http": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "YogBSMotWPAS/X5967pZ+yyWPQkThxhmzAwyCHCSSldzYBkW5W5d6oPfBaPqQOnSHYTpSOSOkpZoAce0vwb6+A==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.WebUtilities": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + } + }, + "Microsoft.AspNetCore.Http.Extensions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "2DgZ9rWrJtuR7RYiew01nGRzuQBDaGHGmK56Rk54vsLLsCdzuFUPqbDTJCS1qJQWTbmbIQ9wGIOjpxA1t0l7/w==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Buffers": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.AspNetCore.Mvc.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ET6uZpfVbGR1NjCuLaLy197cQ3qZUjzl7EG5SL4GfJH/c9KRE89MMBrQegqWsh0w1iRUB/zQaK0anAjxa/pz4g==", + "dependencies": { + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + } + }, + "Microsoft.AspNetCore.ResponseCaching.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "CIHWEKrHzZfFp7t57UXsueiSA/raku56TgRYauV/W1+KAQq6vevz60zjEKaazt3BI76zwMz3B4jGWnCwd8kwQw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.AspNetCore.Routing": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "jAhDBy0wryOnMhhZTtT9z63gJbvCzFuLm8yC6pHzuVu9ZD1dzg0ltxIwT4cfwuNkIL/TixdKsm3vpVOpG8euWQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Routing.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "lRRaPN7jDlUCVCp9i0W+PB0trFaKB0bgMJD7hEJS9Uo4R9MXaMC8X2tJhPLmeVE3SGDdYI4QNKdVmhNvMJGgPQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.WebUtilities": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Dynamic.Runtime": "4.0.11", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "9KPDwvb/hLEVXYruVHVZ8BkebC8j17DmPb56LnqRF74HqSPLjCkrlFUjOtFpQPA2DeADBRTI/e69aCfRBfrhxw==", + "dependencies": { + "System.AppContext": "4.1.0", + "System.Collections": "4.0.11", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "9mMQkZsfL1c2iifBD8MWRmwy59rvsVtR9NOezJj7+g1j4P7g49MJHd8k8faC/v7d5KuHkQ6KOQiSItvoRt9PXA==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "OlAXMU+VQgLz5y5/SBkLvAa9VeiR3dlJqgIebEEH2M2NGA3evm68/Tv7SLWmSxwnEAtA3nmDEZF2pacK6eXh4Q==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "nS2XKqi+1A1umnYNLX2Fbm/XnzCxs5i+zXVJ3VC6r9t2z0NZr9FLnJN4VQpKigdcWH/iFTbMuX6M6WQJcTjVIg==", + "dependencies": { + "Microsoft.DotNet.PlatformAbstractions": "2.1.0", + "Newtonsoft.Json": "9.0.1", + "System.Diagnostics.Debug": "4.0.11", + "System.Dynamic.Runtime": "4.0.11", + "System.Linq": "4.1.0" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "EcnaSsPTqx2MGnHrmWOD0ugbuuqVT8iICqSqPzi45V5/MA1LjUNb0kwgcxBGqizV1R+WeBK7/Gw25Jzkyk9bIw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "+k4AEn68HOJat5gj1TWa6X28WlirNQO9sPIIeQbia+91n03esEtMSSoekSTpMjUzjqtJWQN3McVx0GvSPFHF/Q==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "gA8H7uQOnM5gb+L0uTNjViHYr+hRDqCdfugheGo/MxQnuHzmhhzCBTIPm19qL1z1Xe0NEMabfcOBGv9QghlZ8g==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.Net.Http.Headers": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0", + "System.Buffers": "4.5.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.0.1", + "contentHash": "2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.0.1", + "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", + "dependencies": { + "Microsoft.CSharp": "4.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Dynamic.Runtime": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XDocument": "4.0.11" + } + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "eIHRELiYDQvsMToML81QFkXEEYXUSUT2F28t1SGrevWqP+epFdw80SyAXIKTXOHrIEXReFOEnEr7XlGiC2GgOg==" + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Dynamic.Runtime": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0" + } + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Emit.Lightweight": "4.0.1", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.0.12", + "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", + "dependencies": { + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "hWPhJxc453RCa8Z29O91EmfGeZIHX1ZH2A8L6lYQVSaKzku2DfArSfMEb1/MYYzPQRJZeu0c9dmYeJKxW5Fgng==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Threading": "4.0.11", + "runtime.native.System": "4.0.0" + } + }, + "System.Runtime.Serialization.Primitives": { + "type": "Transitive", + "resolved": "4.1.1", + "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", + "dependencies": { + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g==" + }, + "System.Text.RegularExpressions": { + "type": "Transitive", + "resolved": "4.1.0", + "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "WSKUTtLhPR8gllzIWO2x6l4lmAIfbyMAiTlyXAis4QBDonXK4b4S6F8zGARX4/P8wH3DH+sLdhamCiHn+fTU1A==" + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Extensions": "4.0.0" + } + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + } + } + } +} \ No newline at end of file diff --git a/tests/AspNetCore.RestFramework.Core.Test/AspNetCore.RestFramework.Core.Test.csproj b/tests/AspNetCore.RestFramework.Core.Test/AspNetCore.RestFramework.Core.Test.csproj new file mode 100644 index 0000000..8294512 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/AspNetCore.RestFramework.Core.Test.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + false + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/AspNetCore.RestFramework.Core.Test/Base/BaseControllerTests.cs b/tests/AspNetCore.RestFramework.Core.Test/Base/BaseControllerTests.cs new file mode 100644 index 0000000..d0f0eb0 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Base/BaseControllerTests.cs @@ -0,0 +1,1094 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using AspNetCore.RestFramework.Core.Base; +using AspNetCore.RestFramework.Core.Errors; +using AspNetCore.RestFramework.Core.Test.Support; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Xunit; + +namespace AspNetCore.RestFramework.Core.Test.Base; + +public class BaseControllerTests +{ + public class Delete : IntegrationTests + { + private readonly WebApplicationFactory _factory = new(); + + [Fact] + public async Task Delete_WithObject_ShouldDeleteEntityFromDatabaseAndReturnOk() + { + // Arrange + var client = _factory.CreateClient(); + var context = _factory.Services.GetRequiredService(); + var dbSet = context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + dbSet.Add(customer); + await context.SaveChangesAsync(); + + // Act + var response = await client.DeleteAsync($"api/Customers/{customer.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedCustomer = dbSet.AsNoTracking().FirstOrDefault(x => x.Id == customer.Id); + updatedCustomer.Should().BeNull(); + } + + [Fact] + public async Task Delete_WhenEntityDoesntExist_ReturnsNotFound() + { + // Act + var response = await Client.DeleteAsync($"api/Customers/{Guid.NewGuid()}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Delete_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() + { + // Act + var response = await Client.DeleteAsync($"api/Sellers/{Guid.NewGuid()}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + } + } + + public class DeleteMany : IntegrationTests + { + [Fact] + public async Task DeleteMany_ShouldDeleteManyEntities_AndReturnTheirIds() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }); + var expectedToBeDeletedOne = Guid.NewGuid(); + dbSet.Add(new Customer() + { Id = expectedToBeDeletedOne, CNPJ = "456", Name = "def" }); + var expectedToBeDeletedTwo = Guid.NewGuid(); + dbSet.Add(new Customer() + { Id = expectedToBeDeletedTwo, CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + // Act + var url = $"api/Customers?ids={expectedToBeDeletedOne}&ids={expectedToBeDeletedTwo}"; + var response = await Client.DeleteAsync(url); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var deletedIds = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + var expectedGuids = new List { expectedToBeDeletedOne, expectedToBeDeletedTwo }; + deletedIds.Should().BeEquivalentTo(expectedGuids); + + var entitiesWithDeletedIds = dbSet.Where(m => expectedGuids.Contains(m.Id)).AsNoTracking(); + entitiesWithDeletedIds.Should().BeEmpty(); + } + } + + public class GetSingle : IntegrationTests + { + [Fact] + public async Task GetSingle_WithValidParameter_ShouldReturn1Record() + { + // Arrange + var dbSet = Context.Set(); + var customer1 = new Customer { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + var customer2 = new Customer { Id = Guid.NewGuid(), CNPJ = "456", Name = "def" }; + var customer3 = new Customer { Id = Guid.NewGuid(), CNPJ = "789", Name = "ghi" }; + + + dbSet.AddRange(customer1, customer2, customer3); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync($"api/Customers/{customer1.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customer = JsonConvert.DeserializeObject(responseData); + customer.Should().NotBeNull(); + customer.Id.Should().Be(customer1.Id); + } + + [Fact] + public async Task GetSingle_WithInValidParameter_ShouldReturn404() + { + // Arrange + var dbSet = Context.Set(); + var customer1 = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + var customer2 = new Customer() { Id = Guid.NewGuid(), CNPJ = "456", Name = "def" }; + var customer3 = new Customer() { Id = Guid.NewGuid(), CNPJ = "789", Name = "ghi" }; + + dbSet.AddRange(customer1, customer2, customer3); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync($"api/Customers/{Guid.NewGuid()}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task GetSingle_WithoutFields_ShouldReturnBadRequest() + { + // Arrange + var dbSet = Context.Set(); + var seller1 = new Seller() { Id = Guid.NewGuid(), Name = "Test" }; + + dbSet.Add(seller1); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync($"api/Sellers/{seller1.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var msg = JsonConvert.DeserializeObject(responseData); + msg.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + } + } + + public class ListPaged : IntegrationTests + { + [Fact] + public async Task ListPaged_ShouldReturn200OK() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + + customers.Data.Count.Should().Be(3); + } + + [Fact] + public async Task ListPaged_WithQueryString_ShouldReturnSingleRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?Name=ghi&CNPJ=789"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(1); + customers.Data.First().Name.Should().Be("ghi"); + } + + [Fact] + public async Task ListPaged_WithIdQueryStringFilter_ShouldReturnSingleRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?id=35d948bd-ab3d-4446-912b-2d20c57c4935"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(1); + customers.Data.First().Name.Should().Be("abc"); + } + + [Fact] + public async Task ListPaged_WithIdRangeQueryStringFilter_ShouldReturnTwoRecords() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); + var customerIdOne = "6bdc2b9e-3710-40b9-93dd-c7558b446e21"; + dbSet.Add(new Customer() + { Id = Guid.Parse(customerIdOne), CNPJ = "456", Name = "def" }); + var customerIdTwo = "22ee1df9-c543-4509-a755-e7cd5dc0045e"; + dbSet.Add(new Customer() + { Id = Guid.Parse(customerIdTwo), CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var url = $"api/Customers?ids={customerIdOne}&ids={customerIdTwo}"; + var response = await Client.GetAsync(url); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(2); + customers.Data.ElementAt(0).Name.Should().Be("def"); + customers.Data.ElementAt(1).Name.Should().Be("ghi"); + } + + [Fact] + public async Task ListPaged_WithIdRangeQueryStringFilterAndIdIsNotGuid_ShouldReturnTwoRecords() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new IntAsIdEntity() { Name = "abc" }); + dbSet.Add(new IntAsIdEntity() { Name = "def" }); + dbSet.Add(new IntAsIdEntity() { Name = "ghi" }); + dbSet.Add(new IntAsIdEntity() { Name = "jkl" }); + await Context.SaveChangesAsync(); + + var entities = dbSet.Where(m => new[] { "def", "ghi" }.Contains(m.Name)).AsNoTracking().ToList(); + + // Act + var response = await Client.GetAsync($"api/IntAsIdEntities?ids={entities[0].Id}&ids={entities[1].Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(2); + customers.Data.ElementAt(0).Name.Should().Be("def"); + customers.Data.ElementAt(1).Name.Should().Be("ghi"); + } + + [Fact] + public async Task ListPaged_WithIntegerQueryString_ShouldReturnTwoRecords() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?Age=20"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(2); + } + + [Fact] + public async Task ListPaged_WithIntegerQueryStringAndSortDescParameter_ShouldReturnTwoRecordsSortedDesc() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "124", Name = "abc", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?Age=20&SortDesc=Name,CNPJ"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(3); + + var first = customers.Data.First(); + var second = customers.Data.Skip(1).First(); + var third = customers.Data.Skip(2).First(); + + first.Name.Should().Be("def"); + first.CNPJ.Should().Be("456"); + second.Name.Should().Be("abc"); + second.CNPJ.Should().Be("124"); + third.Name.Should().Be("abc"); + third.CNPJ.Should().Be("123"); + } + + [Fact] + public async Task ListPaged_WithIntegerQueryStringAndSortAscParameter_ShouldReturnTwoRecordsSortedAsc() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "124", Name = "abc", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc", Age = 20 }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi", Age = 25 }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?Age=20&Sort=Name,CNPJ"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(3); + + var first = customers.Data.First(); + var second = customers.Data.Skip(1).First(); + var third = customers.Data.Skip(2).First(); + + first.Name.Should().Be("abc"); + first.CNPJ.Should().Be("123"); + second.Name.Should().Be("abc"); + second.CNPJ.Should().Be("124"); + third.Name.Should().Be("def"); + third.CNPJ.Should().Be("456"); + } + + [Fact] + public async Task ListPaged_WithQueryStringDocumentParameter_ShouldReturnSingleRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { + CNPJ = "123", + Name = "abc", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "XYZ" + }, + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cpf", + Document = "1234" + } + } + }); + dbSet.Add(new Customer() + { + CNPJ = "456", + Name = "def", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "LHA" + } + } + }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?cpf=1234"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(1); + customers.Data.First().Name.Should().Be("abc"); + } + + [Fact] + public async Task ListPaged_WithQueryStringDocumentParameterAndName_ShouldReturnSingleRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { + CNPJ = "123", + Name = "abc", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "XYZ" + }, + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cpf", + Document = "1234" + } + } + }); + dbSet.Add(new Customer() + { + CNPJ = "456", + Name = "def", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "LHA" + } + } + }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?cpf=1234&Name=abc"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(1); + customers.Data.First().Name.Should().Be("abc"); + } + + [Fact] + public async Task ListPaged_WithQueryStringDocumentParameterAndName_ShouldReturnNoRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { + CNPJ = "123", + Name = "abc", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "XYZ" + }, + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cpf", + Document = "1234" + } + } + }); + dbSet.Add(new Customer() + { + CNPJ = "456", + Name = "def", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "LHA" + } + } + }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?cpf=1234&Name=ghi"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Should().BeEmpty(); + } + + [Fact] + public async Task ListPaged_WithQueryStringCustomerParameter_ShouldReturnNoRecord() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { + CNPJ = "123", + Name = "abc", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "XYZ" + }, + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cpf", + Document = "1234" + } + } + }); + dbSet.Add(new Customer() + { + CNPJ = "456", + Name = "def", + CustomerDocument = new List() + { + new CustomerDocument + { + Id = Guid.NewGuid(), + DocumentType = "cnpj", + Document = "LHA" + } + } + }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?cpf=5557"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Should().BeEmpty(); + } + + [Fact] + public async Task ListPaged_WithPageSize1AndPageSize3_ShouldReturn1RecordAnd3Pages() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?pageSize=3&page=1"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(3); + customers.Total.Should().Be(3); + } + + [Fact] + public async Task ListPaged_WithPageSize1AndPageSize3_ShouldReturn1RecordAnd1Pages() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() { CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() { CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() { CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync("api/Customers?pageSize=1&page=3"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(1); + customers.Total.Should().Be(3); + customers.Data.First().Name.Should().Be("ghi"); + } + + [Theory] + [InlineData("", 5)] + [InlineData(" ", 5)] + [InlineData("1a91f9ec-920b-4c92-83b0-6bf40d0209c2", 1)] + [InlineData("10", 2)] + [InlineData("12", 1)] + [InlineData("%0001%", 5)] + [InlineData("5%", 2)] + [InlineData("%7", 2)] + [InlineData("Agua Alta", 1)] + [InlineData("Agua%", 2)] + [InlineData("% Inc", 2)] + [InlineData("aaa", 0)] + public async Task ListPaged_WithSearchTerm_ReturnsExpectedCount(string term, int expectedCount) + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { + Id = Guid.Parse("1a91f9ec-920b-4c92-83b0-6bf40d0209c2"), + Age = 10, + CNPJ = "76.637.568/0001-80", + Name = "Agua Alta" + }); + dbSet.Add(new Customer() + { + Id = Guid.Parse("a71bf8fa-0714-4281-8c51-23e763442919"), + Age = 12, + CNPJ = "24.451.215/0001-97", + Name = "Agua Baixa" + }); + dbSet.Add(new Customer() + { + Id = Guid.Parse("555b437e-3cd8-493c-b502-94cb9ba69a6b"), + Age = 10, + CNPJ = "81.517.224/0001-77", + Name = "Bailão 12 Inc" + }); + dbSet.Add(new Customer() + { + Id = Guid.Parse("f10ca31e-f60b-4d4e-8ca3-a754c4fda6bc"), + Age = 25, + CNPJ = "59.732.451/0001-66", + Name = "Xablau Inc" + }); + dbSet.Add(new Customer() + { + Id = Guid.Parse("c2710e39-f17a-469e-8994-28fd621819b4"), + Age = 28, + CNPJ = "55.387.453/0001-04", + Name = "Problem Solver" + }); + await Context.SaveChangesAsync(); + + // Act + var response = await Client.GetAsync($"api/Customers?search={HttpUtility.UrlEncode(term)}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var responseData = await response.Content.ReadAsStringAsync(); + var customers = JsonConvert.DeserializeObject>>(responseData); + customers.Data.Count.Should().Be(expectedCount); + customers.Total.Should().Be(expectedCount); + } + + [Fact] + public async Task ListPaged_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() + { + // Act + var response = await Client.GetAsync("api/Sellers"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + } + } + + public class Patch : IntegrationTests + { + [Fact] + public async Task Patch_WithFullObject_ShouldUpdateFullObject() + { + // Arrange + var dbSet = Context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + dbSet.Add(customer); + await Context.SaveChangesAsync(); + + var customerToUpdate = new + { + Id = customer.Id, + CNPJ = "aaaa", + Name = "eee" + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PatchAsync($"api/Customers/{customer.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); + updatedCustomer.Name.Should().Be(customerToUpdate.Name); + updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); + } + + [Fact] + public async Task Patch_WithPartialObject_ShouldUpdatePartialObject() + { + // Arrange + var dbSet = Context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + dbSet.Add(customer); + await Context.SaveChangesAsync(); + + var customerToUpdate = new + { + Id = customer.Id, + CNPJ = "aaaa", + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PatchAsync($"api/Customers/{customer.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); + updatedCustomer.Name.Should().Be(customer.Name); + updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); + } + + [Fact] + public async Task Patch_WhenEntityDoesntExist_ReturnsNotFound() + { + // Arrange + var customerToUpdate = new + { + CNPJ = "aaaa", + Name = "eee" + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PatchAsync($"api/Customers/{Guid.NewGuid()}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Patch_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() + { + // Arrange + var seller = new SellerDto() + { + Id = Guid.NewGuid(), + Name = "Seller", + }; + + var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PatchAsync($"api/Sellers/{seller.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + } + + [Fact] + public async Task Patch_WhenPatchIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() + { + // Arrange + var dbSet = Context.Set(); + var entity = new IntAsIdEntity() { Name = "abc" }; + dbSet.Add(entity); + await Context.SaveChangesAsync(); + + var entityToUpdate = new IntAsIdEntityDto() + { + Id = entity.Id, + Name = "aaaa", + }; + + var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PatchAsync($"api/IntAsIdEntities/{entity.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); + + var notUpdatedEntity = dbSet.AsNoTracking().First(x => x.Id == entity.Id); + notUpdatedEntity.Name.Should().Be(entity.Name); + } + } + + public class Post : IntegrationTests + { + [Fact] + public async Task Post_WithValidData_ShouldInsertObject() + { + // Arrange + var dbSet = Context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + + var content = new StringContent(JsonConvert.SerializeObject(customer), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PostAsync("api/Customers", content); + + // Assert + var addedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); + response.StatusCode.Should().Be(HttpStatusCode.Created); + addedCustomer.Name.Should().Be(customer.Name); + addedCustomer.CNPJ.Should().Be(customer.CNPJ); + } + + [Fact] + public async Task Post_WithInvalidData_ShouldNotInsertObjectAndReturn400Error() + { + // Arrange + var dbSet = Context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "567", Name = "ac" }; + + var content = new StringContent(JsonConvert.SerializeObject(customer), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PostAsync("api/Customers", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["Name"].Should().Contain("Name should have at least 3 characters"); + responseMessages.Error["CNPJ"].Should().Contain("CNPJ cannot be 567"); + + var customers = dbSet.AsNoTracking().ToList(); + customers.Should().BeEmpty(); + } + + [Fact] + public async Task Post_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() + { + // Arrange + var dbSet = Context.Set(); + + var seller = new SellerDto() + { + Name = "Seller", + }; + + var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PostAsync("api/Sellers", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + + var sellers = dbSet.AsNoTracking().ToList(); + sellers.Should().BeEmpty(); + } + } + + public class Put : IntegrationTests + { + [Fact] + public async Task Put_WithFullObject_ShouldUpdateFullObject() + { + // Arrange + var dbSet = Context.Set(); + var customer = new Customer() { Id = Guid.NewGuid(), CNPJ = "123", Name = "abc" }; + dbSet.Add(customer); + await Context.SaveChangesAsync(); + + var customerToUpdate = new + { + Id = customer.Id, + CNPJ = "aaaa", + Name = "eee" + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PutAsync($"api/Customers/{customer.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedCustomer = dbSet.AsNoTracking().First(x => x.Id == customer.Id); + updatedCustomer.Name.Should().Be(customerToUpdate.Name); + updatedCustomer.CNPJ.Should().Be(customerToUpdate.CNPJ); + } + + [Fact] + public async Task Put_WhenEntityDoesntExist_ReturnsNotFound() + { + // Arrange + var customerToUpdate = new + { + CNPJ = "aaaa", + Name = "eee" + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PutAsync($"api/Customers/{Guid.NewGuid()}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Put_WhenEntityDoesntImplementGetFields_ReturnsBadRequest() + { + // Arrange + var seller = new SellerDto() + { + Id = Guid.NewGuid(), + Name = "Seller", + }; + + var content = new StringContent(JsonConvert.SerializeObject(seller), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PutAsync($"api/Sellers/{seller.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var responseData = await response.Content.ReadAsStringAsync(); + var responseMessages = JsonConvert.DeserializeObject(responseData); + + responseMessages.Error["msg"].Should().Be(BaseMessages.ERROR_GET_FIELDS); + } + + [Fact] + public async Task Put_WhenPutIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() + { + // Arrange + var dbSet = Context.Set(); + var entity = new IntAsIdEntity() { Name = "abc" }; + dbSet.Add(entity); + Context.SaveChanges(); + + var entityToUpdate = new IntAsIdEntityDto() + { + Id = entity.Id, + Name = "aaaa", + }; + + var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PutAsync($"api/IntAsIdEntities/{entity.Id}", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); + + var notUpdatedEntity = dbSet.AsNoTracking().First(x => x.Id == entity.Id); + notUpdatedEntity.Name.Should().Be(entity.Name); + } + } + + public class PutMany : IntegrationTests + { + [Fact] + public async Task PutMany_ShouldUpdateManyEntities_AndReturnTheirIds() + { + // Arrange + var dbSet = Context.Set(); + dbSet.Add(new Customer() + { Id = Guid.Parse("35d948bd-ab3d-4446-912b-2d20c57c4935"), CNPJ = "123", Name = "abc" }); + dbSet.Add(new Customer() + { Id = Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), CNPJ = "456", Name = "def" }); + dbSet.Add(new Customer() + { Id = Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), CNPJ = "789", Name = "ghi" }); + await Context.SaveChangesAsync(); + + var customerData = new CustomerDto + { + CNPJ = "aaaa", + Name = "eee" + }; + + var expectedGuids = new[] + { + Guid.Parse("6bdc2b9e-3710-40b9-93dd-c7558b446e21"), + Guid.Parse("22ee1df9-c543-4509-a755-e7cd5dc0045e"), + }; + + var content = new StringContent(JsonConvert.SerializeObject(customerData), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = + await Client.PutAsync( + $"api/Customers?ids=6bdc2b9e-3710-40b9-93dd-c7558b446e21&ids=22ee1df9-c543-4509-a755-e7cd5dc0045e", + content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedIds = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + updatedIds.Should().BeEquivalentTo(expectedGuids); + + var updatedEntities = dbSet.Where(m => expectedGuids.Contains(m.Id)).AsNoTracking(); + updatedEntities.Should().BeEquivalentTo( + new[] + { + new Customer() {CNPJ = "aaaa", Name = "eee"}, + new Customer() {CNPJ = "aaaa", Name = "eee"}, + }, + config => config + .Including(m => m.CNPJ) + .Including(m => m.Name) + ); + } + + [Fact] + public async Task PutMany_WhenPutIsNotAllowedByActionOptions_ShouldReturnMethodNotAllowed() + { + // Arrange + var entityToUpdate = new IntAsIdEntityDto() + { + Name = "aaaa", + }; + + var content = new StringContent(JsonConvert.SerializeObject(entityToUpdate), Encoding.UTF8, + "application/json-patch+json"); + + // Act + var response = await Client.PutAsync($"api/IntAsIdEntities?ids=1&ids=2", content); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); + } + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Helpers/PartialJsonObjectTests.cs b/tests/AspNetCore.RestFramework.Core.Test/Helpers/PartialJsonObjectTests.cs new file mode 100644 index 0000000..7ef23b0 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Helpers/PartialJsonObjectTests.cs @@ -0,0 +1,1518 @@ +using System.Collections.Generic; +using System.Linq; +using AspNetCore.RestFramework.Core.Helpers; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace AspNetCore.RestFramework.Core.Test.Helpers; + +public class PartialJsonObjectTests +{ + public class BasicContract + { + public static IEnumerable listOKStubs => FakeData.ListOK.Select(item => (new object[] { item })); + + [Fact(DisplayName = "Should populate JSON and instance")] + public void ShouldPopulateJsonAndInstance() + { + // act + var jsonObj = JsonConvert.SerializeObject(FakeData.OK); + var obj = JsonConvert.DeserializeObject>(jsonObj); + + // assert + obj.Instance.Should().NotBeNull(); + obj.JsonObject.Should().NotBeNull(); + } + + [Theory(DisplayName = "Should populate original JSON")] + [MemberData(nameof(listOKStubs))] + public void ShouldPopulateOriginalJSON(TestModel testObj) + { + // act + var jsonObj = JsonConvert.SerializeObject(testObj); + var obj = JsonConvert.DeserializeObject>(jsonObj); + + // assert + obj.JsonObject.Should().BeEquivalentTo(JToken.Parse(jsonObj)); + } + + [Theory(DisplayName = "Should create a instance object from the original JSON")] + [MemberData(nameof(listOKStubs))] + public void ShouldCreateInstanceFromOriginalJSON(TestModel testObj) + { + // act + var jsonObj = JsonConvert.SerializeObject(testObj); + var obj = JsonConvert.DeserializeObject>(jsonObj); + + // assert + obj.Instance.Should().BeEquivalentTo(testObj); + } + + [Fact(DisplayName = "Field should be represented as set if it is populated")] + public void ShouldRepresentFieldAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["Description"].Parent.Remove(); + jsonObj["ChildItemsList"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + TestModel instance = obj; + + // assert + instance.Should().Be(obj.Instance); + + obj.IsSet(inst => inst.Id).Should().BeTrue(); + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + } + + [Fact(DisplayName = "Should represent field as set if it is populated and receive string as parameter")] + public void ShouldFieldRepresentAsSetWhenIsPopulatedAndReceiveStringAsParam() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet("Id").Should().BeTrue(); + + obj.IsSet("childItemsList").Should().BeTrue(); + obj.IsSet("ChildItemsList.0").Should().BeTrue(); + obj.IsSet("childItemsList.0.Id").Should().BeTrue(); + obj.IsSet("ChildItemsList.$last").Should().BeTrue(); + obj.IsSet("ChildItemsList.$last.Id").Should().BeTrue(); + + obj.IsSet("ChildItemsArray").Should().BeTrue(); + obj.IsSet("childItemsArray.0").Should().BeTrue(); + obj.IsSet("ChildItemsArray.0.id").Should().BeTrue(); + obj.IsSet("ChildItemsArray.$last").Should().BeTrue(); + obj.IsSet("ChildItemsArray.$last.Id").Should().BeTrue(); + + obj.IsSet("ChildMatrixArray").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.1").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.1.2").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.1.2.Id").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.$last").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.1.$last").Should().BeTrue(); + obj.IsSet("ChildMatrixArray.1.$last.id").Should().BeTrue(); + + obj.IsSet("ChildMatrixList").Should().BeTrue(); + obj.IsSet("ChildMatrixList.0").Should().BeTrue(); + obj.IsSet("ChildMatrixList.0.0").Should().BeTrue(); + obj.IsSet("ChildMatrixList.0.0.Id").Should().BeTrue(); + obj.IsSet("ChildMatrixList.$last").Should().BeTrue(); + obj.IsSet("ChildMatrixList.0.$last").Should().BeTrue(); + obj.IsSet("ChildMatrixList.0.$last.id").Should().BeTrue(); + + obj.IsSet("SubObj").Should().BeTrue(); + obj.IsSet("SubObj.SubObj").Should().BeTrue(); + obj.IsSet("SubObj.SubObj.ChildItemsList").Should().BeTrue(); + obj.IsSet("SubObj.SubObj.ChildItemsList.1.Id").Should().BeTrue(); + } + + [Fact(DisplayName = "Should represent field as not set if it is not populated and receive string as parameter")] + public void ShouldFieldRepresentAsNotSetWhenIsNotPopulatedAndReceiveStringAsParam() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet("NonExistingProp").Should().BeFalse(); + obj.IsSet("ChildItemsList.2").Should().BeFalse(); + obj.IsSet("ChildItemsList.$first").Should().BeFalse(); + obj.IsSet("ChildItemsList.$Last").Should().BeFalse(); + } + + [Fact(DisplayName = "Should represent field as set if it is populated and receive multiple string parameters")] + public void ShouldFieldRepresentAsSetWhenIsPopulatedAndReceiveMultipleStringParams() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet("ChildItemsList", "0").Should().BeTrue(); + obj.IsSet("childItemsList", "0", "Id").Should().BeTrue(); + obj.IsSet("ChildItemsList", "$last").Should().BeTrue(); + obj.IsSet("ChildItemsList", "$last", "id").Should().BeTrue(); + + obj.IsSet("ChildItemsArray").Should().BeTrue(); + obj.IsSet("childItemsArray", "0").Should().BeTrue(); + obj.IsSet("ChildItemsArray", "0", "id").Should().BeTrue(); + obj.IsSet("ChildItemsArray", "$last").Should().BeTrue(); + obj.IsSet("ChildItemsArray", "$last", "id").Should().BeTrue(); + + obj.IsSet("ChildMatrixArray").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "1").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "1", "2").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "1", "2", "Id").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "$last").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "1", "$last").Should().BeTrue(); + obj.IsSet("ChildMatrixArray", "1", "$last", "Id").Should().BeTrue(); + + obj.IsSet("ChildMatrixList").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "0").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "0", "0").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "0", "0", "Id").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "$last").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "0", "$last").Should().BeTrue(); + obj.IsSet("ChildMatrixList", "0", "$last", "Id").Should().BeTrue(); + + obj.IsSet("SubObj").Should().BeTrue(); + obj.IsSet("SubObj", "SubObj").Should().BeTrue(); + obj.IsSet("SubObj", "SubObj", "ChildItemsList").Should().BeTrue(); + obj.IsSet("SubObj", "SubObj", "ChildItemsList", "1", "Id").Should().BeTrue(); + } + + [Fact(DisplayName = + "Should represent field as not set if it is not populated and receive multiple string parameter")] + public void ShouldFieldRepresentAsNotSetWhenIsNotPopulatedAndReceiveMultipleStringParameters() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet("ChildItemsList", "NonExistingProp").Should().BeFalse(); + obj.IsSet("ChildItemsList", "2").Should().BeFalse(); + obj.IsSet("ChildItemsList", "$first").Should().BeFalse(); + obj.IsSet("ChildItemsList", "$Last").Should().BeFalse(); + } + + [Fact(DisplayName = "All fields should be represented as set")] + public void ShouldAllFieldsReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.Id).Should().BeTrue(); + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields should be represented as not set")] + public void ShouldAllFieldsReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.Id).Should().BeFalse(); + obj.IsSet(inst => inst.Name).Should().BeFalse(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj).Should().BeFalse(); + } + + private int _index1 = 2; + private const int _index2 = 3; + private static int _index3 = 3; + + [Fact(DisplayName = "Field parse expression to path string")] + public void ShouldParseExpressionToPathString() + { + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.First()).Should() + .Be("ChildItemsList.0"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.Last()).Should() + .Be("ChildItemsList.$last"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList[1]).Should().Be("ChildItemsList.1"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsArray[1]).Should() + .Be("ChildItemsArray.1"); + + for (var index = 0; index < 3; index++) + { + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.ElementAt(index)).Should() + .Be($"ChildItemsList.{index}"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList[index]).Should() + .Be($"ChildItemsList.{index}"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsArray[index]).Should() + .Be($"ChildItemsArray.{index}"); + } + + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.ElementAt(_index1)).Should() + .Be($"ChildItemsList.{_index1}"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.ElementAt(_index2)).Should() + .Be($"ChildItemsList.{_index2}"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.ElementAt(_index3)).Should() + .Be($"ChildItemsList.{_index3}"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsArray.ElementAt(1)).Should() + .Be("ChildItemsArray.1"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList[1].Name).Should() + .Be("ChildItemsList.1.Name"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsArray[1].Name).Should() + .Be("ChildItemsArray.1.Name"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsList.ElementAt(1).Name).Should() + .Be("ChildItemsList.1.Name"); + PartialJsonObject.GetMemberPath(inst => inst.ChildItemsArray.ElementAt(1).Name).Should() + .Be("ChildItemsArray.1.Name"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray[1]).Should() + .Be("ChildMatrixArray.1"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList[1]).Should() + .Be("ChildMatrixList.1"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray[1][2]).Should() + .Be("ChildMatrixArray.1.2"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList[1][2]).Should() + .Be("ChildMatrixList.1.2"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray.ElementAt(1).ElementAt(2)).Should() + .Be("ChildMatrixArray.1.2"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList.ElementAt(1).ElementAt(2)).Should() + .Be("ChildMatrixList.1.2"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray.ElementAt(1).ElementAt(2).Name) + .Should().Be("ChildMatrixArray.1.2.Name"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList.ElementAt(1).ElementAt(2).Name) + .Should() + .Be("ChildMatrixList.1.2.Name"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray[1].ElementAt(2).Name).Should() + .Be("ChildMatrixArray.1.2.Name"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList[1].ElementAt(2).Name).Should() + .Be("ChildMatrixList.1.2.Name"); + + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixArray.ElementAt(1)[2].Name).Should() + .Be("ChildMatrixArray.1.2.Name"); + PartialJsonObject.GetMemberPath(inst => inst.ChildMatrixList.ElementAt(1)[2].Name).Should() + .Be("ChildMatrixList.1.2.Name"); + + PartialJsonObject.GetMemberPath(inst => inst.SubObj.SubObj.ChildItemsList.ElementAt(1)).Should() + .Be("SubObj.SubObj.ChildItemsList.1"); + PartialJsonObject.GetMemberPath(inst => inst.SubObj.SubObj.ChildItemsList[1]).Should() + .Be("SubObj.SubObj.ChildItemsList.1"); + + PartialJsonObject.GetMemberPath(inst => inst.SubObj.SubObj.ChildItemsList.ElementAt(1).Name) + .Should() + .Be("SubObj.SubObj.ChildItemsList.1.Name"); + PartialJsonObject.GetMemberPath(inst => inst.SubObj.SubObj.ChildItemsList[1].Name).Should() + .Be("SubObj.SubObj.ChildItemsList.1.Name"); + } + + [Fact(DisplayName = "Field should be represented as set if it is added manually")] + public void ShouldRepresentFieldAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.Name).Should().BeFalse(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + + // act + obj.Add(inst => inst.Name); + obj.Add(inst => inst.Description); + obj.Add(inst => inst.ChildItemsList); + + // assert + obj.IsSet(inst => inst.Id).Should().BeFalse(); + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj).Should().BeFalse(); + } + + [Fact(DisplayName = "Field should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + + // act + obj.Remove(inst => inst.Id); + obj.Remove(inst => inst.ChildMatrixArray); + + // assert + obj.IsSet(inst => inst.Id).Should().BeFalse(); + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + } + + [Fact(DisplayName = "Should get JSON Path")] + public void ShouldGetJSONPath() + { + // arrange + var testObj = FakeData.OK; + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.GetJSONPath(x => x.Id).Should().Be("Id"); + obj.GetJSONPath(x => x.ChildItemsList[0].Id).Should().Be("ChildItemsList[0].Id"); + obj.GetJSONPath(x => x.ChildItemsArray[0].Id).Should().Be("ChildItemsArray[0].Id"); + obj.GetJSONPath(x => x.ChildMatrixArray[0][0].Id).Should().Be("ChildMatrixArray[0][0].Id"); + obj.GetJSONPath(x => x.ChildMatrixList[0][0].Id).Should().Be("ChildMatrixList[0][0].Id"); + obj.GetJSONPath(x => x.SubObj.ChildItemsList[0].Name).Should().Be("SubObj.ChildItemsList[0].Name"); + } + + [Fact(DisplayName = "Should reset the add configurations if clear the cache")] + public void ShouldResetAddConfigurationsIfClearCache() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.Name).Should().BeFalse(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + + // act + obj.Add(inst => inst.Name); + obj.Add(inst => inst.Description); + obj.Add(inst => inst.ChildItemsList); + + // assert + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + + // act + obj.ClearCache(); + + // assert + obj.IsSet(inst => inst.Name).Should().BeFalse(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + } + + [Fact(DisplayName = "Should reset the remove configurations if clear the cache")] + public void ShouldResetRemoveConfigurationsIfClearCache() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + + // act + obj.Remove(inst => inst.Name); + obj.Remove(inst => inst.Description); + obj.Remove(inst => inst.ChildItemsList); + + // assert + obj.IsSet(inst => inst.Name).Should().BeFalse(); + obj.IsSet(inst => inst.Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + + // act + obj.ClearCache(); + + // assert + obj.IsSet(inst => inst.Name).Should().BeTrue(); + obj.IsSet(inst => inst.Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + } + + [Fact(DisplayName = + "Should object properties be equal and the object reference be different when calling ToObject")] + public void ShouldObjectPropretiesBeEqualAndTheReferenceBeDifferentWhenCallingToObject() + { + // arrange + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(FakeData.OK)); + var obj1 = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // act + var obj2 = obj1.ToObject(); + + // assert + obj2.Should().NotBeSameAs(obj1.Instance); + obj2.Should().BeEquivalentTo(obj1.Instance); + } + + [Fact(DisplayName = "Should get property value if it was populated when calling GetIfSet")] + public void ShouldGetPropertyValueIfItWasPopulatedWhenCallingGetIfSet() + { + // arrange + var testObj = FakeData.OK; + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // act + var nameProp = obj.GetIfSet(x => x.Name, "default value"); + var childItemListItemIdProp = obj.GetIfSet(x => x.ChildItemsList[0].Id, 10000); + var childItemArrayItemIdProp = obj.GetIfSet(x => x.ChildItemsList[1].Id, 10001); + var childItemMatrixItemNameProp = obj.GetIfSet(x => x.ChildMatrixList[0][0].Name, "default value"); + var childMatrixListItemDescriptionProp = + obj.GetIfSet(x => x.ChildMatrixList[0][0].Description, "default value"); + var subObjChildListItemValueProp = obj.GetIfSet(x => x.SubObj.ChildItemsList[0].Value, 10002); + + // assert + nameProp.Should().Be(obj.Instance.Name); + childItemListItemIdProp.Should().Be(1); + childItemArrayItemIdProp.Should().Be(2); + childItemMatrixItemNameProp.Should().Be("Child 1"); + childMatrixListItemDescriptionProp.Should().Be("Description 1"); + subObjChildListItemValueProp.Should().Be(10); + } + + [Fact(DisplayName = "Should get the default value if property was not populated when calling GetIfSet")] + public void ShouldGetDefaultValueIfDefaultValueIfPropertyWasNotPopulatedCallingGetIfSet() + { + // arrange + var obj = JsonConvert.DeserializeObject>("{}"); + + // act + var nameProp = obj.GetIfSet(x => x.Name, "default value"); + var childItemListItemIdProp = obj.GetIfSet(x => x.ChildItemsList[0].Id, 10000); + var childItemArrayItemIdProp = obj.GetIfSet(x => x.ChildItemsList[1].Id, 10001); + var childItemMatrixItemNameProp = obj.GetIfSet(x => x.ChildMatrixList[0][0].Name, "default value"); + var childMatrixListItemDescriptionProp = + obj.GetIfSet(x => x.ChildMatrixList[0][0].Description, "default value"); + var subObjChildListItemValueProp = obj.GetIfSet(x => x.SubObj.ChildItemsList[0].Value, 10002); + + // assert + nameProp.Should().Be("default value"); + childItemListItemIdProp.Should().Be(10000); + childItemArrayItemIdProp.Should().Be(10001); + childItemMatrixItemNameProp.Should().Be("default value"); + childMatrixListItemDescriptionProp.Should().Be("default value"); + subObjChildListItemValueProp.Should().Be(10002); + } + } + + public class Array + { + [Fact(DisplayName = "Field in array should be represented as set if it is populated")] + public void ShouldRepresentFieldInArrayAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["ChildItemsArray"][0]["Name"].Parent.Remove(); + jsonObj["ChildItemsArray"][0]["Value"].Parent.Remove(); + jsonObj["ChildItemsArray"][1]["Description"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in array should be represented as set")] + public void ShouldAllFieldsInArrayReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in array should be represented as not set")] + public void ShouldAllFieldsInArrayReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildItemsArray).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in array should be represented as set if it is added manually")] + public void ShouldRepresentFieldInArrayAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeFalse(); + + // act + obj.Add(inst => inst.ChildItemsArray[0].Name); + obj.Add(inst => inst.ChildItemsArray[1].Description); + + // assert + obj.IsSet(inst => inst.ChildItemsArray).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in array should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldInArrayAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeTrue(); + + // act + obj.Remove(inst => inst.ChildItemsArray[0].Value); + obj.Remove(inst => inst.ChildItemsArray[1].Description); + + // assert + obj.IsSet(inst => inst.ChildItemsArray).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsArray[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsArray[1].Value).Should().BeTrue(); + } + } + + public class ArrayMatrix + { + [Fact(DisplayName = "Field in matrix array should be represented as set if it is populated")] + public void ShouldRepresentFieldInMatrixArrayAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["ChildMatrixArray"][0][0]["Value"].Parent.Remove(); + jsonObj["ChildMatrixArray"][0][1]["Description"].Parent.Remove(); + jsonObj["ChildMatrixArray"][1][0]["Description"].Parent.Remove(); + jsonObj["ChildMatrixArray"][1][1]["Name"].Parent.Remove(); + jsonObj["ChildMatrixArray"][1][2]["Name"].Parent.Remove(); + jsonObj["ChildMatrixArray"][1][2]["Value"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "All fields in matrix array should be represented as set")] + public void ShouldAllFieldsInMatrixArrayReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in matrix array should be represented as not set")] + public void ShouldAllFieldsInMatrixArrayReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in matrix array should be represented as set if it is added manually")] + public void ShouldRepresentFieldInMatrixArrayAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeFalse(); + + obj.Add(inst => inst.ChildMatrixArray[0][0].Description); + obj.Add(inst => inst.ChildMatrixArray[0][1].Id); + obj.Add(inst => inst.ChildMatrixArray[1][0].Name); + obj.Add(inst => inst.ChildMatrixArray[1][1].Description); + obj.Add(inst => inst.ChildMatrixArray[1][2].Name); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Value).Should().BeFalse(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in matrix array should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldInMatrixArrayAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeTrue(); + + obj.Remove(inst => inst.ChildMatrixArray[0][0].Description); + obj.Remove(inst => inst.ChildMatrixArray[0][1].Id); + obj.Remove(inst => inst.ChildMatrixArray[1][0].Name); + obj.Remove(inst => inst.ChildMatrixArray[1][1].Description); + obj.Remove(inst => inst.ChildMatrixArray[1][2].Name); + + // assert + obj.IsSet(inst => inst.ChildMatrixArray).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][0].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[0][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][0].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][1].Value).Should().BeTrue(); + + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixArray[1][2].Value).Should().BeTrue(); + } + } + + public class List + { + [Fact(DisplayName = "Field in list should be represented as set if it is populated")] + public void ShouldRepresentFieldInListAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["ChildItemsList"][0]["Name"].Parent.Remove(); + jsonObj["ChildItemsList"][1]["Description"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in list should be represented as set")] + public void ShouldAllFieldsInListReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in list should be represented as not set")] + public void ShouldAllFieldsInListReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // assert + var obj = JsonConvert.DeserializeObject>(json); + + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in list should be represented as set if it is added manually")] + public void ShouldRepresentFieldInListAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeFalse(); + + // act + obj.Add(inst => inst.ChildItemsList[0].Value); + obj.Add(inst => inst.ChildItemsList[1].Description); + + // assert + obj.IsSet(inst => inst.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in list should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldInListAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeTrue(); + + // act + obj.Remove(inst => inst.ChildItemsList[0].Id); + obj.Remove(inst => inst.ChildItemsList[0].Name); + obj.Remove(inst => inst.ChildItemsList[1].Id); + + // assert + obj.IsSet(inst => inst.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildItemsList[1].Value).Should().BeTrue(); + } + } + + public class ListMatrix + { + [Fact(DisplayName = "Field in matrix list should be represented as set if it is populated")] + public void ShouldRepresentFieldInMatrixListAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["ChildMatrixList"][0][0]["Name"].Parent.Remove(); + jsonObj["ChildMatrixList"][0][0]["Description"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildMatrixList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in matrix list should be represented as set")] + public void ShouldAllFieldsInMatrixListReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildMatrixList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in matrix list should be represented as not set")] + public void ShouldAllFieldsInMatrixListReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildMatrixList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in list matrix should be represented as set if it is added manually")] + public void ShouldRepresentFieldInListMatrixAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeFalse(); + + // act + obj.Add(inst => inst.ChildMatrixList[0][0].Id); + obj.Add(inst => inst.ChildMatrixList[0][0].Name); + + // assert + obj.IsSet(inst => inst.ChildMatrixList).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in list matrix should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldInListMatrixAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeTrue(); + + // act + obj.Remove(inst => inst.ChildMatrixList[0][0].Description); + obj.Remove(inst => inst.ChildMatrixList[0][0].Value); + + // assert + obj.IsSet(inst => inst.ChildMatrixList).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.ChildMatrixList[0][0].Value).Should().BeFalse(); + } + } + + public class NestedObj + { + [Fact(DisplayName = "Field in nested object should be represented as set if it is populated")] + public void ShouldRepresentFieldInNestedObjectAsSetIfItIsPopulated() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + jsonObj["SubObj"]["Name"].Parent.Remove(); + jsonObj["SubObj"]["Description"].Parent.Remove(); + jsonObj["SubObj"]["ChildItemsList"][0]["Name"].Parent.Remove(); + jsonObj["SubObj"]["ChildItemsList"][0]["Value"].Parent.Remove(); + jsonObj["SubObj"]["ChildItemsList"][1]["Description"].Parent.Remove(); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in nested object should be represented as set")] + public void ShouldAllFieldsInNestedObjectReturnTrueToIsSet() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "All fields in nested object should be represented as not set")] + public void ShouldAllFieldsInNestedObjectReturnFalseToIsSet() + { + // arrange + string json = "{}"; + + // assert + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.SubObj).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeFalse(); + } + + [Fact(DisplayName = "Field in nested object should be represented as set if it is added manually")] + public void ShouldRepresentFieldInNestedObjectAsSetIfItIsAddedManually() + { + // arrange + var json = "{}"; + + // act + var obj = JsonConvert.DeserializeObject>(json); + + // assert + obj.IsSet(inst => inst.SubObj.Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeFalse(); + + // act + obj.Add(inst => inst.SubObj.Name); + obj.Add(inst => inst.SubObj.ChildItemsList[0].Name); + obj.Add(inst => inst.SubObj.ChildItemsList[1].Value); + + // assert + obj.IsSet(inst => inst.SubObj).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Value).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Name).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeTrue(); + } + + [Fact(DisplayName = "Field in nested object should be represented as not set if it is removed manually")] + public void ShouldRepresentFieldInNestedObjectAsSetIfItIsRemovedManually() + { + // arrange + var testObj = FakeData.OK; + + // act + var jsonObj = JObject.Parse(JsonConvert.SerializeObject(testObj)); + var obj = JsonConvert.DeserializeObject>(jsonObj.ToString()); + + // assert + obj.IsSet(inst => inst.SubObj.Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeTrue(); + + // act + obj.Remove(inst => inst.SubObj.Description); + obj.Remove(inst => inst.SubObj.ChildItemsList[0].Id); + + // assert + obj.IsSet(inst => inst.SubObj).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.Description).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Id).Should().BeFalse(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[0].Value).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Id).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Name).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Description).Should().BeTrue(); + obj.IsSet(inst => inst.SubObj.ChildItemsList[1].Value).Should().BeTrue(); + } + } +} + +public class FakeData +{ + public static TestModel OK + { + get + { + return new TestModel + { + Id = 1, + Name = "Class Name", + Description = "Class Description", + ChildItemsList = FakeData.ChildItems, + ChildItemsArray = FakeData.ChildItems.ToArray(), + ChildMatrixList = new List> + { + new List + { + new ChildTestModel + { + Id = 1, + Name = "Child 1", + Description = "Description 1", + Value = 10 + } + } + }, + ChildMatrixArray = FakeData.ChildMatrix, + SubObj = FakeData.SubObj, + }; + } + } + + public static List ListOK + { + get + { + return new List + { + new TestModel + { + Id = 1, + Name = "Class Name", + Description = "Class Description" + }, + new TestModel + { + Id = 2, + Name = "Class Name" + }, + new TestModel + { + Id = 3, + Name = "Class Name", + Description = null + }, + new TestModel + { + Id = 4, + Name = "Class Name", + Description = string.Empty + }, + new TestModel + { + Id = 5, + Name = "Class Name", + Description = "Class Description", + ChildItemsList = null + }, + new TestModel + { + Id = 6, + Name = "Class Name", + Description = "Class Description", + SubObj = FakeData.SubObj, + SubObj2 = FakeData.SubObj, + ChildItemsList = FakeData.ChildItems, + ChildMatrixArray = FakeData.ChildMatrix + } + }; + } + } + + public static List ChildItems + { + get + { + return new List + { + new ChildTestModel + { + Id = 1, + Name = "Child 1", + Description = "Description 1", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + } + }; + } + } + + public static ChildTestModel[][] ChildMatrix + { + get + { + return new ChildTestModel[][] + { + new ChildTestModel[] + { + new ChildTestModel + { + Id = 1, + Name = "Child 1", + Description = "Description 1", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + } + }, + new ChildTestModel[] + { + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + } + } + }; + } + } + + + public static TestModel SubObj + { + get + { + return new TestModel + { + Name = "Class Name", + Description = "Class Description", + ChildItemsList = new List + { + new ChildTestModel + { + Id = 1, + Name = "Child 1", + Description = "Description 1", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + } + }, + SubObj = new TestModel + { + Name = "Class Name", + Description = "Class Description", + ChildItemsList = new List + { + new ChildTestModel + { + Id = 1, + Name = "Child 1", + Description = "Description 1", + Value = 10 + }, + new ChildTestModel + { + Id = 2, + Name = "Child 2", + Description = "Description 2", + Value = 10 + } + } + } + }; + } + } +} + +public class ChildTestModel +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public decimal Value { get; set; } +} + +public class TestModel +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public List ChildItemsList { get; set; } + public ChildTestModel[] ChildItemsArray { get; set; } + public ChildTestModel[][] ChildMatrixArray { get; set; } + public List> ChildMatrixList { get; set; } + public TestModel SubObj { get; set; } + public TestModel SubObj2 { get; set; } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/AppDbContext.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/AppDbContext.cs new file mode 100644 index 0000000..cdf9999 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/AppDbContext.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class AppDbContext : DbContext +{ + public DbSet Customer { get; set; } + public DbSet Seller { get; set; } + public DbSet CustomerDocument { get; set; } + public DbSet IntAsIdEntities { get; set; } + + public AppDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => { entity.HasKey(b => b.Id); }); + modelBuilder.Entity(entity => { entity.HasKey(b => b.Id); }); + modelBuilder.Entity(entity => + { + entity.HasOne(b => b.Customer) + .WithMany(b => b.CustomerDocument) + .HasForeignKey(b => b.CustomerId) + .IsRequired(); + }); + modelBuilder.Entity(entity => { entity.HasKey(b => b.Id); }); + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/Controllers.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/Controllers.cs new file mode 100644 index 0000000..5dc802d --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/Controllers.cs @@ -0,0 +1,113 @@ +using System; +using AspNetCore.RestFramework.Core.Base; +using AspNetCore.RestFramework.Core.Filters; +using AspNetCore.RestFramework.Core.Serializer; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +#region Controllers + +[Route("api/[controller]")] +[ApiController] +public class SellersController : BaseController +{ + public SellersController( + Serializer serializer, + AppDbContext dbContext, + ILogger logger) + : base( + serializer, + dbContext, + logger) + { + AllowedFields = new[] + { + nameof(Seller.Name) + }; + + Filters.Add(new QueryStringFilter(AllowedFields)); + Filters.Add(new QueryStringSearchFilter(AllowedFields)); + Filters.Add(new QueryStringIdRangeFilter()); + } +} + +[Route("api/[controller]")] +[ApiController] +public class IntAsIdEntitiesController : BaseController +{ + public IntAsIdEntitiesController( + Serializer serializer, + AppDbContext context, + ILogger logger) + : base( + serializer, + context, + new ActionOptions() { AllowPatch = false, AllowPut = false }, + logger) + { + AllowedFields = new[] + { + nameof(IntAsIdEntity.Id), + nameof(IntAsIdEntity.Name), + }; + + Filters.Add(new QueryStringFilter(AllowedFields)); + Filters.Add(new QueryStringSearchFilter(AllowedFields)); + Filters.Add(new QueryStringIdRangeFilter()); + } +} + +[Route("api/[controller]")] +[ApiController] +public class CustomersController : BaseController +{ + public CustomersController( + CustomerSerializer serializer, + AppDbContext dbContext, + ILogger logger) + : base( + serializer, + dbContext, + logger) + { + AllowedFields = new[] + { + nameof(Customer.Id), + nameof(Customer.Name), + nameof(Customer.CNPJ), + nameof(Customer.Age), + }; + + Filters.Add(new QueryStringFilter(AllowedFields)); + Filters.Add(new QueryStringSearchFilter(AllowedFields)); + Filters.Add(new QueryStringIdRangeFilter()); + Filters.Add(new DocumentFilter()); + Filters.Add(new CustomerDocumentIncludeFilter()); + } +} + +[Route("api/[controller]")] +[ApiController] +public class CustomerDocumentsController : BaseController +{ + public CustomerDocumentsController( + Serializer serializer, + AppDbContext context, ILogger logger) : base(serializer, context, logger) + { + AllowedFields = new[] + { + nameof(CustomerDocument.Document), + nameof(CustomerDocument.DocumentType), + nameof(CustomerDocument.CustomerId) + }; + + Filters.Add(new CustomerFilter()); + Filters.Add(new QueryStringFilter(AllowedFields)); + Filters.Add(new QueryStringSearchFilter(AllowedFields)); + Filters.Add(new QueryStringIdRangeFilter()); + } +} + +#endregion diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/DTOs.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/DTOs.cs new file mode 100644 index 0000000..b104c0f --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/DTOs.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using AspNetCore.RestFramework.Core.Base; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class CustomerDocumentDto : BaseDto +{ + public string Document { get; set; } + public string DocumentType { get; set; } +} + +public class CustomerDto : BaseDto +{ + public CustomerDto() + { + } + + public string Name { get; set; } + public string CNPJ { get; set; } + + public ICollection CustomerDocuments { get; set; } +} + +public class IntAsIdEntityDto : BaseDto +{ + public string Name { get; set; } +} + +public class SellerDto : BaseDto +{ + public string Name { get; set; } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/FakeGenerator.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/FakeGenerator.cs new file mode 100644 index 0000000..53f4417 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/FakeGenerator.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Bogus; +using Bogus.Extensions.Brazil; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public static class FakeDataGenerator +{ + private const int SELLERS_TO_GENERATE = 30; + private const int CUSTOMERS_TO_GENERATE = 500; + private const int DOCUMENTS_TO_GENERATE_MIN = 1; + private const int DOCUMENTS_TO_GENERATE_MAX = 5; + + public static (IList, IList, IList) GenerateFakeData() + { + var sellers = Enumerable + .Range(0, SELLERS_TO_GENERATE) + .Select(_ => + { + var sellerFaker = new Faker() + .RuleFor(m => m.Id, m => Guid.NewGuid()) + .RuleFor(m => m.Name, m => m.Company.CompanyName()); + + return sellerFaker.Generate(); + }) + .ToList(); + + var customerDocuments = new List(); + + var customers = Enumerable + .Range(0, CUSTOMERS_TO_GENERATE) + .Select(_ => + { + var customerFaker = new Faker() + .RuleFor(m => m.Id, m => Guid.NewGuid()) + .RuleFor(m => m.Name, m => m.Company.CompanyName()) + .RuleFor(m => m.Age, m => m.Random.Number(18, 99)) + .RuleFor(m => m.CNPJ, m => m.Company.Cnpj()); + + var customer = customerFaker.Generate(); + + var documentsToGenerate = + new Faker().Random.Number(DOCUMENTS_TO_GENERATE_MIN, DOCUMENTS_TO_GENERATE_MAX); + customerDocuments.AddRange( + Enumerable.Range(0, documentsToGenerate) + .Select(_ => + { + var docFaker = new Faker() + .RuleFor(m => m.Id, m => Guid.NewGuid()) + .RuleFor(m => m.CustomerId, customer.Id) + .RuleFor(m => m.DocumentType, + m => m.Random.ArrayElement(new[] { "cpf", "cnpj", "xpto", "others" })) + .RuleFor(m => m.Document, (f, d) => + { + return d.DocumentType switch + { + "cpf" => f.Person.Cpf(), + "cnpj" => f.Company.Cnpj(), + "xpto" => $"xpto_{f.Random.Number(10000, 99999)}_{f.Random.Word()}", + "others" => f.Address.Country(), + _ => "invalid", + }; + }); + + return docFaker.Generate(); + }) + .ToList() + ); + + return customer; + }) + .ToList(); + + return (sellers, customers, customerDocuments); + } +} + diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/FakeProgram.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/FakeProgram.cs new file mode 100644 index 0000000..d59eda3 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/FakeProgram.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using System.Linq; +using AspNetCore.RestFramework.Core.Extensions; +using AspNetCore.RestFramework.Core.Serializer; +using AspNetCore.RestFramework.Core.Test.Support; +using FluentValidation; +using FluentValidation.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; + +void InitializeDatabase(IHost app) +{ + using var scope = app.Services.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.EnsureDeleted(); + + var (sellers, customers, customerDocuments) = FakeDataGenerator.GenerateFakeData(); + + dbContext.Seller.AddRange(sellers); + dbContext.Customer.AddRange(customers); + dbContext.CustomerDocument.AddRange(customerDocuments); + + dbContext.SaveChanges(); +} + +var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions() +{ + ContentRootPath = Directory.GetCurrentDirectory(), +}); + +var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); +builder.Configuration.AddConfiguration(configuration); + +// Add services to the container. +var assembly = typeof(SellersController).Assembly; +builder.Services.AddControllers() + .AddNewtonsoftJson(config => + { + config.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + config.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + }) + .ConfigureValidationResponseFormat() + .PartManager.ApplicationParts.Add(new AssemblyPart(assembly)); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddFluentValidationAutoValidation(); +builder.Services.AddValidatorsFromAssemblyContaining(); +var defaultConnectionString = + $"Data Source=localhost,1433;Initial Catalog=REPLACE_ME_PROGRAMATICALLY;User Id=sa;Password=Password1;TrustServerCertificate=True"; +var connectionString = configuration.GetConnectionString("AppDbContext") ?? defaultConnectionString; +connectionString = connectionString.Replace("REPLACE_ME_PROGRAMATICALLY", Guid.NewGuid().ToString()); +builder.Services.AddDbContext(options => + options.UseSqlServer(connectionString), ServiceLifetime.Singleton); +// Context.Database.EnsureCreated(); +// Rest framework configuration +builder.Services.AddScoped(); +builder.Services.AddScoped>(); +builder.Services.AddScoped>(); +builder.Services.AddScoped>(); + +// Configure the HTTP request pipeline. +var app = builder.Build(); +app.UseAuthorization(); +app.MapControllers(); +app.UseCors(policyBuilder => +{ + policyBuilder.AllowAnyHeader(); + policyBuilder.AllowAnyMethod(); + policyBuilder.AllowAnyOrigin(); +}); +app.MapGet("/debug/routes", (IActionDescriptorCollectionProvider provider) => +{ + return provider.ActionDescriptors.Items.Select(x => new + { + Action = x.RouteValues["Action"], + Method = x.ActionConstraints.OfType().FirstOrDefault()?.HttpMethods.FirstOrDefault(), + Controller = x.RouteValues["Controller"], + Name = x.AttributeRouteInfo.Name, + Template = x.AttributeRouteInfo.Template + }).ToList(); +}); + +// Ensure a dedicated database is created for each test method +// TODO: This is executed twice. The idea is to move it to `IntegrationTests` and make it work properly +app.Services.GetRequiredService().Database.EnsureCreated(); + +await app.RunAsync(); + +// Allows tests with WebApplicationFactory +// https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0#basic-tests-with-the-default-webapplicationfactory-1 +public partial class FakeProgram +{ +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/Filters.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/Filters.cs new file mode 100644 index 0000000..db6290a --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/Filters.cs @@ -0,0 +1,45 @@ +using System.Linq; +using AspNetCore.RestFramework.Core.Filters; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class CustomerDocumentIncludeFilter : Filter +{ + public override IQueryable AddFilter(IQueryable query, HttpRequest request) + { + return query.Include(x => x.CustomerDocument); + } +} + +public class CustomerFilter : Filter +{ + public override IQueryable AddFilter(IQueryable query, HttpRequest request) + { + return query.Include(x => x.Customer); + } +} + +public class DocumentFilter : Filter +{ + public override IQueryable AddFilter(IQueryable query, HttpRequest request) + { + var queryString = request.Query.Select(x => new { x.Key, x.Value }).ToList(); + + var documentType = ""; + + if (queryString.Any(x => x.Key == "cpf")) + documentType = "cpf"; + + if (queryString.Any(x => x.Key == "cnpj")) + documentType = "cnpj"; + + var document = queryString.FirstOrDefault(x => x.Key == documentType)?.Value; + if (string.IsNullOrWhiteSpace(document)) + return query; + + + return query.Where(x => x.CustomerDocument.Any(x => x.DocumentType == documentType && x.Document == document.Value.ToString())); + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/IntegrationTestsFixture.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/IntegrationTestsFixture.cs new file mode 100644 index 0000000..b5b3713 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/IntegrationTestsFixture.cs @@ -0,0 +1,20 @@ +using System; +using System.Net.Http; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class IntegrationTests +{ + protected readonly HttpClient Client; + protected readonly AppDbContext Context; + + protected IntegrationTests() + { + WebApplicationFactory factory = new(); + Client = factory.CreateClient(); + Context = factory.Services.GetRequiredService(); + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/Models.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/Models.cs new file mode 100644 index 0000000..bd89587 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/Models.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using AspNetCore.RestFramework.Core.Base; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class Customer : BaseModel +{ + public string Name { get; set; } + public string CNPJ { get; set; } + public int Age { get; set; } + + public ICollection CustomerDocument { get; set; } + + public override string[] GetFields() + { + return new[] { "Name", "CNPJ", "Age", "Id", "CustomerDocument", "CustomerDocument:DocumentType", "CustomerDocument:Document" }; + } +} + +public class CustomerDocument : BaseModel +{ + public string Document { get; set; } + public string DocumentType { get; set; } + public Guid CustomerId { get; set; } + public Customer Customer { get; set; } + public override string[] GetFields() + { + return new[] { "Id", "Document", "DocumentType", "CustomerId", "Customer", "Customer:CNPJ", "Customer:Age" }; + } +} + +public class IntAsIdEntity : BaseModel +{ + public string Name { get; set; } + + public override string[] GetFields() + => new[] { nameof(Id), nameof(Name) }; +} + +public class Seller : BaseModel +{ + public string Name { get; set; } + public override string[] GetFields() => throw new NotImplementedException(); +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/Serializers.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/Serializers.cs new file mode 100644 index 0000000..d83304c --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/Serializers.cs @@ -0,0 +1,11 @@ +using System; +using AspNetCore.RestFramework.Core.Serializer; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class CustomerSerializer : Serializer +{ + public CustomerSerializer(AppDbContext applicationDbContext) : base(applicationDbContext) + { + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/Support/Validators.cs b/tests/AspNetCore.RestFramework.Core.Test/Support/Validators.cs new file mode 100644 index 0000000..4d10387 --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/Support/Validators.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using Microsoft.AspNetCore.Http; + +namespace AspNetCore.RestFramework.Core.Test.Support; + +public class CustomerDocumentDtoValidator : AbstractValidator +{ + public CustomerDocumentDtoValidator() + { + RuleFor(m => m.Document) + .MinimumLength(3) + .WithMessage("Name should have at least 3 characters"); + } +} + +public class CustomerDtoValidator : AbstractValidator +{ + public CustomerDtoValidator(IHttpContextAccessor context) + { + RuleFor(m => m.Name) + .MinimumLength(3) + .WithMessage("Name should have at least 3 characters"); + + if (context.HttpContext.Request.Method == HttpMethods.Post) + RuleFor(m => m.CNPJ) + .NotEqual("567") + .WithMessage("CNPJ cannot be 567"); + } +} diff --git a/tests/AspNetCore.RestFramework.Core.Test/packages.lock.json b/tests/AspNetCore.RestFramework.Core.Test/packages.lock.json new file mode 100644 index 0000000..cbf476a --- /dev/null +++ b/tests/AspNetCore.RestFramework.Core.Test/packages.lock.json @@ -0,0 +1,1066 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Bogus": { + "type": "Direct", + "requested": "[34.*, )", + "resolved": "34.0.2", + "contentHash": "2KQAuMn3fLAQ2r6jeiffZnpv0bi3GCW3iRB2v1KeHIEBu8MNTh9dBlLTZwGvM0pr+9doKkU8L5JgCURmTmT88A==" + }, + "coverlet.collector": { + "type": "Direct", + "requested": "[6.*, )", + "resolved": "6.0.2", + "contentHash": "bJShQ6uWRTQ100ZeyiMqcFlhP7WJ+bCuabUs885dJiBEzMsJMSFr7BOyeCw4rgvQokteGi5rKQTlkhfQPUXg2A==" + }, + "FluentAssertions": { + "type": "Direct", + "requested": "[6.*, )", + "resolved": "6.12.1", + "contentHash": "hciWwryyLw3eonfqhFpOMTXyM1/auJChYslEBA+iGJyuBs5O3t/kA8YaeH4iRo/2Fe3ElSYL86C0miivtZ0f3g==", + "dependencies": { + "System.Configuration.ConfigurationManager": "4.4.0" + } + }, + "FluentValidation.AspNetCore": { + "type": "Direct", + "requested": "[11.*, )", + "resolved": "11.3.0", + "contentHash": "jtFVgKnDFySyBlPS8bZbTKEEwJZnn11rXXJ2SQnjDhZ56rQqybBg9Joq4crRLz3y0QR8WoOq4iE4piV81w/Djg==", + "dependencies": { + "FluentValidation": "11.5.1", + "FluentValidation.DependencyInjectionExtensions": "11.5.1" + } + }, + "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { + "type": "Direct", + "requested": "[8.*, )", + "resolved": "8.0.8", + "contentHash": "KL3lI8GmCnnROwDrbWbboVpHiXSNTyoLgYPdHus3hEjAwhSAm1JU5S+rmZk7w3Qt0rQfHVIFxKwCf6yapeZy+w==", + "dependencies": { + "Microsoft.AspNetCore.JsonPatch": "8.0.8", + "Newtonsoft.Json": "13.0.3", + "Newtonsoft.Json.Bson": "1.0.2" + } + }, + "Microsoft.AspNetCore.Mvc.Testing": { + "type": "Direct", + "requested": "[8.*, )", + "resolved": "8.0.8", + "contentHash": "iYJ0tw9dOMNVJ/8VbtTYvS4INGelLShllPzO/jA/UIcKZmjz7Mum43Os3/gDaXdcSHt/d1LlvE4vh8zYwQ+UiQ==", + "dependencies": { + "Microsoft.AspNetCore.TestHost": "8.0.8", + "Microsoft.Extensions.DependencyModel": "8.0.1", + "Microsoft.Extensions.Hosting": "8.0.0" + } + }, + "Microsoft.EntityFrameworkCore.SqlServer": { + "type": "Direct", + "requested": "[8.*, )", + "resolved": "8.0.8", + "contentHash": "A2F52W+hnGqvprx37HcAnYnJv4QoFFdc9cxd/QGNSd1vCu1I0eAEKRd0r9KS3E5I5RRj/m9XJfYCyTdy1cdn5Q==", + "dependencies": { + "Microsoft.Data.SqlClient": "5.1.5", + "Microsoft.EntityFrameworkCore.Relational": "8.0.8" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.*, )", + "resolved": "17.11.1", + "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "dependencies": { + "Microsoft.CodeCoverage": "17.11.1", + "Microsoft.TestPlatform.TestHost": "17.11.1" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.9.0", + "contentHash": "PtU3rZ0ThdmdJqTbK7GkgFf6iBaCR6Q0uvJHznID+XEYk2v6O/b7sRxqnbi3B2gRDXxjTqMkVNayzwsqsFUxRw==", + "dependencies": { + "xunit.analyzers": "1.15.0", + "xunit.assert": "2.9.0", + "xunit.core": "[2.9.0]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.8.2", + "contentHash": "vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==" + }, + "Azure.Core": { + "type": "Transitive", + "resolved": "1.35.0", + "contentHash": "hENcx03Jyuqv05F4RBEPbxz29UrM3Nbhnr6Wl6NQpoU9BCIbL3XLentrxDCTrH54NLS11Exxi/o8MYgT/cnKFA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.1.1", + "System.Diagnostics.DiagnosticSource": "6.0.1", + "System.Memory.Data": "1.0.2", + "System.Numerics.Vectors": "4.5.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Azure.Identity": { + "type": "Transitive", + "resolved": "1.10.3", + "contentHash": "l1Xm2MWOF2Mzcwuarlw8kWQXLZk3UeB55aQXVyjj23aBfDwOZ3gu5GP2kJ6KlmZeZv2TCzw7x4L3V36iNr3gww==", + "dependencies": { + "Azure.Core": "1.35.0", + "Microsoft.Identity.Client": "4.56.0", + "Microsoft.Identity.Client.Extensions.Msal": "4.56.0", + "System.Memory": "4.5.4", + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "FluentValidation": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "0h1Q5lNOLLyYTWMJmyNoMqhY4CBRvvUWvJP1R4F2CnmmzuWwvB0A8aVmw5+lOuwYnwUwCRrdeMLbc81F38ahNQ==" + }, + "FluentValidation.DependencyInjectionExtensions": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "iWM0LS1MDYX06pcjMEQKqHirl2zkjHlNV23mEJSoR1IZI7KQmTa0RcTtGEJpj5+iHvBCfrzP2mYKM4FtRKVb+A==", + "dependencies": { + "FluentValidation": "11.5.1", + "Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0" + } + }, + "Microsoft.AspNetCore.Authentication.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "VloMLDJMf3n/9ic5lCBOa42IBYJgyB1JhzLsL68Zqg+2bEPWfGBj/xCJy/LrKTArN0coOcZp3wyVTZlx0y9pHQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authentication.Core": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "XlVJzJ5wPOYW+Y0J6Q/LVTEyfS4ssLXmt60T0SPP+D8abVhBTl+cgw2gDHlyKYIkcJg7btMVh383NDkMVqD/fg==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Extensions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authorization": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "/L0W8H3jMYWyaeA9gBJqS/tSWBegP9aaTM0mjRhxTttBY9z4RVDRYJ2CwPAmAXIuPr3r1sOw+CS8jFVRGHRezQ==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Authorization.Policy": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "aJCo6niDRKuNg2uS2WMEmhJTooQUGARhV2ENQ2tO5443zVHUo19MSgrgGo9FIrfD+4yKPF8Q+FF33WkWfPbyKw==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Authorization": "2.2.0" + } + }, + "Microsoft.AspNetCore.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ubycklv+ZY7Kutdwuy1W4upWcZ6VFR8WUXU7l7B2+mvbDBBPAcfpi+E+Y5GFe+Q157YfA3C49D2GCjAZc7Mobw==", + "dependencies": { + "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.Hosting.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Hosting.Server.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "1PMijw8RMtuQF60SsD/JlKtVfvh4NORAhF4wjysdABhlhTrYmtgssqyncR0Stq5vqtjplZcj6kbT4LRTglt9IQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.Http": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "YogBSMotWPAS/X5967pZ+yyWPQkThxhmzAwyCHCSSldzYBkW5W5d6oPfBaPqQOnSHYTpSOSOkpZoAce0vwb6+A==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.AspNetCore.WebUtilities": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + } + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "Nxs7Z1q3f1STfLYKJSVXCs1iBl+Ya6E8o4Oy1bCxJ/rNI44E/0f6tbsrVqAWfB7jlnJfyaAtIalBVxPKUPQb4Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Extensions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "2DgZ9rWrJtuR7RYiew01nGRzuQBDaGHGmK56Rk54vsLLsCdzuFUPqbDTJCS1qJQWTbmbIQ9wGIOjpxA1t0l7/w==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Buffers": "4.5.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ziFz5zH8f33En4dX81LW84I6XrYXKf9jg6aM39cM+LffN9KJahViKZ61dGMSO2gd3e+qe5yBRwsesvyqlZaSMg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.AspNetCore.JsonPatch": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "IGhuO/SsjHIIvFP4O/5pn/WcPJor+A+BERBhIkMYrlYcRXnZmbBBNSyqoNI9wFq0oxtsrnYMnzXAIi+0MKVdSA==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.AspNetCore.Mvc.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "ET6uZpfVbGR1NjCuLaLy197cQ3qZUjzl7EG5SL4GfJH/c9KRE89MMBrQegqWsh0w1iRUB/zQaK0anAjxa/pz4g==", + "dependencies": { + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Net.Http.Headers": "2.2.0" + } + }, + "Microsoft.AspNetCore.Mvc.Core": { + "type": "Transitive", + "resolved": "2.2.5", + "contentHash": "/8sr8ixIUD57UFwUntha9bOwex7/AkZfdk1f9oNJG1Ek7p/uuKVa7fuHmYZpQOf35Oxrt+2Ku4WPwMSbNxOuWg==", + "dependencies": { + "Microsoft.AspNetCore.Authentication.Core": "2.2.0", + "Microsoft.AspNetCore.Authorization.Policy": "2.2.0", + "Microsoft.AspNetCore.Hosting.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Http": "2.2.0", + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Mvc.Abstractions": "2.2.0", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "2.2.0", + "Microsoft.AspNetCore.Routing": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.DependencyInjection": "2.2.0", + "Microsoft.Extensions.DependencyModel": "2.1.0", + "Microsoft.Extensions.FileProviders.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "System.Diagnostics.DiagnosticSource": "4.5.0", + "System.Threading.Tasks.Extensions": "4.5.1" + } + }, + "Microsoft.AspNetCore.ResponseCaching.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "CIHWEKrHzZfFp7t57UXsueiSA/raku56TgRYauV/W1+KAQq6vevz60zjEKaazt3BI76zwMz3B4jGWnCwd8kwQw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.AspNetCore.Routing": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "jAhDBy0wryOnMhhZTtT9z63gJbvCzFuLm8yC6pHzuVu9ZD1dzg0ltxIwT4cfwuNkIL/TixdKsm3vpVOpG8euWQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Extensions": "2.2.0", + "Microsoft.AspNetCore.Routing.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.ObjectPool": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.AspNetCore.Routing.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "lRRaPN7jDlUCVCp9i0W+PB0trFaKB0bgMJD7hEJS9Uo4R9MXaMC8X2tJhPLmeVE3SGDdYI4QNKdVmhNvMJGgPQ==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.2.0" + } + }, + "Microsoft.AspNetCore.TestHost": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "mQSMZMA72IK/N79HgLn7tCCkN+stEq6yhq0vr6xfw2wvcfjAV2R6JFUYGUDHmWVUxTjDWjQX+Yrd5S9vQKnPLA==", + "dependencies": { + "System.IO.Pipelines": "8.0.0" + } + }, + "Microsoft.AspNetCore.WebUtilities": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "9ErxAAKaDzxXASB/b5uLEkLgUWv1QbeVxyJYEHQwMaxXOeFFVkQxiq8RyfVcifLU7NR0QY0p3acqx4ZpYfhHDg==", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.2.0", + "System.Text.Encodings.Web": "4.5.0" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.SqlClient": { + "type": "Transitive", + "resolved": "5.1.5", + "contentHash": "6kvhQjY5uBCdBccezFD2smfnpQjQ33cZtUZVrNvxlwoBu6uopM5INH6uSgLI7JRLtlQ3bMPwnhMq4kchsXeZ5w==", + "dependencies": { + "Azure.Identity": "1.10.3", + "Microsoft.Data.SqlClient.SNI.runtime": "5.1.1", + "Microsoft.Identity.Client": "4.56.0", + "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0", + "Microsoft.SqlServer.Server": "1.0.0", + "System.Configuration.ConfigurationManager": "6.0.1", + "System.Diagnostics.DiagnosticSource": "6.0.1", + "System.Runtime.Caching": "6.0.0", + "System.Security.Cryptography.Cng": "5.0.0", + "System.Security.Principal.Windows": "5.0.0", + "System.Text.Encoding.CodePages": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" + } + }, + "Microsoft.Data.SqlClient.SNI.runtime": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "wNGM5ZTQCa2blc9ikXQouybGiyMd6IHPVJvAlBEPtr6JepZEOYeDxGyprYvFVeOxlCXs7avridZQ0nYkHzQWCQ==" + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "iK+jrJzkfbIxutB7or808BPmJtjUEi5O+eSM7cLDwsyde6+3iOujCSfWnrHrLxY3u+EQrJD+aD8DJ6ogPA2Rtw==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "8.0.8", + "Microsoft.EntityFrameworkCore.Analyzers": "8.0.8", + "Microsoft.Extensions.Caching.Memory": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "9mMQkZsfL1c2iifBD8MWRmwy59rvsVtR9NOezJj7+g1j4P7g49MJHd8k8faC/v7d5KuHkQ6KOQiSItvoRt9PXA==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "OlAXMU+VQgLz5y5/SBkLvAa9VeiR3dlJqgIebEEH2M2NGA3evm68/Tv7SLWmSxwnEAtA3nmDEZF2pacK6eXh4Q==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "8.0.8", + "contentHash": "3WnrwdXxKg4L98cDx0lNEEau8U2lsfuBJCs0Yzht+5XVTmahboM7MukKfQHAzVsHUPszm6ci929S7Qas0WfVHA==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "8.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "McP+Lz/EKwvtCv48z0YImw+L1gi1gy5rHhNaNIY2CrjloV+XY8gydT8DjMR6zWeL13AFK+DioVpppwAuO1Gi1w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "C2wqUoh9OmRL1akaCcKSTmRU8z0kckfImG7zLNI8uyi47Lp+zd5LWAD17waPQEqCz3ioWOCrFUo+JJuoeZLOBw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "System.Text.Json": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ihDHu2dJYQird9pl2CbdwuNDfvCZdOS0S7SPlNfhPt0B81UTT+yyZKz2pimFZGUp3AfuBRnqUCxB2SjsZKHVUw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Json": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "5Ou6varcxLBzQ+Agfm0k0pnH7vrEITYlXMDuE6s7ZHlZHz6/G8XJ3iISZDr5rfwfge6RnXJ1+Wc479mMn52vjA==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0", + "System.Text.Json": "8.0.4" + } + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileSystemGlobbing": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + }, + "Microsoft.Extensions.Hosting": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ItYHpdqVp5/oFLT5QqbopnkKlyFG9EW/9nhM6/yfObeKt6Su0wkBio6AizgRHGNwhJuAtlE5VIjow5JOTrip6w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "8.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "8.0.0", + "Microsoft.Extensions.Configuration.Json": "8.0.0", + "Microsoft.Extensions.Configuration.UserSecrets": "8.0.0", + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Configuration": "8.0.0", + "Microsoft.Extensions.Logging.Console": "8.0.0", + "Microsoft.Extensions.Logging.Debug": "8.0.0", + "Microsoft.Extensions.Logging.EventLog": "8.0.0", + "Microsoft.Extensions.Logging.EventSource": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "e+48o7DztoYog+PY430lPxrM4mm3PbA6qucvQtUDDwVo4MO+ejMw7YGc/o2rnxbxj4isPxdfKFzTxvXMwAz83A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Configuration": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Text.Json": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dt0x21qBdudHLW/bjMJpkixv858RRr8eSomgVbU8qljOyfrfDGi1JQvpF9w8S7ziRPtRKisuWaOwFxJM82GxeA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3X9D3sl7EmOu7vQp5MJrmIJBl5XSdOhZPYXUeFfYa6Nnm9+tok8x3t3IVPLhm7UJtPOU61ohFchw8rNm9tIYOQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.EventLog": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "oKcPMrw+luz2DUAKhwFXrmFikZWnyc8l2RKoQwqU3KIZZjcfoJE0zRHAnqATfhRZhtcbjl/QkiY2Xjxp0xu+6w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0", + "System.Text.Json": "8.0.0" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "gA8H7uQOnM5gb+L0uTNjViHYr+hRDqCdfugheGo/MxQnuHzmhhzCBTIPm19qL1z1Xe0NEMabfcOBGv9QghlZ8g==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.Identity.Client": { + "type": "Transitive", + "resolved": "4.56.0", + "contentHash": "rr4zbidvHy9r4NvOAs5hdd964Ao2A0pAeFBJKR95u1CJAVzbd1p6tPTXUZ+5ld0cfThiVSGvz6UHwY6JjraTpA==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.22.0" + } + }, + "Microsoft.Identity.Client.Extensions.Msal": { + "type": "Transitive", + "resolved": "4.56.0", + "contentHash": "H12YAzEGK55vZ+QpxUzozhW8ZZtgPDuWvgA0JbdIR9UhMUplj29JhIgE2imuH8W2Nw9D8JKygR1uxRFtpSNcrg==", + "dependencies": { + "Microsoft.Identity.Client": "4.56.0", + "System.IO.FileSystem.AccessControl": "5.0.0", + "System.Security.Cryptography.ProtectedData": "4.5.0" + } + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==" + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "9wxai3hKgZUb4/NjdRKfQd0QJvtXKDlvmGMYACbEC8DFaicMFCFhQFZq9ZET1kJLwZahf2lfY5Gtcpsx8zYzbg==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "6.35.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2" + } + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "jePrSfGAmqT81JDCNSY+fxVWoGuJKt9e6eJ+vT7+quVS55nWl//jGjUQn4eFtVKt4rt5dXaleZdHRB9J9AJZ7Q==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.35.0" + } + }, + "Microsoft.IdentityModel.Protocols": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "BPQhlDzdFvv1PzaUxNSk+VEPwezlDEVADIKmyxubw7IiELK18uJ06RQ9QKKkds30XI+gDu9n8j24XQ8w7fjWcg==", + "dependencies": { + "Microsoft.IdentityModel.Logging": "6.35.0", + "Microsoft.IdentityModel.Tokens": "6.35.0" + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "LMtVqnECCCdSmyFoCOxIE5tXQqkOLrvGrL7OxHg41DIm1bpWtaCdGyVcTAfOQpJXvzND9zUKIN/lhngPkYR8vg==", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "6.35.0", + "System.IdentityModel.Tokens.Jwt": "6.35.0" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "RN7lvp7s3Boucg1NaNAbqDbxtlLj5Qeb+4uSS1TeK5FSBVM40P4DKaTKChT43sHyKfh7V0zkrMph6DdHvyA4bg==", + "dependencies": { + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.35.0", + "System.Security.Cryptography.Cng": "4.5.0" + } + }, + "Microsoft.Net.Http.Headers": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "iZNkjYqlo8sIOI0bQfpsSoMTmB/kyvmV2h225ihyZT33aTp48ZpF6qYnXxzSXmHt8DpBAwBTX+1s1UFLbYfZKg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0", + "System.Buffers": "4.5.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.SqlServer.Server": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Newtonsoft.Json": "13.0.1" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Newtonsoft.Json.Bson": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==", + "dependencies": { + "Newtonsoft.Json": "12.0.1" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" + } + }, + "System.Formats.Asn1": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "MTvUIktmemNB+El0Fgw9egyqT9AYSIk6DTJeoDSpc3GIHxHCMo8COqkWT1mptX5tZ1SlQ6HJZ0OsSvMth1c12w==" + }, + "System.IdentityModel.Tokens.Jwt": { + "type": "Transitive", + "resolved": "6.35.0", + "contentHash": "yxGIQd3BFK7F6S62/7RdZk3C/mfwyVxvh6ngd1VYMBmbJ1YZZA9+Ku6suylVtso0FjI0wbElpJ0d27CdsyLpBQ==", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", + "Microsoft.IdentityModel.Tokens": "6.35.0" + } + }, + "System.IO.FileSystem.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SxHB3nuNrpptVk+vZ/F+7OHEpoHUIKKMl02bUmYHQr1r+glbZQxs7pRtsf4ENO29TVm2TH3AEeep2fJcy92oYw==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + }, + "System.Memory.Data": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", + "dependencies": { + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.6.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==", + "dependencies": { + "System.Formats.Asn1": "5.0.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.4", + "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.15.0", + "contentHash": "s+M8K/Rtlgr6CmD7AYQKrNTvT5sh0l0ZKDoZ3Z/ExhlIwfV9mGAMR4f7KqIB7SSK7ZOhqDTgTUMYPmKfmvWUWQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.0", + "contentHash": "Z/1pyia//860wEYTKn6Q5dmgikJdRjgE4t5AoxJkK8oTmidzPLEPG574kmm7LFkMLbH6Frwmgb750kcyR+hwoA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.0", + "contentHash": "uRaop9tZsZMCaUS4AfbSPGYHtvywWnm8XXFNUqII7ShWyDBgdchY6gyDNgO4AK1Lv/1NNW61Zq63CsDV6oH6Jg==", + "dependencies": { + "xunit.extensibility.core": "[2.9.0]", + "xunit.extensibility.execution": "[2.9.0]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.0", + "contentHash": "zjDEUSxsr6UNij4gIwCgMqQox+oLDPRZ+mubwWLci+SssPBFQD1xeRR4SvgBuXqbE0QXCJ/STVTp+lxiB5NLVA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.0", + "contentHash": "5ZTQZvmPLlBw6QzCOwM0KnMsZw6eGjbmC176QHZlcbQoMhGIeGcYzYwn5w9yXxf+4phtplMuVqTpTbFDQh2bqQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.0]" + } + }, + "AspNetCore.RestFramework": { + "type": "Project", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "[2.*, )", + "Microsoft.AspNetCore.Mvc.Core": "[2.*, )", + "Microsoft.EntityFrameworkCore": "[8.*, )" + } + } + } + } +} \ No newline at end of file