Initial commit
This commit is contained in:
311
skills/make-story/README.md
Normal file
311
skills/make-story/README.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# Framework Make Story
|
||||
|
||||
Génère une Story Foundry pour fixtures de tests.
|
||||
|
||||
## Vue d'ensemble
|
||||
Cette skill crée une Story Zenstruck Foundry pour orchestrer la création de fixtures de tests cohérentes et réutilisables.
|
||||
|
||||
## Caractéristiques
|
||||
|
||||
### Classe Story générée
|
||||
- Extends Story
|
||||
- Implements StoryInterface
|
||||
- Classe `final`
|
||||
- Méthode `build()` avec scénarios
|
||||
- Utilise Factories pour créer instances
|
||||
- Scénarios prédéfinis
|
||||
|
||||
### Classe AppStory
|
||||
- Point d'entrée global
|
||||
- Attribut #[AsFixture(name: 'main')]
|
||||
- Charge toutes les stories
|
||||
- Gère les dépendances entre stories
|
||||
|
||||
## Utilisation
|
||||
|
||||
```bash
|
||||
Use skill framework:make:story
|
||||
```
|
||||
|
||||
Vous serez invité à fournir le nom de l'entité.
|
||||
|
||||
## Exemple d'utilisation
|
||||
|
||||
```bash
|
||||
EntityName: Product
|
||||
```
|
||||
|
||||
Génère :
|
||||
```php
|
||||
// src/Story/ProductStory.php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Produit par défaut
|
||||
ProductFactory::createOne();
|
||||
|
||||
// Produits avec IDs spécifiques
|
||||
ProductFactory::new()
|
||||
->withSpecificId('01234567-89ab-cdef-0123-456789abcdef')
|
||||
->create();
|
||||
|
||||
// Plusieurs produits
|
||||
ProductFactory::createMany(10);
|
||||
}
|
||||
}
|
||||
|
||||
// src/Story/AppStory.php (updated)
|
||||
#[AsFixture(name: 'main')]
|
||||
final class AppStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
ProductStory::load();
|
||||
// ... autres stories
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Structure créée
|
||||
|
||||
```
|
||||
src/
|
||||
└── Story/
|
||||
├── {EntityName}Story.php
|
||||
└── AppStory.php
|
||||
```
|
||||
|
||||
## Prérequis
|
||||
- L'entité doit exister
|
||||
- La Factory doit exister (créée automatiquement si absente)
|
||||
- StoryInterface doit exister dans Contracts
|
||||
- Zenstruck Foundry installé
|
||||
|
||||
## Usage dans les tests
|
||||
|
||||
### Tests unitaires
|
||||
```php
|
||||
use App\Story\ProductStory;
|
||||
|
||||
final class ProductServiceTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
ProductStory::load();
|
||||
}
|
||||
|
||||
public function testCalculateTotalPrice(): void
|
||||
{
|
||||
$products = ProductFactory::repository()->findAll();
|
||||
$service = new ProductService();
|
||||
|
||||
$total = $service->calculateTotal($products);
|
||||
|
||||
self::assertGreaterThan(0, $total);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tests fonctionnels
|
||||
```php
|
||||
use App\Story\AppStory;
|
||||
|
||||
final class ProductControllerTest extends WebTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
AppStory::load();
|
||||
}
|
||||
|
||||
public function testListProducts(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/products');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('.product-item');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixtures Doctrine
|
||||
```bash
|
||||
# Charger toutes les fixtures
|
||||
php bin/console doctrine:fixtures:load --append
|
||||
|
||||
# Ou juste AppStory via attribut #[AsFixture]
|
||||
```
|
||||
|
||||
## Enrichissement (principe YAGNI)
|
||||
|
||||
### Scénarios métier
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Scénario : catalogue actif
|
||||
ProductFactory::new()
|
||||
->active()
|
||||
->createMany(20);
|
||||
|
||||
// Scénario : produits en promotion
|
||||
ProductFactory::new()
|
||||
->active()
|
||||
->createMany(5, ['price' => 9.99]);
|
||||
|
||||
// Scénario : rupture de stock
|
||||
ProductFactory::new()
|
||||
->outOfStock()
|
||||
->createMany(3);
|
||||
|
||||
// Scénario : nouveau produit vedette
|
||||
ProductFactory::createOne([
|
||||
'name' => 'Featured Product',
|
||||
'price' => 199.99,
|
||||
'stock' => 100,
|
||||
'isActive' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Avec relations
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Charger dépendances
|
||||
CategoryStory::load();
|
||||
|
||||
// Récupérer catégories
|
||||
$electronics = CategoryFactory::find(['name' => 'Electronics']);
|
||||
$books = CategoryFactory::find(['name' => 'Books']);
|
||||
|
||||
// Produits par catégorie
|
||||
ProductFactory::new()
|
||||
->inCategory($electronics)
|
||||
->createMany(10);
|
||||
|
||||
ProductFactory::new()
|
||||
->inCategory($books)
|
||||
->createMany(15);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### États nommés
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// États nommés pour réutilisation
|
||||
$this->addState('premium', ProductFactory::createOne([
|
||||
'name' => 'Premium Product',
|
||||
'price' => 999.99,
|
||||
]));
|
||||
|
||||
$this->addState('cheap', ProductFactory::createOne([
|
||||
'name' => 'Cheap Product',
|
||||
'price' => 9.99,
|
||||
]));
|
||||
|
||||
$this->addState('test_product', ProductFactory::createOne([
|
||||
'id' => Uuid::fromString('01234567-89ab-cdef-0123-456789abcdef'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
$premium = ProductStory::load()->get('premium');
|
||||
$cheap = ProductStory::load()->get('cheap');
|
||||
```
|
||||
|
||||
### Scénarios complexes
|
||||
```php
|
||||
final class OrderStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Charger dépendances
|
||||
UserStory::load();
|
||||
ProductStory::load();
|
||||
|
||||
$users = UserFactory::repository()->findAll();
|
||||
$products = ProductFactory::repository()->findAll();
|
||||
|
||||
// Commandes par utilisateur
|
||||
foreach ($users as $user) {
|
||||
OrderFactory::createOne([
|
||||
'user' => $user,
|
||||
'items' => array_slice($products, 0, rand(1, 5)),
|
||||
]);
|
||||
}
|
||||
|
||||
// Commande de test spécifique
|
||||
$this->addState('test_order', OrderFactory::createOne([
|
||||
'user' => UserStory::load()->get('test_user'),
|
||||
'items' => [ProductStory::load()->get('test_product')],
|
||||
'status' => 'pending',
|
||||
]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AppStory orchestration
|
||||
|
||||
```php
|
||||
#[AsFixture(name: 'main')]
|
||||
final class AppStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Ordre important : dépendances d'abord
|
||||
CategoryStory::load();
|
||||
ProductStory::load();
|
||||
UserStory::load();
|
||||
OrderStory::load();
|
||||
ReviewStory::load();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Foundry
|
||||
|
||||
```yaml
|
||||
# config/packages/zenstruck_foundry.yaml
|
||||
when@dev:
|
||||
zenstruck_foundry:
|
||||
auto_refresh_proxies: false
|
||||
|
||||
when@test:
|
||||
zenstruck_foundry:
|
||||
auto_refresh_proxies: false
|
||||
make_factory:
|
||||
default_namespace: 'App\Factory'
|
||||
```
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```bash
|
||||
# Charger fixtures
|
||||
php bin/console doctrine:fixtures:load --append
|
||||
|
||||
# Purger DB puis charger
|
||||
php bin/console doctrine:fixtures:load
|
||||
|
||||
# Dans tests
|
||||
AppStory::load();
|
||||
ProductStory::load();
|
||||
```
|
||||
|
||||
## Principes Elegant Objects appliqués
|
||||
- Classe finale
|
||||
- Implements StoryInterface
|
||||
- Méthode `build()` claire et concise
|
||||
- Scénarios nommés explicitement
|
||||
- DRY : fixtures centralisées
|
||||
- États nommés pour réutilisation
|
||||
291
skills/make-story/SKILL.md
Normal file
291
skills/make-story/SKILL.md
Normal file
@@ -0,0 +1,291 @@
|
||||
---
|
||||
name: framework:make:story
|
||||
description: Génère Story Foundry pour fixtures de tests
|
||||
license: MIT
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Framework Make Story Skill
|
||||
|
||||
## Description
|
||||
Génère une Story Foundry pour créer des fixtures de tests complexes avec des scénarios prédéfinis.
|
||||
|
||||
La Story orchestre la création de multiples instances d'entités via les Factories et permet de charger des jeux de données cohérents pour les tests.
|
||||
|
||||
## Usage
|
||||
```
|
||||
Use skill framework:make:story
|
||||
|
||||
Vous serez invité à fournir :
|
||||
- Le nom de l'entité (ex: Product, User, Order)
|
||||
```
|
||||
|
||||
## Templates
|
||||
- `Story/UtilisateurStory.php` - Template de story pour une entité
|
||||
- `Story/AppStory.php` - Template de story globale (créé si absent)
|
||||
|
||||
## Variables requises
|
||||
- **{EntityName}** - Nom de l'entité en PascalCase (ex: Utilisateur, Product)
|
||||
- **{entityName}** - Nom de l'entité en camelCase (ex: utilisateur, product)
|
||||
- **{namespace}** - Namespace du projet (défaut: App)
|
||||
|
||||
## Dépendances
|
||||
- Requiert que l'entité existe dans `src/Entity/{EntityName}.php`
|
||||
- Appelle automatiquement `framework:make:factory` si la Factory n'existe pas
|
||||
- Requiert que les Contracts existent (StoryInterface)
|
||||
|
||||
## Outputs
|
||||
- `src/Story/{EntityName}Story.php`
|
||||
- `src/Story/AppStory.php` (si n'existe pas déjà)
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Demander le nom de l'entité (EntityName)
|
||||
2. Vérifier que l'entité existe dans `src/Entity/{EntityName}.php`
|
||||
- Si non : arrêter et demander de créer l'entité d'abord
|
||||
3. Vérifier que la Factory existe dans `src/Factory/{EntityName}Factory.php`
|
||||
- Si non : appeler `framework:make:factory`
|
||||
4. Générer la Story depuis le template :
|
||||
- Remplacer `{EntityName}` par le nom fourni
|
||||
- Remplacer `{entityName}` par la version camelCase
|
||||
5. Vérifier si `src/Story/AppStory.php` existe
|
||||
- Si non : créer AppStory avec le template
|
||||
- Si oui : ajouter `{EntityName}Story::load();` dans la méthode `build()`
|
||||
6. Afficher les fichiers créés
|
||||
|
||||
## Patterns appliqués
|
||||
|
||||
### Classe Story
|
||||
- Extends Story
|
||||
- Implements StoryInterface
|
||||
- Classe `final`
|
||||
- Méthode `build()` créant les fixtures
|
||||
- Utilise les Factories pour créer les instances
|
||||
- Scénarios de tests prédéfinis
|
||||
|
||||
### Classe AppStory
|
||||
- Extends Story
|
||||
- Implements StoryInterface
|
||||
- Classe `final`
|
||||
- Attribut #[AsFixture(name: 'main')]
|
||||
- Méthode `build()` chargeant toutes les stories
|
||||
- Point d'entrée unique pour charger toutes les fixtures
|
||||
|
||||
## Exemple
|
||||
|
||||
```bash
|
||||
Use skill framework:make:story
|
||||
|
||||
# Saisies utilisateur :
|
||||
EntityName: Product
|
||||
|
||||
# Résultat :
|
||||
✓ src/Story/ProductStory.php
|
||||
✓ src/Story/AppStory.php (updated)
|
||||
```
|
||||
|
||||
Fichiers générés :
|
||||
|
||||
```php
|
||||
// src/Story/ProductStory.php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Story;
|
||||
|
||||
use App\Contracts\Story\StoryInterface;
|
||||
use App\Factory\ProductFactory;
|
||||
use Zenstruck\Foundry\Story;
|
||||
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Produit par défaut
|
||||
ProductFactory::createOne();
|
||||
|
||||
// Produits avec IDs spécifiques pour les tests
|
||||
ProductFactory::new()
|
||||
->withSpecificId('01234567-89ab-cdef-0123-456789abcdef')
|
||||
->create();
|
||||
|
||||
// Créer plusieurs produits
|
||||
ProductFactory::createMany(10);
|
||||
}
|
||||
}
|
||||
|
||||
// src/Story/AppStory.php
|
||||
<?php
|
||||
|
||||
namespace App\Story;
|
||||
|
||||
use App\Contracts\Story\StoryInterface;
|
||||
use Zenstruck\Foundry\Attribute\AsFixture;
|
||||
use Zenstruck\Foundry\Story;
|
||||
|
||||
#[AsFixture(name: 'main')]
|
||||
final class AppStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
ProductStory::load();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage dans les tests
|
||||
|
||||
### Charger la story
|
||||
```php
|
||||
use App\Story\ProductStory;
|
||||
|
||||
final class ProductTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
ProductStory::load();
|
||||
}
|
||||
|
||||
public function testProductsAreLoaded(): void
|
||||
{
|
||||
$products = ProductFactory::repository()->findAll();
|
||||
|
||||
self::assertCount(12, $products); // 1 défaut + 1 spécifique + 10 random
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Charger toutes les stories
|
||||
```php
|
||||
use App\Story\AppStory;
|
||||
|
||||
final class IntegrationTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
AppStory::load(); // Charge ProductStory + autres
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixture Doctrine
|
||||
```bash
|
||||
# Dans config/packages/zenstruck_foundry.yaml
|
||||
when@dev:
|
||||
zenstruck_foundry:
|
||||
auto_refresh_proxies: false
|
||||
|
||||
# Charger les fixtures
|
||||
php bin/console doctrine:fixtures:load --append
|
||||
```
|
||||
|
||||
## Enrichissement de la Story
|
||||
|
||||
### Scénarios complexes
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Produits actifs
|
||||
ProductFactory::new()
|
||||
->active()
|
||||
->createMany(5);
|
||||
|
||||
// Produits inactifs
|
||||
ProductFactory::new()
|
||||
->inactive()
|
||||
->createMany(3);
|
||||
|
||||
// Produits en rupture
|
||||
ProductFactory::new()
|
||||
->outOfStock()
|
||||
->createMany(2);
|
||||
|
||||
// Produits premium
|
||||
ProductFactory::new()
|
||||
->expensive()
|
||||
->createMany(3);
|
||||
|
||||
// Produit spécifique pour tests
|
||||
ProductFactory::createOne([
|
||||
'name' => 'Test Product',
|
||||
'price' => 99.99,
|
||||
'stock' => 10,
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Avec relations
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Créer catégories d'abord
|
||||
CategoryStory::load();
|
||||
|
||||
$electronics = CategoryFactory::find(['name' => 'Electronics']);
|
||||
$books = CategoryFactory::find(['name' => 'Books']);
|
||||
|
||||
// Produits électroniques
|
||||
ProductFactory::new()
|
||||
->inCategory($electronics)
|
||||
->createMany(5);
|
||||
|
||||
// Livres
|
||||
ProductFactory::new()
|
||||
->inCategory($books)
|
||||
->createMany(10);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Avec états nommés
|
||||
```php
|
||||
final class ProductStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// États nommés pour réutilisation dans tests
|
||||
$this->addState('premium_product', ProductFactory::createOne([
|
||||
'name' => 'Premium Product',
|
||||
'price' => 999.99,
|
||||
]));
|
||||
|
||||
$this->addState('cheap_product', ProductFactory::createOne([
|
||||
'name' => 'Cheap Product',
|
||||
'price' => 9.99,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage dans test
|
||||
$premium = ProductStory::load()->get('premium_product');
|
||||
```
|
||||
|
||||
## AppStory orchestration
|
||||
|
||||
```php
|
||||
#[AsFixture(name: 'main')]
|
||||
final class AppStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Ordre important : dépendances d'abord
|
||||
CategoryStory::load();
|
||||
ProductStory::load();
|
||||
UserStory::load();
|
||||
OrderStory::load();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
- AppStory est le point d'entrée pour charger toutes les fixtures
|
||||
- Attribut #[AsFixture(name: 'main')] permet de charger via Doctrine Fixtures
|
||||
- Les Stories peuvent avoir des dépendances (charger d'autres Stories)
|
||||
- Méthode `addState()` permet de nommer des instances pour les tests
|
||||
- Respecte le principe DRY : scénarios centralisés
|
||||
16
skills/make-story/templates/Story/AppStory.php
Normal file
16
skills/make-story/templates/Story/AppStory.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Story;
|
||||
|
||||
use App\Contracts\Story\StoryInterface;
|
||||
use Zenstruck\Foundry\Attribute\AsFixture;
|
||||
use Zenstruck\Foundry\Story;
|
||||
|
||||
#[AsFixture(name: 'main')]
|
||||
final class AppStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
UtilisateurStory::load();
|
||||
}
|
||||
}
|
||||
26
skills/make-story/templates/Story/UtilisateurStory.php
Normal file
26
skills/make-story/templates/Story/UtilisateurStory.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Story;
|
||||
|
||||
use App\Contracts\Story\StoryInterface;
|
||||
use App\Factory\UtilisateurFactory;
|
||||
use Zenstruck\Foundry\Story;
|
||||
|
||||
final class UtilisateurStory extends Story implements StoryInterface
|
||||
{
|
||||
public function build(): void
|
||||
{
|
||||
// Utilisateur par défaut
|
||||
UtilisateurFactory::createOne();
|
||||
|
||||
// Utilisateurs avec IDs spécifiques pour les tests
|
||||
UtilisateurFactory::new()
|
||||
->withSpecificId('01234567-89ab-cdef-0123-456789abcdef')
|
||||
->create();
|
||||
|
||||
// Créer plusieurs utilisateurs
|
||||
UtilisateurFactory::createMany(10);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user