toBe(AboHelper::DEFAULT_ONETIME_WINDOW_DAYS); }); it('liefert den konfigurierten Wert aus den Settings', function () { Setting::setContentBySlug('abo-onetime-window-days', 6, 'int'); expect(AboHelper::getOneTimeWindowDays())->toBe(6); }); it('fällt bei 0 oder ungültigem Wert auf den Standard zurück', function () { Setting::setContentBySlug('abo-onetime-window-days', 0, 'int'); expect(AboHelper::getOneTimeWindowDays())->toBe(AboHelper::DEFAULT_ONETIME_WINDOW_DAYS); }); }); describe('isOneTimeWindowOpen', function () { beforeEach(function () { Carbon::setTestNow(Carbon::parse('2026-06-05 10:00:00', 'Europe/Berlin')); }); it('ist offen, wenn die Ausführung innerhalb des Fensters liegt', function () { $abo = new UserAbo; $abo->next_date = '2026-06-09'; // 4 Tage entfernt, Default-Fenster = 4 expect(AboHelper::isOneTimeWindowOpen($abo))->toBeTrue(); }); it('ist offen am Tag der Ausführung', function () { $abo = new UserAbo; $abo->next_date = '2026-06-05'; expect(AboHelper::isOneTimeWindowOpen($abo))->toBeTrue(); }); it('ist geschlossen, wenn die Ausführung weiter als das Fenster entfernt ist', function () { $abo = new UserAbo; $abo->next_date = '2026-06-10'; // 5 Tage entfernt, Fenster = 4 expect(AboHelper::isOneTimeWindowOpen($abo))->toBeFalse(); }); it('berücksichtigt ein konfiguriertes größeres Fenster', function () { Setting::setContentBySlug('abo-onetime-window-days', 7, 'int'); $abo = new UserAbo; $abo->next_date = '2026-06-10'; // 5 Tage entfernt, Fenster = 7 expect(AboHelper::isOneTimeWindowOpen($abo))->toBeTrue(); }); it('ist geschlossen, wenn das Ausführungsdatum in der Vergangenheit liegt', function () { $abo = new UserAbo; $abo->next_date = '2026-06-04'; expect(AboHelper::isOneTimeWindowOpen($abo))->toBeFalse(); }); it('ist geschlossen, wenn kein next_date gesetzt ist', function () { $abo = new UserAbo; $abo->next_date = null; expect(AboHelper::isOneTimeWindowOpen($abo))->toBeFalse(); }); }); describe('UserAboOneTimeItem Model', function () { it('definiert die Beziehung one_time_items auf UserAbo', function () { $relation = (new UserAbo)->one_time_items(); expect($relation)->toBeInstanceOf(Illuminate\Database\Eloquent\Relations\HasMany::class) ->and($relation->getRelated())->toBeInstanceOf(UserAboOneTimeItem::class); }); it('castet die Snapshot-Felder korrekt', function () { $item = new UserAboOneTimeItem([ 'qty' => '3', 'price' => '12.50', 'tax_rate' => '19.00', ]); expect($item->qty)->toBe(3) ->and($item->price)->toBe(12.5) ->and($item->tax_rate)->toBe(19.0); }); }); describe('ShoppingOrderItem is_abo_addon', function () { it('grenzt Abo- und Einmal-Positionen über Scopes ab', function () { $aboSql = ShoppingOrderItem::query()->aboItems()->toSql(); $addonSql = ShoppingOrderItem::query()->addonItems()->toSql(); expect($aboSql)->toContain('is_abo_addon') ->and($addonSql)->toContain('is_abo_addon'); }); it('castet is_abo_addon als bool', function () { $item = new ShoppingOrderItem(['is_abo_addon' => 1]); expect($item->is_abo_addon)->toBeTrue(); }); });