Document créé lors du Sprint 45 (Phase 10 — Consolidation qualité). Objectif Phase 10 : couverture globale ≥ 70% mesurée sur
./...avec CGO activé.
cd apps/go-api
CGO_ENABLED=0 go test ./internal/domain/... ./internal/analysis/... ./contracttest/... \
-timeout 60s -count=1 -vCes tests ne nécessitent pas DuckDB et tournent sur toutes les plateformes.
cd apps/go-api
CGO_ENABLED=1 LEVELUP_DEMO_MODE=true go test ./... -timeout 5m -count=1 -vSur Windows, s'assurer que le compilateur MinGW est disponible :
# MinGW via MSYS2
$env:CGO_ENABLED = "1"
$env:CC = "gcc"
go test ./... -timeout 5m -count=1cd apps/go-api
# 1. Générer le profil brut
CGO_ENABLED=1 LEVELUP_DEMO_MODE=true \
go test -coverprofile=coverage.out.raw -covermode=atomic \
-coverpkg=./... ./... -timeout 5m -count=1
# 2. Filtrer les packages exclus
bash scripts/coverage_filter.sh coverage.out.raw > coverage.out
# 3. Afficher le résumé
go tool cover -func=coverage.out | tail -5
# 4. Ouvrir le rapport HTML
go tool cover -html=coverage.out -o coverage.html
# Puis ouvrir coverage.html dans le navigateur
# 5. Vérifier le ratchet vs baseline
bash scripts/coverage_check.sh coverage.out coverage_baseline.txt| Package | Raison |
|---|---|
internal/api/gen/ |
Code généré par oapi-codegen — ne pas modifier ni tester |
cmd/msal-poc/ |
POC jetable — remplacé par platform/auth |
| Sprint | Seuil CI | Packages cibles |
|---|---|---|
| S45 (infra) | 15% | Baseline honnête établi |
| S46 (handlers) | 35% | api/handlers/ ≥ 75%, api/middleware/ ≥ 80% |
| S47 (sync+DB) | 55% | sync/ ≥ 70%, migration/ ≥ 75%, platform/duckdb/ ≥ 70% |
| S48 (validation) | 70% | validation/ ≥ 70%, tous internal/ ≥ 50% |
La couverture ne peut jamais régresser. Le fichier coverage_baseline.txt contient le
pourcentage de référence. Toute PR qui baisse la couverture de plus de 0.1% fail la CI.
Sur main, si la couverture monte, le baseline est automatiquement mis à jour.
// Exemple : citations_test.go
package handlers_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"errors"
"github.com/go-chi/chi/v5"
"levelup/go-api/internal/api/handlers"
"levelup/go-api/internal/domain"
"levelup/go-api/internal/port"
)
type mockCitationsService struct {
page *domain.CitationsPageResponse
pageErr error
}
func (m *mockCitationsService) GetCitationsPage(_ context.Context) (*domain.CitationsPageResponse, error) {
return m.page, m.pageErr
}
// ... autres méthodes
func TestCitationsHandler_OK(t *testing.T) {
mock := &mockCitationsService{page: &domain.CitationsPageResponse{}}
factory := func(_ context.Context, slug string) (port.CitationsService, string, string, error) {
return mock, "xuid-1", "TestPlayer", nil
}
h := handlers.NewCitationsHandler(factory)
r := chi.NewRouter()
r.Post("/players/{player_slug}/pages/citations", h.GetCitations)
req := httptest.NewRequest(http.MethodPost, "/players/test/pages/citations", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
}// Exemple : writes_test.go
package sync_test
import (
"database/sql"
"testing"
_ "github.com/duckdb/duckdb-go/v2"
"levelup/go-api/internal/sync/testutil"
)
func TestInsertRegistryIfNotExists(t *testing.T) {
db := testutil.NewInMemoryShared(t)
defer db.Close()
// Tester l'insertion
err := InsertRegistryIfNotExists(db, "match-1", ...)
if err != nil { t.Fatal(err) }
// Doublon ignoré
err = InsertRegistryIfNotExists(db, "match-1", ...)
if err != nil { t.Fatal(err) }
}// Exemple : gate_test.go
func TestCheckBinary(t *testing.T) {
// binaire présent
err := checkBinary(os.Args[0]) // binaire de test lui-même
if err != nil { t.Fatal(err) }
// binaire absent
err = checkBinary("/nonexistent/binary")
if err == nil { t.Fatal("expected error for missing binary") }
}| Couche | Répertoire | Type |
|---|---|---|
| Handlers HTTP | internal/api/handlers/*_test.go |
httptest + mock service |
| Middlewares | internal/api/middleware/*_test.go |
httptest |
| Services | internal/service/*_test.go |
mock repo |
| Sync writes | internal/sync/*_test.go |
DuckDB in-memory |
| Migrations | internal/migration/*_test.go |
DuckDB in-memory |
| Platform DuckDB | internal/platform/duckdb/*_test.go |
DuckDB in-memory |
| Validation | internal/validation/*_test.go |
fixtures DuckDB |
| Ops | internal/ops/*_test.go |
t.TempDir() |
| Domain/Analysis | internal/domain/*_test.go, internal/analysis/*_test.go |
unitaire pur |
| Contrat API | contracttest/ |
parsing OpenAPI YAML |
| Golden | tests/golden/ |
snapshot JSON |
Q : Pourquoi CGO_ENABLED=1 est requis pour la couverture complète ?
DuckDB requiert CGO. Sans CGO, les packages platform/duckdb/, sync/, migration/ ne compilent pas.
Le job go-build garde un check CGO=0 rapide pour les packages purement algorithmiques.
Q : Les tests golden comptent-ils dans la couverture ?
Oui, grâce au flag -coverpkg=./.... Sans ce flag, les tests dans tests/golden/ (package
golden_test) n'auraient pas instrumenté les handlers qu'ils exercent.
Q : Comment exclure un nouveau package de la métrique ?
Ajouter le pattern dans scripts/coverage_filter.sh dans le tableau EXCLUDE. Documenter la
raison dans ce fichier.