seed(RolesAndPermissionsSeeder::class); $this->seed(LegalDocumentSeeder::class); Mail::fake(); config()->set('billing.enforce_booking', false); }); /** * @return array{owner: User, pr: PressRelease} */ function legalGateDraft(): array { $owner = User::factory()->create(['is_active' => true]); $owner->assignRole('customer'); $company = Company::factory()->presseecho()->create(); $owner->companies()->attach($company->id, ['role' => 'owner']); $pr = PressRelease::factory()->create([ 'user_id' => $owner->id, 'company_id' => $company->id, 'category_id' => Category::factory()->create()->id, 'portal' => $company->portal->value, 'status' => 'draft', ]); return compact('owner', 'pr'); } test('with the gate off nothing is required and submitting works as before', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = legalGateDraft(); $this->actingAs($owner); expect(app(LegalAcceptanceGate::class)->requiredFor($owner))->toBeEmpty(); LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) ->assertDontSee('Neue Fassung unserer Bedingungen') ->set('aiTextDeclaration', 'none') ->set('aiDeclarationConfirmed', true) ->call('submitForReview') ->assertHasNoErrors(); expect($pr->fresh()->status)->not->toBe(PressReleaseStatus::Draft) ->and(LegalDocumentAcceptance::query()->count())->toBe(0); }); test('with the gate on the submit requires an active acceptance of agb and guidelines and records both', function () { /** @var TestCase $this */ config()->set('legal.acceptance_gate', true); ['owner' => $owner, 'pr' => $pr] = legalGateDraft(); $this->actingAs($owner); expect(app(LegalAcceptanceGate::class)->requiredFor($owner)->pluck('type')->all())->toBe(['agb', 'richtlinien']); $component = LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) ->assertSee('Neue Fassung unserer Bedingungen') ->assertSee('Version 0.6') ->set('aiTextDeclaration', 'none') ->set('aiDeclarationConfirmed', true) ->call('submitForReview') ->assertHasErrors(['legalAcceptanceConfirmed']); expect($pr->fresh()->status)->toBe(PressReleaseStatus::Draft); $component->set('legalAcceptanceConfirmed', true)->call('submitForReview')->assertHasNoErrors(); // Der synchrone Prüf-Job kann die Meldung direkt veröffentlichen — entscheidend ist: nicht mehr Entwurf. expect($pr->fresh()->status)->not->toBe(PressReleaseStatus::Draft) ->and(LegalDocumentAcceptance::query()->where('user_id', $owner->id)->pluck('type')->sort()->values()->all())->toBe(['agb', 'richtlinien']) ->and(LegalDocumentAcceptance::query()->first()->context)->toBe('submit') ->and(app(LegalAcceptanceGate::class)->requiredFor($owner))->toBeEmpty(); // Neue AGB-Fassung: das Gate fordert genau diese eine Fassung erneut. LegalDocument::factory()->agb()->create(['version' => '0.7', 'effective_from' => now()->subDay()->toDateString()]); expect(app(LegalAcceptanceGate::class)->requiredFor($owner)->pluck('version')->all())->toBe(['0.7']); }); test('acceptances are insert-only', function () { config()->set('legal.acceptance_gate', true); ['owner' => $owner] = legalGateDraft(); $gate = app(LegalAcceptanceGate::class); $rows = $gate->accept($owner, $gate->requiredFor($owner), 'gate', '203.0.113.5', 'Pest'); expect($rows)->toHaveCount(2) ->and($rows[0]->ip_hmac)->toStartWith('v1:') ->and(fn () => $rows[0]->delete())->toThrow(LogicException::class) ->and($gate->accept($owner, $gate->requiredFor($owner), 'gate'))->toBe([]); }); test('the api answers 428 with the required documents until they are accepted', function () { /** @var TestCase $this */ config()->set('legal.acceptance_gate', true); ['owner' => $owner, 'pr' => $pr] = legalGateDraft(); Sanctum::actingAs($owner, ['press-releases:write', 'press-releases:read']); $this->postJson("/api/v1/press-releases/{$pr->id}/submit", ['ai_text_declaration' => 'none']) ->assertStatus(428) ->assertJsonPath('required_documents.0.type', 'agb') ->assertJsonPath('required_documents.1.version', '0.2'); $this->postJson('/api/v1/legal/acceptances', ['documents' => [['type' => 'agb', 'version' => '0.5']]]) ->assertUnprocessable(); $this->postJson('/api/v1/legal/acceptances', ['documents' => [ ['type' => 'agb', 'version' => '0.6'], ['type' => 'richtlinien', 'version' => '0.2'], ]])->assertCreated()->assertJsonPath('required', []); expect(LegalDocumentAcceptance::query()->where('context', 'api')->count())->toBe(2); $this->postJson("/api/v1/press-releases/{$pr->id}/submit", ['ai_text_declaration' => 'none'])->assertSuccessful(); $this->getJson('/api/v1/legal/acceptances')->assertSuccessful()->assertJsonPath('gate_enabled', true)->assertJsonCount(2, 'accepted'); });