Testovanie
Reports::fake() nasadí zaznamenávajúci fake za fasádu aj do kontajnera, takže naň dopadne fasáda, vložený ReportsManager, buildery, reports:prune, $report->changeStatusTo() aj trait GivesReports. Meniace volania sa zaznamenajú a nič sa nezapíše: create() vráti neuložené hlásenie v stave Pending, moderate()->open() neuložený ApprovalRequest a prune() vráti 0. Čítanie dôvodov funguje naozaj:
use RoundlyConsulting\Reports\Facades\Reports;
$fake = Reports::fake();
$user->report($post)->for('spam')->create();
Reports::resolve($report, by: $admin, note: 'Removed.');
$fake->assertReported($post, $user, 'spam');
$fake->assertResolved($report, $admin, 'Removed.');
$fake->assertNothingPruned();Všetky asercie
| Asercia | Negatívna | Prejde, keď |
|---|---|---|
assertReported($subject, ?$by, Reason|string|null $reason) | assertNothingReported() | Podalo sa hlásenie o subjekte (od $by, s dôvodom $reason, ak ich zadáte). |
assertModerated($report) | assertNothingModerated() | Prebehlo moderate($report)->…->open(). |
assertResolved($report, ?$by, ?$note) | assertNothingResolved() | Hlásenie bolo vyriešené (kým a s poznámkou, ak ich zadáte). |
assertRejected($report, ?$by, ?$note) | assertNothingRejected() | To isté pre zamietnutia. |
assertStatusChanged($report, ?Status $to) | assertNothingStatusChanged() | changeStatus(), review(), close() alebo changeStatusTo() ho presunuli (na $to). |
assertPruned(?int $days, ?bool $force) | assertNothingPruned() | Prebehlo prune() (s daným oknom a príznakom, ak ich zadáte). |
Asercie porovnávajú hlásenie či model podľa identity: dva uložené modely sa zhodujú, keď ide o ten istý riadok, a neuložený — ako každé hlásenie, ktoré podá fake — sa zhoduje len sám so sebou.
use RoundlyConsulting\Reports\Enums\Status;
use RoundlyConsulting\Reports\Facades\Reports;
it('routes a report through moderation', function () {
$fake = Reports::fake();
$this->actingAs($admin)->post(route('admin.reports.escalate', $report))->assertOk();
$fake->assertModerated($report); // moderate($report)->…->open() ran
$fake->assertStatusChanged($report, Status::InReview); // review() / changeStatus() / changeStatusTo()
$fake->assertNothingResolved();
$fake->assertNothingRejected();
$fake->assertNothingReported();
});
it('rejects and prunes from the console', function () {
$fake = Reports::fake();
Reports::reject($report, by: $admin, note: 'Not a violation.');
$this->artisan('reports:prune', ['--days' => 30])->assertSuccessful();
$fake->assertRejected($report, $admin, 'Not a violation.');
$fake->assertPruned(30, force: false);
$fake->assertNothingModerated();
$fake->assertNothingStatusChanged();
});Čo fake odmietne
Pred zaznamenaním fake odmietne to isté, čo skutočný manažér, s rovnakou výnimkou, a nič nezaznamená — test tak neprejde cez volanie, ktoré by v produkcii zlyhalo:
- create() — neuložený subjekt či nahlasovateľ (LogicException), neznámy dôvod (UnknownReportReasonException), neplatný threshold (InvalidConfigurationException) a duplicita (DuplicateReportException) uloženého hlásenia alebo hlásenia, ktoré podal fake, podľa rovnakých pravidiel prevent_duplicates a duplicate_scope.
- moderate()->open() — žiadni moderátori (MissingModeratorsException), prípadne vybavené hlásenie alebo hlásenie, ktoré už je pod moderáciou — uloženou alebo otvorenou cez fake (ModerationNotAllowedException).
- prune() — bez $days a bez prune_after_days (MissingPruneWindowException), takže reports:prune zlyhá aj pod fake.
- resolve(), reject(), changeStatus(), review() a close() — presun, ktorý strict_transitions nepovoľuje (InvalidStatusTransitionException), overený voči stavu, do ktorého hlásenie naposledy presunul fake, inak voči jeho vlastnému stavu. Fake si presuny pamätá bez zápisu, takže review → resolve → close prejde a resolve → reject vyhodí výnimku — rovnako ako naozaj.
Samotná moderácia sa nesimuluje: kontroly žiadosti v approvals (neuložený moderátor, nedosiahnuteľné kvórum) neprebehnú a počas moderácie sa resolve(), reject() aj close() zaznamenajú ako priame volania, bez obmedzenia na určených moderátorov.
Stavy factory
Report má factory so stavom pre každú fázu životného cyklu. Vytvára model nastavený v reports.model, takže pri hláseniach z factory sa uplatnia casty a udalosti vašej podtriedy:
use RoundlyConsulting\Reports\Enums\Reason;
use RoundlyConsulting\Reports\Models\Report;
Report::factory()->create(); // Pending, reason 'spam'
Report::factory()->forReporter($user)->against($post)->create();
Report::factory()->reason(Reason::Harassment)->create(); // Reason case or slug
Report::factory()->inReview()->create();
Report::factory()->resolved($moderator)->create(); // sets resolver + resolved_at
Report::factory()->rejected()->create();
Report::factory()->closed()->create();
Report::factory()->guest('guest-hash')->create(); // null reporter, guest identifierHlásenia z factory sa zapisujú priamo, takže validácia dôvodu, kontrola duplicít ani prah sa neuplatnia — ReportCreated sa však spustí. Ak chcete bez fake skutočný priebeh, podávajte hlásenia cez fasádu:
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Reports\Enums\Reason;
use RoundlyConsulting\Reports\Enums\Status;
use RoundlyConsulting\Reports\Events\ReportResolved;
use RoundlyConsulting\Reports\Events\ReportThresholdReached;
use RoundlyConsulting\Reports\Exceptions\DuplicateReportException;
use RoundlyConsulting\Reports\Facades\Reports;
it('files, deduplicates and resolves a report', function () {
Event::fake([ReportResolved::class]);
$user = User::factory()->create();
$moderator = User::factory()->create();
$post = Post::factory()->create();
$report = Reports::report($post)->by($user)->for(Reason::Spam)->create();
expect($report->status)->toBe(Status::Pending)
->and($post->isReportedBy($user))->toBeTrue();
expect(fn () => Reports::report($post)->by($user)->for(Reason::Spam)->create())
->toThrow(DuplicateReportException::class);
Reports::resolve($report, by: $moderator, note: 'Removed.');
expect($report->fresh()->status)->toBe(Status::Resolved);
Event::assertDispatched(ReportResolved::class);
});
it('escalates once the threshold is reached', function () {
config()->set('reports.threshold', 3);
Event::fake([ReportThresholdReached::class]);
$post = Post::factory()->create();
foreach (User::factory()->count(3)->create() as $user) {
Reports::report($post)->by($user)->for(Reason::Spam)->create();
}
Event::assertDispatchedTimes(ReportThresholdReached::class, 1);
});Testy spúšťajte s publikovanými migráciami reports a approvals (RefreshDatabase) — vybavenie alebo zamietnutie hlásenia číta approval_requests, aj keď moderáciu netestujete.
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.