Refactor core test runtime and simplify project documentation
This commit is contained in:
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,30 +1,17 @@
|
|||||||
# ============================================
|
# Environment
|
||||||
# Environnement & Configuration
|
|
||||||
# ============================================
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# ============================================
|
# Composer
|
||||||
# Dépendances Composer
|
|
||||||
# ============================================
|
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
# ============================================
|
# Runtime caches and reports
|
||||||
# Base de données
|
|
||||||
# ============================================
|
|
||||||
database/
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Cache & Logs
|
|
||||||
# ============================================
|
|
||||||
coverage/
|
coverage/
|
||||||
var/
|
var/
|
||||||
.php-cs-fixer.cache
|
.php-cs-fixer.cache
|
||||||
.phpstan/
|
.phpstan/
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
|
||||||
# ============================================
|
# IDE / OS
|
||||||
# IDE & OS
|
|
||||||
# ============================================
|
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -11,8 +11,6 @@ Ce dépôt est conçu pour être consommé par des projets applicatifs séparés
|
|||||||
|
|
||||||
## Installation depuis le dépôt Git en HTTPS
|
## Installation depuis le dépôt Git en HTTPS
|
||||||
|
|
||||||
### Pendant le développement du core
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"repositories": [
|
"repositories": [
|
||||||
@@ -22,30 +20,14 @@ Ce dépôt est conçu pour être consommé par des projets applicatifs séparés
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"netig/netslim-core": "^0.3@dev"
|
"netig/netslim-core": "dev-main"
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Après la première release taguée
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://git.netig.net/netig/netslim-core.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"netig/netslim-core": "^0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option locale pendant le développement
|
## Option locale pendant le développement
|
||||||
|
|
||||||
Pour développer le core et une application consommatrice côte à côte, un `path` repository local reste pratique, mais ce n'est pas le mode de consommation par défaut :
|
Pour développer le core et une application consommatrice côte à côte, un `path` repository local reste pratique :
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -56,7 +38,7 @@ Pour développer le core et une application consommatrice côte à côte, un `pa
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"netig/netslim-core": "^0.3@dev"
|
"netig/netslim-core": "dev-main"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -69,7 +51,7 @@ Le package ne porte pas d'application concrète. Un projet consommateur doit fou
|
|||||||
- ses templates applicatifs ;
|
- ses templates applicatifs ;
|
||||||
- son pipeline d'assets.
|
- son pipeline d'assets.
|
||||||
|
|
||||||
Si l'application active `Identity` et exécute le provisionnement initial, elle doit aussi définir `ADMIN_USERNAME`, `ADMIN_EMAIL` et `ADMIN_PASSWORD` dans son `.env`. Ces variables ne sont plus exigées par le bootstrap du noyau seul.
|
Si l'application active `Identity` et exécute le provisionnement initial, elle doit aussi définir `ADMIN_USERNAME`, `ADMIN_EMAIL` et `ADMIN_PASSWORD` dans son `.env`.
|
||||||
|
|
||||||
Si l'application active `Notifications`, elle doit configurer `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_ENCRYPTION`, `MAIL_FROM` et `MAIL_FROM_NAME` pour permettre l'envoi effectif des emails transactionnels.
|
Si l'application active `Notifications`, elle doit configurer `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_ENCRYPTION`, `MAIL_FROM` et `MAIL_FROM_NAME` pour permettre l'envoi effectif des emails transactionnels.
|
||||||
|
|
||||||
@@ -83,12 +65,8 @@ Les templates du socle supposent en particulier :
|
|||||||
|
|
||||||
Le socle expose principalement :
|
Le socle expose principalement :
|
||||||
- `Netig\Netslim\Kernel\...` pour le runtime public ;
|
- `Netig\Netslim\Kernel\...` pour le runtime public ;
|
||||||
- les interfaces applicatives documentées des modules partagés (`Netig\Netslim\Identity\Application\*ServiceInterface`, `Netig\Netslim\Settings\Application\SettingsServiceInterface`, `Netig\Netslim\AuditLog\Application\AuditLogServiceInterface`, `Netig\Netslim\Notifications\Application\NotificationServiceInterface`, `Netig\Netslim\Taxonomy\Application\TaxonomyServiceInterface`, `Netig\Netslim\Media\Application\MediaServiceInterface`) ;
|
- les interfaces applicatives documentées des modules partagés (`Identity`, `Settings`, `AuditLog`, `Notifications`, `Taxonomy`, `Media`) ;
|
||||||
- `Netig\Netslim\Settings\Contracts\...` ;
|
- les contrats publics sous `Netig\Netslim\*/Contracts/` ;
|
||||||
- `Netig\Netslim\AuditLog\Contracts\...` ;
|
|
||||||
- `Netig\Netslim\Notifications\Contracts\...` ;
|
|
||||||
- `Netig\Netslim\Taxonomy\Contracts\...` ;
|
|
||||||
- `Netig\Netslim\Media\Contracts\...` ;
|
|
||||||
- les classes `*Module` des modules partagés.
|
- les classes `*Module` des modules partagés.
|
||||||
|
|
||||||
La frontière détaillée entre API publique et API interne est documentée dans [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md).
|
La frontière détaillée entre API publique et API interne est documentée dans [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md).
|
||||||
@@ -100,8 +78,4 @@ composer install
|
|||||||
composer qa
|
composer qa
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gouvernance du package
|
|
||||||
|
|
||||||
- `docs/PUBLIC_API.md` définit la frontière supportée entre le package et les projets consommateurs ;
|
|
||||||
|
|
||||||
> Quand `netslim-core` est installé via Composer, les chemins runtime détectent automatiquement la racine du projet consommateur pour les scripts CLI et les suites de tests qui n'appellent pas explicitement `Bootstrap::create()`.
|
> Quand `netslim-core` est installé via Composer, les chemins runtime détectent automatiquement la racine du projet consommateur pour les scripts CLI et les suites de tests qui n'appellent pas explicitement `Bootstrap::create()`.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "netig/netslim-core",
|
"name": "netig/netslim-core",
|
||||||
"description": "Reusable kernel and shared modules for NETslim based applications.",
|
"description": "Reusable kernel and shared modules for Netslim-based applications.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"require": {
|
"require": {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return require __DIR__ . '/../tests/Fixtures/Application/config/modules.php';
|
|
||||||
@@ -33,12 +33,4 @@ Quand un projet applicatif a besoin d'une capacité partagée, il doit préfére
|
|||||||
2. à défaut, une extension documentée du runtime ou d'un module ;
|
2. à défaut, une extension documentée du runtime ou d'un module ;
|
||||||
3. en dernier recours, une évolution du core qui ajoute un nouveau point d'intégration public.
|
3. en dernier recours, une évolution du core qui ajoute un nouveau point d'intégration public.
|
||||||
|
|
||||||
Éviter de se brancher directement sur des classes internes permet de garder le core versionnable et évolutif.
|
Éviter de se brancher directement sur des classes internes permet de garder le core petit, lisible et évolutif.
|
||||||
|
|
||||||
## Stabilité attendue
|
|
||||||
|
|
||||||
- l'API publique est la cible de compatibilité entre versions ;
|
|
||||||
- l'API interne peut évoluer à tout moment tant que le comportement public documenté reste cohérent ;
|
|
||||||
- tout nouveau point d'extension réutilisable doit être documenté ici ou dans `README.md` / `MODULES.md`.
|
|
||||||
|
|
||||||
## Versionnement
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use Netig\Netslim\Identity\UI\Http\UserController;
|
|||||||
use Netig\Netslim\Kernel\Http\Infrastructure\Twig\AppExtension;
|
use Netig\Netslim\Kernel\Http\Infrastructure\Twig\AppExtension;
|
||||||
use Netig\Netslim\Kernel\Runtime\Http\MiddlewareRegistrar;
|
use Netig\Netslim\Kernel\Runtime\Http\MiddlewareRegistrar;
|
||||||
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
|
||||||
use Netig\Netslim\Media\Application\MediaServiceInterface;
|
use Netig\Netslim\Media\Application\MediaServiceInterface;
|
||||||
use Netig\Netslim\Media\UI\Http\MediaController;
|
use Netig\Netslim\Media\UI\Http\MediaController;
|
||||||
use Netig\Netslim\Notifications\Application\NotificationServiceInterface;
|
use Netig\Netslim\Notifications\Application\NotificationServiceInterface;
|
||||||
@@ -30,6 +29,7 @@ use PHPUnit\Framework\Attributes\DataProvider;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
use Slim\Views\Twig;
|
use Slim\Views\Twig;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
final class ContainerWiringIntegrationTest extends TestCase
|
final class ContainerWiringIntegrationTest extends TestCase
|
||||||
@@ -56,8 +56,7 @@ final class ContainerWiringIntegrationTest extends TestCase
|
|||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
ModuleRegistry::reset();
|
|
||||||
foreach (self::ENV_DEFAULTS as $key => $value) {
|
foreach (self::ENV_DEFAULTS as $key => $value) {
|
||||||
$this->envBackup[$key] = $_ENV[$key] ?? null;
|
$this->envBackup[$key] = $_ENV[$key] ?? null;
|
||||||
$_ENV[$key] = $value;
|
$_ENV[$key] = $value;
|
||||||
@@ -66,8 +65,6 @@ final class ContainerWiringIntegrationTest extends TestCase
|
|||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
ModuleRegistry::reset();
|
ModuleRegistry::reset();
|
||||||
foreach (self::ENV_DEFAULTS as $key => $_) {
|
foreach (self::ENV_DEFAULTS as $key => $_) {
|
||||||
$previous = $this->envBackup[$key] ?? null;
|
$previous = $this->envBackup[$key] ?? null;
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ namespace Tests\Kernel;
|
|||||||
|
|
||||||
use Netig\Netslim\Kernel\Persistence\Infrastructure\DatabaseNotProvisionedException;
|
use Netig\Netslim\Kernel\Persistence\Infrastructure\DatabaseNotProvisionedException;
|
||||||
use Netig\Netslim\Kernel\Persistence\Infrastructure\DatabaseReadiness;
|
use Netig\Netslim\Kernel\Persistence\Infrastructure\DatabaseReadiness;
|
||||||
use PDO;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestDatabaseFactory;
|
||||||
|
|
||||||
final class DatabaseReadinessTest extends TestCase
|
final class DatabaseReadinessTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testAssertProvisionedFailsWhenModuleTablesAreMissing(): void
|
public function testAssertProvisionedFailsWhenModuleTablesAreMissing(): void
|
||||||
{
|
{
|
||||||
$db = new PDO('sqlite::memory:');
|
$db = TestDatabaseFactory::createInMemory();
|
||||||
$db->exec('CREATE TABLE migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, run_at TEXT)');
|
$db->exec('CREATE TABLE migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, run_at TEXT)');
|
||||||
|
|
||||||
$this->expectException(DatabaseNotProvisionedException::class);
|
$this->expectException(DatabaseNotProvisionedException::class);
|
||||||
@@ -24,7 +24,7 @@ final class DatabaseReadinessTest extends TestCase
|
|||||||
|
|
||||||
public function testAssertProvisionedAcceptsCompleteCoreSchema(): void
|
public function testAssertProvisionedAcceptsCompleteCoreSchema(): void
|
||||||
{
|
{
|
||||||
$db = new PDO('sqlite::memory:');
|
$db = TestDatabaseFactory::createInMemory();
|
||||||
|
|
||||||
$db->exec('CREATE TABLE migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, run_at TEXT)');
|
$db->exec('CREATE TABLE migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, run_at TEXT)');
|
||||||
$db->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, email TEXT, password_hash TEXT, role TEXT, session_version INTEGER, created_at TEXT)');
|
$db->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, email TEXT, password_hash TEXT, role TEXT, session_version INTEGER, created_at TEXT)');
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Kernel;
|
namespace Tests\Kernel;
|
||||||
|
|
||||||
use Netig\Netslim\Kernel\Persistence\Infrastructure\Migrator;
|
use Netig\Netslim\Kernel\Persistence\Infrastructure\Migrator;
|
||||||
use PDO;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestDatabaseFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests unitaires pour Migrator.
|
* Tests unitaires pour Migrator.
|
||||||
@@ -18,14 +18,11 @@ use PHPUnit\Framework\TestCase;
|
|||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
final class MigratorTest extends TestCase
|
final class MigratorTest extends TestCase
|
||||||
{
|
{
|
||||||
private PDO $db;
|
private \PDO $db;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->db = new PDO('sqlite::memory:', options: [
|
$this->db = TestDatabaseFactory::createInMemory();
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRunCreatesMigrationsTable(): void
|
public function testRunCreatesMigrationsTable(): void
|
||||||
|
|||||||
@@ -8,26 +8,23 @@ use Netig\Netslim\Kernel\Runtime\Module\ModuleInterface;
|
|||||||
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
final class ModuleRegistryTest extends TestCase
|
final class ModuleRegistryTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
TestRuntimeFactory::resetRuntime();
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
ModuleRegistry::reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
TestRuntimeFactory::resetRuntime();
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
ModuleRegistry::reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testModulesAreDeclaredInExpectedOrderForFixtureApplication(): void
|
public function testModulesAreDeclaredInExpectedOrderForFixtureApplication(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
|
|
||||||
$moduleClasses = array_map(
|
$moduleClasses = array_map(
|
||||||
static fn (ModuleInterface $module): string => $module::class,
|
static fn (ModuleInterface $module): string => $module::class,
|
||||||
@@ -47,7 +44,7 @@ final class ModuleRegistryTest extends TestCase
|
|||||||
|
|
||||||
public function testModuleClassNamesExposeTheActiveApplicationComposition(): void
|
public function testModuleClassNamesExposeTheActiveApplicationComposition(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
|
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
'Netig\Netslim\Kernel\Runtime\KernelModule',
|
'Netig\Netslim\Kernel\Runtime\KernelModule',
|
||||||
@@ -62,14 +59,14 @@ final class ModuleRegistryTest extends TestCase
|
|||||||
|
|
||||||
public function testApplicationManifestIsResolvedFromTheActiveApplicationRoot(): void
|
public function testApplicationManifestIsResolvedFromTheActiveApplicationRoot(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
|
|
||||||
self::assertFileExists(RuntimePaths::getApplicationConfigPath('modules.php'));
|
self::assertFileExists(RuntimePaths::getApplicationConfigPath('modules.php'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEveryModuleExposesResolvableMetadata(): void
|
public function testEveryModuleExposesResolvableMetadata(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
|
|
||||||
foreach (ModuleRegistry::modules() as $module) {
|
foreach (ModuleRegistry::modules() as $module) {
|
||||||
self::assertNotSame([], $module->definitions(), $module::class . ' should expose DI definitions.');
|
self::assertNotSame([], $module->definitions(), $module::class . ' should expose DI definitions.');
|
||||||
|
|||||||
@@ -6,21 +6,18 @@ namespace Tests\Kernel;
|
|||||||
|
|
||||||
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
||||||
use Netig\Netslim\Kernel\Runtime\Module\ProvidesSchemaInterface;
|
use Netig\Netslim\Kernel\Runtime\Module\ProvidesSchemaInterface;
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
final class ModuleSchemaTest extends TestCase
|
final class ModuleSchemaTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
ModuleRegistry::reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
ModuleRegistry::reset();
|
ModuleRegistry::reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Kernel;
|
namespace Tests\Kernel;
|
||||||
|
|
||||||
use Netig\Netslim\Kernel\Persistence\Infrastructure\Provisioner;
|
use Netig\Netslim\Kernel\Persistence\Infrastructure\Provisioner;
|
||||||
use PDO;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestDatabaseFactory;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class ProvisionerTest extends TestCase
|
final class ProvisionerTest extends TestCase
|
||||||
{
|
{
|
||||||
private PDO $db;
|
private \PDO $db;
|
||||||
|
|
||||||
private string $lockPath;
|
private string $lockPath;
|
||||||
|
|
||||||
@@ -20,13 +21,11 @@ final class ProvisionerTest extends TestCase
|
|||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->db = new PDO('sqlite::memory:', options: [
|
TestRuntimeFactory::resetRuntime();
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
$this->db = TestDatabaseFactory::createInMemory();
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
]);
|
|
||||||
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
||||||
|
|
||||||
$this->lockPath = dirname(__DIR__, 2) . '/database/.provision.lock';
|
$this->lockPath = TestRuntimeFactory::path('database/.provision.lock');
|
||||||
@unlink($this->lockPath);
|
@unlink($this->lockPath);
|
||||||
|
|
||||||
$this->envBackup = [
|
$this->envBackup = [
|
||||||
|
|||||||
@@ -6,23 +6,20 @@ namespace Tests\Kernel;
|
|||||||
|
|
||||||
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
||||||
use Netig\Netslim\Kernel\Runtime\Routing\Routes;
|
use Netig\Netslim\Kernel\Runtime\Routing\Routes;
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
final class RoutesTest extends TestCase
|
final class RoutesTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::resetRuntime();
|
||||||
ModuleRegistry::reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
ModuleRegistry::reset();
|
ModuleRegistry::reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,40 +6,40 @@ namespace Tests\Kernel;
|
|||||||
|
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
final class RuntimePathsTest extends TestCase
|
final class RuntimePathsTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::resetApplicationRoot();
|
TestRuntimeFactory::resetRuntime();
|
||||||
RuntimePaths::resetProjectRoot();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetProjectRootReturnsRepositoryRoot(): void
|
public function testGetProjectRootReturnsIsolatedTestProjectRoot(): void
|
||||||
{
|
{
|
||||||
self::assertSame(dirname(__DIR__, 2), RuntimePaths::getProjectRoot());
|
self::assertSame(TestRuntimeFactory::projectRoot(), RuntimePaths::getProjectRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetConfigPathReturnsRootConfigDirectoryAndFilePath(): void
|
public function testGetConfigPathReturnsRootConfigDirectoryAndFilePath(): void
|
||||||
{
|
{
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/config', RuntimePaths::getConfigPath());
|
self::assertSame(TestRuntimeFactory::path('config'), RuntimePaths::getConfigPath());
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/config/modules.php', RuntimePaths::getConfigPath('modules.php'));
|
self::assertSame(TestRuntimeFactory::path('config/modules.php'), RuntimePaths::getConfigPath('modules.php'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplicationRootDefaultsToProjectRoot(): void
|
public function testApplicationRootDefaultsToProjectRoot(): void
|
||||||
{
|
{
|
||||||
self::assertSame(dirname(__DIR__, 2), RuntimePaths::getApplicationRoot());
|
self::assertSame(TestRuntimeFactory::applicationRoot(), RuntimePaths::getApplicationRoot());
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/config', RuntimePaths::getApplicationConfigPath());
|
self::assertSame(TestRuntimeFactory::path('config'), RuntimePaths::getApplicationConfigPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testApplicationRootCanPointToFixtureApplication(): void
|
public function testApplicationRootCanPointToTheIsolatedFixtureApplication(): void
|
||||||
{
|
{
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__, 2) . '/tests/Fixtures/Application');
|
RuntimePaths::setApplicationRoot(TestRuntimeFactory::applicationRoot());
|
||||||
|
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/tests/Fixtures/Application', RuntimePaths::getApplicationRoot());
|
self::assertSame(TestRuntimeFactory::applicationRoot(), RuntimePaths::getApplicationRoot());
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/tests/Fixtures/Application/config/modules.php', RuntimePaths::getApplicationConfigPath('modules.php'));
|
self::assertSame(TestRuntimeFactory::path('config/modules.php'), RuntimePaths::getApplicationConfigPath('modules.php'));
|
||||||
self::assertSame(dirname(__DIR__, 2) . '/tests/Fixtures/Application/templates/Kernel', RuntimePaths::getApplicationPath('templates/Kernel'));
|
self::assertSame(TestRuntimeFactory::path('templates/Kernel'), RuntimePaths::getApplicationPath('templates/Kernel'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetTwigCacheReturnsFalseInDev(): void
|
public function testGetTwigCacheReturnsFalseInDev(): void
|
||||||
@@ -57,7 +57,7 @@ final class RuntimePathsTest extends TestCase
|
|||||||
|
|
||||||
public function testGetDatabasePathCreatesDatabaseFileWhenMissing(): void
|
public function testGetDatabasePathCreatesDatabaseFileWhenMissing(): void
|
||||||
{
|
{
|
||||||
$dbFile = dirname(__DIR__, 2) . '/database/app.sqlite';
|
$dbFile = TestRuntimeFactory::path('database/app.sqlite');
|
||||||
$dbDir = dirname($dbFile);
|
$dbDir = dirname($dbFile);
|
||||||
$backup = $dbFile . '.bak-test';
|
$backup = $dbFile . '.bak-test';
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,19 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Media;
|
namespace Tests\Media;
|
||||||
|
|
||||||
use Netig\Netslim\Kernel\Persistence\Infrastructure\Migrator;
|
use Netig\Netslim\Kernel\Persistence\Infrastructure\Migrator;
|
||||||
use PDO;
|
|
||||||
use PDOException;
|
use PDOException;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Tests\Support\TestDatabaseFactory;
|
||||||
|
use Tests\Support\TestRuntimeFactory;
|
||||||
|
|
||||||
final class MediaSchemaIntegrationTest extends TestCase
|
final class MediaSchemaIntegrationTest extends TestCase
|
||||||
{
|
{
|
||||||
private PDO $db;
|
private \PDO $db;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->db = new PDO('sqlite::memory:', options: [
|
TestRuntimeFactory::resetRuntime();
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
$this->db = TestDatabaseFactory::createInMemory();
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
||||||
Migrator::run($this->db);
|
Migrator::run($this->db);
|
||||||
|
|||||||
9
tests/README.md
Normal file
9
tests/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Tests du core
|
||||||
|
|
||||||
|
La suite de tests prépare une mini application consommatrice éphémère sous un répertoire temporaire. Les chemins runtime (`database/`, `var/`, `public/media/`) ne pointent donc ni vers le dépôt du core ni vers un projet client réel.
|
||||||
|
|
||||||
|
Organisation :
|
||||||
|
- `Architecture/` : garde-fous de dépendances et de structure
|
||||||
|
- `Kernel/` : runtime, bootstrap, wiring, utilitaires transverses
|
||||||
|
- `Support/` : helpers de tests et fabrication du runtime de test
|
||||||
|
- `Fixtures/` : manifeste et templates minimaux de l'application de test
|
||||||
34
tests/Support/TestDatabaseFactory.php
Normal file
34
tests/Support/TestDatabaseFactory.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fabrique légère pour les bases SQLite de test.
|
||||||
|
*/
|
||||||
|
final class TestDatabaseFactory
|
||||||
|
{
|
||||||
|
public static function createInMemory(): PDO
|
||||||
|
{
|
||||||
|
return new PDO('sqlite::memory:', options: [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFileBacked(string $name): PDO
|
||||||
|
{
|
||||||
|
$path = TestRuntimeFactory::path('database/' . $name . '.sqlite');
|
||||||
|
if (is_file($path)) {
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PDO('sqlite:' . $path, options: [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
163
tests/Support/TestRuntimeFactory.php
Normal file
163
tests/Support/TestRuntimeFactory.php
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support;
|
||||||
|
|
||||||
|
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
||||||
|
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prépare une mini application consommatrice éphémère pour les tests du core.
|
||||||
|
*
|
||||||
|
* Tous les chemins runtime persistants (database/, var/, public/media) sont
|
||||||
|
* créés sous un répertoire temporaire isolé afin que la suite de tests ne
|
||||||
|
* touche ni le dépôt courant ni un projet client réel.
|
||||||
|
*/
|
||||||
|
final class TestRuntimeFactory
|
||||||
|
{
|
||||||
|
private static ?string $projectRoot = null;
|
||||||
|
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
if (self::$projectRoot !== null) {
|
||||||
|
self::applyRuntimeRoots();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseRoot = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'netslim-core-tests';
|
||||||
|
if (!is_dir($baseRoot)) {
|
||||||
|
mkdir($baseRoot, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$projectRoot = $baseRoot . DIRECTORY_SEPARATOR . 'run-' . bin2hex(random_bytes(6));
|
||||||
|
mkdir($projectRoot, 0777, true);
|
||||||
|
|
||||||
|
self::copyDirectory(self::fixtureRoot() . '/config', $projectRoot . '/config');
|
||||||
|
self::copyDirectory(self::fixtureRoot() . '/templates', $projectRoot . '/templates');
|
||||||
|
|
||||||
|
foreach (['database', 'var/cache/twig', 'var/cache/htmlpurifier', 'var/logs', 'public/media'] as $directory) {
|
||||||
|
$path = $projectRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory);
|
||||||
|
if (!is_dir($path)) {
|
||||||
|
mkdir($path, 0777, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$projectRoot = $projectRoot;
|
||||||
|
self::applyRuntimeRoots();
|
||||||
|
|
||||||
|
register_shutdown_function(static function (): void {
|
||||||
|
TestRuntimeFactory::cleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function projectRoot(): string
|
||||||
|
{
|
||||||
|
self::boot();
|
||||||
|
|
||||||
|
return self::$projectRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function applicationRoot(): string
|
||||||
|
{
|
||||||
|
return self::projectRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function path(string $relativePath = ''): string
|
||||||
|
{
|
||||||
|
$root = self::projectRoot();
|
||||||
|
|
||||||
|
return $relativePath === ''
|
||||||
|
? $root
|
||||||
|
: $root . DIRECTORY_SEPARATOR . ltrim(str_replace('/', DIRECTORY_SEPARATOR, $relativePath), DIRECTORY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resetRuntime(): void
|
||||||
|
{
|
||||||
|
self::boot();
|
||||||
|
self::applyRuntimeRoots();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function applyRuntimeRoots(): void
|
||||||
|
{
|
||||||
|
if (self::$projectRoot === null) {
|
||||||
|
throw new \LogicException('Test runtime project root is not initialized.');
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimePaths::setProjectRoot(self::$projectRoot);
|
||||||
|
RuntimePaths::setApplicationRoot(self::$projectRoot);
|
||||||
|
ModuleRegistry::reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cleanup(): void
|
||||||
|
{
|
||||||
|
if (self::$projectRoot === null || !is_dir(self::$projectRoot)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::deleteDirectory(self::$projectRoot);
|
||||||
|
self::$projectRoot = null;
|
||||||
|
RuntimePaths::resetProjectRoot();
|
||||||
|
RuntimePaths::resetApplicationRoot();
|
||||||
|
ModuleRegistry::reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function fixtureRoot(): string
|
||||||
|
{
|
||||||
|
return dirname(__DIR__) . '/Fixtures/Application';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function copyDirectory(string $source, string $destination): void
|
||||||
|
{
|
||||||
|
if (!is_dir($destination)) {
|
||||||
|
mkdir($destination, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = scandir($source);
|
||||||
|
if ($items === false) {
|
||||||
|
throw new \RuntimeException(sprintf('Unable to read fixture directory: %s', $source));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = $source . DIRECTORY_SEPARATOR . $item;
|
||||||
|
$to = $destination . DIRECTORY_SEPARATOR . $item;
|
||||||
|
|
||||||
|
if (is_dir($from)) {
|
||||||
|
self::copyDirectory($from, $to);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy($from, $to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function deleteDirectory(string $path): void
|
||||||
|
{
|
||||||
|
$items = scandir($path);
|
||||||
|
if ($items === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $path . DIRECTORY_SEPARATOR . $item;
|
||||||
|
|
||||||
|
if (is_dir($current)) {
|
||||||
|
self::deleteDirectory($current);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@unlink($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
@rmdir($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
use Netig\Netslim\Kernel\Runtime\Module\ModuleRegistry;
|
use Tests\Support\TestRuntimeFactory;
|
||||||
use Netig\Netslim\Kernel\Runtime\RuntimePaths;
|
|
||||||
|
|
||||||
RuntimePaths::setApplicationRoot(dirname(__DIR__) . '/tests/Fixtures/Application');
|
TestRuntimeFactory::boot();
|
||||||
RuntimePaths::setProjectRoot(dirname(__DIR__));
|
|
||||||
ModuleRegistry::reset();
|
|
||||||
|
|||||||
Reference in New Issue
Block a user