tests/legal: Testdatenbank-Guard vor den DB-Traits, Flags in phpunit.xml gepinnt, Zustimmung an gezeigte Fassung gebunden (Umsetzungsaudit 25.09.: N22, N06, 5.3)
- Tests\TestCase prüft die Standardverbindung in setUpTraits(), also vor RefreshDatabase — bisher erst nach dem Refresh (N22). Eigener Test. - phpunit.xml pinnt IMAGE_RIGHTS_V2, IMAGE_PRECHECK(_STAGE) und LEGAL_ACCEPTANCE_GATE auf die Vorgaben aus .env.example; die Suite hing sonst an der lokalen .env (Audit Abschnitt 6). - Zustimmungsgate: die Livewire-Komponenten tragen die beim Rendern gezeigten Fassungen (Trait-Hook rendering); weicht die aktuelle Fassung beim Absenden ab, wird die Annahme abgelehnt und der neue Wortlaut gezeigt. Ein fehlendes Pflichtdokument ist bei aktivem Gate ein Konfigurationsfehler (N06). - Audit vom 25.09., Decision-Update vom 22.09. und Plan Fassung 2 versioniert; die Antwortkopie „…-211.md" entfernt; U10-Formulierung im Plan präzisiert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
d99f55e555
commit
4e34c3d0d1
10 changed files with 1436 additions and 10 deletions
|
|
@ -97,6 +97,51 @@ test('with the gate on the submit requires an active acceptance of agb and guide
|
|||
expect(app(LegalAcceptanceGate::class)->requiredFor($owner)->pluck('version')->all())->toBe(['0.7']);
|
||||
});
|
||||
|
||||
test('a confirmation is bound to the versions that were shown, a newer version must be read and accepted again', function () {
|
||||
/** @var TestCase $this */
|
||||
config()->set('legal.acceptance_gate', true);
|
||||
['owner' => $owner, 'pr' => $pr] = legalGateDraft();
|
||||
$this->actingAs($owner);
|
||||
|
||||
$component = LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||
->assertSee('Version 0.6')
|
||||
->set('aiTextDeclaration', 'none')
|
||||
->set('aiDeclarationConfirmed', true)
|
||||
->set('legalAcceptanceConfirmed', true);
|
||||
|
||||
expect($component->get('legalAcceptanceVersions')['agb'])->toBe('0.6');
|
||||
|
||||
// Zwischen Anzeige und Absenden wird eine neue AGB-Fassung wirksam.
|
||||
LegalDocument::factory()->agb()->create(['version' => '0.7', 'effective_from' => now()->subDay()->toDateString()]);
|
||||
|
||||
$component->call('submitForReview')->assertHasErrors(['legalAcceptanceConfirmed']);
|
||||
|
||||
expect(LegalDocumentAcceptance::query()->count())->toBe(0)
|
||||
->and($pr->fresh()->status)->toBe(PressReleaseStatus::Draft)
|
||||
->and($component->get('legalAcceptanceConfirmed'))->toBeFalse()
|
||||
->and($component->get('legalAcceptanceVersions')['agb'])->toBe('0.7');
|
||||
|
||||
// Neu gelesen und bestätigt: jetzt wird genau 0.7 protokolliert.
|
||||
$component->assertSee('Version 0.7')
|
||||
->set('aiDeclarationConfirmed', true)
|
||||
->set('legalAcceptanceConfirmed', true)
|
||||
->call('submitForReview')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect(LegalDocumentAcceptance::query()->where('type', 'agb')->pluck('version')->all())->toBe(['0.7'])
|
||||
->and($pr->fresh()->status)->not->toBe(PressReleaseStatus::Draft);
|
||||
});
|
||||
|
||||
test('a missing required document is a configuration error while the gate is on, not a smaller gate', function () {
|
||||
/** @var TestCase $this */
|
||||
config()->set('legal.acceptance_gate', true);
|
||||
['owner' => $owner] = legalGateDraft();
|
||||
|
||||
LegalDocument::query()->where('type', 'richtlinien')->delete();
|
||||
|
||||
expect(fn () => app(LegalAcceptanceGate::class)->requiredFor($owner))->toThrow(RuntimeException::class, 'richtlinien');
|
||||
});
|
||||
|
||||
test('acceptances are insert-only', function () {
|
||||
config()->set('legal.acceptance_gate', true);
|
||||
['owner' => $owner] = legalGateDraft();
|
||||
|
|
|
|||
35
tests/Feature/TestDatabaseGuardTest.php
Normal file
35
tests/Feature/TestDatabaseGuardTest.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Der Guard gegen eine falsch aufgelöste Testdatenbank (Config-Cache →
|
||||
* Dev-DB) muss greifen, BEVOR RefreshDatabase etwas anfasst. Laravel ruft
|
||||
* setUpTraits() vor allen Datenbank-Traits auf; dort hängt der Guard
|
||||
* (Umsetzungsaudit 25.09.2026, N22).
|
||||
*/
|
||||
test('the database guard runs in setUpTraits, ahead of RefreshDatabase', function () {
|
||||
$method = new ReflectionMethod(TestCase::class, 'setUpTraits');
|
||||
$source = implode('', array_slice(file($method->getFileName()), $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1));
|
||||
|
||||
expect($method->getDeclaringClass()->getName())->toBe(TestCase::class)
|
||||
->and(strpos($source, 'assertTestDatabaseIsInMemorySqlite'))->toBeLessThan(strpos($source, 'parent::setUpTraits'))
|
||||
->and(in_array(RefreshDatabase::class, class_uses_recursive(static::class), true))->toBeTrue();
|
||||
});
|
||||
|
||||
test('the database guard rejects anything but sqlite in memory', function () {
|
||||
/** @var TestCase $this */
|
||||
$guard = fn () => $this->assertTestDatabaseIsInMemorySqlite();
|
||||
|
||||
$guard();
|
||||
|
||||
try {
|
||||
config()->set('database.default', 'mysql');
|
||||
|
||||
expect($guard)->toThrow(RuntimeException::class, 'statt SQLite :memory:');
|
||||
} finally {
|
||||
// Sonst räumt RefreshDatabase gegen die falsche Verbindung auf.
|
||||
config()->set('database.default', 'sqlite');
|
||||
}
|
||||
});
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
|
|
@ -17,14 +18,39 @@ abstract class TestCase extends BaseTestCase
|
|||
{
|
||||
parent::setUp();
|
||||
|
||||
if (config('database.default') !== 'sqlite' || config('database.connections.sqlite.database') !== ':memory:') {
|
||||
$this->routeTopicLogChannelsToTestingLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel ruft setUpTraits() direkt nach dem Bootstrap und VOR jedem
|
||||
* Datenbank-Trait auf (RefreshDatabase, DatabaseTruncation, …). Der
|
||||
* Guard muss hier stehen — in setUp() nach parent::setUp() käme er erst
|
||||
* an, wenn RefreshDatabase die falsche Datenbank schon geleert hat
|
||||
* (Umsetzungsaudit 25.09.2026, N22).
|
||||
*/
|
||||
protected function setUpTraits(): array
|
||||
{
|
||||
$this->assertTestDatabaseIsInMemorySqlite();
|
||||
|
||||
return parent::setUpTraits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die Verbindung, die der DatabaseManager tatsächlich als Standard
|
||||
* auflösen wird — ohne sie zu öffnen.
|
||||
*/
|
||||
protected function assertTestDatabaseIsInMemorySqlite(): void
|
||||
{
|
||||
$default = (string) DB::getDefaultConnection();
|
||||
$connection = (array) config('database.connections.'.$default, []);
|
||||
|
||||
if ($default !== 'sqlite' || ($connection['driver'] ?? null) !== 'sqlite' || ($connection['database'] ?? null) !== ':memory:') {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Tests laufen gegen „%s“ statt SQLite :memory: — vermutlich liegt ein Config-Cache. `php artisan config:clear` und erneut starten.',
|
||||
config('database.default'),
|
||||
'Tests laufen gegen „%s“ (%s) statt SQLite :memory: — vermutlich liegt ein Config-Cache. `php artisan config:clear` und erneut starten.',
|
||||
$default,
|
||||
(string) ($connection['database'] ?? '?'),
|
||||
));
|
||||
}
|
||||
|
||||
$this->routeTopicLogChannelsToTestingLog();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue