Un 403 vous dit qu’une porte a été fermée. Il ne dit pas laquelle, ni si la porte que vous vouliez fermer est celle qui s’est fermée. Le composant fournit les outils dont il se sert lui-même pour dire la différence.
Ils vivent sous AccessControl\Test et AccessControl\Bundle\Test, et ils sont publiés exprès : affirmer qu’une porte a été fermée, et laquelle, n’est pas quelque chose qu’une application devrait avoir à rebâtir.
Une réponse que vous tenez
use AccessControl\Test\AccessOutcomeAssertionsTrait;
public function testTheAuthorMayEdit(): void
{
$outcome = new PostVoter()->vote(new AccessRequest($author, 'EDIT', $post));
self::assertAccessGranted($outcome);
}
Trois assertions, et la troisième est celle qu’on oublie : assertAccessAbstained(). Une abstention est un résultat à part entière, alors affirmez-la plutôt que de vous contenter de « pas accordé », qui passe pour la mauvaise raison le jour où le votant se met à refuser.
Une requête qui a tourné
use AccessControl\Bundle\Test\AccessControlAssertionsTrait;
public function testAStrangerCannotEdit(): void
{
$client = static::createClient();
$client->request('GET', '/posts/1/edit');
self::assertResponseStatusCodeSame(403);
self::assertAccessWasDeniedOn('EDIT');
}
À côté : assertAccessWasGrantedOn(), assertAccessWasNotDeniedOn(), assertAccessWasDeniedBy() pour savoir quel votant a fermé la porte, et assertAccessDecisionCount(), qui attrape une question posée deux fois ou pas du tout. getAccessDecisionEvents() remet le journal entier pour tout ce que les autres ne couvrent pas.
Les deux traits portent des noms distincts exprès : l’un parle d’une réponse que vous avez en main, l’autre d’une requête qui a tourné.
Le message d’échec compte autant
Quand une assertion échoue, elle liste chaque décision prise, avec sa raison. Et « aucune décision d’accès n’a été prise » est un message à part entière, parce qu’un contrôleur sans politique d’accès et un point d’entrée dont l’écouteur n’est pas enregistré sont identiques vus de l’extérieur.
C’est le même raisonnement que le zéro de la barre du profileur, appliqué à un test rouge : distinguer « gardé et autorisé » de « jamais gardé » est tout le travail.
Tester les pièces que vous écrivez
- Un gestionnaire de politique :
AccessPolicyHandlerTestTraitoffre deux tests gratuits, à savoir que le gestionnaire revendique les politiques dont il se dit chargé et laisse une politique étrangère tranquille, plus un moyen d’évaluer une politique sans conteneur.FixedOutcomeAccessPolicyrépond ce que vous lui dites de répondre, si bien qu’un composite s’éprouve sans écrire de votant. - Un algorithme de combinaison :
AccessDecisionStrategyTestTraitlui fait traverser un tableau de résultats de votants. C’est ce qui vérifie la parité des quatre algorithmes fournis avec Symfony Security.
Affirmez sur le résultat, pas sur l’appel
Une règle que le projet s’impose, et elle a été apprise à ses dépens : un test doit passer par le vrai chemin. La garde de console ne tournait pas sous --profile, parce que la console remet une commande enveloppée qui ne porte aucun attribut, et tous les tests unitaires passaient sur une commande nue que la console ne fournit jamais.
Une garde qui ne tourne pas est silencieuse, et un bouchon qui a été appelé n’est pas la preuve que quoi que ce soit ait été décidé. Tout ce qui précède lit les événements que le gestionnaire émet, ce qui est aussi la façon d’obtenir le même journal hors de tout framework.