Uebernahme bestehender Legacy-API-Integrationen (Keys, Auth, Pfade)
Bestehende Kunden-Integrationen der Altportale laufen nach dem Relaunch weiter, ohne dass Kunden etwas umstellen muessen: 1. legacy:import-api-keys uebernimmt die api_keys aus den Legacy-DBs (sf_guard_user_profile) als Sanctum-Tokens mit identischem Secret (Sanctum loest Tokens ohne id|-Praefix per sha256-Hash auf) — nur fuer berechtigte Nutzer (aktiver Zahlungsstatus/Bestandsschutz, ApiAccessEligibilityService); idempotent, mit Dry-Run. Probe gegen die lokalen Snapshots: 406 Keys wuerden uebernommen. 2. Die Auth-Middleware uebersetzt die alten Uebergabeformen (X-ApiKey- Header, api_key-Parameter) fuer gueltige Keys in Bearer; unbekannte Keys erhalten weiterhin 410 mit Migrationshinweis. 3. Die dokumentierten /service/...-Pfade der Alt-API sind als Aliasse auf die v1-Controller gelegt (pressreleases inkl. Bilder, companies lesend); nicht uebernommene Alt-Endpoints (Pressemappen/Kontakte anlegen) antworten 410 mit Hinweis auf Kundenkonto und neue Doku. API-Seite + OpenAPI entsprechend aktualisiert; Go-Live-Checkliste um den Import-Schritt (nach dem Grandfathering) ergaenzt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3364fd13b8
commit
616b7847d4
11 changed files with 425 additions and 13 deletions
128
tests/Feature/Api/LegacyApiCompatibilityTest.php
Normal file
128
tests/Feature/Api/LegacyApiCompatibilityTest.php
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Enums\UserPaymentOptionStatus;
|
||||
use App\Models\PaymentOption;
|
||||
use App\Models\User;
|
||||
use App\Models\UserPaymentOption;
|
||||
use App\Services\Api\LegacyApiKeyImporter;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Übernahme bestehender Legacy-API-Integrationen (06.07.2026): importierte
|
||||
* api_keys bleiben gültig, alte Übergabeformen (X-ApiKey/api_key) und die
|
||||
* dokumentierten /service/…-Pfade funktionieren weiter.
|
||||
*/
|
||||
function eligibleLegacyApiUser(int $legacyId = 4711): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'is_active' => true,
|
||||
'portal' => Portal::Businessportal24,
|
||||
'legacy_portal' => 'businessportal24',
|
||||
'legacy_id' => $legacyId,
|
||||
]);
|
||||
|
||||
// Aktiver Zahlungsstatus → API-berechtigt (ApiAccessEligibilityService).
|
||||
UserPaymentOption::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'payment_option_id' => PaymentOption::factory()->create()->id,
|
||||
'status' => UserPaymentOptionStatus::Grandfathered->value,
|
||||
'grandfathered_until' => null,
|
||||
'current_period_start' => now()->subMonth(),
|
||||
'current_period_end' => now()->addMonths(11),
|
||||
'stripe_subscription_id' => null,
|
||||
]);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
test('the importer turns legacy api keys into sanctum tokens idempotently', function () {
|
||||
$user = eligibleLegacyApiUser();
|
||||
|
||||
// Nicht berechtigter Kandidat (inaktiv) und unbekanntes Legacy-Mapping.
|
||||
$inactive = User::factory()->create([
|
||||
'is_active' => false,
|
||||
'legacy_portal' => 'businessportal24',
|
||||
'legacy_id' => 999,
|
||||
]);
|
||||
|
||||
$rows = [
|
||||
['legacy_id' => 4711, 'api_key' => 'legacy-key-abc123'],
|
||||
['legacy_id' => 999, 'api_key' => 'legacy-key-inactive'],
|
||||
['legacy_id' => 123456, 'api_key' => 'legacy-key-unmapped'],
|
||||
];
|
||||
|
||||
$importer = app(LegacyApiKeyImporter::class);
|
||||
$result = $importer->import('businessportal24', $rows);
|
||||
|
||||
expect($result['imported'])->toBe(1);
|
||||
expect($result['ineligible'])->toBe(1);
|
||||
expect($result['ineligible_users'])->toBe([$inactive->email]);
|
||||
expect($result['unknown_user'])->toBe(1);
|
||||
|
||||
$token = PersonalAccessToken::findToken('legacy-key-abc123');
|
||||
expect($token)->not->toBeNull();
|
||||
expect($token->tokenable_id)->toBe($user->id);
|
||||
expect($token->name)->toBe(LegacyApiKeyImporter::TOKEN_NAME);
|
||||
expect($token->abilities)->toBe(LegacyApiKeyImporter::ABILITIES);
|
||||
|
||||
// Re-Run legt keine Duplikate an.
|
||||
$rerun = $importer->import('businessportal24', $rows);
|
||||
expect($rerun['imported'])->toBe(0);
|
||||
expect($rerun['already_present'])->toBe(1);
|
||||
expect(PersonalAccessToken::query()->where('token', hash('sha256', 'legacy-key-abc123'))->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('an imported legacy key authenticates via the old header and query forms', function () {
|
||||
/** @var TestCase $this */
|
||||
eligibleLegacyApiUser();
|
||||
app(LegacyApiKeyImporter::class)->import('businessportal24', [
|
||||
['legacy_id' => 4711, 'api_key' => 'legacy-key-abc123'],
|
||||
]);
|
||||
|
||||
// Dokumentierter Alt-Header der Portale: X-ApiKey.
|
||||
$this->getJson('/api/v1/categories', ['X-ApiKey' => 'legacy-key-abc123'])
|
||||
->assertOk();
|
||||
|
||||
// Verbreitete Schreibvariante + Query-Parameter.
|
||||
$this->getJson('/api/v1/categories', ['X-Api-Key' => 'legacy-key-abc123'])
|
||||
->assertOk();
|
||||
$this->getJson('/api/v1/categories?api_key=legacy-key-abc123')
|
||||
->assertOk();
|
||||
});
|
||||
|
||||
test('unknown legacy keys still get the 410 migration hint', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->getJson('/api/v1/categories', ['X-ApiKey' => 'gibt-es-nicht'])
|
||||
->assertStatus(410)
|
||||
->assertJsonStructure(['message', 'migration_url', 'docs_url']);
|
||||
});
|
||||
|
||||
test('the documented legacy service paths keep working with an imported key', function () {
|
||||
/** @var TestCase $this */
|
||||
eligibleLegacyApiUser();
|
||||
app(LegacyApiKeyImporter::class)->import('businessportal24', [
|
||||
['legacy_id' => 4711, 'api_key' => 'legacy-key-abc123'],
|
||||
]);
|
||||
|
||||
$this->getJson('/service/pressreleases', ['X-ApiKey' => 'legacy-key-abc123'])
|
||||
->assertOk();
|
||||
|
||||
$this->getJson('/service/users/me/companies', ['X-ApiKey' => 'legacy-key-abc123'])
|
||||
->assertOk();
|
||||
});
|
||||
|
||||
test('the legacy service paths require authentication', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->getJson('/service/pressreleases')->assertUnauthorized();
|
||||
});
|
||||
|
||||
test('retired legacy service endpoints answer with a helpful 410 instead of 404', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->postJson('/service/users/me/companies', ['company' => ['name' => 'Test']])
|
||||
->assertStatus(410)
|
||||
->assertJsonStructure(['message', 'account_url', 'docs_url']);
|
||||
|
||||
$this->getJson('/service/irgendwas')->assertStatus(410);
|
||||
});
|
||||
|
|
@ -7,11 +7,13 @@ use App\Models\User;
|
|||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('legacy api key requests receive gone response', function () {
|
||||
test('unknown legacy api key requests receive gone response', function () {
|
||||
/** @var TestCase $this */
|
||||
// Übernahme 06.07.: importierte Legacy-Keys sind gültige Tokens
|
||||
// (LegacyApiCompatibilityTest); nur unbekannte Keys bekommen 410.
|
||||
$this->getJson('/api/v1/categories?api_key=legacy-key')
|
||||
->assertStatus(410)
|
||||
->assertJsonPath('message', 'Legacy API keys are no longer supported.');
|
||||
->assertJsonStructure(['message', 'migration_url', 'docs_url']);
|
||||
});
|
||||
|
||||
test('api user can read only own companies', function () {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ test('api v1 documentation is publicly available', function () {
|
|||
->assertHeader('content-type', 'application/yaml; charset=UTF-8')
|
||||
->assertSee('openapi: 3.1.0', false)
|
||||
->assertSee('/press-releases:', false)
|
||||
->assertSee('legacy API keys are no longer supported', false);
|
||||
// Übernahme der Legacy-Integrationen (06.07.): Alt-Schlüssel bleiben
|
||||
// gültig, unbekannte bekommen den Migrationshinweis.
|
||||
->assertSee('Bestehende API-Schlüssel der Altportale wurden übernommen', false);
|
||||
});
|
||||
|
||||
test('the public api page documents the real endpoints', function () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue