mivita/tests/Feature/OrderWithdrawalTest.php
Kevin 63c36555a5 Widerruf: Gesetzliche Online-Widerrufsfunktion (Formular, Bestätigungsmail, Admin-Verwaltung)
- Öffentliches Widerrufsformular /widerruf mit Bestell-Lookup (throttled)
- Bestätigungsmail an Kunde + Weiterleitung an bestellungen@ (config app.withdrawal_mail)
- Admin-Übersicht/-Detail unter /admin/withdrawals inkl. Sidenav-Eintrag
- Widerruf-Button in Portal-Bestellansicht und Footer-Links
- Model OrderWithdrawal + Migration + Factory + Tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:56:02 +00:00

192 lines
6 KiB
PHP

<?php
use App\Mail\MailWithdrawalConfirmation;
use App\Models\Country;
use App\Models\OrderWithdrawal;
use App\Models\ShoppingOrder;
use App\Models\ShoppingOrderItem;
use App\Models\ShoppingUser;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
/**
* @return array{order: ShoppingOrder, item: ShoppingOrderItem}
*/
function makeShopOrder(string $email = 'kunde@example.com'): array
{
makeShopEnv();
$country = Country::first();
$product = makeProduct(['1']);
$shoppingUser = ShoppingUser::create([
'auth_user_id' => 0,
'billing_firstname' => 'Max',
'billing_lastname' => 'Mustermann',
'billing_email' => $email,
'billing_country_id' => $country->id,
'shipping_country_id' => $country->id,
'is_from' => 'shopping',
]);
$order = ShoppingOrder::create([
'shopping_user_id' => $shoppingUser->id,
'auth_user_id' => 0,
'user_shop_id' => 0,
'payment_for' => 6,
'country_id' => $country->id,
'language' => 'de',
'total' => 119,
'txaction' => 'paid',
]);
$item = ShoppingOrderItem::create([
'shopping_order_id' => $order->id,
'product_id' => $product->id,
'qty' => 2,
'price' => 59.5,
]);
return ['order' => $order, 'item' => $item];
}
it('zeigt die Widerrufsseite an', function () {
$this->get(route('withdrawal.create'))->assertOk();
});
it('zeigt nach erfolgreicher Identifikation die Bestellung mit Absende-Button', function () {
['order' => $order] = makeShopOrder('kunde@example.com');
$response = $this->post(route('withdrawal.lookup'), [
'order_number' => (string) $order->id,
'email' => 'kunde@example.com',
]);
$response->assertOk()
->assertSee('#'.$order->id)
->assertSee(__('withdrawal.submit'));
});
it('legt bei vollständigem Widerruf einen Datensatz an und versendet die Eingangsbestätigung', function () {
Mail::fake();
['order' => $order] = makeShopOrder('kunde@example.com');
$response = $this->post(route('withdrawal.store'), [
'order_number' => (string) $order->id,
'email' => 'kunde@example.com',
'scope' => 'full',
]);
$response->assertOk();
$withdrawal = OrderWithdrawal::first();
expect($withdrawal)->not->toBeNull()
->and($withdrawal->shopping_order_id)->toBe($order->id)
->and($withdrawal->type)->toBe(OrderWithdrawal::TYPE_FULL)
->and($withdrawal->status)->toBe(OrderWithdrawal::STATUS_RECEIVED)
->and($withdrawal->customer_email)->toBe('kunde@example.com')
->and($withdrawal->declared_at)->not->toBeNull();
Mail::assertSent(MailWithdrawalConfirmation::class, function (MailWithdrawalConfirmation $mail) {
return $mail->hasTo('kunde@example.com')
&& $mail->hasBcc(config('app.withdrawal_mail'));
});
});
it('findet die Bestellung auch über Groß-/Kleinschreibung der E-Mail', function () {
Mail::fake();
['order' => $order] = makeShopOrder('kunde@example.com');
$this->post(route('withdrawal.store'), [
'order_number' => (string) $order->id,
'email' => 'KUNDE@EXAMPLE.COM',
'scope' => 'full',
])->assertOk();
expect(OrderWithdrawal::count())->toBe(1);
});
it('speichert nur die ausgewählten Positionen beim teilweisen Widerruf', function () {
Mail::fake();
['order' => $order, 'item' => $item] = makeShopOrder();
$this->post(route('withdrawal.store'), [
'order_number' => (string) $order->id,
'email' => 'kunde@example.com',
'scope' => 'partial',
'items' => [$item->id => 1],
])->assertOk();
$withdrawal = OrderWithdrawal::first();
expect($withdrawal->type)->toBe(OrderWithdrawal::TYPE_PARTIAL)
->and($withdrawal->items)->toHaveCount(1)
->and($withdrawal->items[0]['qty'])->toBe(1)
->and($withdrawal->items[0]['shopping_order_item_id'])->toBe($item->id);
});
it('lehnt einen teilweisen Widerruf ohne ausgewählte Artikel ab', function () {
Mail::fake();
['order' => $order] = makeShopOrder();
$this->post(route('withdrawal.store'), [
'order_number' => (string) $order->id,
'email' => 'kunde@example.com',
'scope' => 'partial',
'items' => [],
])->assertRedirect();
expect(OrderWithdrawal::count())->toBe(0);
Mail::assertNothingSent();
});
it('findet keine Bestellung bei falscher E-Mail', function () {
['order' => $order] = makeShopOrder('kunde@example.com');
$this->post(route('withdrawal.lookup'), [
'order_number' => (string) $order->id,
'email' => 'falsch@example.com',
])->assertRedirect();
expect(OrderWithdrawal::count())->toBe(0);
});
it('widerruft keine Nicht-Shop-Bestellung (payment_for != 6)', function () {
makeShopEnv();
$country = Country::first();
$shoppingUser = ShoppingUser::create([
'auth_user_id' => 0,
'billing_firstname' => 'Max',
'billing_lastname' => 'Mustermann',
'billing_email' => 'kunde@example.com',
'billing_country_id' => $country->id,
'shipping_country_id' => $country->id,
'is_from' => 'user_order',
]);
$order = ShoppingOrder::create([
'shopping_user_id' => $shoppingUser->id,
'auth_user_id' => 0,
'user_shop_id' => 0,
'payment_for' => 3,
'country_id' => $country->id,
'language' => 'de',
'total' => 119,
]);
$this->post(route('withdrawal.store'), [
'order_number' => (string) $order->id,
'email' => 'kunde@example.com',
'scope' => 'full',
])->assertRedirect();
expect(OrderWithdrawal::count())->toBe(0);
});
it('validiert Pflichtfelder', function () {
$this->post(route('withdrawal.store'), [
'order_number' => '',
'email' => 'keine-mail',
'scope' => 'full',
])->assertSessionHasErrors(['order_number', 'email']);
});