R0.4: Seeder produktionssicher (PlanSeeder, keine Fake-Stripe-IDs, Dev-User-Guard)

- PlanSeeder (Starter/Business/Pro/Agency) haengt jetzt im DatabaseSeeder
- PaymentOptionSeeder erfindet keine prod_/price_placeholder-IDs mehr
  (Spalten nullable; echte Verknuepfung via Stripe-Sync/Pflege)
- Demo-Zugaenge admin@pressekonto.test/test@example.com (Passwort
  "password") werden nur noch in local/testing angelegt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-03 07:58:30 +00:00
parent c678e3a21f
commit b64aa9fbf5
3 changed files with 61 additions and 6 deletions

View file

@ -19,10 +19,17 @@ class DatabaseSeeder extends Seeder
$this->call([
RolesAndPermissionsSeeder::class,
AdminPresetSeeder::class,
PlanSeeder::class,
PaymentOptionSeeder::class,
// CategorySeeder::class,
]);
// Demo-Zugänge (Passwort "password") nur in lokalen Umgebungen –
// niemals in Produktion anlegen.
if (! app()->environment(['local', 'testing'])) {
return;
}
$adminUser = User::firstOrCreate([
'email' => 'admin@pressekonto.test',
], [

View file

@ -21,8 +21,8 @@ class PaymentOptionSeeder extends Seeder
'currency' => 'EUR',
'interval' => 'monthly',
'is_hidden' => false,
'stripe_product_id' => 'prod_placeholder_monthly',
'stripe_price_id' => 'price_placeholder_monthly',
'stripe_product_id' => null,
'stripe_price_id' => null,
'translations' => [
'de' => ['name' => 'Monatspaket', 'description' => 'Monatliche Veroeffentlichungen'],
'en' => ['name' => 'Monthly package', 'description' => 'Monthly publication package'],
@ -35,8 +35,8 @@ class PaymentOptionSeeder extends Seeder
'currency' => 'EUR',
'interval' => 'yearly',
'is_hidden' => false,
'stripe_product_id' => 'prod_placeholder_yearly',
'stripe_price_id' => 'price_placeholder_yearly',
'stripe_product_id' => null,
'stripe_price_id' => null,
'translations' => [
'de' => ['name' => 'Jahrespaket', 'description' => 'Jaehrliche Veroeffentlichungen'],
'en' => ['name' => 'Yearly package', 'description' => 'Yearly publication package'],
@ -49,8 +49,8 @@ class PaymentOptionSeeder extends Seeder
'currency' => 'EUR',
'interval' => 'once',
'is_hidden' => false,
'stripe_product_id' => 'prod_placeholder_onetime',
'stripe_price_id' => 'price_placeholder_onetime',
'stripe_product_id' => null,
'stripe_price_id' => null,
'translations' => [
'de' => ['name' => 'Reichweiten-Boost', 'description' => 'Einmaliger Promotion-Boost'],
'en' => ['name' => 'Reach boost', 'description' => 'One-time promotion boost'],

View file

@ -0,0 +1,48 @@
<?php
use App\Models\PaymentOption;
use App\Models\Plan;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Tests\TestCase;
test('database seeder seeds roles, plans and payment options', function () {
/** @var TestCase $this */
$this->seed(DatabaseSeeder::class);
expect(Plan::query()->count())->toBe(4);
expect(PaymentOption::query()->count())->toBe(3);
});
test('payment option seeder does not invent stripe ids', function () {
/** @var TestCase $this */
$this->seed(DatabaseSeeder::class);
expect(PaymentOption::query()->whereNotNull('stripe_price_id')->exists())->toBeFalse();
expect(PaymentOption::query()->whereNotNull('stripe_product_id')->exists())->toBeFalse();
});
test('dev users are seeded outside production', function () {
/** @var TestCase $this */
$this->seed(DatabaseSeeder::class);
expect(User::findByEmail('admin@pressekonto.test'))->not->toBeNull();
expect(User::findByEmail('test@example.com'))->not->toBeNull();
});
test('dev users are not seeded in production', function () {
/** @var TestCase $this */
$originalEnv = $this->app['env'];
$this->app['env'] = 'production';
try {
// Direkt statt über artisan db:seed – das Command würde in
// "production" interaktiv um Bestätigung bitten.
(new DatabaseSeeder)->setContainer($this->app)->__invoke();
} finally {
$this->app['env'] = $originalEnv;
}
expect(User::query()->count())->toBe(0);
expect(Plan::query()->count())->toBe(4);
});