Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@
coverage*.json
coverage*.xml
coverage*.info

# SQLite — fichier de base de données généré au démarrage (artefact, pas du code source)
*.db
*.db-shm
*.db-wal
69 changes: 65 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,32 @@ L'objectif est de progresser phase par phase en construisant une API REST compl
|-----------|------------|
| Framework | ASP.NET Core Web API (.NET 10) |
| Langage | C# |
| Stockage | Liste statique en mémoire |
| Stockage | SQLite via Entity Framework Core |
| Tests | xUnit + Moq |
| Architecture | DDD — Aggregate Root, Value Object, Application Service |

---

## Dépendances NuGet

### Projet `tasks`

| Package | Rôle |
|---------|------|
| `Microsoft.EntityFrameworkCore.Sqlite` | Driver SQLite pour EF Core — permet de lire/écrire dans un fichier `.db` |
| `Microsoft.EntityFrameworkCore.Design` | Outils de design nécessaires pour générer les migrations (code de création de table) |
| `Microsoft.EntityFrameworkCore.Tools` | Commandes CLI `dotnet ef` — créer, appliquer et supprimer les migrations |

### Pourquoi SQLite et pas PostgreSQL ?

SQLite est une base de données SQL **sans serveur** : le moteur est embarqué dans l'application et les données sont stockées dans un fichier local (`tasks.db`). Contrairement à PostgreSQL qui requiert un processus serveur actif, SQLite ne nécessite rien d'autre que le package NuGet.

Ce n'est **pas** un équivalent de H2 (Spring) ou du provider `InMemory` d'EF Core — ceux-ci stockent les données en RAM et les perdent au redémarrage. SQLite persiste réellement les données sur disque, comme le ferait PostgreSQL, mais sans infrastructure.

Pour les **tests**, SQLite peut aussi fonctionner en mode mémoire (`DataSource=:memory:`) : chaque test obtient une base isolée, vide, détruite à la fin du test — comportement similaire à H2 dans Spring Boot.

---

## Lancer le projet

```bash
Expand All @@ -29,6 +49,46 @@ dotnet run

---

## Commandes EF Core — Migrations

Les migrations traduisent les classes C# en instructions SQL et versionnent l'évolution du schéma de base de données.

### Option A — Console du Gestionnaire de packages (Visual Studio)

Outils → Gestionnaire de packages NuGet → Console du Gestionnaire de packages
Vérifier que le **"Projet par défaut"** est bien `tasks`.

```powershell
# Créer une migration
Add-Migration <NomDeLaMigration>

# Appliquer les migrations en attente sur la base
Update-Database

# Annuler la dernière migration (avant application)
Remove-Migration
```

### Option B — Terminal

```bash
# Créer une migration
dotnet ef migrations add <NomDeLaMigration> --project tasks --startup-project tasks

# Appliquer les migrations en attente sur la base
dotnet ef database update --project tasks --startup-project tasks

# Annuler la dernière migration (avant application)
dotnet ef migrations remove --project tasks --startup-project tasks
```

> Les migrations sont appliquées automatiquement au démarrage de l'application via `db.Database.Migrate()` dans `Program.cs`.
> `Update-Database` / `dotnet ef database update` restent utiles pour appliquer manuellement sans relancer l'API.

Référence complète : [`docs/reference/efcore-setup.md`](docs/reference/efcore-setup.md)

---

## Tests unitaires

```bash
Expand Down Expand Up @@ -118,7 +178,8 @@ dotnet-api-sandbox/
│ ├── reference/
│ │ ├── aide-memoire.md # Concepts clés : SOLID, DDD, architecture, DTOs
│ │ ├── unit-tests-xunit.md # Patterns xUnit (AAA, Theory, assertions, Moq)
│ │ └── unit-tests-controllers.md # Tests unitaires des controllers
│ │ ├── unit-tests-controllers.md # Tests unitaires des controllers
│ │ └── efcore-setup.md # Configuration EF Core complète (SQLite → PostgreSQL/SQL Server)
│ └── roadmap/
│ └── LEARNING_PATH.md # Curriculum complet (17 phases)
└── tasks.slnx # Solution Visual Studio
Expand All @@ -137,8 +198,8 @@ dotnet-api-sandbox/
| 5 | Gestion des erreurs (Exceptions + Middleware) | ✅ |
| 6 | Tests unitaires (xUnit + Moq) | ✅ |
| 7 | Principes SOLID | ✅ |
| 8 | Domain-Driven Design (DDD) | 🔄 |
| 9 | Entity Framework Core | |
| 8 | Domain-Driven Design (DDD) | |
| 9 | Entity Framework Core | 🔄 |
| 10 | Tests d'intégration | ⬜ |
| 11 | FluentValidation | ⬜ |
| 12 | Logging (Serilog) | ⬜ |
Expand Down
201 changes: 201 additions & 0 deletions Tasks.Tests/TaskRepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using tasks.Domain;
using tasks.Infrastructure.Persistence;
using tasks.Repositories;

namespace Tasks.Tests
{
public class TaskRepositoryTests : IDisposable
{
private readonly SqliteConnection _connection;
private readonly AppDbContext _context;
private readonly TaskRepository _repository;

public TaskRepositoryTests()
{
// Ouvre une connexion SQLite in-memory qui reste ouverte pendant tout le test
_connection = new SqliteConnection("DataSource=:memory:");
_connection.Open();

// Configure EF Core pour utiliser cette connexion
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(_connection)
.Options;

// Crée le schéma (tables) sans passer par les migrations
_context = new AppDbContext(options);
_context.Database.EnsureCreated();

_repository = new TaskRepository(_context);
}

// Détruit la connexion in-memory à la fin de chaque test
public void Dispose()
{
_context.Dispose();
_connection.Dispose();
}

private TaskItem CreateAndAdd(string title = "Test Task")
{
var task = new TaskItem(new TaskTitle(title));
return _repository.Add(task);
}

// ───── Add ─────

[Fact]
public void Add_DoitPersisterLaTache()
{
var task = new TaskItem(new TaskTitle("Apprendre EF Core"));

var result = _repository.Add(task);

Assert.NotNull(result);
Assert.True(result.Id > 0);
Assert.Equal("Apprendre EF Core", result.Title.Value);
}

[Fact]
public void Add_DoitGenererUnIdUnique_PourChaqueTache()
{
var task1 = CreateAndAdd("Tâche 1");
var task2 = CreateAndAdd("Tâche 2");

Assert.NotEqual(task1.Id, task2.Id);
}

// ───── GetAll ─────

[Fact]
public void GetAll_DoitRetournerToutesLesTaches()
{
CreateAndAdd("Tâche 1");
CreateAndAdd("Tâche 2");
CreateAndAdd("Tâche 3");

var result = _repository.GetAll();

Assert.Equal(3, result.Count());
}

[Fact]
public void GetAll_DoitRetournerListeVide_QuandAucuneTache()
{
var result = _repository.GetAll();

Assert.Empty(result);
}

// ───── GetById ─────

[Fact]
public void GetById_DoitRetournerLaTache_QuandElleExiste()
{
var task = CreateAndAdd("Tâche existante");

var result = _repository.GetById(task.Id);

Assert.NotNull(result);
Assert.Equal(task.Id, result.Id);
Assert.Equal("Tâche existante", result.Title.Value);
}

[Fact]
public void GetById_DoitRetournerNull_QuandElleNExistePas()
{
var result = _repository.GetById(999);

Assert.Null(result);
}

// ───── GetByTitle ─────

[Fact]
public void GetByTitle_DoitRetournerLesTaches_QuandLeTitreCorrespond()
{
CreateAndAdd("Apprendre ASP.NET");
CreateAndAdd("Apprendre EF Core");
CreateAndAdd("Écrire des tests");

var result = _repository.GetByTitle("Apprendre");

Assert.Equal(2, result.Count());
}

[Fact]
public void GetByTitle_DoitRetournerListeVide_QuandAucuneCorrespondance()
{
CreateAndAdd("Apprendre EF Core");

var result = _repository.GetByTitle("PostgreSQL");

Assert.Empty(result);
}

// ───── Delete ─────

[Fact]
public void Delete_DoitSupprimerLaTache()
{
var task = CreateAndAdd("Tâche à supprimer");

_repository.Delete(task);

var result = _repository.GetById(task.Id);
Assert.Null(result);
}

[Fact]
public void Delete_DoitDiminuerLeNombreDeTaches()
{
CreateAndAdd("Tâche 1");
var task2 = CreateAndAdd("Tâche 2");

_repository.Delete(task2);

Assert.Single(_repository.GetAll());
}

// ───── Update ─────

[Fact]
public void Update_DoitMettreAJourLeTitre()
{
var task = CreateAndAdd("Ancien titre");
task.UpdateTitle(new TaskTitle("Nouveau titre"));

_repository.Update(task);

var result = _repository.GetById(task.Id);
Assert.Equal("Nouveau titre", result!.Title.Value);
}

[Fact]
public void Update_DoitMettreAJourIsDone()
{
var task = CreateAndAdd("Tâche");
task.MarkAsDone(true);

_repository.Update(task);

var result = _repository.GetById(task.Id);
Assert.True(result!.IsDone);
}

// ───── Patch ─────

[Fact]
public void Patch_DoitModifierPartiellementLaTache()
{
var task = CreateAndAdd("Titre original");
task.MarkAsDone(true);

_repository.Patch(task);

var result = _repository.GetById(task.Id);
Assert.True(result!.IsDone);
}
}
}
1 change: 1 addition & 0 deletions Tasks.Tests/Tasks.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
Expand Down
Loading
Loading