From 9f8e084f03836a551e4dfbd720dd8690d973a332 Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 02:02:58 +0000
Subject: [PATCH 01/24] docs: adiciona ROADMAP com plano de qualidade nota 10
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plano em 8 fases: quick wins de CI, testes na solution, limpeza do
legado (NetFull/NetCore/DP), cobertura de testes por pacote, analisadores
e nullable, SourceLink e READMEs por pacote, documentação e hardening
de CI/CD.
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
ROADMAP.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 201 insertions(+)
create mode 100644 ROADMAP.md
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..1f7002e
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,201 @@
+# ROADMAP — Plano de qualidade "nota 10"
+
+Plano de implementação para elevar o Codout.Framework da avaliação atual
+(**6,5/10**, jun/2026) até **10/10**. As fases estão em ordem de execução
+recomendada: cada uma cria a base da seguinte. Os ganhos de nota são
+estimativas para acompanhamento, não ciência exata.
+
+## Princípios
+
+- **Não quebrar consumidores**: pacotes já publicados no NuGet.org seguem
+ SemVer. Mudança que altere API pública exige bump major e aviso no
+ CHANGELOG.
+- **Cada pacote anda no próprio ritmo**: bumps de versão apenas nos pacotes
+ efetivamente tocados (ver CLAUDE.md).
+- **CI primeiro**: nenhuma fase é considerada concluída sem o `dotnet.yml`
+ verde validando o resultado.
+
+---
+
+## Fase 1 — Quick wins de CI e higiene (esforço: pequeno · ganho: +0,25)
+
+Correções de baixo risco que destravam o resto.
+
+- [ ] `dotnet.yml`: atualizar `dotnet-version` de `9.0.x` para `10.0.x`
+ (o core targeta `net10.0` via `Directory.Build.props`).
+- [ ] Adicionar `Codout.Framework.Api.Dto` e `Softprime.Multitenancy` à
+ `Codout.Framework.sln` (estão no `release-packages.json` mas fora da
+ solution — hoje o CI de PR não os builda).
+- [ ] Remover `appveyor.yml` (referencia Visual Studio 2012; o pipeline real
+ é GitHub Actions).
+- [ ] Corrigir badge de build do README (aponta para `build.yml`/`dotnet.yml`
+ de forma inconsistente).
+
+**Critério de aceite**: `dotnet build` da solution verde no CI com SDK 10.
+
+## Fase 2 — Testes na solution (esforço: pequeno · ganho: +0,25)
+
+Hoje os 4 projetos de teste existem fora da solution; o `dotnet test
+Codout.Framework.sln` do `release.yml` roda zero testes.
+
+- [ ] Criar pasta `tests/` na raiz.
+- [ ] Migrar `NetCore/Codout.Framework.EF.Tests` para
+ `tests/Codout.Framework.EF.Tests` e atualizar `TEST_PROJECT` no
+ `core-release.yml` (linha 39).
+- [ ] Adicionar os projetos de teste ativos (EF.Tests e, se viável por
+ caminho, Mcp.Tests) à `Codout.Framework.sln`.
+- [ ] Remover do escopo os testes legados (`NetFull/*.Tests`,
+ `NetCore/Codout.Framework.NetCore.Tests`) — dependem de infraestrutura
+ externa (appsettings com conexões reais) e serão substituídos na Fase 4.
+
+**Critério de aceite**: `dotnet test Codout.Framework.sln` executa testes de
+verdade (> 0) no CI de PR e no gate do `release.yml`.
+
+## Fase 3 — Limpeza do legado (esforço: médio · ganho: +1,0)
+
+Remover ~2.000 linhas de código morto que distorcem a leitura do repo e
+impedem políticas globais de qualidade (Fase 5).
+
+- [ ] **Decisão prévia (usuário)**: deletar ou arquivar em branch
+ `archive/legacy` antes de remover. Recomendação: branch de arquivo +
+ remoção do master.
+- [ ] Remover `NetFull/` (EF6, .NET Framework — EOL).
+- [ ] Remover `NetCore/` (netcoreapp2.x — EOL; testes já migrados na Fase 2).
+- [ ] Remover `src/NetCore/` (Cosmos e DocumentDB em `netcoreapp2.0` com SDK
+ `Microsoft.Azure.DocumentDB.Core` descontinuado e anti-padrão
+ `AllAsync().Result` em `CosmosRepository.cs`). Se houver demanda real
+ por Cosmos, recriar do zero como `Codout.Framework.Cosmos` com SDK
+ `Microsoft.Azure.Cosmos` — fora do escopo deste plano.
+- [ ] Remover `Codout.Framework.DP` (quebrado: referencia
+ `Codout.Framework.DAL` inexistente; `IsPackable=false`).
+- [ ] Remover/renomear `Shared/Codout.Framework.Shared.Commom` (typo
+ "Commom") e avaliar se `Shared/` ainda tem consumidor.
+- [ ] Atualizar CLAUDE.md (seção "Pacotes excluídos do release automatizado")
+ e CHANGELOG (`### Repository`) refletindo as remoções.
+
+**Critério de aceite**: nenhum csproj com target EOL no repo; busca por
+"Commom" retorna vazio.
+
+## Fase 4 — Cobertura de testes (esforço: grande · ganho: +1,5)
+
+O maior gap: 19 de 23 pacotes publicáveis sem teste algum. Priorizar por
+risco × facilidade, em `tests/.Tests`:
+
+| Onda | Pacotes | Abordagem |
+|------|---------|-----------|
+| 1 | Data, Domain, Common, DynamicLinq, Security.* | Unit puro (sem I/O) — maior retorno imediato |
+| 2 | EF (ampliar), Mongo, NH | EF: SQLite in-memory; Mongo: Testcontainers ou EphemeralMongo; NH: SQLite |
+| 3 | Mailer, Mailer.Razor, Api.Client, Storage | Fakes/mocks (HttpMessageHandler fake, template rendering, contratos de abstração) |
+| 4 | Storage.Azure, Mailer.AWS, Mailer.SendGrid, Api, Application, Multitenancy | Azurite para Azure; demais via mocks dos SDKs |
+
+- [ ] Onda 1 concluída e no CI.
+- [ ] Onda 2 concluída (Testcontainers exige Docker no runner — `ubuntu-latest` já tem).
+- [ ] Onda 3 concluída.
+- [ ] Onda 4 concluída.
+- [ ] Coleta de cobertura com `coverlet.collector` + publicação de relatório
+ no CI (artifact ou Codecov + badge no README).
+- [ ] Meta de cobertura: ≥ 70 % nos pacotes core (Data/Domain/Common/EF),
+ ≥ 50 % nos demais.
+
+**Critério de aceite**: todo pacote publicável tem projeto de teste; metas de
+cobertura atingidas e visíveis no CI.
+
+## Fase 5 — Qualidade estática (esforço: médio · ganho: +0,75)
+
+Aplicar via `Directory.Build.props` (depois da Fase 3, para não gastar
+esforço em código morto):
+
+- [ ] `enable ` global, corrigindo projetos em ondas
+ (13/37 já têm). Para projetos ainda não migrados, opt-out temporário
+ explícito no csproj com comentário `TODO`.
+- [ ] `true ` +
+ `true ` +
+ `latest-recommended `.
+- [ ] Expandir `.editorconfig` com convenções C# (indentação, naming,
+ `dotnet_diagnostic.*` para calibrar severidade dos analyzers).
+- [ ] Resolver os `[Obsolete]` pendentes em `Codout.Framework.Common`
+ (Crypto, NumericExtensions, WebPageFetcher): remover no próximo bump
+ major ou documentar substitutos.
+- [ ] Atualizar `System.ComponentModel.Annotations` 5.0.0 → versão atual (ou
+ remover a referência onde o shared framework já cobre).
+
+**Critério de aceite**: `dotnet build` sem warnings na solution inteira;
+nullable ativo em 100 % dos projetos publicáveis.
+
+## Fase 6 — Empacotamento profissional (esforço: médio · ganho: +0,75)
+
+Tudo no `Directory.Build.props`, exceto onde indicado:
+
+- [ ] SourceLink: `Microsoft.SourceLink.GitHub`, `PublishRepositoryUrl`,
+ `EmbedUntrackedSources`, `IncludeSymbols` + `SymbolPackageFormat=snupkg`.
+- [ ] `ContinuousIntegrationBuild=true` nos workflows de release (build
+ determinístico).
+- [ ] `PackageReadmeFile`: criar `README.md` curto por pacote (o que é,
+ instalação, exemplo mínimo) e empacotar — hoje 19/20 pacotes têm página
+ vazia no NuGet.org.
+- [ ] `Description` e `PackageTags` individualizados em cada csproj.
+- [ ] Publicar os snupkg no fluxo de release (NuGet.org aceita no mesmo
+ `dotnet nuget push`).
+- [ ] Republicar pacotes com bump **patch** (mudança só de empacotamento) —
+ confirmar com o usuário antes, pois publicar exige tag.
+
+**Critério de aceite**: página de cada pacote no NuGet.org com README, ícone
+e símbolos depuráveis (F12 no código do framework a partir de um consumidor).
+
+## Fase 7 — Documentação (esforço: médio · ganho: +0,5)
+
+- [ ] Reescrever a tabela de módulos do README: remover "Zenvia", "DAL",
+ "Kendo.DynamicLinq", "DP", "Shared"; incluir Security.*, Storage,
+ Storage.Azure, Application, Mcp, Image.Extensions.
+- [ ] Seção "Qual pacote eu instalo?" com cenários comuns (EF + API,
+ Mongo, multitenancy, mailer).
+- [ ] `GenerateDocumentationFile=true` global (via `Directory.Build.props`)
+ e completar XML docs nos tipos públicos de `Domain`, `Common`, `Data`
+ e `Api` — com `TreatWarningsAsErrors` da Fase 5, o CS1591 força a
+ conclusão (calibrar severidade se necessário).
+- [ ] Atualizar instruções de build/teste do README (caminhos reais da
+ pasta `tests/`).
+
+**Critério de aceite**: README sem referência a módulo inexistente; XML docs
+completos nos 4 pacotes core.
+
+## Fase 8 — Hardening de CI/CD (esforço: pequeno · ganho: +0,5)
+
+- [ ] Dependabot (`.github/dependabot.yml`) para NuGet + GitHub Actions,
+ agrupando updates minor/patch.
+- [ ] CodeQL (`github/codeql-action`) para análise de segurança em PR.
+- [ ] Gate de cobertura no `dotnet.yml` (falhar se cair abaixo da meta).
+- [ ] Branch protection em `master`: exigir CI verde + 1 review (config no
+ GitHub, não no repo).
+- [ ] Concurrency/cancel-in-progress nos workflows de PR para economizar
+ runner.
+
+**Critério de aceite**: PRs só mergeiam com CI + CodeQL verdes; dependências
+atualizadas automaticamente via PRs do Dependabot.
+
+---
+
+## Resumo de progressão
+
+| Fase | Tema | Esforço | Nota acumulada (alvo) |
+|------|------|---------|----------------------|
+| — | Estado atual | — | 6,5 |
+| 1 | Quick wins CI | P | 6,75 |
+| 2 | Testes na solution | P | 7,0 |
+| 3 | Limpeza do legado | M | 8,0 |
+| 4 | Cobertura de testes | G | 9,5 *(maior fase — pode rodar em paralelo às 5–8)* |
+| 5 | Qualidade estática | M | +0,75 dentro do teto |
+| 6 | Empacotamento | M | +0,75 dentro do teto |
+| 7 | Documentação | M | +0,5 dentro do teto |
+| 8 | Hardening CI/CD | P | **10,0** |
+
+## Pontos de decisão (perguntar ao usuário antes de executar)
+
+1. **Fase 3**: deletar legado direto ou arquivar em branch `archive/legacy`
+ primeiro? (Recomendação: arquivar e deletar.)
+2. **Fase 4, onda 2**: Testcontainers (fiel, requer Docker) vs. bibliotecas
+ in-memory (mais rápido, menos fiel) para Mongo.
+3. **Fase 6**: republicar todos os pacotes com bump patch após SourceLink/
+ README, ou deixar para o próximo release natural de cada pacote?
+4. **Fase 5**: `[Obsolete]` de `Common` — remover em bump major agora ou
+ manter até 7.0?
From 6ce41266c8d926dfdb7a42dd03176373791c894b Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 02:52:47 +0000
Subject: [PATCH 02/24] chore: quick wins de CI e higiene (Fase 1 do ROADMAP)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- dotnet.yml: SDK 9.0.x -> 10.0.x (core targeta net10.0)
- Adiciona Api.Dto e Softprime.Multitenancy à solution
- Isola obj/bin do Softprime.Multitenancy (dois csproj na mesma pasta
corrompiam o restore um do outro)
- Remove appveyor.yml obsoleto (Visual Studio 2012)
- Corrige badge de build do README (workflow dotnet.yml)
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
.github/workflows/dotnet.yml | 2 +-
Codout.Framework.sln | 211 ++++++++++++++++++
.../Softprime.Multitenancy.csproj | 17 +-
README.md | 4 +-
appveyor.yml | 21 --
5 files changed, 230 insertions(+), 25 deletions(-)
delete mode 100644 appveyor.yml
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 382d37e..a623634 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -19,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
diff --git a/Codout.Framework.sln b/Codout.Framework.sln
index 4ef8528..4cb75ef 100644
--- a/Codout.Framework.sln
+++ b/Codout.Framework.sln
@@ -68,100 +68,310 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Bcrypt", "s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Scrypt", "src\Security\Codout.Security.Scrypt\Codout.Security.Scrypt.csproj", "{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Api.Dto", "Codout.Framework.Api.Dto\Codout.Framework.Api.Dto.csproj", "{26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Codout.Multitenancy", "Codout.Multitenancy", "{99E702AB-714F-56EC-2FD9-16223EED3D31}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softprime.Multitenancy", "Codout.Multitenancy\Softprime.Multitenancy.csproj", "{F5E486C4-6B92-4CEE-903F-54B3E7BE1406}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{281F92E8-F224-4036-858E-F04203D9107B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{281F92E8-F224-4036-858E-F04203D9107B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Debug|x64.Build.0 = Debug|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Debug|x86.Build.0 = Debug|Any CPU
{281F92E8-F224-4036-858E-F04203D9107B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{281F92E8-F224-4036-858E-F04203D9107B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Release|x64.ActiveCfg = Release|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Release|x64.Build.0 = Release|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Release|x86.ActiveCfg = Release|Any CPU
+ {281F92E8-F224-4036-858E-F04203D9107B}.Release|x86.Build.0 = Release|Any CPU
{C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Debug|x86.Build.0 = Debug|Any CPU
{C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|x64.Build.0 = Release|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1B0EF6F-49FD-4199-8BA3-7FD7BA3DD4BA}.Release|x86.Build.0 = Release|Any CPU
{9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|x64.Build.0 = Debug|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Debug|x86.Build.0 = Debug|Any CPU
{9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|x64.ActiveCfg = Release|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|x64.Build.0 = Release|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|x86.ActiveCfg = Release|Any CPU
+ {9F190AFF-FADE-4E54-96D8-FFD6F14ECBEA}.Release|x86.Build.0 = Release|Any CPU
{C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|x64.Build.0 = Debug|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Debug|x86.Build.0 = Debug|Any CPU
{C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|x64.ActiveCfg = Release|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|x64.Build.0 = Release|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|x86.ActiveCfg = Release|Any CPU
+ {C2EEFA12-A0DD-4EC4-BCFE-00B1DADC5797}.Release|x86.Build.0 = Release|Any CPU
{269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|x64.Build.0 = Debug|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Debug|x86.Build.0 = Debug|Any CPU
{269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|Any CPU.Build.0 = Release|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|x64.ActiveCfg = Release|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|x64.Build.0 = Release|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|x86.ActiveCfg = Release|Any CPU
+ {269065CB-D3A4-4EFE-8F05-E4ED417B7083}.Release|x86.Build.0 = Release|Any CPU
{777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|x64.Build.0 = Debug|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Debug|x86.Build.0 = Debug|Any CPU
{777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|x64.ActiveCfg = Release|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|x64.Build.0 = Release|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|x86.ActiveCfg = Release|Any CPU
+ {777EEB29-52E1-4585-BB10-ECF95563EBD7}.Release|x86.Build.0 = Release|Any CPU
{4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|x64.Build.0 = Debug|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Debug|x86.Build.0 = Debug|Any CPU
{4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|x64.ActiveCfg = Release|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|x64.Build.0 = Release|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|x86.ActiveCfg = Release|Any CPU
+ {4459FDE2-3D9F-408A-8D6B-863B1D05B865}.Release|x86.Build.0 = Release|Any CPU
{DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|x64.Build.0 = Debug|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Debug|x86.Build.0 = Debug|Any CPU
{DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|x64.ActiveCfg = Release|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|x64.Build.0 = Release|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|x86.ActiveCfg = Release|Any CPU
+ {DA7E9861-F5C1-45DF-8509-EC1D76F0EA9C}.Release|x86.Build.0 = Release|Any CPU
{1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|x64.Build.0 = Debug|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Debug|x86.Build.0 = Debug|Any CPU
{1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|x64.ActiveCfg = Release|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|x64.Build.0 = Release|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|x86.ActiveCfg = Release|Any CPU
+ {1B24E7A5-880C-431E-B859-C77037F4CF00}.Release|x86.Build.0 = Release|Any CPU
{5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|x64.Build.0 = Debug|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Debug|x86.Build.0 = Debug|Any CPU
{5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|x64.ActiveCfg = Release|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|x64.Build.0 = Release|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|x86.ActiveCfg = Release|Any CPU
+ {5D77842D-DD96-4927-976A-FFFD8207ED70}.Release|x86.Build.0 = Release|Any CPU
{A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|x64.Build.0 = Debug|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Debug|x86.Build.0 = Debug|Any CPU
{A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|x64.ActiveCfg = Release|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|x64.Build.0 = Release|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|x86.ActiveCfg = Release|Any CPU
+ {A51B2EDB-B261-41B2-B86F-BBA84AE46288}.Release|x86.Build.0 = Release|Any CPU
{B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|x64.Build.0 = Debug|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Debug|x86.Build.0 = Debug|Any CPU
{B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|x64.ActiveCfg = Release|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|x64.Build.0 = Release|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|x86.ActiveCfg = Release|Any CPU
+ {B4D2F699-D825-4CC3-90D1-000E158D0450}.Release|x86.Build.0 = Release|Any CPU
{1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|x64.Build.0 = Debug|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Debug|x86.Build.0 = Debug|Any CPU
{1C645251-DB97-405B-9899-173FEB02E5B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C645251-DB97-405B-9899-173FEB02E5B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Release|x64.ActiveCfg = Release|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Release|x64.Build.0 = Release|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Release|x86.ActiveCfg = Release|Any CPU
+ {1C645251-DB97-405B-9899-173FEB02E5B4}.Release|x86.Build.0 = Release|Any CPU
{A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|x64.Build.0 = Debug|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Debug|x86.Build.0 = Debug|Any CPU
{A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|x64.ActiveCfg = Release|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|x64.Build.0 = Release|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|x86.ActiveCfg = Release|Any CPU
+ {A7759C7F-BC27-4BE5-A9D2-F94D0B3CB2F4}.Release|x86.Build.0 = Release|Any CPU
{FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|x64.Build.0 = Debug|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Debug|x86.Build.0 = Debug|Any CPU
{FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|x64.ActiveCfg = Release|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|x64.Build.0 = Release|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|x86.ActiveCfg = Release|Any CPU
+ {FA89B3EA-E38C-4F35-A3DA-828D1274956F}.Release|x86.Build.0 = Release|Any CPU
{7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|x64.Build.0 = Debug|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Debug|x86.Build.0 = Debug|Any CPU
{7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|x64.ActiveCfg = Release|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|x64.Build.0 = Release|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|x86.ActiveCfg = Release|Any CPU
+ {7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E}.Release|x86.Build.0 = Release|Any CPU
{52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|x64.Build.0 = Debug|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Debug|x86.Build.0 = Debug|Any CPU
{52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|x64.ActiveCfg = Release|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|x64.Build.0 = Release|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|x86.ActiveCfg = Release|Any CPU
+ {52DE6A78-4383-E3EC-3109-2410414F8EEE}.Release|x86.Build.0 = Release|Any CPU
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|x64.Build.0 = Debug|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|x86.Build.0 = Debug|Any CPU
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|x64.ActiveCfg = Release|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|x64.Build.0 = Release|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|x86.ActiveCfg = Release|Any CPU
+ {B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|x86.Build.0 = Release|Any CPU
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|x64.Build.0 = Debug|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|x86.Build.0 = Debug|Any CPU
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|x64.ActiveCfg = Release|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|x64.Build.0 = Release|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|x86.ActiveCfg = Release|Any CPU
+ {6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|x86.Build.0 = Release|Any CPU
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|x64.Build.0 = Debug|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|x86.Build.0 = Debug|Any CPU
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|x64.ActiveCfg = Release|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|x64.Build.0 = Release|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|x86.Build.0 = Release|Any CPU
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|x64.Build.0 = Debug|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|x86.Build.0 = Debug|Any CPU
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|Any CPU.Build.0 = Release|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|x64.ActiveCfg = Release|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|x64.Build.0 = Release|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|x86.ActiveCfg = Release|Any CPU
+ {732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|x86.Build.0 = Release|Any CPU
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|x64.Build.0 = Debug|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|x86.Build.0 = Debug|Any CPU
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|x64.ActiveCfg = Release|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|x64.Build.0 = Release|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|x86.ActiveCfg = Release|Any CPU
+ {EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|x86.Build.0 = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|x64.Build.0 = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Debug|x86.Build.0 = Debug|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|x64.ActiveCfg = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|x64.Build.0 = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|x86.ActiveCfg = Release|Any CPU
+ {26168F0A-B6BC-4401-9AB0-4C902EC2B0E1}.Release|x86.Build.0 = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|x64.Build.0 = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Debug|x86.Build.0 = Debug|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x64.ActiveCfg = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x64.Build.0 = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x86.ActiveCfg = Release|Any CPU
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -191,6 +401,7 @@ Global
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
{732C166B-24BF-4A84-8C5F-4EDB6340CE45} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
+ {F5E486C4-6B92-4CEE-903F-54B3E7BE1406} = {99E702AB-714F-56EC-2FD9-16223EED3D31}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F077B358-8F0D-4791-8E85-DDED3550C27A}
diff --git a/Codout.Multitenancy/Softprime.Multitenancy.csproj b/Codout.Multitenancy/Softprime.Multitenancy.csproj
index 25e04fe..77cc661 100644
--- a/Codout.Multitenancy/Softprime.Multitenancy.csproj
+++ b/Codout.Multitenancy/Softprime.Multitenancy.csproj
@@ -1,8 +1,21 @@
-
+
+
+
+
+ obj\softprime\
+ obj\softprime\
+ bin\softprime\
+
+
+
6.3.0
netstandard2.0
+
+ $(DefaultItemExcludes);obj/**;bin/**
@@ -14,4 +27,6 @@
+
+
diff --git a/README.md b/README.md
index 38403f6..9df5e25 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 28955e7..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-version: '{build}'
-image: Visual Studio 2012
-
-init:
- - git config --global core.autocrlf true
-
-pull_requests:
- do_not_increment_build_number: true
-
-environment:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
- DOTNET_CLI_TELEMETRY_OPTOUT: true
-
-before_build:
- - cmd: dotnet restore
-
-branches:
- only:
- - master
-
-test: off
From b361024f4d621cb36b5edf79d41d17491bfa68bf Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 02:53:38 +0000
Subject: [PATCH 03/24] test: move EF.Tests para tests/ e inclui na solution
(Fase 2)
dotnet test na solution agora executa testes de verdade no CI de PR
e no gate do release.yml. Atualiza TEST_PROJECT no core-release.yml.
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
.github/workflows/core-release.yml | 2 +-
Codout.Framework.sln | 17 +++++++++++++++++
.../ClientGeneratedIdConventionTests.cs | 0
.../Codout.Framework.EF.Tests.csproj | 0
4 files changed, 18 insertions(+), 1 deletion(-)
rename {NetCore => tests}/Codout.Framework.EF.Tests/ClientGeneratedIdConventionTests.cs (100%)
rename {NetCore => tests}/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj (100%)
diff --git a/.github/workflows/core-release.yml b/.github/workflows/core-release.yml
index 24cea42..d8b32d8 100644
--- a/.github/workflows/core-release.yml
+++ b/.github/workflows/core-release.yml
@@ -36,7 +36,7 @@ env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
CONFIGURATION: Release
- TEST_PROJECT: NetCore/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj
+ TEST_PROJECT: tests/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj
DEFAULT_PROJECTS: >-
Codout.Framework.Data/Codout.Framework.Data.csproj
Codout.Framework.Domain/Codout.Framework.Domain.csproj
diff --git a/Codout.Framework.sln b/Codout.Framework.sln
index 4cb75ef..495124a 100644
--- a/Codout.Framework.sln
+++ b/Codout.Framework.sln
@@ -74,6 +74,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Codout.Multitenancy", "Codo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softprime.Multitenancy", "Codout.Multitenancy\Softprime.Multitenancy.csproj", "{F5E486C4-6B92-4CEE-903F-54B3E7BE1406}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.EF.Tests", "tests\Codout.Framework.EF.Tests\Codout.Framework.EF.Tests.csproj", "{A3AE642A-CB84-43DC-B1C3-583918B97173}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -372,6 +376,18 @@ Global
{F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x64.Build.0 = Release|Any CPU
{F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x86.ActiveCfg = Release|Any CPU
{F5E486C4-6B92-4CEE-903F-54B3E7BE1406}.Release|x86.Build.0 = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|x64.Build.0 = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Debug|x86.Build.0 = Debug|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x64.ActiveCfg = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x64.Build.0 = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x86.ActiveCfg = Release|Any CPU
+ {A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -402,6 +418,7 @@ Global
{732C166B-24BF-4A84-8C5F-4EDB6340CE45} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
{F5E486C4-6B92-4CEE-903F-54B3E7BE1406} = {99E702AB-714F-56EC-2FD9-16223EED3D31}
+ {A3AE642A-CB84-43DC-B1C3-583918B97173} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F077B358-8F0D-4791-8E85-DDED3550C27A}
diff --git a/NetCore/Codout.Framework.EF.Tests/ClientGeneratedIdConventionTests.cs b/tests/Codout.Framework.EF.Tests/ClientGeneratedIdConventionTests.cs
similarity index 100%
rename from NetCore/Codout.Framework.EF.Tests/ClientGeneratedIdConventionTests.cs
rename to tests/Codout.Framework.EF.Tests/ClientGeneratedIdConventionTests.cs
diff --git a/NetCore/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj b/tests/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj
similarity index 100%
rename from NetCore/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj
rename to tests/Codout.Framework.EF.Tests/Codout.Framework.EF.Tests.csproj
From a5155772abb169c99a48b1cff14ad8ab4a4b734c Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 02:55:33 +0000
Subject: [PATCH 04/24] =?UTF-8?q?chore!:=20remove=20=C3=A1rvores=20legadas?=
=?UTF-8?q?=20NetFull,=20NetCore,=20src/NetCore,=20DP=20e=20Shared=20(Fase?=
=?UTF-8?q?=203)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Remove ~2k linhas de código morto: targets EOL (netcoreapp2.0, EF6),
typo 'Commom', Codout.Framework.DP quebrado e appveyor remanescentes.
Nenhum desses projetos era publicado no NuGet; histórico preservado
no git. Atualiza CLAUDE.md e CHANGELOG.
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
CHANGELOG.md | 13 +
CLAUDE.md | 10 +-
.../Codout.Framework.DP.csproj | 28 -
Codout.Framework.DP/DPRepository.cs | 159 ----
Codout.Framework.DP/DbContext.cs | 20 -
Codout.Framework.DP/IDbStrategy.cs | 9 -
.../Codout.Framework.Commom.csproj | 17 -
.../Codout.Framework.NetCore.Data.csproj | 11 -
NetCore/Codout.Framework.Data/DatabaseCore.cs | 81 --
NetCore/Codout.Framework.Data/DatabaseUtil.cs | 761 ------------------
.../Codout.Framework.NetCore.Tests.csproj | 29 -
.../DomainTest.cs | 16 -
.../RepositoryTest.cs | 27 -
.../UnitOfWorkTest.cs | 55 --
.../UnitTesteContextEF.cs | 14 -
.../UnitTesteORMs.cs | 87 --
.../appsettings.json | 7 -
.../Codout.Framework.Commom.csproj | 54 --
.../Extensions/Images.cs | 139 ----
.../Codout.Framework.Commom/Helpers/Http.cs | 32 -
.../Helpers/ILocalData.cs | 9 -
.../Codout.Framework.Commom/Helpers/Local.cs | 77 --
.../Logger/ExceptionLogger.cs | 129 ---
.../Properties/AssemblyInfo.cs | 36 -
.../Codout.Framework.Data/AdoDatabaseUtil.cs | 48 --
.../Codout.Framework.NetFull.Data.csproj | 47 --
.../Properties/AssemblyInfo.cs | 36 -
.../Codout.Framework.NetFull.Tests/App.config | 17 -
.../Codout.Framework.NetFull.Tests.csproj | 96 ---
.../DomainTest.cs | 14 -
.../Properties/AssemblyInfo.cs | 20 -
.../RepositoryTest.cs | 12 -
.../UnitOfWorkTest.cs | 32 -
.../UnitTestEF_Full.cs | 36 -
.../packages.config | 6 -
.../App.config | 17 -
.../Codout.Framework.EF6.csproj | 65 --
.../EFRepository.cs | 249 ------
.../EFUnitOfWork.cs | 67 --
.../Properties/AssemblyInfo.cs | 36 -
.../packages.config | 4 -
Shared.msbuild | 36 -
.../Codout.Framework.Shared.Commom.projitems | 16 -
.../Codout.Framework.Shared.Commom.shproj | 13 -
.../Email/Configuration/Configuration.cs | 19 -
.../Email/Configuration/EmailConfiguration.cs | 155 ----
.../Email/Email.cs | 50 --
...Framework.NetCore.Repository.Cosmos.csproj | 32 -
.../CosmosRepository.cs | 192 -----
...ework.NetCore.Repository.DocumentDB.csproj | 25 -
.../DocumentDBContext.cs | 20 -
.../DocumentDBRepository.cs | 142 ----
52 files changed, 16 insertions(+), 3306 deletions(-)
delete mode 100644 Codout.Framework.DP/Codout.Framework.DP.csproj
delete mode 100644 Codout.Framework.DP/DPRepository.cs
delete mode 100644 Codout.Framework.DP/DbContext.cs
delete mode 100644 Codout.Framework.DP/IDbStrategy.cs
delete mode 100644 NetCore/Codout.Framework.Commom/Codout.Framework.Commom.csproj
delete mode 100644 NetCore/Codout.Framework.Data/Codout.Framework.NetCore.Data.csproj
delete mode 100644 NetCore/Codout.Framework.Data/DatabaseCore.cs
delete mode 100644 NetCore/Codout.Framework.Data/DatabaseUtil.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/Codout.Framework.NetCore.Tests.csproj
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/DomainTest.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/RepositoryTest.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/UnitOfWorkTest.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/UnitTesteContextEF.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/UnitTesteORMs.cs
delete mode 100644 NetCore/Codout.Framework.NetCore.Tests/appsettings.json
delete mode 100644 NetFull/Codout.Framework.Commom/Codout.Framework.Commom.csproj
delete mode 100644 NetFull/Codout.Framework.Commom/Extensions/Images.cs
delete mode 100644 NetFull/Codout.Framework.Commom/Helpers/Http.cs
delete mode 100644 NetFull/Codout.Framework.Commom/Helpers/ILocalData.cs
delete mode 100644 NetFull/Codout.Framework.Commom/Helpers/Local.cs
delete mode 100644 NetFull/Codout.Framework.Commom/Logger/ExceptionLogger.cs
delete mode 100644 NetFull/Codout.Framework.Commom/Properties/AssemblyInfo.cs
delete mode 100644 NetFull/Codout.Framework.Data/AdoDatabaseUtil.cs
delete mode 100644 NetFull/Codout.Framework.Data/Codout.Framework.NetFull.Data.csproj
delete mode 100644 NetFull/Codout.Framework.Data/Properties/AssemblyInfo.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/App.config
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/Codout.Framework.NetFull.Tests.csproj
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/DomainTest.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/Properties/AssemblyInfo.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/RepositoryTest.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/UnitOfWorkTest.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/UnitTestEF_Full.cs
delete mode 100644 NetFull/Codout.Framework.NetFull.Tests/packages.config
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/App.config
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/Codout.Framework.EF6.csproj
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/EFRepository.cs
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/EFUnitOfWork.cs
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/Properties/AssemblyInfo.cs
delete mode 100644 NetFull/Codout.Framework.Repository.EF6/packages.config
delete mode 100644 Shared.msbuild
delete mode 100644 Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.projitems
delete mode 100644 Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.shproj
delete mode 100644 Shared/Codout.Framework.Shared.Commom/Email/Configuration/Configuration.cs
delete mode 100644 Shared/Codout.Framework.Shared.Commom/Email/Configuration/EmailConfiguration.cs
delete mode 100644 Shared/Codout.Framework.Shared.Commom/Email/Email.cs
delete mode 100644 src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/Codout.Framework.NetCore.Repository.Cosmos.csproj
delete mode 100644 src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/CosmosRepository.cs
delete mode 100644 src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/Codout.Framework.NetCore.Repository.DocumentDB.csproj
delete mode 100644 src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBContext.cs
delete mode 100644 src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBRepository.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 729ab3b..428f53f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,19 @@ A partir desta entrada cada pacote NuGet é versionado individualmente
`Directory.Build.props`). As entradas abaixo identificam o pacote e a
versão afetados.
+## 2026-06-12
+
+### Repository
+
+- Removidas as árvores legadas `NetFull/`, `NetCore/`, `src/NetCore/` (Cosmos/DocumentDB em `netcoreapp2.0`, EOL), `Codout.Framework.DP` (quebrado, referenciava `Codout.Framework.DAL` inexistente), `Shared/Codout.Framework.Shared.Commom` e `Shared.msbuild`. Nenhum desses projetos era publicado no NuGet (`IsPackable=false` ou fora de `.github/release-packages.json`); o histórico permanece no git. O typo "Commom" deixa de existir no repositório.
+- Removido `appveyor.yml` (referenciava Visual Studio 2012; o pipeline real é GitHub Actions).
+- `Codout.Framework.Api.Dto` e `Softprime.Multitenancy` adicionados à `Codout.Framework.sln`; `Softprime.Multitenancy` passou a usar `obj`/`bin` isolados para coexistir com `Codout.Multitenancy` na mesma pasta sem corromper o restore.
+- Testes movidos para a pasta `tests/` na raiz e incluídos na solution — `dotnet test Codout.Framework.sln` (CI de PR e gate do `release.yml`) agora executa os testes de verdade. `core-release.yml` atualizado para o novo caminho.
+
+### Build
+
+- `dotnet.yml`: SDK do CI atualizado de 9.0.x para 10.0.x, alinhado ao `TargetFramework` `net10.0` do `Directory.Build.props`.
+
## 2026-05-12
### Build
diff --git a/CLAUDE.md b/CLAUDE.md
index bc25dde..50a1d88 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -57,15 +57,11 @@ Quando o usuário pedir "gerar nova versão e publicar do pacote X" (ou equivale
- **Codout.Framework.Mcp**: usa workflow próprio (`.github/workflows/mcp-release.yml`) com passo `--validate` específico do CLI. Tag deve ser `mcp-v` (NÃO use `mcp` como short-name no `release.yml` — está intencionalmente bloqueado por padrão de tag para evitar duplo release).
- **Mass release** (todos os pacotes de uma vez via `.github/workflows/mass-release.yml`): só execute se o usuário pedir explicitamente "mass release" ou "publicar todos". Mesmo assim, oriente o usuário a disparar via Actions UI com `dry_run: true` primeiro e revisar os artifacts antes de re-disparar com `dry_run: false`. Não tente disparar pela CLI sem autorização explícita.
-## Pacotes excluídos do release automatizado
+## Projetos legados removidos
-Os seguintes csproj têm `false ` e **não** estão em `.github/release-packages.json`. Não tente publicá-los antes de modernizá-los:
+Em 2026-06-12 as árvores legadas foram **removidas do repositório** (histórico preservado no git): `NetFull/`, `NetCore/`, `src/NetCore/` (Cosmos/DocumentDB), `Codout.Framework.DP`, `Shared/` e `Shared.msbuild`. Nenhum deles era publicado no NuGet. Se o usuário pedir por um deles (ex.: suporte a Cosmos), a recomendação é recriar do zero como pacote moderno (ex.: `Codout.Framework.Cosmos` com SDK `Microsoft.Azure.Cosmos`) — não recuperar o código antigo do histórico sem modernizá-lo.
-- `Codout.Framework.DP` — implementa um `IRepository` antigo (sem `Where`, `AllReadOnly`, `WherePaged`, `Refresh`, overloads de `CancellationToken`, etc.) e referencia a pasta legada `Codout.Framework.DAL` que não existe mais.
-- `src/NetCore/Codout.Framework.NetCore.Repository.Cosmos` — `netcoreapp2.0` (fora de suporte), SDK legado `Microsoft.Azure.DocumentDB.Core`, refs pra projetos `NetStandard.*` que sumiram do repo.
-- `src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB` — idem.
-
-Se o usuário pedir pra publicar um deles, **avise que está deprecated** e pergunte se quer fazer o port completo antes (não tente packar como está — vai falhar).
+Os shared projects ativos na raiz (`Codout.Framework.Api.Shared`, `Codout.Framework.Dto.Shared`) **não** são legados — são `.shproj` importados por Api, Api.Client, Api.Dto e Application.
## Cuidados ao usar `dotnet pack`
diff --git a/Codout.Framework.DP/Codout.Framework.DP.csproj b/Codout.Framework.DP/Codout.Framework.DP.csproj
deleted file mode 100644
index 832e5b3..0000000
--- a/Codout.Framework.DP/Codout.Framework.DP.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- 6.2.2
- netstandard2.1
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Codout.Framework.DP/DPRepository.cs b/Codout.Framework.DP/DPRepository.cs
deleted file mode 100644
index 7ccd8a2..0000000
--- a/Codout.Framework.DP/DPRepository.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-using System;
-using System.Data;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading.Tasks;
-using Codout.Framework.DAL.Entity;
-using Codout.Framework.DAL.Repository;
-using Dapper;
-using Dapper.Contrib.Extensions;
-using MicroOrm.Dapper.Repositories;
-using MicroOrm.Dapper.Repositories.SqlGenerator;
-
-namespace Codout.Framework.DP
-{
- public class DPRepository : DapperRepository, IRepository where T : class, IEntity
- {
- protected IDbConnection DbConnection { get; }
-
- public DPRepository(IDbConnection connection, ISqlGenerator sqlGenerator)
- : base(connection, sqlGenerator)
- {
- DbConnection = connection;
- }
-
- private IDbConnection CreateConnection()
- {
- DbConnection.Open();
- return DbConnection;
- }
-
- public void Dispose()
- {
- DbConnection.Dispose();
- }
-
- public IQueryable All()
- {
- using var con = CreateConnection();
-
- return DbConnection.GetAll().AsQueryable();
- }
-
- public IQueryable Find(Expression> predicate)
- {
- using var con = CreateConnection();
-
- return base.Find(predicate).AsQueryable();
- }
-
- public IQueryable Find(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public T Get(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public T Get(object key)
- {
- using var con = CreateConnection();
-
- return DbConnection.Get(key);
- }
-
- public T Load(object key)
- {
- return Get(key);
- }
-
- public void Delete(T entity)
- {
- using var con = CreateConnection();
-
- var result = DbConnection.Delete(entity);
- }
-
- public void Delete(Expression> predicate)
- {
- using var con = CreateConnection();
-
- base.Delete(predicate);
- }
-
- public T Save(T entity)
- {
- using var con = CreateConnection();
-
- var result = DbConnection.Insert(entity);
-
- return entity;
- }
-
- public T SaveOrUpdate(T entity)
- {
- using var con = CreateConnection();
-
- var result = DbConnection.Update(entity);
-
- return entity;
- }
-
- public void Update(T entity)
- {
- throw new NotImplementedException();
- }
-
- public T Merge(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task LoadAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task DeleteAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task DeleteAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveOrUpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task UpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task MergeAsync(T entity)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/Codout.Framework.DP/DbContext.cs b/Codout.Framework.DP/DbContext.cs
deleted file mode 100644
index ee37225..0000000
--- a/Codout.Framework.DP/DbContext.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Data;
-
-namespace Codout.Framework.DP
-{
- public class DbContext
- {
- private IDbStrategy _dbStrategy;
-
- public DbContext SetStrategy(IDbStrategy dbStrategy)
- {
- _dbStrategy = dbStrategy;
- return this;
- }
-
- public IDbConnection GetDbContext(string connectionString)
- {
- return _dbStrategy.GetConnection(connectionString);
- }
- }
-}
diff --git a/Codout.Framework.DP/IDbStrategy.cs b/Codout.Framework.DP/IDbStrategy.cs
deleted file mode 100644
index 9e9c9b5..0000000
--- a/Codout.Framework.DP/IDbStrategy.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Data;
-
-namespace Codout.Framework.DP
-{
- public interface IDbStrategy
- {
- IDbConnection GetConnection(string connectionString);
- }
-}
\ No newline at end of file
diff --git a/NetCore/Codout.Framework.Commom/Codout.Framework.Commom.csproj b/NetCore/Codout.Framework.Commom/Codout.Framework.Commom.csproj
deleted file mode 100644
index e3ecec9..0000000
--- a/NetCore/Codout.Framework.Commom/Codout.Framework.Commom.csproj
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- netcoreapp2.2
-
-
-
- Miscellaneous utility library package (CPF / CNPJ Validation, Range of Dates, Converters and etc) for .Net Core development
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetCore/Codout.Framework.Data/Codout.Framework.NetCore.Data.csproj b/NetCore/Codout.Framework.Data/Codout.Framework.NetCore.Data.csproj
deleted file mode 100644
index 32c981d..0000000
--- a/NetCore/Codout.Framework.Data/Codout.Framework.NetCore.Data.csproj
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- netcoreapp2.0
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetCore/Codout.Framework.Data/DatabaseCore.cs b/NetCore/Codout.Framework.Data/DatabaseCore.cs
deleted file mode 100644
index 62e82f2..0000000
--- a/NetCore/Codout.Framework.Data/DatabaseCore.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-namespace Codout.Framework.NetCore.Data
-{
-
- public abstract class DatabaseCore
- {
- ///
- /// Executa um comando SQL SELECT no banco de dados conectado.
- /// ATENÇÃO na superclasse DB ela não faz nada!!!
- /// SOMENTE UTILIZAR QUANDO O OBJETO FOR DO TIPO DBCon
- ///
- /// Query a ser executada
- public virtual void ExecutaConsulta(string sql)
- {
- }
-
- ///
- /// Executa um comando SQL SELECT no banco de dados conectado.
- /// ATENÇÃO na superclasse DB ela não faz nada!!!
- /// SOMENTE UTILIZAR QUANDO O OBJETO FOR DO TIPO DBCon
- ///
- /// Query a ser executada
- /// se true, converte para maiúsculo.
- public virtual void ExecutaConsulta(string sql, bool upperCase)
- {
- }
-
- ///
- /// Move para o próximo registro.
- /// ATENÇÃO na superclasse DB não faz nada!!!
- ///
- /// Retorna true se obtiver sucesso
- public virtual bool MoveProximo()
- {
- return true;
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do número da mesma (inicando em zero).
- /// ATENÇÃO na superclasse DB não faz nada!!!
- ///
- /// Número da coluna
- /// Retorna um objeto com o valor da coluna
- public virtual object Valor(int numero)
- {
- return null;
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do nome da coluna.
- /// ATENÇÃO na superclasse DB não faz nada!!!
- ///
- /// Nome da coluna
- /// Retorna um objeto com o valor da coluna
- public virtual object Valor(string nomeColuna)
- {
- return null;
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do nome da coluna.
- /// ATENÇÃO na superclasse DB não faz nada!!!
- ///
- /// Nome da coluna
- /// Retorna um byte[] com o valor da coluna
- public virtual byte[] ValorBytes(string nomeColuna)
- {
- return null;
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do nome da coluna.
- /// ATENÇÃO na superclasse DB não faz nada!!!
- ///
- /// Numero coluna
- /// Retorna um byte[] com o valor da coluna
- public virtual byte[] ValorBytes(int numeroColuna)
- {
- return null;
- }
- }
-}
diff --git a/NetCore/Codout.Framework.Data/DatabaseUtil.cs b/NetCore/Codout.Framework.Data/DatabaseUtil.cs
deleted file mode 100644
index 78263cf..0000000
--- a/NetCore/Codout.Framework.Data/DatabaseUtil.cs
+++ /dev/null
@@ -1,761 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Data.Common;
-
-namespace Codout.Framework.NetCore.Data
-{
- public abstract class DatabaseUtil : DatabaseCore
- {
-
- #region Atributos locais
- ///
- /// Nome da classe para identificação
- ///
- private readonly string _className = "Codout.Framework.NetCore.Data.DatabaseUtil.";
-
- ///
- /// indica se o reader está aberto ou não
- ///
- private bool _readerAberto;
-
- ///
- /// indicador para controlar a primeira execução de um comando preparado
- ///
- private bool _primeiroExecutaComandoPrep = true;
-
- private DbCommand _comando;
- private DbDataReader _reader;
- private DbTransaction _transacao;
- #endregion
-
- #region Construtor / Destrutor
- ///
- /// Construtor com conexão
- ///
- /// Objeto defidamente tipado da conexão
- protected DatabaseUtil(DbConnection objDbConnection)
- {
-
- Conexao = objDbConnection;
- }
-
- ///
- /// Destrutor, libera a conexão com o banco de dados caso ainda não esteja feito.
- ///
- ~DatabaseUtil()
- {
- Desconecta();
- }
-
- public DataTable GetSchema()
- {
- return Conexao.GetSchema();
- }
- #endregion
-
- #region Conexão / Desconexão
- ///
- /// Efetua a conexão com o banco de dados, já disponiblizando os métodos de consultas através de reader e comandos (comand).
- ///
- /// Indica a string da conexão a ser utilizada para conectar no banco
- public void Conecta(string stringConexao)
- {
- try
- {
-
- if (Conectado)
- Conexao.Close();
- Conexao.ConnectionString = stringConexao;
- Conexao.Open();
- _comando = Conexao.CreateCommand();
- Conectado = true;
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "Conecta()", ex);
- }
-
- }
-
- ///
- /// Realiza a desconexão com o banco de dados.
- ///
- public void Desconecta()
- {
- try
- {
- if (Conectado)
- Conexao.Close();
- Conexao.Dispose();
- _comando.Dispose();
- Conectado = false;
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "Desconecta()", ex);
- }
- }
- #endregion
-
- #region Controle de Transações
- ///
- /// Inicia uma transação na conexão atual.
- ///
- public void IniciaTransacao()
- {
- try
- {
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
-
- if (Conectado)
- {
- _transacao = Conexao.BeginTransaction(IsolationLevel.ReadCommitted);
- _comando.Transaction = _transacao;
- }
- else
- {
- throw new Exception("Transação não pode ser iniciada, sem a conexão com o banco, utilize antes DbCon.Conecta()");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "IniciaTransacao()", ex);
- }
- }
-
- ///
- /// Efetua o commit (confirmando toda transação) no banco de dados, dos comandos executados após o início da transação com o método IniciaTransacao.
- ///
- public void ConfirmaTransacao()
- {
- try
- {
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
- if (_transacao != null)
- _transacao.Commit();
- else
- {
- throw new Exception("Transação não pode ser confimada, provavelmente não foi iniciada.");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ConfirmaTransacao()", ex);
- }
- }
-
- ///
- /// Efetua o rollback nos comandos realizados após o início da transação com o método IniciaTransacao.
- ///
- public void CancelaTransacao()
- {
- try
- {
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
- if (_transacao != null)
- _transacao.Rollback();
- else
- {
- throw new Exception("Transação não pode ser cancelada, provavelmente não foi iniciada.");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "CancelaTransacao()", ex);
- }
- }
- #endregion
-
- #region Execução de comandos (INSERT, UPDATE e DELETE)
- ///
- /// Executa um comando SQL no banco de dados conectado
- /// Somente comandos (INSERT, UPDATE, DELETE).
- ///
- /// Comando SQL a ser executado
- /// returna o número de linhas afetadas pelo commando SQL
- public int ExecutaComando(string sql)
- {
- return ExecutaComando(sql, true);
- }
-
- ///
- /// Executa um comando SQL no banco de dados conectado
- /// Somente comandos (INSERT, UPDATE, DELETE).
- ///
- /// Comando SQL a ser executado
- /// true se deseja converter a query para maiúsculas
- /// returna o número de linhas afetadas pelo commando SQL
- public int ExecutaComando(string sql, bool upperCase)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
-
- int linhas;
- try
- {
- _comando.CommandText = (upperCase) ? sql.ToUpper() : sql;
- _comando.Parameters.Clear();
- linhas = _comando.ExecuteNonQuery();
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ExecutaComando()", ex);
- }
-
- return linhas;
- }
-
- ///
- /// Pepara o objeto command para executar querys parametrizadas, deve ser utilizada antes do método ExecutaComandoPrep, pois inicializa os parametros.
- ///
- /// Comando SQL a ser executado
- /// /// Lista de camos com nome(chave) e tipo(valor) do campo
- public void PreparaComando(string sql, List> camposKeyValuePair)
- {
- PreparaComando(sql, camposKeyValuePair, true);
- }
-
- ///
- /// Pepara o objeto command para executar querys parametrizadas, deve ser utilizada antes do método ExecutaComandoPrep, pois inicializa os parametros.
- ///
- /// Comando SQL a ser executado
- /// Lista de camos com nome(chave) e tipo(valor) do campo
- /// se true, converte a query para maiúsculos
- public void PreparaComando(string sql, List> camposKeyValuePair, bool upperCase)
- {
-
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
-
- _comando.CommandText = (upperCase) ? sql.ToUpper() : sql;
- _comando.Parameters.Clear();
-
- foreach (var campo in camposKeyValuePair)
- {
- var parametro = _comando.CreateParameter();
- parametro.ParameterName = campo.Key;
- parametro.DbType = campo.Value;
- _comando.Parameters.Add(parametro);
- }
-
- _primeiroExecutaComandoPrep = true;
-
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "PreparaComando()", ex);
- }
- }
-
- ///
- /// Executa comandos (INSERT, UPDATE e DELETE) parametrizados.
- /// os valores devem estar na seqüência utilizada na chamada ao método PreparaComando().
- ///
- /// Valores para serem utilizados na execução do comando parametrizado
- public void ExecutaComandoPrep(List valores)
- {
- ExecutaComandoPrep(valores, true);
- }
-
- ///
- /// Executa comandos (INSERT, UPDATE e DELETE) parametrizados.
- /// os valores devem estar na seqüência utilizada na chamada ao método PreparaComando().
- ///
- /// Valores para serem utilizados na execução do comando parametrizado
- /// se true, converte os valores para maiúsculos conforme o caso.
- public void ExecutaComandoPrep(List valores, bool upperCase)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- var i = 0;
- foreach (var valor in valores)
- {
- //se o valor estiver nulo, então vamos setar o valor nulo também
- _comando.Parameters[i].Value = valor.Equals(null) ? DBNull.Value : valor;
- i++;
- }
-
- //se for a primeira vez então devemos executar o Prepare antes de tudo
- if (_primeiroExecutaComandoPrep)
- {
- _primeiroExecutaComandoPrep = false;
-
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
-
- _comando.Prepare();
- }
-
- _comando.ExecuteNonQuery();
-
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ExecutaComandoPrep()", ex);
- }
- }
- #endregion
-
- #region Execução de comandos com PREPARE e EXECUÇÃO DIRETAMENTE
- ///
- /// Executa comandos (INSERT, UPDATE e DELETE) parametrizados.
- /// Já executa o prepare e o comando diretamente, não precisa utilizar o método PreparaComando antes,
- /// até porque não tem efeito nenhum :)
- ///
- /// Query a ser executada, com parametro sinalizados com '@'
- /// Vetor de chave(nome campo)/valor(dbType do banco) de parametros da query, NÃO inserir '@' antes de cada nome de campo
- /// Valores para serem utilizados na execução do comando parametrizado
- public void ExecutaComandoParametrizado(string sql, List> camposKeyValuePair, List valores)
- {
- ExecutaComandoParametrizado(sql, camposKeyValuePair, valores, true);
- }
-
- ///
- /// Executa comandos (INSERT, UPDATE e DELETE) parametrizados.
- /// Já executa o prepare e o comando diretamente, não precisa utilizar o método PreparaComando antes,
- /// até porque não tem efeito nenhum :)
- ///
- /// Query a ser executada, com parametro sinalizados com '@'
- /// Vetor de chave(nome campo)/valor(dbType do banco) de parametros da query, NÃO inserir '@' antes de cada nome de campo
- /// Valores para serem utilizados na execução do comando parametrizado
- /// indica se coloca o sql em uppercase (maíusculas)
- public void ExecutaComandoParametrizado(string sql, List> camposKeyValuePair, List valores, bool upperCase)
- {
- ExecutaComandoParametrizado(sql, camposKeyValuePair, valores, upperCase, false);
- }
-
- ///
- /// Executa comandos (INSERT, UPDATE e DELETE) parametrizados.
- /// Já executa o prepare e o comando diretamente, não precisa utilizar o método PreparaComando antes,
- /// até porque não tem efeito nenhum :)
- ///
- /// Query a ser executada, com parametro sinalizados com '@'
- /// Vetor de chave(nome campo)/valor(dbType do banco) de parametros da query, NÃO inserir '@' antes de cada nome de campo
- /// Valores para serem utilizados na execução do comando parametrizado
- /// se true, converte a query para maiúsculas
- /// indica se é uma consulta (SELECT)
- internal void ExecutaComandoParametrizado(string sql, List> camposKeyValuePair, List valores, bool upperCase, bool isConsultaSql)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
-
- var fullName = Conexao.GetType().FullName;
- var simboloParametro = "@";
- //quandor for Oracle ou PostGreesql, trocamos @ por :
- if (fullName.Contains("Oracle") || fullName.Contains("Npgsql"))
- {
- simboloParametro = ":";
- sql = sql.Replace("@", simboloParametro);
- }
-
- _comando.CommandText = (upperCase) ? sql.ToUpper() : sql;
- _comando.Parameters.Clear();
-
- var i = 0;
- foreach (var campo in camposKeyValuePair)
- {
- var parametro = _comando.CreateParameter();
- parametro.ParameterName = simboloParametro + campo.Key;
- parametro.DbType = campo.Value;
- parametro.Value = valores[i].Equals(null) ? DBNull.Value : valores[i];
-
- _comando.Parameters.Add(parametro);
- i++;
- }
-
- //se for a primeira vez então devemos executar o Prepare antes de tudo
- //se o reader estiver aberto é preciso fechar, se não dá erro
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
-
- _comando.Prepare();
-
- if (isConsultaSql)
- {
- _reader = _comando.ExecuteReader();
- _readerAberto = true;
- }
- else
- _comando.ExecuteNonQuery();
-
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ExecutaComandoParametrizado()", ex);
- }
- }
- #endregion
-
- #region Execução de Consultas
- ///
- /// Executa um comando SQL SELECT COUNT no banco de dados conectado.
- /// Retorna um valor através do ExecuteScalar, que é mais rápido pois busca apenas a primeira linha/coluna do cursor retornado pelo banco.
- ///
- /// Query a ser executada
- /// Retorna o resultado da contagem
- public int ExecutaCount(string sql)
- {
- return ExecutaCount(sql, true);
- }
-
- ///
- /// Executa um comando SQL SELECT COUNT no banco de dados conectado.
- /// Retorna um valor através do ExecuteScalar, que é mais rápido pois busca apenas a primeira linha/coluna do cursor retornado pelo banco.
- ///
- /// Query a ser executada
- /// se true, converte a query para maiúsculas
- /// Retorna o resultado da contagem
- public int ExecutaCount(string sql, bool upperCase)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
- _comando.Parameters.Clear();
- _comando.CommandText = sql.ToUpper();
- return (int)_comando.ExecuteScalar();
- }
- catch (Exception ex)
- {
- _readerAberto = false;
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ExecutaCount()", ex);
- }
- }
-
- ///
- /// Executa um comando SQL SELECT no banco de dados conectado e prepara um cursor (DataReader) para ser lido.
- /// Vale lembrar que o datareader somente permite leitura para frente.
- ///
- /// Query a ser executada
- public override void ExecutaConsulta(string sql)
- {
- ExecutaConsulta(sql, true);
- }
-
- ///
- /// Executa um comando SQL SELECT no banco de dados conectado e prepara um cursor (DataReader) para ser lido.
- /// Vale lembrar que o datareader somente permite leitura para frente.
- ///
- /// Query a ser executada
- /// se true, converte a query para maiúsculas
- public override void ExecutaConsulta(string sql, bool upperCase)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
- _comando.Parameters.Clear();
- _comando.CommandText = (upperCase) ? sql.ToUpper() : sql;
- _reader = _comando.ExecuteReader();
- _readerAberto = true;
- }
- catch (Exception ex)
- {
- _readerAberto = false;
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ExecutaConsulta()", ex);
- }
- }
-
- ///
- /// Executa uma SELECT com parâmetros indicados por @, exemplo: SELECT * FROM TESTE WHERE DATA = @DATAFILTRO
- ///
- /// Query a ser executada, com parametro sinalizados com '@'
- /// Vetor de chave(nome campo)/valor(dbType do banco) de parametros da query, NÃO inserir '@' antes de cada nome de campo
- /// Valores para serem utilizados na execução do comando parametrizado
- public void ExecutaConsultaParametrizada(string sql, List> camposKeyValuePair, List valores)
- {
- ExecutaComandoParametrizado(sql, camposKeyValuePair, valores, true, true);
- }
-
- ///
- /// Libera o datareader caso esteja aberto
- ///
- internal void LiberaDataReader()
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- {
- if (!_reader.IsClosed) _reader = null;
- _readerAberto = false;
- }
- }
- catch (Exception ex)
- {
- _readerAberto = false;
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "LiberaDataReader()", ex);
- }
- }
- #endregion
-
- #region Navegação e busca de dados
- ///
- /// Move o datareader para o próximo registro
- ///
- /// Retorna true se obtiver sucesso
- public override bool MoveProximo()
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- if (_reader.Read())
- return true;
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "MoveProximo()", ex);
- }
-
- return false;
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do número da mesma (inicando em zero).
- ///
- /// Número da coluna
- /// Retorna um objeto com o valor da coluna
- public override object Valor(int numero)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- return _reader.GetValue(numero);
- else
- {
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "Valor()", ex);
- }
- }
-
- ///
- /// Obtem um vetor de bytes do conteúdo da coluna, através do nome da coluna.
- ///
- /// Nome da coluna
- /// Retorna um vetor de bytes com o valor da coluna
- public override byte[] ValorBytes(string nomeColuna)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- {
- return ValorBytes(_reader.GetOrdinal(nomeColuna));
- }
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ValorBytes()", ex);
- }
- }
-
-
- ///
- /// Obtem um vetor de bytes do conteúdo da coluna, através do número da mesma (inicando em zero).
- ///
- /// Número da coluna
- /// Retorna um vetor de bytes com o valor da coluna
- public override byte[] ValorBytes(int numeroColuna)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- {
- return (byte[])_reader.GetValue(numeroColuna);
- }
- else
- {
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ValorBytes()", ex);
- }
- }
-
- ///
- /// Obtem o valor da coluna indicada, através do nome da coluna.
- ///
- /// Nome da coluna
- /// Retorna um objeto com o valor da coluna
- public override object Valor(string nomeColuna)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- return _reader.GetValue(_reader.GetOrdinal(nomeColuna));
- else
- {
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "Valor()", ex);
- }
- }
-
- ///
- /// Obtem o número de campos retornados no DataReader atual.
- ///
- public int NumeroCampos()
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- return _reader.FieldCount;
- else
- {
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "NumeroCampos()", ex);
- }
- }
-
- ///
- /// Obtem o tipo de dados da coluna indicada, no DataReader atual.
- ///
- /// Nome da coluna a ser verificada
- /// System.Type do campo
- public Type ObtemTipoCampo(string nomeColuna)
- {
- if (!Conectado)
- {
- throw new Exception("Comando não pode ser executado sem antes conectar ao banco, use DbCon.Conecta!");
- }
-
- try
- {
- if (_readerAberto)
- return _reader.GetFieldType(_reader.GetOrdinal(nomeColuna));
- else
- {
- throw new Exception("O reader não está aberto, provavelmente não foi executado o método ExecutaConsulta");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Erro: " + ex.Message + Environment.NewLine + "Origem->" + _className + "ObtemTipoCampo()", ex);
- }
- }
- #endregion
-
- #region Propriedades
- ///
- /// Propriedade que indica se está conectado no banco de dados.
- ///
- public bool Conectado { get; private set; }
-
- ///
- /// Propriedade que retorna o objeto DbConnection utilizado no momento.
- ///
- public DbConnection Conexao { get; }
- #endregion
-
- }
-}
diff --git a/NetCore/Codout.Framework.NetCore.Tests/Codout.Framework.NetCore.Tests.csproj b/NetCore/Codout.Framework.NetCore.Tests/Codout.Framework.NetCore.Tests.csproj
deleted file mode 100644
index 513cc7a..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/Codout.Framework.NetCore.Tests.csproj
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- netstandard2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
diff --git a/NetCore/Codout.Framework.NetCore.Tests/DomainTest.cs b/NetCore/Codout.Framework.NetCore.Tests/DomainTest.cs
deleted file mode 100644
index b8a40bd..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/DomainTest.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using Codout.Framework.Domain.DAL;
-
-namespace Codout.Framework.NetCore.Tests
-{
- public abstract class Entity : EntityWithTypedId
- {
- }
-
- public class Customer : Entity
- {
- public string Nome { get; set; }
- }
-
-
-}
diff --git a/NetCore/Codout.Framework.NetCore.Tests/RepositoryTest.cs b/NetCore/Codout.Framework.NetCore.Tests/RepositoryTest.cs
deleted file mode 100644
index da6fa61..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/RepositoryTest.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Codout.Framework.DAL.Repository;
-using Codout.Framework.EF;
-using Codout.Framework.Mongo;
-using Codout.Framework.NetCore.Repository.Mongo;
-
-namespace Codout.Framework.NetCore.Tests
-{
-
- public interface ICustomerRepository : IRepository
- { }
-
-
- public class RepositoryCustomerEF : EFRepository, ICustomerRepository
- {
- public RepositoryCustomerEF(DbContext context) : base(context)
- {
- }
- }
-
- public class RepositoryCustomerMongo : MongoRepository, ICustomerRepository
- {
- public RepositoryCustomerMongo(MongoDbContext context) : base(context)
- {
- }
- }
-}
diff --git a/NetCore/Codout.Framework.NetCore.Tests/UnitOfWorkTest.cs b/NetCore/Codout.Framework.NetCore.Tests/UnitOfWorkTest.cs
deleted file mode 100644
index 26d86cc..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/UnitOfWorkTest.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using Codout.Framework.DAL;
-using Codout.Framework.EF;
-using Codout.Framework.Mongo;
-
-namespace Codout.Framework.NetCore.Tests
-{
- public interface IUnitOfWorkTest : IUnitOfWork
- {
- ICustomerRepository Customers { get; }
- }
-
- #region UnitOfWorkTestEF
- public class UnitOfWorkTestEF : EFUnitOfWork, IUnitOfWorkTest
- {
- private ICustomerRepository _customers;
-
- public UnitOfWorkTestEF(UnitTesteContextEF instance)
- : base(instance)
- {
- }
-
- public ICustomerRepository Customers => _customers ?? (_customers = new RepositoryCustomerEF(DbContext));
-
- public new void Dispose()
- {
- _customers?.Dispose();
- GC.SuppressFinalize(this);
- }
- }
- #endregion
-
- #region UnitOfWorkTestMongo
- public class UnitOfWorkTestMongo : MongoUnitOfWork, IUnitOfWorkTest
- {
-
- private ICustomerRepository _customers;
-
- public UnitOfWorkTestMongo(MongoDbContext instance)
- : base(instance)
- {
- }
-
- public ICustomerRepository Customers => _customers ?? (_customers = new RepositoryCustomerMongo(MongoDbContext));
-
- public new void Dispose()
- {
- _customers?.Dispose();
- GC.SuppressFinalize(this);
- }
- }
- #endregion
-
-}
-
diff --git a/NetCore/Codout.Framework.NetCore.Tests/UnitTesteContextEF.cs b/NetCore/Codout.Framework.NetCore.Tests/UnitTesteContextEF.cs
deleted file mode 100644
index 8592240..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/UnitTesteContextEF.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Codout.Framework.NetCore.Tests
-{
- public class UnitTesteContextEF : DbContext
- {
- public UnitTesteContextEF(DbContextOptions options)
- : base(options)
- {
- }
-
- public DbSet Customers { get; set; }
- }
-}
diff --git a/NetCore/Codout.Framework.NetCore.Tests/UnitTesteORMs.cs b/NetCore/Codout.Framework.NetCore.Tests/UnitTesteORMs.cs
deleted file mode 100644
index f96c414..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/UnitTesteORMs.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
-using System.IO;
-using Codout.Framework.Mongo;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-
-namespace Codout.Framework.NetCore.Tests
-{
- [TestClass]
- public class UnitTesteORMs
- {
- [TestMethod]
- public void TestaInclusaoELeituraDBSQLEF()
- {
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json");
- var config = configuration.Build();
-
- var builder = new DbContextOptionsBuilder();
- builder.UseSqlServer(config.GetConnectionString("EF"));
-
- IUnitOfWorkTest unitOfWorkTest = new UnitOfWorkTestEF(new UnitTesteContextEF(builder.Options));
-
- var guid = Guid.Parse("97A7513F-7D57-4171-96B5-AE02E2A9C6CE");
-
- var cliente = unitOfWorkTest.Customers.Get(guid);
-
- if (cliente == null)
- {
- cliente = new Customer { Nome = "José da Silva" };
- cliente.SetId((guid));
- unitOfWorkTest.Customers.Save(cliente);
- }
- else
- {
- cliente.Nome = $"José da Silva + {DateTime.Now.ToShortDateString()} + {DateTime.Now.ToLongTimeString()}";
- unitOfWorkTest.Customers.Update(cliente);
- }
-
- unitOfWorkTest.SaveChanges();
-
- var obj = unitOfWorkTest.Customers.Get(guid);
-
- Assert.AreEqual(guid, obj.Id);
- }
-
- [TestMethod]
- public void TestaInclusaoELeituraMongoDB()
- {
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json");
- var config = configuration.Build();
-
- var mongoDBOptions = new MongoDBOptions
- {
- ConnectionString = config.GetConnectionString("MongoDB"),
- DatabaseName = config["MongoDBDatabaseName"]
- };
-
- //**** CHECAR A STRING DE CONEXÃO NO ARQUIVO appsettings.json
- IUnitOfWorkTest unitOfWorkTest = new UnitOfWorkTestMongo(new MongoDbContext(mongoDBOptions));
-
- Guid? guid = Guid.Parse("97A7513F-7D57-4171-96B5-AE02E2A9C6CE");
-
- Customer cliente = unitOfWorkTest.Customers.Get(guid);
- if (cliente == null)
- {
- cliente = new Customer { Nome = "José da Silva" };
- cliente.SetId((guid));
- unitOfWorkTest.Customers.Save(cliente);
- }
- else
- {
- cliente.Nome = $"José da Silva + {DateTime.Now.ToShortDateString()} + {DateTime.Now.ToLongTimeString()}";
- unitOfWorkTest.Customers.Update(cliente);
- }
- unitOfWorkTest.SaveChanges();
-
- var obj = unitOfWorkTest.Customers.Get(guid);
-
- Assert.AreEqual(guid, obj.Id);
- }
- }
-}
diff --git a/NetCore/Codout.Framework.NetCore.Tests/appsettings.json b/NetCore/Codout.Framework.NetCore.Tests/appsettings.json
deleted file mode 100644
index 59d57c4..0000000
--- a/NetCore/Codout.Framework.NetCore.Tests/appsettings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "MongoDBDatabaseName": "FrameworkTeste",
- "ConnectionStrings": {
- "MongoDB": "mongodb://localhost:32771",
- "EF": "Server=.\\SQL2014;Database=FrameworkTeste;Integrated Security=true;MultipleActiveResultSets=true"
- }
-}
diff --git a/NetFull/Codout.Framework.Commom/Codout.Framework.Commom.csproj b/NetFull/Codout.Framework.Commom/Codout.Framework.Commom.csproj
deleted file mode 100644
index 1b29571..0000000
--- a/NetFull/Codout.Framework.Commom/Codout.Framework.Commom.csproj
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {503E31F1-E4EF-4FA5-825C-9381ED6930CA}
- Library
- Properties
- Codout.Framework.Commom
- Codout.Framework.Commom
- v4.6.2
- 512
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Commom/Extensions/Images.cs b/NetFull/Codout.Framework.Commom/Extensions/Images.cs
deleted file mode 100644
index 682431e..0000000
--- a/NetFull/Codout.Framework.Commom/Extensions/Images.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using System;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-
-namespace Codout.Framework.Commom.Extensions
-{
- ///
- /// Extensões comuns para tipos relacionadas a imagens.
- ///
- public static class Images
- {
- #region CreateThumbnail
- ///
- /// Cria uma miniatura da imagem
- ///
- /// Imagem original
- /// A (máxima) largura.
- /// Retorna a imagem
- public static Image CreateThumbnail(this Image image, int width)
- {
- //overload for just maximum width
- return image.CreateThumbnail(width, 0);
- }
- #endregion
-
- #region CreateThumbnail
- ///
- /// Cria uma miniatura da imagem
- ///
- /// Imagem original
- /// A (máxima) largura.
- /// A (máxima) altura.
- /// Retorna a imagem
- public static Image CreateThumbnail(this Image image, int maxWidth, int maxHeight)
- {
- double centerWidth = 0;
- double centerHeight = 0;
- double width = Convert.ToDouble(image.Width);
- double height = Convert.ToDouble(image.Height);
- double widthFinal;
- double heightFinal;
- double newWidth = width;
- double newHeight = height;
-
- double ratioWidth = maxWidth > 0 ? maxWidth / width : 0;
- double ratioHeight = maxHeight > 0 ? (maxHeight / height) : 0;
- double ratio = Math.Max(ratioWidth, ratioHeight);
-
- // Se a imagem é maior que o permitido, encolhe ela!
- if (ratio < 1)
- {
- newWidth = Math.Floor(Convert.ToDouble(ratio * width));
- newHeight = Math.Floor(Convert.ToDouble(ratio * height));
- widthFinal = (maxWidth != 0 && newWidth > maxWidth) ? maxWidth : newWidth;
- heightFinal = (maxHeight != 0 && newHeight > maxHeight) ? maxHeight : newHeight;
- if (maxWidth != 0 && (newWidth - maxWidth) != 0) centerWidth = (maxWidth - newWidth) / 2;
- if (maxHeight != 0 && (newHeight - maxHeight) != 0) centerHeight = (maxHeight - newHeight) / 2;
- }
- else
- {
- widthFinal = newWidth;
- heightFinal = newHeight;
- centerWidth = 0;
- centerHeight = 0;
- }
-
- //Criando a imagem no tamanho passado pelo parametro com o mesmo formado de pixel da imagem original
- Image oImgFinal = new Bitmap(Convert.ToInt32(widthFinal), Convert.ToInt32(heightFinal), image.PixelFormat);
-
- //Transformando o fundo em um Gráfico
- Graphics graphic = Graphics.FromImage(oImgFinal);
- // Alteramos algumas propriedades do objeto oGraphic para melhorar a qualidade final da imagem.
- graphic.CompositingQuality = CompositingQuality.HighQuality;
- graphic.SmoothingMode = SmoothingMode.HighQuality;
- graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphic.FillRectangle(Brushes.White, 0, 0, Convert.ToInt32(widthFinal), Convert.ToInt32(heightFinal));
-
- //Redimencionando Imagem original
- var finalSize = new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
- //Criando um fundo para colocar a imagem original
- var rectangle = new Rectangle(Convert.ToInt32(centerWidth), Convert.ToInt32(centerHeight), finalSize.Width, finalSize.Height);
- graphic.FillRectangle(Brushes.White, rectangle);
-
- // redimencionando a imagem original e o fundo dentro da imagem nova.
- graphic.DrawImage(image, rectangle);
-
- //E por fim produzimos a saída da página como uma imagem JPG.
- image.Dispose();
-
- return oImgFinal;
- }
- #endregion
-
- #region Crop
- ///
- /// Método de corte de uma imagem
- ///
- /// Imagem a ser recortada
- /// Altura
- /// Largura
- /// Coordenada X
- /// Coordenada Y
- ///
- public static Image Crop(this Image image, int width, int height, int x, int y)
- {
- Image bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
-
- Graphics graphic = Graphics.FromImage(bmp);
- graphic.CompositingQuality = CompositingQuality.HighQuality;
- graphic.SmoothingMode = SmoothingMode.HighQuality;
- graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphic.FillRectangle(Brushes.White, 0, 0, Convert.ToInt32(width), Convert.ToInt32(height));
-
- graphic.DrawImage(image, new Rectangle(0, 0, width, height), x, y, width, height, GraphicsUnit.Pixel);
-
- image.Dispose();
- graphic.Dispose();
-
- return bmp;
- }
- #endregion
-
- #region ToImage
- ///
- /// Converte um byte[] em uma Imagem
- ///
- /// Byte array contendo uma imagem.
- /// Uma imagem se a conversão ocorrer com sucesso.
- public static Image ToImage(this byte[] image)
- {
- if (image == null)
- return new Bitmap(1, 1);
-
- return new ImageConverter().ConvertFrom(image) as Image;
- }
- #endregion
- }
-}
diff --git a/NetFull/Codout.Framework.Commom/Helpers/Http.cs b/NetFull/Codout.Framework.Commom/Helpers/Http.cs
deleted file mode 100644
index bd5313c..0000000
--- a/NetFull/Codout.Framework.Commom/Helpers/Http.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Web;
-
-namespace Codout.Framework.Commom.Helpers
-{
- public static class Http
- {
- ///
- /// Obtem o endereço IP de uma requisção Web
- ///
- /// Retorna o endereço IP
- public static string GetClientIp()
- {
- if (HttpContext.Current == null)
- return string.Empty;
-
- string result;
- var ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
- if (!string.IsNullOrEmpty(ip))
- {
- string[] ipRange = ip.Split(',');
- int le = ipRange.Length - 1;
- result = ipRange[0];
- }
- else
- {
- result = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
- }
-
- return result;
- }
- }
-}
diff --git a/NetFull/Codout.Framework.Commom/Helpers/ILocalData.cs b/NetFull/Codout.Framework.Commom/Helpers/ILocalData.cs
deleted file mode 100644
index c22075d..0000000
--- a/NetFull/Codout.Framework.Commom/Helpers/ILocalData.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Codout.Framework.Commom.Helpers
-{
- public interface ILocalData
- {
- object this[object key] { get; set; }
- int Count { get; }
- void Clear();
- }
-}
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Commom/Helpers/Local.cs b/NetFull/Codout.Framework.Commom/Helpers/Local.cs
deleted file mode 100644
index e4cd22f..0000000
--- a/NetFull/Codout.Framework.Commom/Helpers/Local.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.Collections;
-using System.Web;
-
-namespace Codout.Framework.Commom.Helpers
-{
- public class Local
- {
- public static object Obj = new object();
-
- public static ILocalData Data { get; } = new LocalData();
-
- #region Nested type: LocalData
-
- private class LocalData : ILocalData
- {
- [ThreadStatic]
- private static Hashtable _localData;
-
- private static readonly object LocalDataHashtableKey = new object();
-
- private static Hashtable LocalHashtable
- {
- get
- {
- if (!RunningInWeb)
- {
- lock (Obj)
- {
- return _localData ?? (_localData = new Hashtable());
- }
- }
-
- var webHashtable = HttpContext.Current.Items[LocalDataHashtableKey] as Hashtable;
-
- if (webHashtable == null)
- {
- lock (Obj)
- {
- webHashtable = new Hashtable();
- HttpContext.Current.Items[LocalDataHashtableKey] = webHashtable;
- }
- }
-
- return webHashtable;
- }
- }
-
- private static bool RunningInWeb
- {
- get { return HttpContext.Current != null; }
- }
-
- #region ILocalData Members
-
- public object this[object key]
- {
- get { return LocalHashtable[key]; }
- set { LocalHashtable[key] = value; }
- }
-
- public int Count
- {
- get { return LocalHashtable.Count; }
- }
-
- public void Clear()
- {
- LocalHashtable.Clear();
- }
-
- #endregion
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Commom/Logger/ExceptionLogger.cs b/NetFull/Codout.Framework.Commom/Logger/ExceptionLogger.cs
deleted file mode 100644
index b08916f..0000000
--- a/NetFull/Codout.Framework.Commom/Logger/ExceptionLogger.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Web;
-
-namespace Codout.Framework.Commom.Logger
-{
- public class ExceptionLogger
- {
- private static ExceptionLogger _instance;
-
- private ExceptionLogger()
- {
-
- }
-
- public static ExceptionLogger Get { get { return _instance ?? (_instance = new ExceptionLogger()); } }
-
- private string GetExceptionTypeStack(Exception e)
- {
- if (e.InnerException != null)
- {
- var message = new StringBuilder();
- message.AppendLine(GetExceptionTypeStack(e.InnerException));
- message.AppendLine(" " + e.GetType());
- return (message.ToString());
- }
- return " " + e.GetType();
- }
-
- private string GetExceptionMessageStack(Exception e)
- {
- if (e.InnerException != null)
- {
- var message = new StringBuilder();
- message.AppendLine(GetExceptionMessageStack(e.InnerException));
- message.AppendLine(" " + e.Message);
- return (message.ToString());
- }
- return " " + e.Message;
- }
-
- private string GetExceptionCallStack(Exception e)
- {
- if (e.InnerException != null)
- {
- var message = new StringBuilder();
- message.AppendLine(GetExceptionCallStack(e.InnerException));
- message.AppendLine("--- Next Call Stack:");
- message.AppendLine(e.StackTrace);
- return (message.ToString());
- }
- return e.StackTrace;
- }
-
- private static TimeSpan GetSystemUpTime()
- {
- var upTime = new PerformanceCounter("System", "System Up Time");
- upTime.NextValue();
- return TimeSpan.FromSeconds(upTime.NextValue());
- }
-
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- private class MEMORYSTATUSEX
- {
- public uint dwLength;
- public uint dwMemoryLoad;
- public ulong ullTotalPhys;
- public ulong ullAvailPhys;
- public ulong ullTotalPageFile;
- public ulong ullAvailPageFile;
- public ulong ullTotalVirtual;
- public ulong ullAvailVirtual;
- public ulong ullAvailExtendedVirtual;
-
- public MEMORYSTATUSEX()
- {
- this.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
- }
- }
-
- [return: MarshalAs(UnmanagedType.Bool)]
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);
-
- public string GetExceptionDetails(Exception exception)
- {
- var error = new List();
-
- error.Add(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
-
- var memStatus = new MEMORYSTATUSEX();
- if (GlobalMemoryStatusEx(memStatus))
- {
- error.Add(memStatus.ullTotalPhys / (1024 * 1024) + "Mb");
- error.Add(memStatus.ullAvailPhys / (1024 * 1024) + "Mb");
- }
-
- if (HttpContext.Current != null)
- {
- error.Add(HttpContext.Current.Request.UserAgent);
- error.Add(HttpContext.Current.Request.Browser.Version);
- error.Add(HttpContext.Current.Request.UserLanguages != null ? string.Join(";", HttpContext.Current.Request.UserLanguages) : "");
- error.Add(HttpContext.Current.Request.UserHostAddress);
- error.Add(HttpContext.Current.Request.Url.AbsoluteUri);
- error.Add(
- $"{HttpContext.Current.Request.Browser.ScreenPixelsWidth}x{HttpContext.Current.Request.Browser.ScreenPixelsHeight}");
-
- var vars = new List();
- foreach (string key in HttpContext.Current.Request.Form.Keys)
- vars.Add($"{key}: {HttpContext.Current.Request.Form[key]}");
-
- error.Add(string.Join(", ", vars.ToArray()));
- }
-
- error.Add(GetExceptionTypeStack(exception));
- error.Add(GetExceptionMessageStack(exception));
- error.Add(GetExceptionCallStack(exception));
- var thisProcess = Process.GetCurrentProcess();
- foreach (ProcessModule module in thisProcess.Modules)
- error.Add(module.FileName + " " + module.FileVersionInfo.FileVersion);
-
- return string.Join(";", error.ToArray());
-
- }
- }
-}
diff --git a/NetFull/Codout.Framework.Commom/Properties/AssemblyInfo.cs b/NetFull/Codout.Framework.Commom/Properties/AssemblyInfo.cs
deleted file mode 100644
index 150d63e..0000000
--- a/NetFull/Codout.Framework.Commom/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Codout.Framework.Commom")]
-[assembly: AssemblyDescription("Miscellaneous utility library package (CPF / CNPJ Validation, Range of Dates, Converters and etc) for .Net Full Framework development")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Codout")]
-[assembly: AssemblyProduct("Codout.Framework.Commom")]
-[assembly: AssemblyCopyright("Copyright Codout © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("503e31f1-e4ef-4fa5-825c-9381ed6930ca")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/NetFull/Codout.Framework.Data/AdoDatabaseUtil.cs b/NetFull/Codout.Framework.Data/AdoDatabaseUtil.cs
deleted file mode 100644
index 9a1ed54..0000000
--- a/NetFull/Codout.Framework.Data/AdoDatabaseUtil.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Data.Common;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Codout.Framework.NetFull.Data
-{
- ///
- ///
- ///
- public class AdoDatabaseUtil
- {
-
- private NetStandard.Data.DatabaseUtil databaseUtil;
-
- public AdoDatabaseUtil(string nomeProvider)
- {
- try
- {
- var dbCon = DbProviderFactories.GetFactory(nomeProvider);
-
- databaseUtil = new NetStandard.Data.DatabaseUtil(dbCon.CreateConnection());
- }
- catch (Exception ex)
- {
- throw new Exception(ex.Message, ex);
- }
- }
-
- public static DataTable FactoryDatabase(string nomeProvider)
- {
- try
- {
- var lista = DbProviderFactories.GetFactoryClasses();
- return lista;
- }
- catch (Exception ex)
- {
- throw new Exception(ex.Message, ex);
- }
- }
-
- public NetStandard.Data.DatabaseUtil DBUtil { get { return databaseUtil; } }
- }
-}
diff --git a/NetFull/Codout.Framework.Data/Codout.Framework.NetFull.Data.csproj b/NetFull/Codout.Framework.Data/Codout.Framework.NetFull.Data.csproj
deleted file mode 100644
index 8e61701..0000000
--- a/NetFull/Codout.Framework.Data/Codout.Framework.NetFull.Data.csproj
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {644016F5-0FB0-4DA2-A02C-A113DDD56198}
- Library
- Properties
- Codout.Framework.NetFull.Data
- Codout.Framework.NetFull.Data
- v4.6.2
- 512
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Data/Properties/AssemblyInfo.cs b/NetFull/Codout.Framework.Data/Properties/AssemblyInfo.cs
deleted file mode 100644
index 437282e..0000000
--- a/NetFull/Codout.Framework.Data/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Codout.Framework.NetFull.Data")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Codout.Framework.NetFull.Data")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("644016f5-0fb0-4da2-a02c-a113ddd56198")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/NetFull/Codout.Framework.NetFull.Tests/App.config b/NetFull/Codout.Framework.NetFull.Tests/App.config
deleted file mode 100644
index 6ed2182..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/App.config
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.NetFull.Tests/Codout.Framework.NetFull.Tests.csproj b/NetFull/Codout.Framework.NetFull.Tests/Codout.Framework.NetFull.Tests.csproj
deleted file mode 100644
index a5831a5..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/Codout.Framework.NetFull.Tests.csproj
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {5683B394-932E-4E7A-95E1-D77E30A5FFCC}
- Library
- Properties
- Codout.Framework.NetFull.Tests
- Codout.Framework.NetFull.Tests
- v4.6.2
- 512
- {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 15.0
- $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
- $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
- False
- UnitTest
-
-
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll
-
-
- ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll
-
-
- ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
-
-
- ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {decef05f-6f17-448e-a850-38a0977db5bc}
- Codout.Framework.DAL
-
-
- {c1b0ef6f-49fd-4199-8ba3-7fd7ba3dd4ba}
- Codout.Framework.Domain
-
-
- {c2eefa12-a0dd-4ec4-bcfe-00b1dadc5797}
- Codout.Framework.Mongo
-
-
- {e13a6faa-e08e-455c-a487-3103496c91b4}
- Codout.Framework.EF6
-
-
-
-
-
-
- Este projeto faz referência a pacotes do NuGet que não estão presentes neste computador. Use a Restauração de Pacotes do NuGet para baixá-los. Para obter mais informações, consulte http://go.microsoft.com/fwlink/?LinkID=322105. O arquivo ausente é {0}.
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.NetFull.Tests/DomainTest.cs b/NetFull/Codout.Framework.NetFull.Tests/DomainTest.cs
deleted file mode 100644
index 688565f..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/DomainTest.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Codout.Framework.Domain.DAL;
-using System;
-
-namespace Codout.Framework.NetFull.Tests
-{
- public abstract class Entity : EntityWithTypedId
- {
- }
-
- public class Customer : Entity
- {
- public string Nome { get; set; }
- }
-}
diff --git a/NetFull/Codout.Framework.NetFull.Tests/Properties/AssemblyInfo.cs b/NetFull/Codout.Framework.NetFull.Tests/Properties/AssemblyInfo.cs
deleted file mode 100644
index ea4abee..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Codout.Framework.NetFull.Tests")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Codout.Framework.NetFull.Tests")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-[assembly: ComVisible(false)]
-
-[assembly: Guid("5683b394-932e-4e7a-95e1-d77e30a5ffcc")]
-
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/NetFull/Codout.Framework.NetFull.Tests/RepositoryTest.cs b/NetFull/Codout.Framework.NetFull.Tests/RepositoryTest.cs
deleted file mode 100644
index 26530b5..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/RepositoryTest.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Data.Entity;
-using Codout.Framework.EF6;
-
-namespace Codout.Framework.NetFull.Tests
-{
- public class RepositoryCustomer : EFRepository
- {
- public RepositoryCustomer(DbContext context) : base(context)
- {
- }
- }
-}
diff --git a/NetFull/Codout.Framework.NetFull.Tests/UnitOfWorkTest.cs b/NetFull/Codout.Framework.NetFull.Tests/UnitOfWorkTest.cs
deleted file mode 100644
index 9a5a91c..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/UnitOfWorkTest.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Data.Entity;
-using Codout.Framework.EF6;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Codout.Framework.NetFull.Tests
-{
- public class UnitOfWorkTest : EFUnitOfWork
- {
-
- private RepositoryCustomer _customers;
-
- public RepositoryCustomer Customers => _customers ?? (_customers = new RepositoryCustomer(DbContext));
-
- public new void Dispose()
- {
- _customers?.Dispose();
- GC.SuppressFinalize(this);
- }
- }
-
- public class UnitTesteContext : DbContext
- {
- // string conexão LocalDB
- //data source=(LocalDb)\MSSQLLocalDB;initial catalog=CodoutFameworkTeste;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework
- public UnitTesteContext() : base("data source=(LocalDb)\\MSSQLLocalDB;initial catalog=CodoutFameworkTeste;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework")
- {
- }
-
- public DbSet Customers { get; set; }
- }
-}
diff --git a/NetFull/Codout.Framework.NetFull.Tests/UnitTestEF_Full.cs b/NetFull/Codout.Framework.NetFull.Tests/UnitTestEF_Full.cs
deleted file mode 100644
index a69c819..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/UnitTestEF_Full.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
-
-namespace Codout.Framework.NetFull.Tests
-{
- [TestClass]
- public class UnitTestEF_Full
- {
- [TestMethod]
- public void TestaInclusaoELeituraDBSQLEF_Full()
- {
- //**** CHECAR A STRING DE CONEXÃO NO ARQUIVO UnitOfWorkTest.cs
- UnitOfWorkTest unitOfWorkTest = new UnitOfWorkTest();
-
- Guid? guid = Guid.Parse("97A7513F-7D57-4171-96B5-AE02E2A9C6CE");
-
- Customer cliente = unitOfWorkTest.Customers.Get(guid);
- if (cliente == null)
- {
- cliente = new Customer { Nome = "José da Silva" };
- cliente.SetId((guid));
- unitOfWorkTest.Customers.Save(cliente);
- }
- else
- {
- cliente.Nome = $"José da Silva + {DateTime.Now.ToShortDateString()} + {DateTime.Now.ToLongTimeString()}";
- unitOfWorkTest.Customers.Update(cliente);
- }
- unitOfWorkTest.SaveChanges();
-
- var obj = unitOfWorkTest.Customers.Get(guid);
-
- Assert.AreEqual(guid, obj.Id);
- }
- }
-}
diff --git a/NetFull/Codout.Framework.NetFull.Tests/packages.config b/NetFull/Codout.Framework.NetFull.Tests/packages.config
deleted file mode 100644
index 82b80e8..0000000
--- a/NetFull/Codout.Framework.NetFull.Tests/packages.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Repository.EF6/App.config b/NetFull/Codout.Framework.Repository.EF6/App.config
deleted file mode 100644
index 13ecece..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/App.config
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Repository.EF6/Codout.Framework.EF6.csproj b/NetFull/Codout.Framework.Repository.EF6/Codout.Framework.EF6.csproj
deleted file mode 100644
index d4dbb23..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/Codout.Framework.EF6.csproj
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {E13A6FAA-E08E-455C-A487-3103496C91B4}
- Library
- Properties
- Codout.Framework.EF6
- Codout.Framework.EF6
- v4.6.2
- 512
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll
-
-
- ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {decef05f-6f17-448e-a850-38a0977db5bc}
- Codout.Framework.DAL
-
-
-
-
\ No newline at end of file
diff --git a/NetFull/Codout.Framework.Repository.EF6/EFRepository.cs b/NetFull/Codout.Framework.Repository.EF6/EFRepository.cs
deleted file mode 100644
index 5f3493d..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/EFRepository.cs
+++ /dev/null
@@ -1,249 +0,0 @@
-using System;
-using System.Data.Entity;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading.Tasks;
-using Codout.Framework.DAL.Entity;
-using Codout.Framework.DAL.Repository;
-
-namespace Codout.Framework.EF6
-{
-
- ///
- /// Repositório genérico de dados para EntityFramework Full (6.1.3)
- ///
- /// Classe que define o tipo do repositório
- public class EFRepository : IRepository
- where T : class, IEntity
- {
- protected DbContext Context = null;
-
- public EFRepository(DbContext context)
- {
- Context = context;
- }
-
- protected DbSet DbSet => Context.Set();
-
- ///
- /// Retorna todos os objetos do repositório (pode ser lento)
- ///
- /// Lista de objetos
- public IQueryable All()
- {
- return DbSet.AsQueryable();
- }
-
- ///
- /// Retorna uma lista de objetos do repositório de acordo com o filtro apresentado
- ///
- /// Lista de objetos
- ///
- public IQueryable Find(Expression> predicate)
- {
- return DbSet.Where(predicate).AsQueryable();
- }
-
- ///
- /// Retorna uma lista de objetos do repositório de acordo com o filtro e com opção de paginação
- ///
- /// Filtro de bojetos
- /// Retorna o todal de objetos
- /// Indica o índice da paginação
- /// Tamanho da página
- /// Lista de objetos
- public IQueryable Find(Expression> filter, out int total, int index = 0, int size = 50 )
- {
- var skipCount = index * size;
-
- var resetSet = filter != null
- ? DbSet.Where(filter).AsQueryable()
- : DbSet.AsQueryable();
-
- resetSet = skipCount == 0
- ? resetSet.Take(size)
- : resetSet.Skip(skipCount).Take(size);
-
- total = resetSet.Count();
-
- return resetSet.AsQueryable();
- }
-
- ///
- /// Retorna um objeto do repositório de acordo com o filtro (não usar para ID nullabe)
- ///
- /// Filtro
- /// objeto
- public T Get(Expression> predicate)
- {
- return DbSet.Find(predicate);
- }
-
- ///
- /// Retorna um objeto de acordo com a Key
- ///
- /// Key do objeto
- /// objeto
- public T Get(object key)
- {
- return DbSet.Find(new[] { key });
- }
-
- ///
- /// Efetua a carga do objeto conforme a key
- ///
- /// Key do objeto
- /// Objeto
- public T Load(object keys)
- {
- return Get(keys);
- }
-
- ///
- /// Delete o objeto indicado do repositório de dados
- ///
- /// Objeto a ser deletado
- public void Delete(T entity)
- {
- DbSet.Remove(entity);
- }
-
- ///
- /// Deletra uma lista de objetos confrome o filtro
- ///
- /// Filtro de objetos a serem deletados
- public void Delete(Expression> predicate)
- {
- DbSet.RemoveRange(DbSet.Where(predicate));
- }
-
- ///
- /// Salva o objeto no repositório
- ///
- /// Objeto a ser salvo
- /// Retorna o mesmo objeto, para o caso de retornar algum Id gerado
- public T Save(T entity)
- {
- if (entity == null)
- {
- throw new ArgumentNullException("entity");
- }
- return DbSet.Add(entity);
- }
-
- ///
- /// Salva ou atualiza o objeto em questão (USAR SOMENTE SE O ID NÃO FOI SETADO)
- ///
- /// Objeto a ser salvo/atualizado
- /// Retorna o mesmo objeto, para o caso de retornar algum Id gerado
- public T SaveOrUpdate(T entity)
- {
- if (entity.IsTransient())
- DbSet.Add(entity);
- else
- Context.Entry(entity).State = EntityState.Modified;
-
- return entity;
- }
-
- ///
- /// Atualiza o objeto no repositório
- ///
- /// Objeto a ser atulizado
- public void Update(T entity)
- {
- Context.Entry(entity).State = EntityState.Modified;
- }
-
- ///
- /// Efetua o Merge do objeto no repositório
- ///
- /// Objeto a ser mesclado
- /// Retorna o mesmo objeto, para o caso de retornar algum Id gerado
- public T Merge(T entity)
- {
- Context.Entry(entity).State = EntityState.Modified;
- return entity;
- }
-
- private bool _disposed;
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- // Libera os componentes
- }
- }
- _disposed = true;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- public Task> AllAsync()
- {
- throw new NotImplementedException();
- }
-
- public Task> FindAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task> FindAsync(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task LoadAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task DeleteAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task DeleteAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveOrUpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task UpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task MergeAsync(T entity)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/NetFull/Codout.Framework.Repository.EF6/EFUnitOfWork.cs b/NetFull/Codout.Framework.Repository.EF6/EFUnitOfWork.cs
deleted file mode 100644
index 1d2a7af..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/EFUnitOfWork.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data.Entity;
-using Codout.Framework.DAL;
-using Codout.Framework.DAL.Entity;
-using Codout.Framework.DAL.Repository;
-
-namespace Codout.Framework.EF6
-{
- ///
- /// Unit of Work para repositório genérico com EntityFramework Full (6.1.3)
- ///
- public abstract class EFUnitOfWork : IUnitOfWork where T : DbContext, new()
- {
- private readonly IDictionary _repositories = new Dictionary();
-
- protected EFUnitOfWork()
- {
- DbContext = new T();
- }
-
- private bool _disposed;
-
- ///
- /// Conexto do EntityFrameworkCore
- ///
- public DbContext DbContext { get; }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- DbContext.Dispose();
- }
- }
- _disposed = true;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Efetua o SaveChanges do contexto (sessão) em questão
- ///
- public void SaveChanges()
- {
- DbContext.SaveChanges();
- }
-
- ///
- /// Repositório Genérico que será controlado
- ///
- /// Tipo do objeto
- /// Repositório concreto
- public IRepository Repository() where TEntity : class, IEntity
- {
- if (!_repositories.ContainsKey(typeof(TEntity)))
- _repositories.Add(typeof(TEntity), new EFRepository(DbContext));
- return _repositories[typeof(TEntity)] as IRepository;
- }
- }
-}
diff --git a/NetFull/Codout.Framework.Repository.EF6/Properties/AssemblyInfo.cs b/NetFull/Codout.Framework.Repository.EF6/Properties/AssemblyInfo.cs
deleted file mode 100644
index 1460504..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Codout.Framework.EF6")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Codout.Framework.EF6")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("e13a6faa-e08e-455c-a487-3103496c91b4")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/NetFull/Codout.Framework.Repository.EF6/packages.config b/NetFull/Codout.Framework.Repository.EF6/packages.config
deleted file mode 100644
index 02a4d9b..0000000
--- a/NetFull/Codout.Framework.Repository.EF6/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/Shared.msbuild b/Shared.msbuild
deleted file mode 100644
index e95b734..0000000
--- a/Shared.msbuild
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
- $(AssemblyName)
- Copyright (c) Codout and contributors (Clovis Coli Jr, Marcelo Paiva).
- Clovis Coli Jr, Marcelo Paiva
- Codout Solutions http://codout.com
- https://github.com/Codout/Codout.Framework/blob/master/LICENSE
- https://github.com/Codout/Codout.Framework/raw/master/logo-nuget.png
- https://github.com/Codout/Codout.Framework
- git
- Codout;Framework;ORM;DAL;NHibernate;
-
-
-
- 2.0.0
-
-
-
-
- $(DefineConstants);NETCORE
- portable
-
-
-
- false
- false
- false
- false
- false
- false
- false
- false
-
-
-
\ No newline at end of file
diff --git a/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.projitems b/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.projitems
deleted file mode 100644
index 73d4258..0000000
--- a/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.projitems
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
- true
- a494d36e-af26-408d-92db-797511d60588
-
-
- Codout.Framework.Shared.Commom
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.shproj b/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.shproj
deleted file mode 100644
index 57d9bae..0000000
--- a/Shared/Codout.Framework.Shared.Commom/Codout.Framework.Shared.Commom.shproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- a494d36e-af26-408d-92db-797511d60588
- 14.0
-
-
-
-
-
-
-
-
diff --git a/Shared/Codout.Framework.Shared.Commom/Email/Configuration/Configuration.cs b/Shared/Codout.Framework.Shared.Commom/Email/Configuration/Configuration.cs
deleted file mode 100644
index c74dee7..0000000
--- a/Shared/Codout.Framework.Shared.Commom/Email/Configuration/Configuration.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Configuration;
-
-namespace Codout.Framework.Commom.Email.Configuration
-{
- ///
- /// Acessa as configurações de envio de Emails
- ///
- public static class Email
- {
- ///
- /// Obtem as configurações de envio de Email
- ///
- ///
- public static EmailConfiguration EmailConfiguration
- {
- get { return (EmailConfiguration)ConfigurationManager.GetSection("softprime/email"); }
- }
- }
-}
diff --git a/Shared/Codout.Framework.Shared.Commom/Email/Configuration/EmailConfiguration.cs b/Shared/Codout.Framework.Shared.Commom/Email/Configuration/EmailConfiguration.cs
deleted file mode 100644
index f97e9d2..0000000
--- a/Shared/Codout.Framework.Shared.Commom/Email/Configuration/EmailConfiguration.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System.Configuration;
-
-namespace Codout.Framework.Commom.Email.Configuration
-{
- ///
- /// Obtem as configurações do envio de Email
- ///
- public class EmailConfiguration : ConfigurationSection
- {
-
- ///
- /// Define se o formato do Email é HTML ou Texto puro
- ///
- [ConfigurationProperty("isBodyHtml", IsRequired = true)]
- public bool IsBodyHtml
- {
- get
- {
- return (bool)this["isBodyHtml"];
- }
- set
- {
- this["isBodyHtml"] = value;
- }
- }
-
- ///
- /// Login para autenticação da conta de Email
- ///
- [ConfigurationProperty("userName", IsRequired = true)]
- public string UserName
- {
- get
- {
- return (string)this["userName"];
- }
- set
- {
- this["userName"] = value;
- }
- }
-
- ///
- /// Senha para autenticação da conta de Email
- ///
- [ConfigurationProperty("password", IsRequired = true)]
- public string Password
- {
- get
- {
- return (string)this["password"];
- }
- set
- {
- this["password"] = value;
- }
- }
-
- ///
- /// Obtém ou define o nome ou o endereço IP do host usado para transações de SMTP.
- ///
- [ConfigurationProperty("smtp", IsRequired = true)]
- public string Smtp
- {
- get
- {
- return (string)this["smtp"];
- }
- set
- {
- this["smtp"] = value;
- }
- }
-
- ///
- /// btém ou define a porta usada para transações de SMTP.
- ///
- [ConfigurationProperty("port", IsRequired = true)]
- public int Port
- {
- get
- {
- return (int)this["port"];
- }
- set
- {
- this["port"] = value;
- }
- }
-
- ///
- /// Especifique se o SmtpClient usa Secure Sockets Layer (SSL) para criptografar a conexão.
- ///
- [ConfigurationProperty("enableSsl", IsRequired = true)]
- public bool EnableSsl
- {
- get
- {
- return (bool)this["enableSsl"];
- }
- set
- {
- this["enableSsl"] = value;
- }
- }
-
- ///
- /// Especifica como e-mails enviados serão tratados.
- ///
- [ConfigurationProperty("defaultCredentials", IsRequired = true)]
- public bool DefaultCredentials
- {
- get
- {
- return (bool)this["defaultCredentials"];
- }
- set
- {
- this["defaultCredentials"] = value;
- }
- }
-
- ///
- /// Email de origem
- ///
- [ConfigurationProperty("emailFrom", IsRequired = true)]
- public string EmailFrom
- {
- get
- {
- return (string)this["emailFrom"];
- }
- set
- {
- this["emailFrom"] = value;
- }
- }
-
- ///
- /// Nome do Remetente do Email
- ///
- [ConfigurationProperty("displayName", IsRequired = false)]
- public string DisplayName
- {
- get
- {
- return (string)this["displayName"];
- }
- set
- {
- this["displayName"] = value;
- }
- }
- }
-}
diff --git a/Shared/Codout.Framework.Shared.Commom/Email/Email.cs b/Shared/Codout.Framework.Shared.Commom/Email/Email.cs
deleted file mode 100644
index 210ed19..0000000
--- a/Shared/Codout.Framework.Shared.Commom/Email/Email.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Net.Mail;
-using System.Text;
-
-namespace Codout.Framework.Commom.Email
-{
- ///
- /// Classe de envio de Emails
- ///
- public class Email
- {
- ///
- /// Envia Email
- /// Obs.: Verifique se existe configuração da conta de envio de Emails no arquivo .config
- ///
- /// Email de destino
- /// Assunto
- /// Mensagem do Email
- public void Send(string subject, string message, string emailTo)
- {
- if (string.IsNullOrWhiteSpace(emailTo))
- throw new Exception("Email must not be empty");
-
- var from = string.IsNullOrWhiteSpace(Configuration.Email.EmailConfiguration.DisplayName)
- ? new MailAddress(Configuration.Email.EmailConfiguration.EmailFrom)
- : new MailAddress(Configuration.Email.EmailConfiguration.EmailFrom,
- Configuration.Email.EmailConfiguration.DisplayName);
-
- var to = new MailAddress(emailTo.Trim());
-
- var mailMessage = new MailMessage(from, to)
- {
- Body = message,
- BodyEncoding = Encoding.UTF8,
- Subject = subject,
- SubjectEncoding = Encoding.UTF8,
- IsBodyHtml = Configuration.Email.EmailConfiguration.IsBodyHtml
- };
-
- var client = new SmtpClient(Configuration.Email.EmailConfiguration.Smtp, Configuration.Email.EmailConfiguration.Port)
- {
- UseDefaultCredentials = Configuration.Email.EmailConfiguration.DefaultCredentials,
- EnableSsl = Configuration.Email.EmailConfiguration.EnableSsl,
- Credentials = new System.Net.NetworkCredential(Configuration.Email.EmailConfiguration.UserName, Configuration.Email.EmailConfiguration.Password)
- };
-
- client.Send(mailMessage);
- }
- }
-}
diff --git a/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/Codout.Framework.NetCore.Repository.Cosmos.csproj b/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/Codout.Framework.NetCore.Repository.Cosmos.csproj
deleted file mode 100644
index 3d3d1cc..0000000
--- a/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/Codout.Framework.NetCore.Repository.Cosmos.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- 6.2.2
- netcoreapp2.0
-
- false
- true
- Marcelo & Clovis
- Codout
- Generic Repository Deployment Package for Azure Cosmos DB in .Net Projects
- https://github.com/Codout/Codout.Framework
- https://github.com/Codout/Codout.Framework
- Azure CosmosDB repository generic .net core
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/CosmosRepository.cs b/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/CosmosRepository.cs
deleted file mode 100644
index 461cd55..0000000
--- a/src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/CosmosRepository.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using Codout.Framework.NetStandard.Domain.Entity;
-using Codout.Framework.NetStandard.Repository;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Threading.Tasks;
-using Microsoft.Azure.Documents.Client;
-using Microsoft.Azure.Documents.Linq;
-using System.Reflection;
-using Microsoft.Azure.Documents;
-
-namespace Codout.Framework.NetCore.Repository.Cosmos
-{
- ///
- /// Repositório genérico de dados para Azure CosmosDB
- ///
- /// Classe que define o tipo do repositório
- public class CosmosRepository : IRepository where T : class, IEntity
- {
- private static DocumentClient client;
- private static string DatabaseId;
- private static string CollectionId;
-
- public CosmosRepository(string endPoint, string key, string databaseId, string collectionId)
- {
- client = new DocumentClient(new Uri(endPoint), key, new ConnectionPolicy { EnableEndpointDiscovery = false });
- DatabaseId = databaseId;
- CollectionId = collectionId;
- }
-
- ///
- public IQueryable All()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Retorna todos os objetos do repositório (pode ser lento)
- ///
- /// Lista de objetos
- public async Task> AllAsync()
- {
- IDocumentQuery query = client.CreateDocumentQuery(
- UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
- new FeedOptions { MaxItemCount = -1 })
- .AsDocumentQuery();
-
- List results = new List();
- while (query.HasMoreResults)
- {
- results.AddRange(await query.ExecuteNextAsync());
- }
-
- return results.AsQueryable();
- }
-
- public void Delete(T entity)
- {
- }
-
- public void Delete(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public async Task DeleteAsync(T entity)
- {
- await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, GetIdValue(entity).ToString()));
- }
-
- public Task DeleteAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- private bool _disposed;
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- // Libera os componentes
- }
- }
- _disposed = true;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- public IQueryable Find(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public IQueryable Find(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public Task> FindAsync(Expression> predicate)
- {
- return AllAsync().Result.Where(predicate);
- }
-
- public Task> FindAsync(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public T Get(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public T Get(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public Task GetAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public T Load(object key)
- {
- throw new NotImplementedException();
- }
-
- public Task LoadAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public T Merge(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task MergeAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public T Save(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public T SaveOrUpdate(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task SaveOrUpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public void Update(T entity)
- {
- throw new NotImplementedException();
- }
-
- public Task UpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public static object GetIdValue(T entity)
- {
- return entity.GetType().GetTypeInfo().GetProperty("Id").GetValue(entity);
- }
- }
-}
diff --git a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/Codout.Framework.NetCore.Repository.DocumentDB.csproj b/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/Codout.Framework.NetCore.Repository.DocumentDB.csproj
deleted file mode 100644
index 9907699..0000000
--- a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/Codout.Framework.NetCore.Repository.DocumentDB.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- 6.2.2
- netcoreapp2.0
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBContext.cs b/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBContext.cs
deleted file mode 100644
index ed000e9..0000000
--- a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBContext.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.Azure.Documents.Client;
-
-namespace Codout.Framework.NetCore.Repository.DocumentDB
-{
- public class DocumentDBContext
- {
-
- private static DocumentClient client;
- private static string DatabaseId;
- private static string CollectionId;
-
- public DocumentDBContext()
- {
- var database = new Microsoft.Azure.Documents.Database();
- }
- }
-}
diff --git a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBRepository.cs b/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBRepository.cs
deleted file mode 100644
index ed80c34..0000000
--- a/src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/DocumentDBRepository.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Text;
-using System.Threading.Tasks;
-using Codout.Framework.NetStandard.Domain.Entity;
-using Codout.Framework.NetStandard.Repository;
-
-namespace Codout.Framework.NetCore.Repository.DocumentDB
-{
- ///
- public class DocumentDBRepository : IRepository where T : class, IEntity
- {
- public void Dispose()
- {
- throw new NotImplementedException();
- }
-
- ///
- public IQueryable All()
- {
- throw new NotImplementedException();
- }
-
- ///
- public async Task> AllAsync()
- {
- throw new NotImplementedException();
- }
-
- public IQueryable Find(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public async Task> FindAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public IQueryable Find(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public async Task> FindAsync(Expression> filter, out int total, int index = 0, int size = 50)
- {
- throw new NotImplementedException();
- }
-
- public T Get(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public async Task GetAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public T Get(object key)
- {
- throw new NotImplementedException();
- }
-
- public async Task GetAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public T Load(object key)
- {
- throw new NotImplementedException();
- }
-
- public async Task LoadAsync(object key)
- {
- throw new NotImplementedException();
- }
-
- public void Delete(T entity)
- {
- throw new NotImplementedException();
- }
-
- public async Task DeleteAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public void Delete(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public async Task DeleteAsync(Expression> predicate)
- {
- throw new NotImplementedException();
- }
-
- public T Save(T entity)
- {
- throw new NotImplementedException();
- }
-
- public async Task SaveAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public T SaveOrUpdate(T entity)
- {
- throw new NotImplementedException();
- }
-
- public async Task SaveOrUpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public void Update(T entity)
- {
- throw new NotImplementedException();
- }
-
- public async Task UpdateAsync(T entity)
- {
- throw new NotImplementedException();
- }
-
- public T Merge(T entity)
- {
- throw new NotImplementedException();
- }
-
- public async Task MergeAsync(T entity)
- {
- throw new NotImplementedException();
- }
- }
-}
From 6789171059b1d70552b3109867f94f1cc766cc1c Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 03:00:23 +0000
Subject: [PATCH 05/24] ci: adiciona Dependabot, CodeQL e cobertura no CI de PR
(Fase 8 parcial)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- dependabot.yml: updates semanais de NuGet e GitHub Actions, minors
e patches agrupados num PR
- codeql.yml: análise de segurança C# em push/PR/agenda semanal
- dotnet.yml: concurrency com cancel-in-progress em PR e coleta de
cobertura (XPlat Code Coverage) com upload de artifact
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
.github/dependabot.yml | 22 ++++++++++++++++++
.github/workflows/codeql.yml | 44 ++++++++++++++++++++++++++++++++++++
.github/workflows/dotnet.yml | 16 ++++++++++---
3 files changed, 79 insertions(+), 3 deletions(-)
create mode 100644 .github/dependabot.yml
create mode 100644 .github/workflows/codeql.yml
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..0a9fb44
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ open-pull-requests-limit: 5
+ groups:
+ # Agrupa updates minor/patch num PR só; majors saem individualmente
+ # para revisão de breaking changes.
+ nuget-minor-patch:
+ update-types: ["minor", "patch"]
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ groups:
+ actions:
+ patterns: ["*"]
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..7ae255a
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,44 @@
+name: CodeQL
+
+on:
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ["master"]
+ schedule:
+ - cron: "30 5 * * 1"
+
+jobs:
+ analyze:
+ name: Analyze (csharp)
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ packages: read
+ actions: read
+ contents: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: csharp
+ build-mode: manual
+
+ - name: Build
+ run: |
+ dotnet restore Codout.Framework.sln
+ dotnet build Codout.Framework.sln --no-restore --configuration Release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:csharp"
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index a623634..3dafd11 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,5 +1,4 @@
-# This workflow will build a .NET project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
+# CI de PR/push: build + testes da solution completa.
name: .NET
@@ -9,6 +8,10 @@ on:
pull_request:
branches: [ "master" ]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
jobs:
build:
@@ -25,4 +28,11 @@ jobs:
- name: Build
run: dotnet build --no-restore
- name: Test
- run: dotnet test --no-build --verbosity normal
+ run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
+ - name: Upload coverage
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: coverage
+ path: ./coverage
+ retention-days: 14
From 48e317e58c661e8499ad852f564c6434de891ab3 Mon Sep 17 00:00:00 2001
From: Claude
Date: Fri, 12 Jun 2026 03:16:42 +0000
Subject: [PATCH 06/24] =?UTF-8?q?test:=20su=C3=ADtes=20para=20Common,=20Se?=
=?UTF-8?q?curity,=20Image,=20Data,=20Domain,=20EF,=20Mailer=20e=20Storage?=
=?UTF-8?q?=20(Fase=204,=20leva=201)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
561 testes (10 projetos em tests/) cobrindo: extensões e helpers do
Common, hash+verify dos 3 providers de Security, contratos de Data,
identidade/igualdade de Domain, EFRepository+UnitOfWork contra SQLite
in-memory (incl. SaveOrUpdate 6.3.0), Mailer com mocks e renderização
Razor real, Storage e Storage.Azure offline.
Bugs pré-existentes encontrados estão documentados como
characterization tests e em tests/FINDINGS-{A,B,C}.md — nenhuma
mudança de comportamento em código de produção.
https://claude.ai/code/session_01BnjVoHxcLymD2Mp9RcdG3u
---
Codout.Framework.sln | 135 +++++++++
.../Codout.Framework.Common.Tests.csproj | 22 ++
.../Extensions/DateTimeExtensionsTests.cs | 141 +++++++++
.../Extensions/NumericExtensionsTests.cs | 84 ++++++
.../Extensions/ObjectExtensionsTests.cs | 80 +++++
.../Extensions/StringExtensionsTests.cs | 206 +++++++++++++
.../Extensions/ValidationExtensionsTests.cs | 151 ++++++++++
.../Helpers/EnumHelperTests.cs | 85 ++++++
.../Helpers/InflectorTests.cs | 95 ++++++
.../Helpers/LimitedListTests.cs | 70 +++++
.../Helpers/NumberToTextTests.cs | 46 +++
.../Helpers/SlugHelperTests.cs | 75 +++++
.../Security/CryptoStringTests.cs | 105 +++++++
.../Security/CryptoTests.cs | 44 +++
.../Security/RandomPasswordTests.cs | 68 +++++
.../Security/SecureHashTests.cs | 129 ++++++++
.../Codout.Framework.Data.Tests.csproj | 22 ++
.../EntityAbstractionsTests.cs | 93 ++++++
.../RepositoryContractTests.cs | 99 ++++++
.../TestEntities.cs | 25 ++
.../UnitOfWorkContractTests.cs | 67 +++++
.../AuditAndEventsTests.cs | 78 +++++
.../Codout.Framework.Domain.Tests.csproj | 23 ++
.../EntityEqualityTests.cs | 181 +++++++++++
.../EntityIdentityTests.cs | 147 +++++++++
.../ValidatableObjectTests.cs | 69 +++++
.../ValueObjectTests.cs | 129 ++++++++
.../EFRepositoryAsyncTests.cs | 185 ++++++++++++
.../EFRepositoryCrudTests.cs | 281 +++++++++++++++++
.../EFRepositorySaveOrUpdateTests.cs | 222 ++++++++++++++
.../EFUnitOfWorkTests.cs | 262 ++++++++++++++++
.../InterceptorTests.cs | 165 ++++++++++
.../SpecificationTests.cs | 158 ++++++++++
.../TestInfrastructure.cs | 126 ++++++++
.../AzureStorageArgumentValidationTests.cs | 174 +++++++++++
.../AzureStorageBlobUriTests.cs | 96 ++++++
.../AzureStorageConstructionTests.cs | 93 ++++++
...odout.Framework.Storage.Azure.Tests.csproj | 25 ++
.../ServiceCollectionExtensionsTests.cs | 100 ++++++
.../Codout.Framework.Storage.Tests.csproj | 22 ++
.../InMemoryStorageContractTests.cs | 284 ++++++++++++++++++
.../StorageExceptionsTests.cs | 106 +++++++
.../StorageModelsTests.cs | 67 +++++
.../StorageOptionsTests.cs | 59 ++++
.../Codout.Image.Extensions.Tests.csproj | 22 ++
.../ImageExtensionsTests.cs | 148 +++++++++
.../Codout.Mailer.Razor.Tests.csproj | 32 ++
.../ConfigureServicesTests.cs | 77 +++++
.../RazorRenderingIntegrationTests.cs | 89 ++++++
.../RazorViewTemplateEngineTests.cs | 114 +++++++
.../Templates/Welcome.cshtml | 7 +
.../Codout.Mailer.Razor.Tests/WelcomeModel.cs | 8 +
.../Codout.Mailer.Tests/AwsDispatcherTests.cs | 113 +++++++
.../Codout.Mailer.Tests.csproj | 27 ++
.../ConfigureServicesTests.cs | 80 +++++
tests/Codout.Mailer.Tests/ExtensionsTests.cs | 73 +++++
.../Codout.Mailer.Tests/HtmlUtilitiesTests.cs | 146 +++++++++
.../MailerServiceBaseTests.cs | 149 +++++++++
tests/Codout.Mailer.Tests/ModelsTests.cs | 61 ++++
.../SendGridDispatcherTests.cs | 65 ++++
tests/Codout.Security.Tests/Argon2Tests.cs | 179 +++++++++++
tests/Codout.Security.Tests/BcryptTests.cs | 115 +++++++
.../Codout.Security.Tests.csproj | 26 ++
tests/Codout.Security.Tests/CoreTests.cs | 69 +++++
tests/Codout.Security.Tests/ScryptTests.cs | 106 +++++++
tests/FINDINGS-A.md | 86 ++++++
tests/FINDINGS-B.md | 68 +++++
tests/FINDINGS-C.md | 119 ++++++++
68 files changed, 6873 insertions(+)
create mode 100644 tests/Codout.Framework.Common.Tests/Codout.Framework.Common.Tests.csproj
create mode 100644 tests/Codout.Framework.Common.Tests/Extensions/DateTimeExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Extensions/NumericExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Extensions/ObjectExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Extensions/StringExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Extensions/ValidationExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Helpers/EnumHelperTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Helpers/InflectorTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Helpers/LimitedListTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Helpers/NumberToTextTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Helpers/SlugHelperTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Security/CryptoStringTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Security/CryptoTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Security/RandomPasswordTests.cs
create mode 100644 tests/Codout.Framework.Common.Tests/Security/SecureHashTests.cs
create mode 100644 tests/Codout.Framework.Data.Tests/Codout.Framework.Data.Tests.csproj
create mode 100644 tests/Codout.Framework.Data.Tests/EntityAbstractionsTests.cs
create mode 100644 tests/Codout.Framework.Data.Tests/RepositoryContractTests.cs
create mode 100644 tests/Codout.Framework.Data.Tests/TestEntities.cs
create mode 100644 tests/Codout.Framework.Data.Tests/UnitOfWorkContractTests.cs
create mode 100644 tests/Codout.Framework.Domain.Tests/AuditAndEventsTests.cs
create mode 100644 tests/Codout.Framework.Domain.Tests/Codout.Framework.Domain.Tests.csproj
create mode 100644 tests/Codout.Framework.Domain.Tests/EntityEqualityTests.cs
create mode 100644 tests/Codout.Framework.Domain.Tests/EntityIdentityTests.cs
create mode 100644 tests/Codout.Framework.Domain.Tests/ValidatableObjectTests.cs
create mode 100644 tests/Codout.Framework.Domain.Tests/ValueObjectTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/EFRepositoryAsyncTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/EFRepositoryCrudTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/EFRepositorySaveOrUpdateTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/EFUnitOfWorkTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/InterceptorTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/SpecificationTests.cs
create mode 100644 tests/Codout.Framework.EF.Tests/TestInfrastructure.cs
create mode 100644 tests/Codout.Framework.Storage.Azure.Tests/AzureStorageArgumentValidationTests.cs
create mode 100644 tests/Codout.Framework.Storage.Azure.Tests/AzureStorageBlobUriTests.cs
create mode 100644 tests/Codout.Framework.Storage.Azure.Tests/AzureStorageConstructionTests.cs
create mode 100644 tests/Codout.Framework.Storage.Azure.Tests/Codout.Framework.Storage.Azure.Tests.csproj
create mode 100644 tests/Codout.Framework.Storage.Azure.Tests/ServiceCollectionExtensionsTests.cs
create mode 100644 tests/Codout.Framework.Storage.Tests/Codout.Framework.Storage.Tests.csproj
create mode 100644 tests/Codout.Framework.Storage.Tests/InMemoryStorageContractTests.cs
create mode 100644 tests/Codout.Framework.Storage.Tests/StorageExceptionsTests.cs
create mode 100644 tests/Codout.Framework.Storage.Tests/StorageModelsTests.cs
create mode 100644 tests/Codout.Framework.Storage.Tests/StorageOptionsTests.cs
create mode 100644 tests/Codout.Image.Extensions.Tests/Codout.Image.Extensions.Tests.csproj
create mode 100644 tests/Codout.Image.Extensions.Tests/ImageExtensionsTests.cs
create mode 100644 tests/Codout.Mailer.Razor.Tests/Codout.Mailer.Razor.Tests.csproj
create mode 100644 tests/Codout.Mailer.Razor.Tests/ConfigureServicesTests.cs
create mode 100644 tests/Codout.Mailer.Razor.Tests/RazorRenderingIntegrationTests.cs
create mode 100644 tests/Codout.Mailer.Razor.Tests/RazorViewTemplateEngineTests.cs
create mode 100644 tests/Codout.Mailer.Razor.Tests/Templates/Welcome.cshtml
create mode 100644 tests/Codout.Mailer.Razor.Tests/WelcomeModel.cs
create mode 100644 tests/Codout.Mailer.Tests/AwsDispatcherTests.cs
create mode 100644 tests/Codout.Mailer.Tests/Codout.Mailer.Tests.csproj
create mode 100644 tests/Codout.Mailer.Tests/ConfigureServicesTests.cs
create mode 100644 tests/Codout.Mailer.Tests/ExtensionsTests.cs
create mode 100644 tests/Codout.Mailer.Tests/HtmlUtilitiesTests.cs
create mode 100644 tests/Codout.Mailer.Tests/MailerServiceBaseTests.cs
create mode 100644 tests/Codout.Mailer.Tests/ModelsTests.cs
create mode 100644 tests/Codout.Mailer.Tests/SendGridDispatcherTests.cs
create mode 100644 tests/Codout.Security.Tests/Argon2Tests.cs
create mode 100644 tests/Codout.Security.Tests/BcryptTests.cs
create mode 100644 tests/Codout.Security.Tests/Codout.Security.Tests.csproj
create mode 100644 tests/Codout.Security.Tests/CoreTests.cs
create mode 100644 tests/Codout.Security.Tests/ScryptTests.cs
create mode 100644 tests/FINDINGS-A.md
create mode 100644 tests/FINDINGS-B.md
create mode 100644 tests/FINDINGS-C.md
diff --git a/Codout.Framework.sln b/Codout.Framework.sln
index 495124a..047ead2 100644
--- a/Codout.Framework.sln
+++ b/Codout.Framework.sln
@@ -78,6 +78,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.EF.Tests", "tests\Codout.Framework.EF.Tests\Codout.Framework.EF.Tests.csproj", "{A3AE642A-CB84-43DC-B1C3-583918B97173}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Common.Tests", "tests\Codout.Framework.Common.Tests\Codout.Framework.Common.Tests.csproj", "{F6F7B69B-6463-42FE-9A69-24B955A41B01}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Tests", "tests\Codout.Security.Tests\Codout.Security.Tests.csproj", "{EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Image.Extensions.Tests", "tests\Codout.Image.Extensions.Tests\Codout.Image.Extensions.Tests.csproj", "{83C5C172-E450-404B-A906-804B7BE2705C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Data.Tests", "tests\Codout.Framework.Data.Tests\Codout.Framework.Data.Tests.csproj", "{7A223A32-04EC-4F53-BE85-E6F1379E11FE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Domain.Tests", "tests\Codout.Framework.Domain.Tests\Codout.Framework.Domain.Tests.csproj", "{4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Mailer.Tests", "tests\Codout.Mailer.Tests\Codout.Mailer.Tests.csproj", "{56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Mailer.Razor.Tests", "tests\Codout.Mailer.Razor.Tests\Codout.Mailer.Razor.Tests.csproj", "{3C9B2F21-27B8-4E51-BAC6-D3401179FB51}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Storage.Tests", "tests\Codout.Framework.Storage.Tests\Codout.Framework.Storage.Tests.csproj", "{6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Storage.Azure.Tests", "tests\Codout.Framework.Storage.Azure.Tests\Codout.Framework.Storage.Azure.Tests.csproj", "{C5338C76-F592-4DA9-B20F-2C931749326C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -388,6 +406,114 @@ Global
{A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x64.Build.0 = Release|Any CPU
{A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x86.ActiveCfg = Release|Any CPU
{A3AE642A-CB84-43DC-B1C3-583918B97173}.Release|x86.Build.0 = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|x64.Build.0 = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Debug|x86.Build.0 = Debug|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|x64.ActiveCfg = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|x64.Build.0 = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|x86.ActiveCfg = Release|Any CPU
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01}.Release|x86.Build.0 = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|x64.Build.0 = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Debug|x86.Build.0 = Debug|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|x64.ActiveCfg = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|x64.Build.0 = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|x86.ActiveCfg = Release|Any CPU
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769}.Release|x86.Build.0 = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|x64.Build.0 = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Debug|x86.Build.0 = Debug|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|x64.ActiveCfg = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|x64.Build.0 = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|x86.ActiveCfg = Release|Any CPU
+ {83C5C172-E450-404B-A906-804B7BE2705C}.Release|x86.Build.0 = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|x64.Build.0 = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Debug|x86.Build.0 = Debug|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|x64.ActiveCfg = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|x64.Build.0 = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|x86.ActiveCfg = Release|Any CPU
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE}.Release|x86.Build.0 = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|x64.Build.0 = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Debug|x86.Build.0 = Debug|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|x64.ActiveCfg = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|x64.Build.0 = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|x86.ActiveCfg = Release|Any CPU
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A}.Release|x86.Build.0 = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|x64.Build.0 = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Debug|x86.Build.0 = Debug|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|x64.ActiveCfg = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|x64.Build.0 = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|x86.ActiveCfg = Release|Any CPU
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B}.Release|x86.Build.0 = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|x64.Build.0 = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Debug|x86.Build.0 = Debug|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|x64.ActiveCfg = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|x64.Build.0 = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|x86.ActiveCfg = Release|Any CPU
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51}.Release|x86.Build.0 = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|x64.Build.0 = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Debug|x86.Build.0 = Debug|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|x64.ActiveCfg = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|x64.Build.0 = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|x86.ActiveCfg = Release|Any CPU
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC}.Release|x86.Build.0 = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|x64.Build.0 = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Debug|x86.Build.0 = Debug|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|x64.ActiveCfg = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|x64.Build.0 = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|x86.ActiveCfg = Release|Any CPU
+ {C5338C76-F592-4DA9-B20F-2C931749326C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -419,6 +545,15 @@ Global
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
{F5E486C4-6B92-4CEE-903F-54B3E7BE1406} = {99E702AB-714F-56EC-2FD9-16223EED3D31}
{A3AE642A-CB84-43DC-B1C3-583918B97173} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {F6F7B69B-6463-42FE-9A69-24B955A41B01} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {EC2CEEE3-07A2-4EA5-ABD7-9827F0C30769} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {83C5C172-E450-404B-A906-804B7BE2705C} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {7A223A32-04EC-4F53-BE85-E6F1379E11FE} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {4E61BF3A-F824-4BDE-9997-21FC53BBDD7A} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {56D21A7D-F8A5-436A-B52D-B8427DBE5E3B} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {3C9B2F21-27B8-4E51-BAC6-D3401179FB51} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {6EB333E6-A4CF-4D5D-99F4-9E9B00D1DECC} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {C5338C76-F592-4DA9-B20F-2C931749326C} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F077B358-8F0D-4791-8E85-DDED3550C27A}
diff --git a/tests/Codout.Framework.Common.Tests/Codout.Framework.Common.Tests.csproj b/tests/Codout.Framework.Common.Tests/Codout.Framework.Common.Tests.csproj
new file mode 100644
index 0000000..a1489c1
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Codout.Framework.Common.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Codout.Framework.Common.Tests/Extensions/DateTimeExtensionsTests.cs b/tests/Codout.Framework.Common.Tests/Extensions/DateTimeExtensionsTests.cs
new file mode 100644
index 0000000..4e2aaed
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Extensions/DateTimeExtensionsTests.cs
@@ -0,0 +1,141 @@
+using Codout.Framework.Common.Extensions;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Extensions;
+
+public class DateTimeExtensionsTests
+{
+ [Fact]
+ public void IsWeekDay_SegundaASexta_RetornaTrue()
+ {
+ new DateTime(2026, 6, 8).IsWeekDay().Should().BeTrue(); // segunda-feira
+ new DateTime(2026, 6, 12).IsWeekDay().Should().BeTrue(); // sexta-feira
+ new DateTime(2026, 6, 13).IsWeekDay().Should().BeFalse(); // sábado
+ new DateTime(2026, 6, 14).IsWeekDay().Should().BeFalse(); // domingo
+ }
+
+ [Fact]
+ public void IsWeekEnd_SabadoEDomingo_RetornaTrue()
+ {
+ new DateTime(2026, 6, 13).IsWeekEnd().Should().BeTrue();
+ new DateTime(2026, 6, 14).IsWeekEnd().Should().BeTrue();
+ new DateTime(2026, 6, 10).IsWeekEnd().Should().BeFalse();
+ }
+
+ [Fact]
+ public void CountWeekdays_UmaSemanaCompleta_RetornaCinco()
+ {
+ var start = new DateTime(2026, 6, 8); // segunda
+ var end = new DateTime(2026, 6, 15); // segunda seguinte
+ start.CountWeekdays(end).Should().Be(5);
+ }
+
+ [Fact]
+ public void CountWeekends_UmaSemanaCompleta_RetornaDois()
+ {
+ var start = new DateTime(2026, 6, 8);
+ var end = new DateTime(2026, 6, 15);
+ start.CountWeekends(end).Should().Be(2);
+ }
+
+ [Fact]
+ public void IsDate_ValidaConversao()
+ {
+ "2026-06-12".IsDate().Should().BeTrue();
+ "não é data".IsDate().Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData(1, "1st")]
+ [InlineData(2, "2nd")]
+ [InlineData(3, "3rd")]
+ [InlineData(4, "4th")]
+ [InlineData(11, "11th")]
+ [InlineData(21, "21st")]
+ [InlineData(22, "22nd")]
+ [InlineData(23, "23rd")]
+ public void GetDateDayWithSuffix_RetornaSufixoCorreto(int day, string expected)
+ {
+ new DateTime(2026, 1, day).GetDateDayWithSuffix().Should().Be(expected);
+ }
+
+ [Fact]
+ public void GetAge_AntesDoAniversario_SubtraiUm()
+ {
+ var nascimento = new DateTime(2000, 12, 31);
+ var referencia = new DateTime(2020, 6, 15);
+ nascimento.GetAge(referencia).Should().Be(19);
+ }
+
+ [Fact]
+ public void GetAge_DepoisDoAniversario_RetornaIdadeCheia()
+ {
+ var nascimento = new DateTime(2000, 1, 2);
+ var referencia = new DateTime(2020, 6, 15);
+ nascimento.GetAge(referencia).Should().Be(20);
+ }
+
+ [Fact]
+ public void GetAge_NoDiaDoAniversario_ComportamentoAtual()
+ {
+ // BUG?: no próprio dia do aniversário (mesmo DayOfYear) a comparação
+ // estrita "<" faz a idade ser subtraída em 1. Quem nasceu em 15/06/2000
+ // deveria completar 20 anos em 15/06/2020, mas o método retorna 19.
+ var nascimento = new DateTime(2000, 6, 15);
+ var referencia = new DateTime(2020, 6, 15);
+ nascimento.GetAge(referencia).Should().Be(19);
+ }
+
+ [Fact]
+ public void Diff_RetornaDiferencaEntreDatas()
+ {
+ var d1 = new DateTime(2026, 6, 12, 10, 0, 0);
+ var d2 = new DateTime(2026, 6, 10, 10, 0, 0);
+ d1.Diff(d2).Should().Be(TimeSpan.FromDays(2));
+ d1.DiffDays(d2).Should().Be(2);
+ d1.DiffHours(d2).Should().Be(48);
+ d1.DiffMinutes(d2).Should().Be(48 * 60);
+ }
+
+ [Fact]
+ public void DiffDays_ComStringsInvalidas_RetornaZero()
+ {
+ "abc".DiffDays("def").Should().Be(0);
+ }
+
+ [Fact]
+ public void DiffDays_ComStringsValidas_CalculaDiferenca()
+ {
+ "2026-06-12".DiffDays("2026-06-10").Should().Be(2);
+ }
+
+ [Fact]
+ public void TimeDiff_FormataDiferencaLegivel()
+ {
+ var start = new DateTime(2020, 1, 1, 0, 0, 0);
+ var end = new DateTime(2022, 1, 1, 0, 0, 5);
+ var result = start.TimeDiff(end);
+ result.Should().Contain("2 anos");
+ result.Should().Contain("5 segundos");
+ }
+
+ [Fact]
+ public void DaysAgoEDaysFromNow_RetornamDatasRelativas()
+ {
+ 2.DaysAgo().Should().BeCloseTo(DateTime.Now.AddDays(-2), TimeSpan.FromSeconds(5));
+ 2.DaysFromNow().Should().BeCloseTo(DateTime.Now.AddDays(2), TimeSpan.FromSeconds(5));
+ 3.HoursAgo().Should().BeCloseTo(DateTime.Now.AddHours(-3), TimeSpan.FromSeconds(5));
+ 10.MinutesFromNow().Should().BeCloseTo(DateTime.Now.AddMinutes(10), TimeSpan.FromSeconds(5));
+ }
+
+ [Fact]
+ public void ReadableDiff_RetornaTextoComAtras()
+ {
+ var start = new DateTime(2026, 6, 10, 10, 0, 0);
+ var end = new DateTime(2026, 6, 12, 11, 0, 0);
+ var result = start.ReadableDiff(end);
+ result.Should().Contain("2 dias");
+ result.Should().EndWith("atrás");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Extensions/NumericExtensionsTests.cs b/tests/Codout.Framework.Common.Tests/Extensions/NumericExtensionsTests.cs
new file mode 100644
index 0000000..add8d29
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Extensions/NumericExtensionsTests.cs
@@ -0,0 +1,84 @@
+using Codout.Framework.Common.Extensions;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Extensions;
+
+public class NumericExtensionsTests
+{
+ [Theory]
+ [InlineData("123", true)]
+ [InlineData("007", true)]
+ [InlineData("0", false)]
+ [InlineData("-5", false)]
+ [InlineData("12.3", false)]
+ [InlineData("abc", false)]
+ public void IsNaturalNumber_ValidaNumeroNatural(string input, bool expected)
+ {
+ input.IsNaturalNumber().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("123", true)]
+ [InlineData("0", true)]
+ [InlineData("-5", false)]
+ [InlineData("1.5", false)]
+ public void IsWholeNumber_ValidaNumeroInteiroSemSinal(string input, bool expected)
+ {
+ input.IsWholeNumber().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("123", true)]
+ [InlineData("-123", true)]
+ [InlineData("--123", false)]
+ [InlineData("1.5", false)]
+ [InlineData("abc", false)]
+ public void IsInteger_ValidaInteiroComSinal(string input, bool expected)
+ {
+ input.IsInteger().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(0, true)]
+ [InlineData(2, true)]
+ [InlineData(-4, true)]
+ [InlineData(3, false)]
+ [InlineData(-7, false)]
+ public void IsEven_ValidaPar(int value, bool expected)
+ {
+ value.IsEven().Should().Be(expected);
+ value.IsOdd().Should().Be(!expected);
+ }
+
+ [Theory]
+ [InlineData(3.14159, 2, 3.14)]
+ [InlineData(3.999, 0, 3.0)]
+ [InlineData(-1.239, 2, -1.23)]
+ public void Truncate_TruncaCasasDecimais(double value, int precision, double expected)
+ {
+ value.Truncate(precision).Should().Be(expected);
+ }
+
+ [Fact]
+ public void Random_ComLimites_RetornaDentroDoIntervalo()
+ {
+ for (var i = 0; i < 50; i++)
+ NumericExtensions.Random(5, 10).Should().BeInRange(5, 9);
+ }
+
+ [Fact]
+ public void Random_SemParametros_RetornaEntreZeroEUm()
+ {
+ NumericExtensions.Random().Should().BeInRange(0.0, 1.0);
+ }
+
+ [Fact]
+ public void Random_ComLimiteSuperior_RetornaMenorQueLimite()
+ {
+#pragma warning disable CS0618 // Random(int) está marcado como Obsolete
+ for (var i = 0; i < 50; i++)
+ NumericExtensions.Random(10).Should().BeInRange(0, 9);
+#pragma warning restore CS0618
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Extensions/ObjectExtensionsTests.cs b/tests/Codout.Framework.Common.Tests/Extensions/ObjectExtensionsTests.cs
new file mode 100644
index 0000000..db31387
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Extensions/ObjectExtensionsTests.cs
@@ -0,0 +1,80 @@
+using Codout.Framework.Common.Extensions;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Extensions;
+
+public class ObjectExtensionsTests
+{
+ private class Pessoa
+ {
+ public string? Nome { get; set; }
+ public int Idade { get; set; }
+ }
+
+ [Fact]
+ public void ChangeTypeTo_ConverteTiposSimples()
+ {
+ "42".ChangeTypeTo().Should().Be(42);
+ "true".ChangeTypeTo().Should().Be(true);
+ }
+
+ [Fact]
+ public void ChangeTypeTo_ConverteParaNullable()
+ {
+ "42".ChangeTypeTo().Should().Be(42);
+ ((object?)null!).ChangeTypeTo().Should().BeNull();
+ }
+
+ [Fact]
+ public void ChangeTypeTo_ConverteParaGuid()
+ {
+ var guid = Guid.NewGuid();
+ guid.ToString().ChangeTypeTo().Should().Be(guid);
+ }
+
+ [Fact]
+ public void ChangeTypeTo_IntParaLong_LancaExcecao()
+ {
+ // Comportamento intencional documentado no código (caso SQLite/PK Int64)
+ var act = () => 42.ChangeTypeTo();
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ChangeTypeTo_TipoNulo_LancaArgumentNullException()
+ {
+ var act = () => "x".ChangeTypeTo(null!);
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ToDictionary_ConvertePropriedadesPublicas()
+ {
+ var dict = new Pessoa { Nome = "Ana", Idade = 30 }.ToDictionary();
+ dict.Should().Contain("Nome", "Ana");
+ dict.Should().Contain("Idade", 30);
+ }
+
+ [Fact]
+ public void CopyTo_CopiaPropriedadesEntreObjetos()
+ {
+ var origem = new Pessoa { Nome = "Ana", Idade = 30 };
+ var destino = new Pessoa();
+
+ var resultado = origem.CopyTo(destino);
+
+ resultado.Nome.Should().Be("Ana");
+ resultado.Idade.Should().Be(30);
+ }
+
+ [Fact]
+ public void FromDictionary_PreencheObjeto()
+ {
+ var dict = new Dictionary { ["Nome"] = "Bia", ["Idade"] = 25 };
+ var pessoa = dict.FromDictionary(new Pessoa());
+
+ pessoa.Nome.Should().Be("Bia");
+ pessoa.Idade.Should().Be(25);
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Codout.Framework.Common.Tests/Extensions/StringExtensionsTests.cs
new file mode 100644
index 0000000..1f9c0db
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Extensions/StringExtensionsTests.cs
@@ -0,0 +1,206 @@
+using Codout.Framework.Common.Extensions;
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Extensions;
+
+public class StringExtensionsTests
+{
+ [Theory]
+ [InlineData("abc", "ABC", true)]
+ [InlineData("abc", "abc", true)]
+ [InlineData("abc", "abd", false)]
+ public void Matches_ComparaIgnorandoCase(string source, string compare, bool expected)
+ {
+ source.Matches(compare).Should().Be(expected);
+ }
+
+ [Fact]
+ public void MatchesTrimmed_IgnoraEspacosNasBordas()
+ {
+ " abc ".MatchesTrimmed("ABC").Should().BeTrue();
+ " abc ".MatchesTrimmed("ab c").Should().BeFalse();
+ }
+
+ [Fact]
+ public void MatchesRegex_ValidaPadrao()
+ {
+ "abc123".MatchesRegex(@"^[a-z]+\d+$").Should().BeTrue();
+ "123abc".MatchesRegex(@"^[a-z]+\d+$").Should().BeFalse();
+ }
+
+ [Fact]
+ public void Chop_RemoveUltimosCaracteres()
+ {
+ "abcdef".Chop(2).Should().Be("abcd");
+ "abcdef".Chop().Should().Be("abcde");
+ "ab".Chop(0).Should().Be("ab");
+ }
+
+ [Fact]
+ public void Chop_ComStringAlvo_RemoveAtePadrao()
+ {
+ "documento.txt".Chop(".txt").Should().Be("documento");
+ }
+
+ [Fact]
+ public void Chop_ComStringAlvoInexistente_LancaExcecao()
+ {
+ // BUG?: quando o padrão não é encontrado, LastIndexOf retorna -1 e o
+ // código chama Remove(-1, 0), que lança ArgumentOutOfRangeException
+ // em vez de retornar a string original.
+ var act = () => "abc".Chop("zzz");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Clip_RemoveCaracteresDoInicio()
+ {
+ "abcdef".Clip(2).Should().Be("cdef");
+ "abcdef".Clip().Should().Be("bcdef");
+ "ab".Clip(5).Should().Be("ab");
+ }
+
+ [Fact]
+ public void Clip_ComStringAlvo_RemoveAtePadrao()
+ {
+ "prefixo:valor".Clip(":").Should().Be(":valor");
+ }
+
+ [Fact]
+ public void FastReplace_SubstituiIgnorandoCase()
+ {
+ "Hello World, hello!".FastReplace("hello", "bye").Should().Be("bye World, bye!");
+ }
+
+ [Fact]
+ public void FastReplace_OriginalNula_RetornaNull()
+ {
+ ((string?)null!).FastReplace("a", "b").Should().BeNull();
+ }
+
+ [Fact]
+ public void Crop_RetornaTextoEntreDelimitadores()
+ {
+ "negrito ".Crop("", " ").Should().Be("negrito");
+ "sem delimitador".Crop("", " ").Should().Be(string.Empty);
+ }
+
+ [Fact]
+ public void Squeeze_RemoveEspacosExcedentes()
+ {
+ " a b c ".Squeeze().Should().Be("a b c");
+ }
+
+ [Fact]
+ public void ToAlphaNumericOnly_RemoveCaracteresNaoAlfanumericos()
+ {
+ "ab-12!cd #34".ToAlphaNumericOnly().Should().Be("ab12cd34");
+ }
+
+ [Theory]
+ [InlineData("(11) 98765-4321", "11987654321")]
+ [InlineData("abc", "")]
+ [InlineData("", "")]
+ public void OnlyNumbers_RetornaSomenteDigitos(string input, string expected)
+ {
+ input.OnlyNumbers().Should().Be(expected);
+ }
+
+ [Fact]
+ public void ToWords_SeparaPalavras()
+ {
+ " uma frase de teste ".ToWords().Should().Equal("uma", "frase", "de", "teste");
+ }
+
+ [Fact]
+ public void StripHtml_RemoveTags()
+ {
+ "Texto importante & outro
".StripHtml().Should().Be("Texto importante& outro");
+ }
+
+ [Fact]
+ public void FindMatches_RetornaOcorrencias()
+ {
+ "a1 b2 c3".FindMatches(@"[a-z]\d").Should().Equal("a1", "b2", "c3");
+ }
+
+ [Fact]
+ public void ToDelimitedList_ConcatenaComDelimitador()
+ {
+ new[] { "a", "b", "c" }.ToDelimitedList().Should().Be("a,b,c");
+ new[] { "a", "b" }.ToDelimitedList(";").Should().Be("a;b");
+ }
+
+ [Fact]
+ public void Strip_RemovePadroesSeparadosPorVirgula()
+ {
+ "abc123def".Strip(@"\d").Should().Be("abcdef");
+ }
+
+ [Fact]
+ public void ToFormattedString_FormataComArgumentos()
+ {
+ "{0}-{1}".ToFormattedString(1, "a").Should().Be("1-a");
+ }
+
+ [Fact]
+ public void ToEnum_ConvertePorNomeIgnorandoCase()
+ {
+ "friday".ToEnum().Should().Be(DayOfWeek.Friday);
+ "inexistente".ToEnum().Should().Be(default(DayOfWeek));
+ }
+
+ [Theory]
+ [InlineData("abcdef", 3, "abc")]
+ [InlineData("ab", 5, "ab")]
+ [InlineData("", 3, "")]
+ public void Truncate_LimitaTamanho(string input, int max, string expected)
+ {
+ input.Truncate(max).Should().Be(expected);
+ }
+
+ [Fact]
+ public void HtmlEncode_HtmlDecode_SaoInversos()
+ {
+ // BUG?: HtmlEncode substitui '&' por "&" por ÚLTIMO, depois de já ter
+ // gerado as entidades; com isso "é" vira "é" (duplo escape) em
+ // vez de "é". O roundtrip com HtmlDecode ainda funciona porque o
+ // decode desfaz na ordem inversa.
+ const string texto = "café & ação ";
+ var encoded = texto.HtmlEncode();
+
+ encoded.Should().Contain("&");
+ encoded.Should().Contain("eacute;");
+ encoded.Should().NotContain("é");
+ encoded.Should().NotContain("<");
+
+ encoded.HtmlDecode().Should().Be(texto);
+ }
+
+ [Fact]
+ public void Pluralize_UsaSingularOuPluralConformeQuantidade()
+ {
+ 1.Pluralize("casa").Should().Be("1 casa");
+ 2.Pluralize("casa").Should().Be("2 casas");
+ }
+
+ [Fact]
+ public void RemoveAccents_ComportamentoAtual()
+ {
+ // BUG?: RemoveAccents usa Encoding.GetEncoding("iso-8859-8") (hebraico),
+ // que não está registrado por padrão no .NET (Core); o método lança
+ // ArgumentException em runtime moderno em vez de remover acentos.
+ var act = () => "ação".RemoveAccents();
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void RemoveCharactersSpecial_SemReplaceAccents_RemoveSimbolos()
+ {
+ // replaceAccents = false para evitar o caminho do RemoveAccents (ver teste acima)
+ "olá! @mundo#".RemoveCharactersSpecial(allowWhiteSpace: true, replaceAccents: false)
+ .Should().Be("olá mundo");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Extensions/ValidationExtensionsTests.cs b/tests/Codout.Framework.Common.Tests/Extensions/ValidationExtensionsTests.cs
new file mode 100644
index 0000000..71b5f62
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Extensions/ValidationExtensionsTests.cs
@@ -0,0 +1,151 @@
+using Codout.Framework.Common.Extensions;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Extensions;
+
+public class ValidationExtensionsTests
+{
+ [Theory]
+ [InlineData("usuario@dominio.com", true)]
+ [InlineData("nome.sobrenome@empresa.com.br", true)]
+ [InlineData("sem-arroba.com", false)]
+ [InlineData("a@b", false)]
+ public void IsEmail_ValidaFormato(string input, bool expected)
+ {
+ input.IsEmail().Should().Be(expected);
+ }
+
+ [Fact]
+ public void IsEmail_StringVazia_RetornaTrue()
+ {
+ // BUG?: e-mail vazio/whitespace é considerado válido pela implementação
+ // atual (provavelmente para campos opcionais). Documentando o comportamento.
+ string.Empty.IsEmail().Should().BeTrue();
+ " ".IsEmail().Should().BeTrue();
+ }
+
+ [Theory]
+ [InlineData("529.982.247-25", true)] // CPF válido conhecido
+ [InlineData("52998224725", true)] // mesmo CPF sem máscara
+ [InlineData("529.982.247-24", false)] // dígito verificador errado
+ [InlineData("11111111111", false)] // sequência repetida
+ [InlineData("123", false)]
+ [InlineData("", false)]
+ [InlineData(null, false)]
+ public void IsCpf_ValidaDigitosVerificadores(string? input, bool expected)
+ {
+ input!.IsCpf().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("11.222.333/0001-81", true)] // CNPJ válido conhecido
+ [InlineData("11222333000181", true)]
+ [InlineData("11.222.333/0001-80", false)] // dígito errado
+ [InlineData("123", false)]
+ public void IsCnpj_ValidaDigitosVerificadores(string input, bool expected)
+ {
+ input.IsCnpj().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("01310-100", true)]
+ [InlineData("01310100", true)]
+ [InlineData("01.310-100", true)]
+ [InlineData("1310-100", false)]
+ [InlineData("abcde-fgh", false)]
+ public void IsCep_ValidaFormato(string input, bool expected)
+ {
+ input.IsCep().Should().Be(expected);
+ }
+
+ [Fact]
+ public void IsGuid_ValidaFormato()
+ {
+ Guid.NewGuid().ToString().IsGuid().Should().BeTrue();
+ "não-é-um-guid".IsGuid().Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("192.168.0.1", true)]
+ [InlineData("255.255.255.255", true)]
+ [InlineData("256.1.1.1", false)]
+ [InlineData("1.2.3", false)]
+ public void IsIpAddress_ValidaFormato(string input, bool expected)
+ {
+ input.IsIpAddress().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("https://www.exemplo.com", true)]
+ [InlineData("http://exemplo.com/caminho?x=1", true)]
+ [InlineData("ftp://arquivos.exemplo.com", true)]
+ [InlineData("não é url", false)]
+ public void IsUrl_ValidaFormato(string input, bool expected)
+ {
+ input.IsUrl().Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("Senha123!", true)]
+ [InlineData("abcdefgh", false)] // sem número/maiúscula/símbolo
+ [InlineData("Ab1!", false)] // curta demais
+ public void IsStrongPassword_ValidaComplexidade(string input, bool expected)
+ {
+ input.IsStrongPassword().Should().Be(expected);
+ }
+
+ [Fact]
+ public void IsValidLuhn_ValidaAlgoritmo()
+ {
+ // 4111111111111111 é um número de teste Visa que passa no Luhn
+ var valid = "4111111111111111".Select(c => c - '0').ToArray();
+ valid.IsValidLuhn().Should().BeTrue();
+
+ var invalid = "4111111111111112".Select(c => c - '0').ToArray();
+ invalid.IsValidLuhn().Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("4111 1111 1111 1111", true)] // Visa de teste
+ [InlineData("5500-0000-0000-0004", true)] // MasterCard de teste
+ [InlineData("1234567890123456", false)]
+ public void IsCreditCardAny_ValidaCartoes(string input, bool expected)
+ {
+ input.IsCreditCardAny().Should().Be(expected);
+ }
+
+ [Fact]
+ public void IsCreditCardVisa_ValidaSomenteVisa()
+ {
+ "4111111111111111".IsCreditCardVisa().Should().BeTrue();
+ "5500000000000004".IsCreditCardVisa().Should().BeFalse();
+ }
+
+ [Fact]
+ public void CleanCreditCardNumber_RemoveNaoNumericos()
+ {
+ "4111-1111 1111x1111".CleanCreditCardNumber().Should().Be("4111111111111111");
+ }
+
+ [Theory]
+ [InlineData("12345", true)]
+ [InlineData("12345-6789", true)]
+ [InlineData("1234", false)]
+ public void IsZipCodeAny_ValidaFormatoAmericano(string input, bool expected)
+ {
+ input.IsZipCodeAny().Should().Be(expected);
+ }
+
+ [Fact]
+ public void IsInscricaoEstadual_Isento_RetornaTrue()
+ {
+ ValidationExtensions.IsInscricaoEstadual("SP", "ISENTO").Should().BeTrue();
+ }
+
+ [Fact]
+ public void IsInscricaoEstadual_NumeroInvalido_RetornaFalse()
+ {
+ ValidationExtensions.IsInscricaoEstadual("MT", "00000000001").Should().BeFalse();
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Helpers/EnumHelperTests.cs b/tests/Codout.Framework.Common.Tests/Helpers/EnumHelperTests.cs
new file mode 100644
index 0000000..92e2edf
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Helpers/EnumHelperTests.cs
@@ -0,0 +1,85 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Helpers;
+
+public class EnumHelperTests
+{
+ private enum Status
+ {
+ [Description("Em andamento")]
+ EmAndamento,
+
+ [Description("Concluído")]
+ Concluido,
+
+ SemDescricao
+ }
+
+ private enum Idioma
+ {
+ // GetLocalizedName lê DisplayAttribute.GetDescription(), então é a
+ // propriedade Description (e não Name) que precisa estar preenchida.
+ [Display(Description = "Português")]
+ Portugues,
+
+ Ingles
+ }
+
+ [Fact]
+ public void GetDescription_ComAtributo_RetornaDescricao()
+ {
+ Status.EmAndamento.GetDescription().Should().Be("Em andamento");
+ Status.Concluido.GetDescription().Should().Be("Concluído");
+ }
+
+ [Fact]
+ public void GetDescription_SemAtributo_RetornaNome()
+ {
+ Status.SemDescricao.GetDescription().Should().Be("SemDescricao");
+ }
+
+ [Fact]
+ public void GetDescription_PorTipoENome_RetornaDescricao()
+ {
+ EnumHelper.GetDescription(typeof(Status), nameof(Status.Concluido)).Should().Be("Concluído");
+ }
+
+ [Fact]
+ public void GetValueFromDescription_RetornaValorDoEnum()
+ {
+ EnumHelper.GetValueFromDescription("Em andamento").Should().Be(Status.EmAndamento);
+ EnumHelper.GetValueFromDescription("SemDescricao").Should().Be(Status.SemDescricao);
+ }
+
+ [Fact]
+ public void GetValueFromDescription_DescricaoInexistente_RetornaDefault()
+ {
+ EnumHelper.GetValueFromDescription("Não existe").Should().Be(default(Status));
+ }
+
+ [Fact]
+ public void GetValueFromDescription_TipoNaoEnum_LancaInvalidOperationException()
+ {
+ var act = () => EnumHelper.GetValueFromDescription("x");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void GetLocalizedName_ComDisplayAttribute_RetornaNome()
+ {
+ Idioma.Portugues.GetLocalizedName().Should().Be("Português");
+ Idioma.Ingles.GetLocalizedName().Should().Be("Ingles");
+ }
+
+ [Fact]
+ public void GetDicionary_RetornaParesValorDescricao()
+ {
+ var entries = EnumHelper.GetDicionary(typeof(Status));
+ entries.Should().HaveCount(3);
+ entries.Select(e => e.Value).Should().Contain("Em andamento");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Helpers/InflectorTests.cs b/tests/Codout.Framework.Common.Tests/Helpers/InflectorTests.cs
new file mode 100644
index 0000000..01af234
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Helpers/InflectorTests.cs
@@ -0,0 +1,95 @@
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Helpers;
+
+public class InflectorTests
+{
+ [Theory]
+ [InlineData("casa", "casas")]
+ [InlineData("cão", "cães")]
+ [InlineData("alemão", "alemães")]
+ [InlineData("irmão", "irmãos")]
+ [InlineData("papel", "papéis")]
+ [InlineData("animal", "animais")]
+ public void MakePlural_PluralizaPortugues(string singular, string plural)
+ {
+ singular.MakePlural().Should().Be(plural);
+ }
+
+ [Theory]
+ [InlineData("casas", "casa")]
+ [InlineData("cães", "cão")]
+ [InlineData("animais", "animal")]
+ public void MakeSingular_SingularizaPortugues(string plural, string singular)
+ {
+ plural.MakeSingular().Should().Be(singular);
+ }
+
+ [Fact]
+ public void MakePlural_PalavraIncontavel_NaoAltera()
+ {
+ "fénix".MakePlural().Should().Be("fénix");
+ }
+
+ [Theory]
+ [InlineData("MyTestString", "my_test_string")]
+ [InlineData("my test", "my_test")]
+ public void AddUnderscores_ConverteParaSnakeCase(string input, string expected)
+ {
+ input.AddUnderscores().Should().Be(expected);
+ }
+
+ [Fact]
+ public void ToPascalCase_ConverteUnderscores()
+ {
+ "minha_propriedade_teste".ToPascalCase().Should().Be("MinhaPropriedadeTeste");
+ }
+
+ [Fact]
+ public void ToCamelCase_ConverteUnderscores()
+ {
+ "minha_propriedade".ToCamelCase().Should().Be("minhaPropriedade");
+ }
+
+ [Fact]
+ public void ToHumanCase_ConverteParaTextoLegivel()
+ {
+ "meu_campo_teste".ToHumanCase().Should().Be("Meu campo teste");
+ }
+
+ [Fact]
+ public void ToTitleCase_CapitalizaCadaPalavra()
+ {
+ "meu_campo_teste".ToTitleCase().Should().Be("Meu Campo Teste");
+ }
+
+ [Fact]
+ public void MakeInitialCapsELowerCase_AlteramPrimeiraLetra()
+ {
+ "teste ABC".MakeInitialCaps().Should().Be("Teste abc");
+ "Teste".MakeInitialLowerCase().Should().Be("teste");
+ }
+
+ [Theory]
+ [InlineData("1", "1st")]
+ [InlineData("2", "2nd")]
+ [InlineData("3", "3rd")]
+ [InlineData("4", "4th")]
+ [InlineData("11", "11th")]
+ [InlineData("12", "12th")]
+ [InlineData("13", "13th")]
+ [InlineData("21", "21st")]
+ [InlineData("abc", "abc")]
+ public void AddOrdinalSuffix_AdicionaSufixoIngles(string input, string expected)
+ {
+ input.AddOrdinalSuffix().Should().Be(expected);
+ }
+
+ [Fact]
+ public void ConvertUnderscoresToDashes_SubstituiUnderscores()
+ {
+ "a_b_c".ConvertUnderscoresToDashes().Should().Be("a-b-c");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Helpers/LimitedListTests.cs b/tests/Codout.Framework.Common.Tests/Helpers/LimitedListTests.cs
new file mode 100644
index 0000000..9111d86
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Helpers/LimitedListTests.cs
@@ -0,0 +1,70 @@
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Helpers;
+
+public class LimitedListTests
+{
+ [Fact]
+ public void Add_AbaixoDoLimite_IncrementaCount()
+ {
+ var list = new LimitedList(3) { 1, 2 };
+ list.Count.Should().Be(2);
+ list[0].Should().Be(1);
+ list[1].Should().Be(2);
+ }
+
+ [Fact]
+ public void Add_AlemDoLimite_DescartaOMaisAntigo()
+ {
+ var list = new LimitedList(3) { 1, 2, 3, 4 };
+
+ list.Count.Should().Be(3);
+ list[0].Should().Be(2);
+ list[1].Should().Be(3);
+ list[2].Should().Be(4);
+ }
+
+ [Fact]
+ public void Contains_EncontraItens()
+ {
+ var list = new LimitedList(2) { "a", "b" };
+ list.Contains("a").Should().BeTrue();
+ list.Contains("z").Should().BeFalse();
+ }
+
+ [Fact]
+ public void Clear_ZeraContagem()
+ {
+ var list = new LimitedList(2) { 1, 2 };
+ list.Clear();
+ list.Count.Should().Be(0);
+ list.Contains(1).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Indexer_ForaDoIntervalo_LancaExcecao()
+ {
+ var list = new LimitedList(2);
+ var act = () => list[5];
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ToArray_RetornaTodosOsSlots()
+ {
+ var list = new LimitedList(3) { 1, 2, 3 };
+ list.ToArray().Should().Equal(1, 2, 3);
+ }
+
+ [Fact]
+ public void Enumeracao_PercorreOsSlots()
+ {
+ var list = new LimitedList(3) { 7, 8, 9 };
+ var items = new List();
+ foreach (int item in list)
+ items.Add(item);
+ items.Should().Equal(7, 8, 9);
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Helpers/NumberToTextTests.cs b/tests/Codout.Framework.Common.Tests/Helpers/NumberToTextTests.cs
new file mode 100644
index 0000000..319b76a
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Helpers/NumberToTextTests.cs
@@ -0,0 +1,46 @@
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Helpers;
+
+public class NumberToTextTests
+{
+ [Theory]
+ [InlineData("1", "hum real")]
+ [InlineData("2", "dois reais")]
+ [InlineData("2.50", "dois reais e cinqüenta centavos")]
+ [InlineData("0.01", "um centavo")]
+ [InlineData("0.25", "vinte e cinco centavos")]
+ [InlineData("100", "cem reais")]
+ [InlineData("101", "cento e um reais")]
+ [InlineData("1000", "hum mil reais")]
+ public void ToString_ConverteValorPorExtenso(string valor, string expected)
+ {
+ var n = new NumberToText(decimal.Parse(valor, System.Globalization.CultureInfo.InvariantCulture));
+ n.ToString().Should().Be(expected);
+ }
+
+ [Fact]
+ public void ToString_Zero_RetornaVazio()
+ {
+ new NumberToText(0m).ToString().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void SetNumero_PermiteReuso()
+ {
+ var n = new NumberToText();
+ n.SetNumero(2m);
+ n.ToString().Should().Be("dois reais");
+
+ n.SetNumero(3m);
+ n.ToString().Should().Be("três reais");
+ }
+
+ [Fact]
+ public void ToString_ArredondaParaDuasCasas()
+ {
+ new NumberToText(1.999m).ToString().Should().Be("dois reais");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Helpers/SlugHelperTests.cs b/tests/Codout.Framework.Common.Tests/Helpers/SlugHelperTests.cs
new file mode 100644
index 0000000..824af0b
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Helpers/SlugHelperTests.cs
@@ -0,0 +1,75 @@
+using Codout.Framework.Common.Helpers;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Helpers;
+
+public class SlugHelperTests
+{
+ [Theory]
+ [InlineData("Olá Mundo!", "ola-mundo")]
+ [InlineData("Ação & Reação", "acao-reacao")]
+ [InlineData("hello world", "hello-world")]
+ [InlineData("UPPER Case", "upper-case")]
+ public void ToSlug_GeraSlugLimpo(string input, string expected)
+ {
+ input.ToSlug().Should().Be(expected);
+ }
+
+ [Fact]
+ public void ToSlug_ColapsaEspacosEHifens()
+ {
+ " muitos espaços --- e hifens ".ToSlug().Should().Be("muitos-espacos-e-hifens");
+ }
+
+ [Fact]
+ public void ToSlug_EntradaVaziaOuWhitespace_LancaArgumentException()
+ {
+ var actEmpty = () => "".ToSlug();
+ var actSpace = () => " ".ToSlug();
+ actEmpty.Should().Throw();
+ actSpace.Should().Throw();
+ }
+
+ [Fact]
+ public void ToSlug_ResultadoVazio_UsaFallbackComHash()
+ {
+ // Apenas caracteres não permitidos resultam em slug vazio → fallback "item-{hash}"
+ "!!!@@@###".ToSlug().Should().StartWith("item-");
+ }
+
+ [Fact]
+ public void ToSlug_ComAllowEmptySlug_RetornaVazio()
+ {
+ var config = new SlugConfig { AllowEmptySlug = true };
+ "!!!".ToSlug(config).Should().BeEmpty();
+ }
+
+ [Fact]
+ public void ToSlug_ComMaxLength_TruncaSemHifenFinal()
+ {
+ var config = new SlugConfig { MaxLength = 10 };
+ var slug = "uma frase bem longa para truncar".ToSlug(config);
+ slug.Length.Should().BeLessThanOrEqualTo(10);
+ slug.Should().NotEndWith("-");
+ }
+
+ [Fact]
+ public void ToSlug_ConfigStrict_SubstituiPontosEUnderscores()
+ {
+ "arquivo.nome_teste".ToSlug(SlugConfig.Strict).Should().Be("arquivo-nome-teste");
+ }
+
+ [Fact]
+ public void ToSlug_ConfigExtended_PreservaMaiusculas()
+ {
+ "Hello World".ToSlug(SlugConfig.Extended).Should().Be("Hello-World");
+ }
+
+ [Fact]
+ public void SlugHelper_ConfigNula_LancaArgumentNullException()
+ {
+ var act = () => new SlugHelper(null!);
+ act.Should().Throw();
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Security/CryptoStringTests.cs b/tests/Codout.Framework.Common.Tests/Security/CryptoStringTests.cs
new file mode 100644
index 0000000..7e7e31a
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Security/CryptoStringTests.cs
@@ -0,0 +1,105 @@
+using System.Security.Cryptography;
+using Codout.Framework.Common.Security;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Security;
+
+public class CryptoStringTests
+{
+ // Senha que atende aos critérios de "senha forte" (>= 12 chars, maiúscula,
+ // minúscula, dígito e caractere especial)
+ private const string StrongPassword = "S3nh@F0rte!2026";
+
+ [Fact]
+ public void EncryptDecrypt_Roundtrip_RecuperaTextoOriginal()
+ {
+ const string texto = "informação confidencial: çãé€";
+
+ var encrypted = CryptoString.Encrypt(texto, StrongPassword);
+ var decrypted = CryptoString.Decrypt(encrypted, StrongPassword);
+
+ decrypted.Should().Be(texto);
+ }
+
+ [Fact]
+ public void Encrypt_MesmoTexto_GeraCiphertextsDiferentes()
+ {
+ var c1 = CryptoString.Encrypt("texto", StrongPassword);
+ var c2 = CryptoString.Encrypt("texto", StrongPassword);
+ c1.Should().NotBe(c2); // salt e nonce aleatórios
+ }
+
+ [Fact]
+ public void Decrypt_SenhaErrada_LancaCryptographicException()
+ {
+ var encrypted = CryptoString.Encrypt("texto", StrongPassword);
+ var act = () => CryptoString.Decrypt(encrypted, "0utr@Senh4!Forte");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Decrypt_CiphertextCorrompido_LancaExcecao()
+ {
+ var encrypted = CryptoString.Encrypt("texto", StrongPassword);
+ var bytes = Convert.FromBase64String(encrypted);
+ bytes[^1] ^= 0xFF; // corrompe o último byte
+ var corrompido = Convert.ToBase64String(bytes);
+
+ var act = () => CryptoString.Decrypt(corrompido, StrongPassword);
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Decrypt_Base64Invalido_LancaArgumentException()
+ {
+ var act = () => CryptoString.Decrypt("@@não-base64@@", StrongPassword);
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Encrypt_SenhaFraca_ComOpcoesPadrao_LancaArgumentException()
+ {
+ var act = () => CryptoString.Encrypt("texto", "fraca");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Encrypt_SenhaFraca_ComOpcoesLegacy_Funciona()
+ {
+ var encrypted = CryptoString.Encrypt("texto", "abc123", CryptoOptions.Legacy);
+ CryptoString.Decrypt(encrypted, "abc123", CryptoOptions.Legacy).Should().Be("texto");
+ }
+
+ [Fact]
+ public void Encrypt_TextoVazio_LancaArgumentException()
+ {
+ var act = () => CryptoString.Encrypt("", StrongPassword);
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ExtensionMethods_EncryptDecrypt_Funcionam()
+ {
+ var encrypted = "segredo".Encrypt(StrongPassword);
+ encrypted.Decrypt(StrongPassword).Should().Be("segredo");
+ }
+
+ [Fact]
+ public void Decrypt_ToleraEspacosNoLugarDeMais()
+ {
+ // O Decrypt normaliza ' ' para '+' (caso comum em querystrings)
+ var encrypted = CryptoString.Encrypt("texto qualquer", StrongPassword);
+ var comEspacos = encrypted.Replace('+', ' ');
+ CryptoString.Decrypt(comEspacos, StrongPassword).Should().Be("texto qualquer");
+ }
+
+ [Fact]
+ public void SecureBuffer_AposDispose_LancaObjectDisposedException()
+ {
+ var buffer = SecureMemory.Allocate(16);
+ buffer.Dispose();
+ var act = () => { _ = buffer.Span.Length; };
+ act.Should().Throw();
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Security/CryptoTests.cs b/tests/Codout.Framework.Common.Tests/Security/CryptoTests.cs
new file mode 100644
index 0000000..fcf28e0
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Security/CryptoTests.cs
@@ -0,0 +1,44 @@
+using Codout.Framework.Common.Security;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Security;
+
+public class CryptoTests
+{
+#pragma warning disable CS0618 // Métodos legados marcados como Obsolete
+ [Fact]
+ public void Md5Encrypt_RetornaHashConhecido()
+ {
+ // Vetor de teste padrão do MD5 para "abc"
+ Crypto.Md5Encrypt("abc").Should().Be("900150983CD24FB0D6963F7D28E17F72");
+ }
+
+ [Fact]
+ public void Sha1Encrypt_RetornaHashConhecido()
+ {
+ Crypto.Sha1Encrypt("abc").Should().Be("A9993E364706816ABA3E25717850C26C9CD0D89D");
+ }
+
+ [Fact]
+ public void Sha256Encrypt_RetornaHashConhecido()
+ {
+ Crypto.Sha256Encrypt("abc")
+ .Should().Be("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD");
+ }
+
+ [Fact]
+ public void Sha512Encrypt_RetornaHashCom128CaracteresHex()
+ {
+ var hash = Crypto.Sha512Encrypt("abc");
+ hash.Should().HaveLength(128);
+ hash.Should().MatchRegex("^[0-9A-F]+$");
+ }
+#pragma warning restore CS0618
+
+ [Fact]
+ public void ByteArrayToString_ConverteParaHexMaiusculo()
+ {
+ Crypto.ByteArrayToString(new byte[] { 0x00, 0xAB, 0xFF }).Should().Be("00ABFF");
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Security/RandomPasswordTests.cs b/tests/Codout.Framework.Common.Tests/Security/RandomPasswordTests.cs
new file mode 100644
index 0000000..efc18b8
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Security/RandomPasswordTests.cs
@@ -0,0 +1,68 @@
+using Codout.Framework.Common.Security;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Security;
+
+public class RandomPasswordTests
+{
+ private const string AllowedChars =
+ "abcdefgijkmnopqrstwxyz" +
+ "ABCDEFGHJKLMNPQRSTWXYZ" +
+ "23456789" +
+ "*$-+?_&=!%{}/";
+
+ [Fact]
+ public void Generate_SemParametros_RespeitaTamanhoPadrao()
+ {
+ for (var i = 0; i < 20; i++)
+ {
+ var password = RandomPassword.Generate();
+ password.Length.Should().BeInRange(8, 10);
+ }
+ }
+
+ [Fact]
+ public void Generate_ComTamanhoExato_RetornaTamanhoSolicitado()
+ {
+ RandomPassword.Generate(12).Should().HaveLength(12);
+ RandomPassword.Generate(4).Should().HaveLength(4);
+ }
+
+ [Fact]
+ public void Generate_ComIntervalo_RespeitaLimites()
+ {
+ for (var i = 0; i < 20; i++)
+ {
+ var password = RandomPassword.Generate(6, 9);
+ password.Length.Should().BeInRange(6, 9);
+ }
+ }
+
+ [Fact]
+ public void Generate_UsaSomenteCaracteresPermitidos()
+ {
+ var password = RandomPassword.Generate(50);
+ password.Should().NotBeNull();
+ foreach (var c in password!)
+ AllowedChars.Should().Contain(c.ToString());
+ }
+
+ [Theory]
+ [InlineData(0, 5)]
+ [InlineData(5, 0)]
+ [InlineData(-1, 5)]
+ [InlineData(9, 5)] // min > max
+ public void Generate_ParametrosInvalidos_RetornaNull(int min, int max)
+ {
+ RandomPassword.Generate(min, max).Should().BeNull();
+ }
+
+ [Fact]
+ public void Generate_GeraSenhasDiferentes()
+ {
+ var p1 = RandomPassword.Generate(20);
+ var p2 = RandomPassword.Generate(20);
+ p1.Should().NotBe(p2);
+ }
+}
diff --git a/tests/Codout.Framework.Common.Tests/Security/SecureHashTests.cs b/tests/Codout.Framework.Common.Tests/Security/SecureHashTests.cs
new file mode 100644
index 0000000..bc6a45b
--- /dev/null
+++ b/tests/Codout.Framework.Common.Tests/Security/SecureHashTests.cs
@@ -0,0 +1,129 @@
+using System.Security.Cryptography;
+using System.Text;
+using Codout.Framework.Common.Security;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Common.Tests.Security;
+
+public class SecureHashTests
+{
+ [Theory]
+ [InlineData(SecureHashAlgorithm.Sha256)]
+ [InlineData(SecureHashAlgorithm.Sha384)]
+ [InlineData(SecureHashAlgorithm.Sha512)]
+ public void ComputeHash_VerifyHash_RoundtripValido(SecureHashAlgorithm algorithm)
+ {
+ const string texto = "senha-super-secreta";
+
+ var hash = SecureHash.ComputeHash(texto, algorithm);
+
+ SecureHash.VerifyHash(texto, hash, algorithm).Should().BeTrue();
+ SecureHash.VerifyHash("senha-errada", hash, algorithm).Should().BeFalse();
+ }
+
+ [Fact]
+ public void ComputeHash_MesmoTexto_GeraHashesDiferentes()
+ {
+ // Salt aleatório a cada chamada
+ var h1 = SecureHash.ComputeHash("texto");
+ var h2 = SecureHash.ComputeHash("texto");
+ h1.Should().NotBe(h2);
+ }
+
+ [Fact]
+ public void ComputeHash_RetornaBase64Valido()
+ {
+ var hash = SecureHash.ComputeHash("texto");
+ var act = () => Convert.FromBase64String(hash);
+ act.Should().NotThrow();
+ }
+
+ [Fact]
+ public void ComputeHash_TextoVazio_LancaArgumentException()
+ {
+ var act = () => SecureHash.ComputeHash("");
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ComputeHash_SaltMuitoPequeno_LancaArgumentException()
+ {
+ var salt = new byte[8]; // mínimo é 16
+ var act = () => SecureHash.ComputeHash("texto", SecureHashAlgorithm.Sha256, salt);
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void ComputeHash_ComSaltCustomizado_EhVerificavel()
+ {
+ var salt = new byte[32];
+ RandomNumberGenerator.Fill(salt);
+
+ var hash = SecureHash.ComputeHash("texto", SecureHashAlgorithm.Sha256, salt);
+ SecureHash.VerifyHash("texto", hash).Should().BeTrue();
+ }
+
+ [Fact]
+ public void ComputeHash_OpcoesInvalidas_LancaArgumentException()
+ {
+ var act = () => SecureHash.ComputeHash("texto", SecureHashAlgorithm.Sha256,
+ new HashOptions { SaltSize = 8 });
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void VerifyHash_HashInvalido_RetornaFalse()
+ {
+ SecureHash.VerifyHash("texto", "isto não é base64!!!").Should().BeFalse();
+ SecureHash.VerifyHash("texto", Convert.ToBase64String(new byte[4])).Should().BeFalse();
+ }
+
+ [Fact]
+ public async Task ComputeStreamHashAsync_RetornaSha256Conhecido()
+ {
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes("abc"));
+ var hash = await SecureHash.ComputeStreamHashAsync(stream);
+ hash.Should().Be("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
+ }
+
+ [Fact]
+ public async Task ComputeFileHashAsync_ArquivoInexistente_LancaFileNotFound()
+ {
+ var act = () => SecureHash.ComputeFileHashAsync("/caminho/inexistente.bin");
+ await act.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task VerifyFileIntegrityAsync_ArquivoIntegro_RetornaTrue()
+ {
+ var path = Path.Combine(Path.GetTempPath(), $"securehash-{Guid.NewGuid():N}.txt");
+ try
+ {
+ await File.WriteAllTextAsync(path, "conteúdo de teste");
+ var hash = await SecureHash.ComputeFileHashAsync(path);
+
+ (await SecureHash.VerifyFileIntegrityAsync(path, hash)).Should().BeTrue();
+ (await SecureHash.VerifyFileIntegrityAsync(path, new string('0', 64))).Should().BeFalse();
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
+
+ [Fact]
+ public void ExtensionMethods_ToSecureHashEVerifyAgainstHash_Funcionam()
+ {
+ const string texto = "minha-senha";
+ var hash = texto.ToSecureHash();
+ texto.VerifyAgainstHash(hash).Should().BeTrue();
+ "outra".VerifyAgainstHash(hash).Should().BeFalse();
+ }
+
+ [Fact]
+ public void GetSupportedAlgorithms_IncluiSha256()
+ {
+ SecureHash.GetSupportedAlgorithms().Should().Contain(SecureHashAlgorithm.Sha256);
+ }
+}
diff --git a/tests/Codout.Framework.Data.Tests/Codout.Framework.Data.Tests.csproj b/tests/Codout.Framework.Data.Tests/Codout.Framework.Data.Tests.csproj
new file mode 100644
index 0000000..b7a7ca8
--- /dev/null
+++ b/tests/Codout.Framework.Data.Tests/Codout.Framework.Data.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Codout.Framework.Data.Tests/EntityAbstractionsTests.cs b/tests/Codout.Framework.Data.Tests/EntityAbstractionsTests.cs
new file mode 100644
index 0000000..d0317d6
--- /dev/null
+++ b/tests/Codout.Framework.Data.Tests/EntityAbstractionsTests.cs
@@ -0,0 +1,93 @@
+using Codout.Framework.Data.Auditing;
+using Codout.Framework.Data.Entity;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Data.Tests;
+
+///
+/// Abstrações de entidade e auditoria: variância, marker interfaces e visibilidade.
+///
+public class EntityAbstractionsTests
+{
+ [Fact]
+ public void IEntity_of_TId_is_covariant_so_specialized_ids_upcast()
+ {
+ var entity = new StringIdEntity("abc");
+
+ // Compila apenas porque IEntity é covariante.
+ IEntity upcast = entity;
+
+ upcast.Id.Should().Be("abc");
+ }
+
+ [Fact]
+ public void IEntity_of_TId_extends_IEntity()
+ {
+ typeof(IEntity).Should().Implement();
+ }
+
+ [Fact]
+ public void IClientGeneratedId_is_a_pure_marker_interface()
+ {
+ typeof(IClientGeneratedId).GetMembers().Should().BeEmpty(
+ "o marker existe apenas para opt-in da ClientGeneratedIdConvention");
+ typeof(IClientGeneratedId).IsInterface.Should().BeTrue();
+ }
+
+ [Fact]
+ public void IHasAssignedId_is_internal_to_the_assembly()
+ {
+ var type = typeof(IEntity).Assembly.GetType("Codout.Framework.Data.Entity.IHasAssignedId`1");
+
+ type.Should().NotBeNull();
+ type!.IsPublic.Should().BeFalse("é detalhe interno, não faz parte da API pública");
+ }
+
+ [Fact]
+ public void IAuditable_contract_round_trips_audit_data()
+ {
+ IAuditable auditable = new AuditableStub();
+ var now = DateTime.UtcNow;
+
+ auditable.CreatedAt = now;
+ auditable.CreatedBy = "ana";
+ auditable.UpdatedAt = now.AddMinutes(1);
+ auditable.UpdatedBy = "bia";
+
+ auditable.CreatedAt.Should().Be(now);
+ auditable.CreatedBy.Should().Be("ana");
+ auditable.UpdatedAt.Should().Be(now.AddMinutes(1));
+ auditable.UpdatedBy.Should().Be("bia");
+ }
+
+ [Fact]
+ public void ISoftDeletable_contract_round_trips_deletion_data()
+ {
+ ISoftDeletable deletable = new SoftDeletableStub();
+ var now = DateTime.UtcNow;
+
+ deletable.IsDeleted = true;
+ deletable.DeletedAt = now;
+ deletable.DeletedBy = "ana";
+
+ deletable.IsDeleted.Should().BeTrue();
+ deletable.DeletedAt.Should().Be(now);
+ deletable.DeletedBy.Should().Be("ana");
+ }
+
+ private class AuditableStub : IAuditable
+ {
+ public DateTime CreatedAt { get; set; }
+ public string? CreatedBy { get; set; }
+ public DateTime? UpdatedAt { get; set; }
+ public string? UpdatedBy { get; set; }
+ }
+
+ private class SoftDeletableStub : ISoftDeletable
+ {
+ public bool IsDeleted { get; set; }
+ public DateTime? DeletedAt { get; set; }
+ public string? DeletedBy { get; set; }
+ }
+}
diff --git a/tests/Codout.Framework.Data.Tests/RepositoryContractTests.cs b/tests/Codout.Framework.Data.Tests/RepositoryContractTests.cs
new file mode 100644
index 0000000..dd20811
--- /dev/null
+++ b/tests/Codout.Framework.Data.Tests/RepositoryContractTests.cs
@@ -0,0 +1,99 @@
+using System.Reflection;
+using Codout.Framework.Data.Repository;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Data.Tests;
+
+///
+/// Codout.Framework.Data é composto por abstrações puras (sem lógica concreta).
+/// Estes testes travam o CONTRATO compilado no assembly — defaults de parâmetros,
+/// overloads de CancellationToken e disposability — para que mudanças quebradoras
+/// na interface sejam detectadas antes de quebrar as implementações (EF, NH, Mongo).
+///
+public class RepositoryContractTests
+{
+ private static readonly Type Contract = typeof(IRepository);
+
+ [Fact]
+ public void IRepository_is_disposable()
+ {
+ Contract.Should().Implement();
+ }
+
+ [Fact]
+ public void WherePaged_has_default_index_0_and_size_50()
+ {
+ var method = Contract.GetMethod("WherePaged")!;
+ var parameters = method.GetParameters();
+
+ parameters.Should().HaveCount(4);
+ parameters[1].IsOut.Should().BeTrue("total é retornado por out");
+
+ parameters[2].Name.Should().Be("index");
+ parameters[2].HasDefaultValue.Should().BeTrue();
+ parameters[2].DefaultValue.Should().Be(0);
+
+ parameters[3].Name.Should().Be("size");
+ parameters[3].HasDefaultValue.Should().BeTrue();
+ parameters[3].DefaultValue.Should().Be(50, "tamanho de página default do contrato");
+ }
+
+ [Theory]
+ [InlineData("GetAsync")]
+ [InlineData("LoadAsync")]
+ [InlineData("DeleteAsync")]
+ [InlineData("SaveAsync")]
+ [InlineData("SaveOrUpdateAsync")]
+ [InlineData("UpdateAsync")]
+ [InlineData("MergeAsync")]
+ [InlineData("RefreshAsync")]
+ public void Every_async_command_has_a_CancellationToken_overload(string methodName)
+ {
+ var overloads = Contract.GetMethods().Where(m => m.Name == methodName).ToList();
+
+ overloads.Should().NotBeEmpty();
+ overloads.Should().Contain(
+ m => m.GetParameters().Any(p => p.ParameterType == typeof(CancellationToken)),
+ $"{methodName} deve ter overload com CancellationToken");
+ }
+
+ [Theory]
+ [InlineData("FirstOrDefaultAsync")]
+ [InlineData("AnyAsync")]
+ [InlineData("CountAsync")]
+ [InlineData("ToListAsync")]
+ public void Async_query_helpers_take_an_optional_CancellationToken(string methodName)
+ {
+ var method = Contract.GetMethod(methodName)!;
+ var last = method.GetParameters().Last();
+
+ last.ParameterType.Should().Be(typeof(CancellationToken));
+ last.IsOptional.Should().BeTrue("o token é opcional (default) nesses helpers");
+ }
+
+ [Fact]
+ public void Synchronous_and_asynchronous_query_surface_is_complete()
+ {
+ var methodNames = Contract.GetMethods().Select(m => m.Name).Distinct();
+
+ methodNames.Should().Contain(
+ [
+ "All", "AllReadOnly", "Where", "WhereReadOnly", "WherePaged",
+ "Get", "Load", "Delete", "Save", "SaveOrUpdate", "Update",
+ "Merge", "Refresh", "IncludeMany"
+ ]);
+ }
+
+ [Fact]
+ public void Type_constraint_requires_class_implementing_IEntity()
+ {
+ var t = typeof(IRepository<>).GetGenericArguments()[0];
+
+ t.GenericParameterAttributes
+ .HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)
+ .Should().BeTrue();
+ t.GetGenericParameterConstraints()
+ .Should().Contain(typeof(Codout.Framework.Data.Entity.IEntity));
+ }
+}
diff --git a/tests/Codout.Framework.Data.Tests/TestEntities.cs b/tests/Codout.Framework.Data.Tests/TestEntities.cs
new file mode 100644
index 0000000..6cc7183
--- /dev/null
+++ b/tests/Codout.Framework.Data.Tests/TestEntities.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+using Codout.Framework.Data.Entity;
+
+namespace Codout.Framework.Data.Tests;
+
+///
+/// Implementações mínimas das abstrações de entidade, usadas pelos testes de contrato.
+///
+internal class FakeEntity : IEntity
+{
+ public IEnumerable GetSignatureProperties() => [];
+
+ public bool IsTransient() => true;
+}
+
+internal class StringIdEntity : IEntity
+{
+ public StringIdEntity(string id) => Id = id;
+
+ public string Id { get; }
+
+ public IEnumerable GetSignatureProperties() => [];
+
+ public bool IsTransient() => false;
+}
diff --git a/tests/Codout.Framework.Data.Tests/UnitOfWorkContractTests.cs b/tests/Codout.Framework.Data.Tests/UnitOfWorkContractTests.cs
new file mode 100644
index 0000000..e231abe
--- /dev/null
+++ b/tests/Codout.Framework.Data.Tests/UnitOfWorkContractTests.cs
@@ -0,0 +1,67 @@
+using System.Data;
+using Codout.Framework.Data;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Data.Tests;
+
+///
+/// Contrato do IUnitOfWork e do (obsoleto) IUnitOfWorkProvider.
+///
+public class UnitOfWorkContractTests
+{
+ [Fact]
+ public void IUnitOfWork_is_disposable_and_async_disposable()
+ {
+ typeof(IUnitOfWork).Should().Implement();
+ typeof(IUnitOfWork).Should().Implement();
+ }
+
+ [Fact]
+ public void Commit_and_BeginTransaction_have_IsolationLevel_overloads()
+ {
+ typeof(IUnitOfWork).GetMethod("Commit", [typeof(IsolationLevel)]).Should().NotBeNull();
+ typeof(IUnitOfWork).GetMethod("BeginTransaction", [typeof(IsolationLevel)]).Should().NotBeNull();
+ typeof(IUnitOfWork).GetMethod("BeginTransactionAsync",
+ [typeof(IsolationLevel), typeof(CancellationToken)]).Should().NotBeNull();
+ }
+
+ [Theory]
+ [InlineData("CommitAsync")]
+ [InlineData("RollbackAsync")]
+ public void Async_transaction_methods_take_an_optional_CancellationToken(string methodName)
+ {
+ var method = typeof(IUnitOfWork).GetMethod(methodName, [typeof(CancellationToken)])!;
+
+ method.GetParameters().Single().IsOptional.Should().BeTrue();
+ }
+
+ [Fact]
+ public void InTransaction_helpers_are_part_of_the_contract()
+ {
+ typeof(IUnitOfWork).GetMethods().Select(m => m.Name)
+ .Should().Contain(["InTransaction", "InTransactionAsync"]);
+ }
+
+ [Fact]
+ public void IUnitOfWorkProvider_is_marked_obsolete_but_still_compiles()
+ {
+ var attribute = typeof(IUnitOfWorkProvider<>)
+ .GetCustomAttributes(typeof(ObsoleteAttribute), false)
+ .Cast()
+ .Single();
+
+ attribute.IsError.Should().BeFalse("ainda é warning, não erro — remoção prevista para versão futura");
+ attribute.Message.Should().Contain("dependency injection");
+ }
+
+ [Fact]
+ public void IUnitOfWorkProvider_type_parameter_is_covariant()
+ {
+ var t = typeof(IUnitOfWorkProvider<>).GetGenericArguments()[0];
+
+ t.GenericParameterAttributes
+ .HasFlag(System.Reflection.GenericParameterAttributes.Covariant)
+ .Should().BeTrue("declarado como ");
+ }
+}
diff --git a/tests/Codout.Framework.Domain.Tests/AuditAndEventsTests.cs b/tests/Codout.Framework.Domain.Tests/AuditAndEventsTests.cs
new file mode 100644
index 0000000..10dfb12
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/AuditAndEventsTests.cs
@@ -0,0 +1,78 @@
+using Codout.Framework.Domain.Entities;
+using Codout.Framework.Domain.Entities.Events;
+using Codout.Framework.Domain.Interfaces;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Domain.Tests;
+
+///
+/// Bases de auditoria (AuditEntity / AuditEntityBase) e eventos de entidade
+/// (EntityCreated / EntityChanged / EntityDeleted).
+///
+public class AuditAndEventsTests
+{
+ private class AuditedOrder : AuditEntity
+ {
+ public string? Number { get; set; }
+ }
+
+ private class AuditedCustomer : AuditEntityBase
+ {
+ public string? Name { get; set; }
+ }
+
+ [Fact]
+ public void AuditEntity_exposes_audit_properties_and_implements_IAudit()
+ {
+ var createdAt = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
+ var updatedAt = createdAt.AddDays(1);
+
+ var order = new AuditedOrder
+ {
+ Number = "PED-1",
+ CreatedAt = createdAt,
+ CreatedBy = "ana",
+ UpdatedAt = updatedAt,
+ UpdatedBy = "bia"
+ };
+
+ order.Should().BeAssignableTo();
+ order.Should().BeAssignableTo>();
+ order.CreatedAt.Should().Be(createdAt);
+ order.CreatedBy.Should().Be("ana");
+ order.UpdatedAt.Should().Be(updatedAt);
+ order.UpdatedBy.Should().Be("bia");
+ }
+
+ [Fact]
+ public void AuditEntity_audit_fields_start_unset()
+ {
+ var order = new AuditedOrder();
+
+ order.CreatedAt.Should().BeNull();
+ order.UpdatedAt.Should().BeNull();
+ order.CreatedBy.Should().BeNull();
+ order.UpdatedBy.Should().BeNull();
+ }
+
+ [Fact]
+ public void AuditEntityBase_is_a_nullable_guid_entity_with_audit()
+ {
+ var customer = new AuditedCustomer { Name = "Ana" };
+
+ customer.Should().BeAssignableTo();
+ customer.Should().BeAssignableTo();
+ customer.Id.Should().BeNull("AuditEntityBase é store-generated");
+ }
+
+ [Fact]
+ public void Entity_events_carry_the_entity()
+ {
+ var order = new AuditedOrder { Number = "PED-1" };
+
+ new EntityCreated(order).Entity.Should().BeSameAs(order);
+ new EntityChanged(order).Entity.Should().BeSameAs(order);
+ new EntityDeleted(order).Entity.Should().BeSameAs(order);
+ }
+}
diff --git a/tests/Codout.Framework.Domain.Tests/Codout.Framework.Domain.Tests.csproj b/tests/Codout.Framework.Domain.Tests/Codout.Framework.Domain.Tests.csproj
new file mode 100644
index 0000000..84da2ef
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/Codout.Framework.Domain.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Codout.Framework.Domain.Tests/EntityEqualityTests.cs b/tests/Codout.Framework.Domain.Tests/EntityEqualityTests.cs
new file mode 100644
index 0000000..48a6803
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/EntityEqualityTests.cs
@@ -0,0 +1,181 @@
+using Codout.Framework.Domain.Base;
+using Codout.Framework.Domain.Entities;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Domain.Tests;
+
+///
+/// Igualdade de Entity<TId>: identidade por Id quando persistido, assinatura de
+/// domínio ([DomainSignature]) quando transient, e nunca igualdade entre tipos distintos.
+///
+public class EntityEqualityTests
+{
+ private class Person : Entity
+ {
+ [DomainSignature]
+ public string? Name { get; set; }
+
+ // Propriedade fora da assinatura: não deve influenciar a igualdade.
+ public int Age { get; set; }
+ }
+
+ private class Company : Entity
+ {
+ [DomainSignature]
+ public string? Name { get; set; }
+ }
+
+ // Entidade sem nenhuma propriedade [DomainSignature].
+ private class Anonymous : Entity
+ {
+ public string? Tag { get; set; }
+ }
+
+ [Fact]
+ public void Transient_entities_with_same_domain_signature_are_equal()
+ {
+ var a = new Person { Name = "Ana", Age = 30 };
+ var b = new Person { Name = "Ana", Age = 99 };
+
+ a.Equals(b).Should().BeTrue("ambas transient com mesma [DomainSignature]; Age não faz parte da assinatura");
+ }
+
+ [Fact]
+ public void Transient_entities_with_different_domain_signature_are_not_equal()
+ {
+ var a = new Person { Name = "Ana" };
+ var b = new Person { Name = "Bia" };
+
+ a.Equals(b).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Persisted_entities_with_same_id_are_equal_even_with_different_signature()
+ {
+ var a = new Person { Name = "Ana" };
+ var b = new Person { Name = "Bia" };
+ a.SetId(7);
+ b.SetId(7);
+
+ a.Equals(b).Should().BeTrue("identidade persistida (Id) prevalece sobre a assinatura");
+ }
+
+ [Fact]
+ public void Persisted_entities_with_different_ids_are_not_equal_even_with_same_signature()
+ {
+ var a = new Person { Name = "Ana" };
+ var b = new Person { Name = "Ana" };
+ a.SetId(1);
+ b.SetId(2);
+
+ a.Equals(b).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Persisted_entity_is_not_equal_to_transient_with_same_signature()
+ {
+ var persisted = new Person { Name = "Ana" };
+ persisted.SetId(1);
+ var transient = new Person { Name = "Ana" };
+
+ persisted.Equals(transient).Should().BeFalse("um persistido e um transient nunca são o mesmo objeto de domínio");
+ transient.Equals(persisted).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Entities_of_different_types_with_same_id_are_not_equal()
+ {
+ var person = new Person { Name = "Acme" };
+ var company = new Company { Name = "Acme" };
+ person.SetId(5);
+ company.SetId(5);
+
+ person.Equals(company).Should().BeFalse("tipos CLR diferentes nunca são iguais");
+ }
+
+ [Fact]
+ public void Same_reference_is_always_equal()
+ {
+ var a = new Person { Name = "Ana" };
+ a.Equals(a).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Entity_is_never_equal_to_null_or_other_kind_of_object()
+ {
+ var a = new Person { Name = "Ana" };
+
+ a.Equals(null).Should().BeFalse();
+ a.Equals("Ana").Should().BeFalse();
+ }
+
+ [Fact]
+ public void Transient_entities_without_signature_properties_fall_back_to_reference_equality()
+ {
+ var a = new Anonymous { Tag = "x" };
+ var b = new Anonymous { Tag = "x" };
+
+ a.Equals(b).Should().BeFalse("sem [DomainSignature] a comparação transient cai em igualdade de referência");
+ a.Equals(a).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Persisted_entities_with_same_id_have_same_hashcode()
+ {
+ var a = new Person { Name = "Ana" };
+ var b = new Person { Name = "Bia" };
+ a.SetId(7);
+ b.SetId(7);
+
+ a.GetHashCode().Should().Be(b.GetHashCode());
+ }
+
+ [Fact]
+ public void Transient_entities_with_same_signature_have_same_hashcode()
+ {
+ var a = new Person { Name = "Ana", Age = 1 };
+ var b = new Person { Name = "Ana", Age = 2 };
+
+ a.GetHashCode().Should().Be(b.GetHashCode(), "hash transient deriva da assinatura de domínio");
+ }
+
+ [Fact]
+ public void Hashcode_is_cached_and_stable_across_persistence()
+ {
+ // Design (padrão S#arp Architecture): o hash é congelado no primeiro uso para que
+ // a entidade não "se perca" dentro de um HashSet/Dictionary quando ganhar Id.
+ var person = new Person { Name = "Ana" };
+ var transientHash = person.GetHashCode();
+
+ person.SetId(42);
+
+ person.GetHashCode().Should().Be(transientHash, "o hash é cacheado no primeiro GetHashCode()");
+
+ var freshWithSameId = new Person { Name = "Ana" };
+ freshWithSameId.SetId(42);
+ freshWithSameId.GetHashCode().Should().NotBe(transientHash,
+ "uma instância nova com o mesmo Id calcula o hash por Id, diferente do hash transient congelado");
+ }
+
+ [Fact]
+ public void Entity_survives_hashset_membership_after_gaining_id()
+ {
+ var person = new Person { Name = "Ana" };
+ var set = new HashSet { person };
+
+ person.SetId(99);
+
+ set.Contains(person).Should().BeTrue("o cache de hash garante que a entidade continua localizável");
+ }
+
+ [Fact]
+ public void GetSignatureProperties_returns_only_domain_signature_properties()
+ {
+ var person = new Person { Name = "Ana", Age = 30 };
+
+ var properties = person.GetSignatureProperties().Select(p => p.Name);
+
+ properties.Should().BeEquivalentTo(["Name"]);
+ }
+}
diff --git a/tests/Codout.Framework.Domain.Tests/EntityIdentityTests.cs b/tests/Codout.Framework.Domain.Tests/EntityIdentityTests.cs
new file mode 100644
index 0000000..e69312f
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/EntityIdentityTests.cs
@@ -0,0 +1,147 @@
+using Codout.Framework.Data.Entity;
+using Codout.Framework.Domain.Entities;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Domain.Tests;
+
+///
+/// Identidade das entidades: semântica de IsTransient/SetId para diferentes tipos de Id
+/// e a invariante de kernel da ClientGeneratedEntity (Id atribuído pela aplicação no ctor).
+///
+public class EntityIdentityTests
+{
+ private class IntEntity : Entity;
+
+ private class GuidEntity : Entity;
+
+ private class NullableGuidEntity : Entity;
+
+ private class StringEntity : Entity;
+
+ private class Document : ClientGeneratedEntity
+ {
+ public string? Title { get; set; }
+ }
+
+ private class ConcreteBase : EntityBase;
+
+ [Fact]
+ public void Int_entity_is_transient_when_id_is_zero()
+ {
+ var entity = new IntEntity();
+ entity.IsTransient().Should().BeTrue();
+
+ entity.SetId(1);
+ entity.IsTransient().Should().BeFalse();
+ }
+
+ [Fact]
+ public void Guid_entity_is_transient_when_id_is_empty()
+ {
+ var entity = new GuidEntity();
+ entity.IsTransient().Should().BeTrue("Guid.Empty é o default");
+
+ entity.SetId(Guid.NewGuid());
+ entity.IsTransient().Should().BeFalse();
+ }
+
+ [Fact]
+ public void Nullable_guid_entity_is_transient_when_id_is_null()
+ {
+ var entity = new NullableGuidEntity();
+ entity.IsTransient().Should().BeTrue();
+
+ entity.SetId(Guid.NewGuid());
+ entity.IsTransient().Should().BeFalse();
+ }
+
+ [Fact]
+ public void String_entity_is_transient_when_id_is_null()
+ {
+ var entity = new StringEntity();
+ entity.IsTransient().Should().BeTrue();
+
+ entity.SetId("abc");
+ entity.IsTransient().Should().BeFalse();
+ }
+
+ [Fact]
+ public void SetId_assigns_the_id()
+ {
+ var entity = new IntEntity();
+ entity.SetId(123);
+ entity.Id.Should().Be(123);
+ }
+
+ [Fact]
+ public void EntityBase_uses_nullable_guid_id_and_starts_transient()
+ {
+ var entity = new ConcreteBase();
+
+ entity.Should().BeAssignableTo>();
+ entity.Id.Should().BeNull();
+ entity.IsTransient().Should().BeTrue("EntityBase é store-generated: nasce sem Id");
+ }
+
+ // ---- ClientGeneratedEntity: invariante de kernel (identity client-generated) ----
+
+ [Fact]
+ public void ClientGeneratedEntity_assigns_id_in_constructor()
+ {
+ var doc = new Document();
+
+ doc.Id.Should().NotBeNull("a aplicação atribui a PK na criação");
+ doc.Id.Should().NotBe(Guid.Empty);
+ doc.IsTransient().Should().BeFalse("já nasce com identidade");
+ }
+
+ [Fact]
+ public void ClientGeneratedEntity_generates_unique_ids_per_instance()
+ {
+ var a = new Document();
+ var b = new Document();
+
+ a.Id.Should().NotBe(b.Id!.Value);
+ }
+
+ [Fact]
+ public void ClientGeneratedEntity_implements_the_IClientGeneratedId_marker()
+ {
+ new Document().Should().BeAssignableTo(
+ "o marker é o opt-in da ClientGeneratedIdConvention no EF");
+ }
+
+ [Fact]
+ public void ClientGeneratedEntity_allows_rehydration_to_override_the_ctor_id()
+ {
+ // O EF materializa via ctor sem-args e sobrescreve o Id em seguida (SetId/setter).
+ var doc = new Document();
+ var persistedId = Guid.NewGuid();
+
+ doc.SetId(persistedId);
+
+ doc.Id.Should().Be(persistedId);
+ }
+
+ [Fact]
+ public void Two_client_generated_entities_are_not_equal_because_ids_differ()
+ {
+ var a = new Document { Title = "x" };
+ var b = new Document { Title = "x" };
+
+ a.Equals(b).Should().BeFalse("ambas já nascem persist-ready com Ids distintos");
+ }
+
+ [Fact]
+ public void Client_generated_entities_with_same_id_are_equal()
+ {
+ var id = Guid.NewGuid();
+ var a = new Document { Title = "x" };
+ var b = new Document { Title = "y" };
+ a.SetId(id);
+ b.SetId(id);
+
+ a.Equals(b).Should().BeTrue();
+ }
+}
diff --git a/tests/Codout.Framework.Domain.Tests/ValidatableObjectTests.cs b/tests/Codout.Framework.Domain.Tests/ValidatableObjectTests.cs
new file mode 100644
index 0000000..8b18255
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/ValidatableObjectTests.cs
@@ -0,0 +1,69 @@
+using System.ComponentModel.DataAnnotations;
+using System.Reflection;
+using Codout.Framework.Domain.Base;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Domain.Tests;
+
+///
+/// ValidatableObject: validação via DataAnnotations (Validator.TryValidateObject com
+/// validateAllProperties habilitado).
+///
+public class ValidatableObjectTests
+{
+ private class RegistrationForm : ValidatableObject
+ {
+ [Required]
+ public string? Name { get; set; }
+
+ [Range(1, 10)]
+ public int Score { get; set; } = 5;
+
+ [StringLength(5)]
+ public string? Nickname { get; set; }
+
+ protected override IEnumerable GetTypeSpecificSignatureProperties() => [];
+ }
+
+ [Fact]
+ public void Object_with_all_annotations_satisfied_is_valid()
+ {
+ var form = new RegistrationForm { Name = "Ana", Score = 10, Nickname = "an" };
+
+ form.IsValid().Should().BeTrue();
+ form.ValidationResults().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void Missing_required_property_makes_object_invalid()
+ {
+ var form = new RegistrationForm { Name = null };
+
+ form.IsValid().Should().BeFalse();
+ form.ValidationResults().Should().ContainSingle()
+ .Which.MemberNames.Should().Contain("Name");
+ }
+
+ [Fact]
+ public void Out_of_range_property_is_reported()
+ {
+ var form = new RegistrationForm { Name = "Ana", Score = 11 };
+
+ form.IsValid().Should().BeFalse("validateAllProperties=true valida além de [Required]");
+ form.ValidationResults().Should().ContainSingle()
+ .Which.MemberNames.Should().Contain("Score");
+ }
+
+ [Fact]
+ public void Multiple_violations_are_all_reported()
+ {
+ var form = new RegistrationForm { Name = null, Score = 0, Nickname = "muito-longo" };
+
+ var results = form.ValidationResults();
+
+ results.Should().HaveCount(3);
+ results.SelectMany(r => r.MemberNames)
+ .Should().BeEquivalentTo(["Name", "Score", "Nickname"]);
+ }
+}
diff --git a/tests/Codout.Framework.Domain.Tests/ValueObjectTests.cs b/tests/Codout.Framework.Domain.Tests/ValueObjectTests.cs
new file mode 100644
index 0000000..e16275b
--- /dev/null
+++ b/tests/Codout.Framework.Domain.Tests/ValueObjectTests.cs
@@ -0,0 +1,129 @@
+using Codout.Framework.Domain.Base;
+using FluentAssertions;
+using Xunit;
+
+namespace Codout.Framework.Domain.Tests;
+
+///
+/// Value objects: igualdade estrutural por TODAS as propriedades públicas, operadores
+/// ==/!= null-safe e a proibição de [DomainSignature] em value objects.
+///
+public class ValueObjectTests
+{
+ private class Money : ValueObject
+ {
+ public Money(string currency, decimal amount)
+ {
+ Currency = currency;
+ Amount = amount;
+ }
+
+ public string Currency { get; }
+ public decimal Amount { get; }
+ }
+
+ private class Weight : ValueObject
+ {
+ public Weight(decimal amount)
+ {
+ Amount = amount;
+ }
+
+ public decimal Amount { get; }
+ }
+
+ private class BadValueObject : ValueObject
+ {
+ [DomainSignature]
+ public string? Code { get; set; }
+ }
+
+ [Fact]
+ public void Value_objects_with_same_property_values_are_equal()
+ {
+ var a = new Money("BRL", 10.50m);
+ var b = new Money("BRL", 10.50m);
+
+ a.Equals(b).Should().BeTrue();
+ (a == b).Should().BeTrue();
+ (a != b).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Value_objects_with_different_property_values_are_not_equal()
+ {
+ var a = new Money("BRL", 10.50m);
+ var b = new Money("USD", 10.50m);
+ var c = new Money("BRL", 9.99m);
+
+ a.Equals(b).Should().BeFalse();
+ a.Equals(c).Should().BeFalse();
+ (a != b).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Equal_value_objects_have_same_hashcode()
+ {
+ var a = new Money("BRL", 10.50m);
+ var b = new Money("BRL", 10.50m);
+
+ a.GetHashCode().Should().Be(b.GetHashCode());
+ }
+
+ [Fact]
+ public void Different_value_objects_have_different_hashcodes()
+ {
+ var a = new Money("BRL", 10.50m);
+ var b = new Money("USD", 99.99m);
+
+ a.GetHashCode().Should().NotBe(b.GetHashCode());
+ }
+
+ [Fact]
+ public void Value_objects_of_different_types_are_not_equal_even_with_matching_values()
+ {
+ var money = new Money("1", 1m);
+ var weight = new Weight(1m);
+
+ money.Equals(weight).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equality_operator_handles_nulls()
+ {
+ Money? nullA = null;
+ Money? nullB = null;
+ var value = new Money("BRL", 1m);
+
+ (nullA == nullB).Should().BeTrue("null == null é consistente com C#");
+ (nullA == value).Should().BeFalse();
+ (value == nullA).Should().BeFalse();
+ (value != nullA).Should().BeTrue();
+ value.Equals(null).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Value_object_works_in_linq_set_operations()
+ {
+ var list = new[]
+ {
+ new Money("BRL", 1m),
+ new Money("BRL", 1m),
+ new Money("USD", 2m)
+ };
+
+ list.Distinct().Should().HaveCount(2, "igualdade estrutural deduplica");
+ }
+
+ [Fact]
+ public void DomainSignature_on_value_object_property_throws()
+ {
+ var a = new BadValueObject { Code = "x" };
+ var b = new BadValueObject { Code = "x" };
+
+ var act = () => a.Equals(b);
+
+ act.Should().Throw(
+ "[DomainSignature] é proibido em value objects: a assinatura já é o conjunto de todas as propriedades");
+ }
+}
diff --git a/tests/Codout.Framework.EF.Tests/EFRepositoryAsyncTests.cs b/tests/Codout.Framework.EF.Tests/EFRepositoryAsyncTests.cs
new file mode 100644
index 0000000..1fbe98f
--- /dev/null
+++ b/tests/Codout.Framework.EF.Tests/EFRepositoryAsyncTests.cs
@@ -0,0 +1,185 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Xunit;
+
+namespace Codout.Framework.EF.Tests;
+
+///
+/// Superfície assíncrona do EFRepository: queries, comandos e overloads de
+/// CancellationToken (incluindo tokens já cancelados).
+///
+public class EFRepositoryAsyncTests : SqliteTestBase
+{
+ private static readonly CancellationToken Cancelled = new(canceled: true);
+
+ private Guid Seed(params Customer[] customers)
+ {
+ using var context = CreateContext();
+ context.Customers.AddRange(customers);
+ context.SaveChanges();
+ return customers.First().Id!.Value;
+ }
+
+ [Fact]
+ public async Task GetAsync_by_predicate_and_by_key_round_trip()
+ {
+ var id = Seed(new Customer { Name = "Ana", Age = 30 });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ (await repository.GetAsync(c => c.Name == "Ana")).Id.Should().Be(id);
+ (await repository.GetAsync((object)id)).Name.Should().Be("Ana");
+ (await repository.LoadAsync(id)).Name.Should().Be("Ana");
+ (await repository.GetAsync(c => c.Name == "Zoe")).Should().BeNull();
+ }
+
+ [Fact]
+ public async Task FirstOrDefault_Any_Count_ToList_work()
+ {
+ Seed(
+ new Customer { Name = "Ana", Age = 18 },
+ new Customer { Name = "Bia", Age = 40 },
+ new Customer { Name = "Caio", Age = 65 });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ (await repository.FirstOrDefaultAsync(c => c.Age > 100)).Should().BeNull();
+ (await repository.AnyAsync(c => c.Age >= 40)).Should().BeTrue();
+ (await repository.AnyAsync(c => c.Age > 100)).Should().BeFalse();
+ (await repository.CountAsync(c => c.Age >= 40)).Should().Be(2);
+ (await repository.ToListAsync(c => c.Age >= 40)).Should().HaveCount(2);
+ }
+
+ [Fact]
+ public async Task SaveAsync_and_DeleteAsync_round_trip()
+ {
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ await repository.SaveAsync(new Customer { Name = "Ana" });
+ await repository.SaveAsync(new Customer { Name = "Bia" }, CancellationToken.None);
+ await context.SaveChangesAsync();
+ }
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ var ana = await repository.GetAsync(c => c.Name == "Ana");
+ await repository.DeleteAsync(ana);
+ await context.SaveChangesAsync();
+ }
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ await repository.DeleteAsync(c => c.Name == "Bia");
+ await context.SaveChangesAsync();
+ }
+
+ using var verify = CreateContext();
+ (await verify.Customers.CountAsync()).Should().Be(0);
+ }
+
+ [Fact]
+ public async Task UpdateAsync_persists_changes_of_detached_entity()
+ {
+ var id = Seed(new Customer { Name = "Ana", Age = 30 });
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ var detached = new Customer { Name = "Atualizada", Age = 31 };
+ detached.SetId(id);
+
+ await repository.UpdateAsync(detached);
+ await context.SaveChangesAsync();
+ }
+
+ using var verify = CreateContext();
+ (await verify.Customers.SingleAsync(c => c.Id == id)).Name.Should().Be("Atualizada");
+ }
+
+ [Fact]
+ public async Task MergeAsync_attaches_detached_entity()
+ {
+ var id = Seed(new Customer { Name = "Ana", Age = 30 });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var detached = new Customer { Name = "Ana", Age = 30 };
+ detached.SetId(id);
+
+ var merged = await repository.MergeAsync(detached);
+
+ merged.Should().BeSameAs(detached);
+ context.Entry(detached).State.Should().Be(EntityState.Unchanged);
+ }
+
+ [Fact]
+ public async Task RefreshAsync_restores_database_values()
+ {
+ var id = Seed(new Customer { Name = "Ana", Age = 30 });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var entity = await repository.GetAsync((object)id);
+ entity.Name = "Local";
+
+ var refreshed = await repository.RefreshAsync(entity);
+
+ refreshed.Name.Should().Be("Ana");
+ }
+
+ [Fact]
+ public async Task Cancelled_token_aborts_async_queries()
+ {
+ Seed(new Customer { Name = "Ana" });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ await FluentActions.Awaiting(() => repository.GetAsync(c => c.Name == "Ana", Cancelled))
+ .Should().ThrowAsync();
+ await FluentActions.Awaiting(() => repository.CountAsync(c => true, Cancelled))
+ .Should().ThrowAsync();
+ await FluentActions.Awaiting(() => repository.ToListAsync(c => true, Cancelled))
+ .Should().ThrowAsync();
+ await FluentActions.Awaiting(() => repository.DeleteAsync(c => true, Cancelled))
+ .Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task Cancelled_token_aborts_GetAsync_by_key_for_untracked_entity()
+ {
+ var id = Seed(new Customer { Name = "Ana" });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ await FluentActions.Awaiting(() => repository.GetAsync((object)id, Cancelled))
+ .Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task RefreshAsync_with_token_currently_ignores_cancellation()
+ {
+ // BUG?: RefreshAsync(entity, cancellationToken) delega para o Refresh SÍNCRONO
+ // (Task.FromResult(Refresh(entity))): bloqueia a thread e IGNORA o token.
+ // Characterization test do comportamento atual — ver tests/FINDINGS-B.md.
+ var id = Seed(new Customer { Name = "Ana", Age = 30 });
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var entity = await repository.GetAsync((object)id);
+ entity.Name = "Local";
+
+ var refreshed = await repository.RefreshAsync(entity, Cancelled);
+
+ refreshed.Name.Should().Be("Ana", "o reload acontece mesmo com token cancelado");
+ }
+}
diff --git a/tests/Codout.Framework.EF.Tests/EFRepositoryCrudTests.cs b/tests/Codout.Framework.EF.Tests/EFRepositoryCrudTests.cs
new file mode 100644
index 0000000..508c5a0
--- /dev/null
+++ b/tests/Codout.Framework.EF.Tests/EFRepositoryCrudTests.cs
@@ -0,0 +1,281 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Xunit;
+
+namespace Codout.Framework.EF.Tests;
+
+///
+/// CRUD básico do EFRepository sobre SQLite in-memory: queries (All/AllReadOnly/Where/
+/// WhereReadOnly/WherePaged/Get/Load), comandos (Save/Delete/Update/Merge/Refresh)
+/// e Includes.
+///
+public class EFRepositoryCrudTests : SqliteTestBase
+{
+ private static Customer NewCustomer(string name, int age = 20) => new() { Name = name, Age = age };
+
+ private Guid SeedCustomers(params Customer[] customers)
+ {
+ using var context = CreateContext();
+ context.Customers.AddRange(customers);
+ context.SaveChanges();
+ return customers.First().Id!.Value;
+ }
+
+ [Fact]
+ public void Save_then_Get_by_key_round_trips()
+ {
+ Guid id;
+ using (var context = new EFRepositoryScope(CreateContext()))
+ {
+ var saved = context.Repository.Save(NewCustomer("Ana", 30));
+ context.Context.SaveChanges();
+ id = saved.Id!.Value;
+ }
+
+ using var verify = new EFRepositoryScope(CreateContext());
+ var loaded = verify.Repository.Get(id);
+
+ loaded.Should().NotBeNull();
+ loaded.Name.Should().Be("Ana");
+ loaded.Age.Should().Be(30);
+ }
+
+ [Fact]
+ public void Save_throws_on_null_entity()
+ {
+ using var scope = new EFRepositoryScope(CreateContext());
+
+ var act = () => scope.Repository.Save(null!);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Get_by_predicate_returns_single_match()
+ {
+ SeedCustomers(NewCustomer("Ana"), NewCustomer("Bia"));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ scope.Repository.Get(c => c.Name == "Bia").Name.Should().Be("Bia");
+ scope.Repository.Get(c => c.Name == "Zoe").Should().BeNull();
+ }
+
+ [Fact]
+ public void Get_by_predicate_throws_when_multiple_match()
+ {
+ SeedCustomers(NewCustomer("Ana", 30), NewCustomer("Bia", 30));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ var act = () => scope.Repository.Get(c => c.Age == 30);
+
+ act.Should().Throw("Get usa SingleOrDefault");
+ }
+
+ [Fact]
+ public void Load_delegates_to_Get_by_key()
+ {
+ var id = SeedCustomers(NewCustomer("Ana"));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ scope.Repository.Load(id).Name.Should().Be("Ana");
+ }
+
+ [Fact]
+ public void All_returns_every_entity()
+ {
+ SeedCustomers(NewCustomer("Ana"), NewCustomer("Bia"), NewCustomer("Caio"));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ scope.Repository.All().Count().Should().Be(3);
+ }
+
+ [Fact]
+ public void All_tracks_entities_but_AllReadOnly_does_not()
+ {
+ SeedCustomers(NewCustomer("Ana"));
+
+ using (var tracked = new EFRepositoryScope(CreateContext()))
+ {
+ _ = tracked.Repository.All().ToList();
+ tracked.Context.ChangeTracker.Entries().Should().NotBeEmpty();
+ }
+
+ using var untracked = new EFRepositoryScope(CreateContext());
+ _ = untracked.Repository.AllReadOnly().ToList();
+ untracked.Context.ChangeTracker.Entries().Should().BeEmpty("AsNoTracking não rastreia");
+ }
+
+ [Fact]
+ public void Where_filters_and_WhereReadOnly_does_not_track()
+ {
+ SeedCustomers(NewCustomer("Ana", 18), NewCustomer("Bia", 40), NewCustomer("Caio", 65));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ var tracked = scope.Repository.Where(c => c.Age >= 40).ToList();
+ tracked.Should().HaveCount(2);
+
+ _ = scope.Repository.WhereReadOnly(c => c.Age >= 40).ToList();
+ scope.Context.ChangeTracker.Entries().Count().Should().Be(2, "apenas o Where tracked rastreou");
+ }
+
+ [Fact]
+ public void WherePaged_returns_page_and_total()
+ {
+ SeedCustomers(
+ NewCustomer("A", 30), NewCustomer("B", 30), NewCustomer("C", 30),
+ NewCustomer("D", 30), NewCustomer("E", 10));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+
+ var page0 = scope.Repository.WherePaged(c => c.Age == 30, out var total, index: 0, size: 3).ToList();
+ total.Should().Be(4, "total conta TODOS os que casam com o predicado");
+ page0.Should().HaveCount(3);
+
+ var page1 = scope.Repository.WherePaged(c => c.Age == 30, out total, index: 1, size: 3).ToList();
+ page1.Should().HaveCount(1, "última página parcial");
+ total.Should().Be(4);
+ }
+
+ [Fact]
+ public void Delete_entity_removes_the_row()
+ {
+ var id = SeedCustomers(NewCustomer("Ana"));
+
+ using (var scope = new EFRepositoryScope(CreateContext()))
+ {
+ var entity = scope.Repository.Get(id);
+ scope.Repository.Delete(entity);
+ scope.Context.SaveChanges();
+ }
+
+ using var verify = new EFRepositoryScope(CreateContext());
+ verify.Repository.All().Count().Should().Be(0);
+ }
+
+ [Fact]
+ public void Delete_by_predicate_removes_only_matches()
+ {
+ SeedCustomers(NewCustomer("Ana", 18), NewCustomer("Bia", 40), NewCustomer("Caio", 70));
+
+ using (var scope = new EFRepositoryScope(CreateContext()))
+ {
+ scope.Repository.Delete(c => c.Age >= 40);
+ scope.Context.SaveChanges();
+ }
+
+ using var verify = new EFRepositoryScope(CreateContext());
+ verify.Repository.All().Single().Name.Should().Be("Ana");
+ }
+
+ [Fact]
+ public void Delete_throws_on_null_entity()
+ {
+ using var scope = new EFRepositoryScope(CreateContext());
+ var act = () => scope.Repository.Delete((Customer)null!);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Update_marks_detached_entity_as_modified_and_persists()
+ {
+ var id = SeedCustomers(NewCustomer("Ana", 30));
+
+ using (var scope = new EFRepositoryScope(CreateContext()))
+ {
+ var detached = new Customer { Name = "Ana Maria", Age = 31 };
+ detached.SetId(id);
+
+ scope.Repository.Update(detached);
+ scope.Context.Entry(detached).State.Should().Be(EntityState.Modified);
+ scope.Context.SaveChanges();
+ }
+
+ using var verify = new EFRepositoryScope(CreateContext());
+ var reloaded = verify.Repository.Get(id);
+ reloaded.Name.Should().Be("Ana Maria");
+ reloaded.Age.Should().Be(31);
+ }
+
+ [Fact]
+ public void Merge_attaches_detached_entity_as_unchanged()
+ {
+ var id = SeedCustomers(NewCustomer("Ana", 30));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ var detached = new Customer { Name = "Ana", Age = 30 };
+ detached.SetId(id);
+
+ var merged = scope.Repository.Merge(detached);
+
+ merged.Should().BeSameAs(detached);
+ scope.Context.Entry(detached).State.Should().Be(EntityState.Unchanged,
+ "Attach com PK preenchida rastreia sem marcar como Modified");
+ }
+
+ [Fact]
+ public void Refresh_restores_database_values_over_local_changes()
+ {
+ var id = SeedCustomers(NewCustomer("Ana", 30));
+
+ using var scope = new EFRepositoryScope(CreateContext());
+ var entity = scope.Repository.Get(id);
+ entity.Name = "Renomeada";
+
+ var refreshed = scope.Repository.Refresh(entity);
+
+ refreshed.Should().BeSameAs(entity);
+ entity.Name.Should().Be("Ana", "Reload descarta a mutação local");
+ scope.Context.Entry(entity).State.Should().Be(EntityState.Unchanged);
+ }
+
+ [Fact]
+ public void IncludeMany_loads_related_collection()
+ {
+ Guid blogId;
+ using (var seed = CreateContext())
+ {
+ var blog = new Blog { Title = "blog" };
+ blog.Posts.Add(new Post { BlogId = blog.Id!.Value, Content = "p1" });
+ blog.Posts.Add(new Post { BlogId = blog.Id!.Value, Content = "p2" });
+ seed.Blogs.Add(blog);
+ seed.SaveChanges();
+ blogId = blog.Id!.Value;
+ }
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var loaded = repository.IncludeMany(b => b.Posts).Single(b => b.Id == blogId);
+
+ loaded.Posts.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Dispose_does_not_dispose_the_context()
+ {
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ repository.Dispose();
+
+ // O contexto continua usável: o ciclo de vida é do DI/UnitOfWork.
+ var act = () => context.Customers.Count();
+ act.Should().NotThrow();
+ }
+
+ [Fact]
+ public void Constructor_throws_on_null_context()
+ {
+ var act = () => new EFRepository(null!);
+ act.Should().Throw();
+ }
+
+ /// Par contexto+repositório com descarte do contexto ao final do escopo.
+ private sealed class EFRepositoryScope(TestDbContext context) : IDisposable
+ {
+ public TestDbContext Context { get; } = context;
+ public EFRepository Repository { get; } = new(context);
+ public void Dispose() => Context.Dispose();
+ }
+}
diff --git a/tests/Codout.Framework.EF.Tests/EFRepositorySaveOrUpdateTests.cs b/tests/Codout.Framework.EF.Tests/EFRepositorySaveOrUpdateTests.cs
new file mode 100644
index 0000000..62f4fc0
--- /dev/null
+++ b/tests/Codout.Framework.EF.Tests/EFRepositorySaveOrUpdateTests.cs
@@ -0,0 +1,222 @@
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using Xunit;
+
+namespace Codout.Framework.EF.Tests;
+
+///
+/// SaveOrUpdate / SaveOrUpdateAsync — a lógica delicada do fix 6.3.0:
+/// insert vs. update decidido por Entry().State + inspeção da PK + Find no banco,
+/// e NÃO por IEntity.IsTransient() (que falha para Id pré-atribuído no ctor).
+///
+public class EFRepositorySaveOrUpdateTests : SqliteTestBase
+{
+ private Guid Seed(string name = "Ana", int age = 30)
+ {
+ using var context = CreateContext();
+ var customer = new Customer { Name = name, Age = age };
+ context.Customers.Add(customer);
+ context.SaveChanges();
+ return customer.Id!.Value;
+ }
+
+ // ---- Insert: entidade nova com Id pré-atribuído (o caso que o IsTransient errava) ----
+
+ [Fact]
+ public void Detached_new_entity_with_preassigned_id_is_inserted()
+ {
+ var customer = new Customer { Name = "Ana", Age = 30 }; // Id setado no ctor (ClientGeneratedEntity)
+ customer.IsTransient().Should().BeFalse("o cenário-chave: Id pré-atribuído engana o IsTransient()");
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ var result = repository.SaveOrUpdate(customer);
+
+ result.Should().BeSameAs(customer);
+ context.Entry(customer).State.Should().Be(EntityState.Added, "linha não existe ⇒ INSERT");
+ context.SaveChanges();
+ }
+
+ using var verify = CreateContext();
+ verify.Customers.Single().Name.Should().Be("Ana");
+ }
+
+ [Fact]
+ public void Entity_with_default_key_is_added_without_database_roundtrip()
+ {
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var invoice = new Invoice { Number = "INV-1" }; // Id int = 0 (store-generated)
+ repository.SaveOrUpdate(invoice);
+
+ context.Entry(invoice).State.Should().Be(EntityState.Added);
+ context.SaveChanges();
+ invoice.Id.Should().BeGreaterThan(0, "autoincrement preenche a chave");
+ }
+
+ // ---- Update: entidade detached cuja linha já existe ----
+
+ [Fact]
+ public void Detached_entity_with_existing_row_updates_the_row()
+ {
+ var id = Seed("Ana", 30);
+
+ var detached = new Customer { Name = "Ana Maria", Age = 31 };
+ detached.SetId(id);
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ var result = repository.SaveOrUpdate(detached);
+
+ result.Should().BeSameAs(detached, "o contrato devolve a instância recebida");
+ context.SaveChanges();
+ }
+
+ using var verify = CreateContext();
+ var reloaded = verify.Customers.Single(c => c.Id == id);
+ reloaded.Name.Should().Be("Ana Maria");
+ reloaded.Age.Should().Be(31);
+ verify.Customers.Count().Should().Be(1, "UPDATE, não um segundo INSERT");
+ }
+
+ [Fact]
+ public void Detached_update_copies_values_into_the_tracked_instance_not_the_argument()
+ {
+ var id = Seed("Ana");
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var detached = new Customer { Name = "Nova" };
+ detached.SetId(id);
+
+ repository.SaveOrUpdate(detached);
+
+ // Quem fica tracked é a instância carregada pelo Find (existing); a recebida
+ // permanece Detached. Comportamento documentado do fix 6.3.0 (preserva
+ // OriginalValues/tokens de concorrência do banco).
+ context.Entry(detached).State.Should().Be(EntityState.Detached);
+
+ var trackedEntry = context.ChangeTracker.Entries().Single();
+ trackedEntry.State.Should().Be(EntityState.Modified);
+ trackedEntry.Entity.Name.Should().Be("Nova");
+ }
+
+ // ---- Entidade já rastreada ----
+
+ [Fact]
+ public void Tracked_unchanged_entity_is_not_forced_to_modified()
+ {
+ var id = Seed("Ana");
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ var tracked = repository.Get(id);
+ repository.SaveOrUpdate(tracked);
+
+ context.Entry(tracked).State.Should().Be(EntityState.Unchanged,
+ "mudança de comportamento 6.3.0: confia no change tracker, sem force full update");
+ }
+
+ [Fact]
+ public void Tracked_entity_with_mutation_persists_via_change_tracker()
+ {
+ var id = Seed("Ana");
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ var tracked = repository.Get(id);
+ tracked.Name = "Mudada";
+
+ repository.SaveOrUpdate(tracked);
+ context.SaveChanges();
+ }
+
+ using var verify = CreateContext();
+ verify.Customers.Single(c => c.Id == id).Name.Should().Be("Mudada");
+ }
+
+ [Fact]
+ public void SaveOrUpdate_throws_on_null_entity()
+ {
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+
+ FluentActions.Invoking(() => repository.SaveOrUpdate(null!))
+ .Should().Throw();
+ }
+
+ // ---- Variantes assíncronas ----
+
+ [Fact]
+ public async Task SaveOrUpdateAsync_inserts_detached_new_entity_with_preassigned_id()
+ {
+ var customer = new Customer { Name = "Async", Age = 1 };
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ await repository.SaveOrUpdateAsync(customer);
+
+ context.Entry(customer).State.Should().Be(EntityState.Added);
+ await context.SaveChangesAsync();
+ }
+
+ using var verify = CreateContext();
+ (await verify.Customers.CountAsync()).Should().Be(1);
+ }
+
+ [Fact]
+ public async Task SaveOrUpdateAsync_updates_existing_row_from_detached_entity()
+ {
+ var id = Seed("Ana", 30);
+
+ var detached = new Customer { Name = "Async Update", Age = 99 };
+ detached.SetId(id);
+
+ using (var context = CreateContext())
+ {
+ var repository = new EFRepository(context);
+ await repository.SaveOrUpdateAsync(detached);
+ await context.SaveChangesAsync();
+ }
+
+ using var verify = CreateContext();
+ var reloaded = await verify.Customers.SingleAsync(c => c.Id == id);
+ reloaded.Name.Should().Be("Async Update");
+ reloaded.Age.Should().Be(99);
+ }
+
+ [Fact]
+ public async Task SaveOrUpdateAsync_honors_a_cancelled_token_when_a_lookup_is_needed()
+ {
+ var id = Seed("Ana");
+
+ var detached = new Customer { Name = "X" };
+ detached.SetId(id);
+
+ using var context = CreateContext();
+ var repository = new EFRepository(context);
+ var cancelled = new CancellationToken(canceled: true);
+
+ var act = () => repository.SaveOrUpdateAsync(detached, cancelled);
+
+ await act.Should().ThrowAsync