api: neue Firmen dem bestehenden Newsroom zuordnen (Roadmap 1a.4)
connektar legt je Meldung eine Firma an. ApiNewsroomAssignment prüft vor dem Anlegen, ob unter derselben Firmen-E-Mail schon dieselbe Firma steht (Namensregel der Klassifizierung), und gibt dann deren ID zurück — der Alt-Vertrag (201 + Location) bleibt. Nie-veröffentlicht-Adressen werden nie zugeordnet, abweichende Namen bekommen wie bisher eine neue Firma. Liegt die Firma auf dem anderen Portal, wird sie both; bei fremd belegtem Slug bleibt es beim alten Verhalten. LegacyPressReleaseController: both ist kein Ziel-Portal für eine Meldung — dann entscheidet das Portal des API-Nutzers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4303266841
commit
8dad652d67
8 changed files with 367 additions and 34 deletions
164
tests/Feature/Api/ApiNewsroomAssignmentTest.php
Normal file
164
tests/Feature/Api/ApiNewsroomAssignmentTest.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\CompanyAddressClass;
|
||||
use App\Enums\Portal;
|
||||
use App\Models\ActivityEvent;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyEmailGroup;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Laufende Zuordnung neuer API-Firmen zum bestehenden Newsroom über die
|
||||
* Firmen-E-Mail (Roadmap 1a.4). connektar legt je Meldung eine Firma an —
|
||||
* ohne die Zuordnung entstünde je Meldung ein neuer Newsroom.
|
||||
*/
|
||||
function assignmentApiUser(Portal $portal = Portal::Businessportal24): User
|
||||
{
|
||||
test()->seed(RolesAndPermissionsSeeder::class);
|
||||
config()->set('billing.enforce_booking', false);
|
||||
config()->set('app.theme', 'portal');
|
||||
|
||||
$user = User::factory()->create(['is_active' => true, 'portal' => $portal]);
|
||||
$user->forceFill(['registration_type' => 'apiuser'])->save();
|
||||
|
||||
Sanctum::actingAs($user, ['press-releases:read', 'press-releases:write', 'companies:read']);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $overrides
|
||||
*/
|
||||
function postApiCompany(array $overrides = []): int
|
||||
{
|
||||
$response = test()->postJson('/service/users/me/companies', [
|
||||
'company' => array_merge(['name' => 'Muster GmbH', 'email' => 'info@muster.de'], $overrides),
|
||||
])->assertCreated();
|
||||
|
||||
return (int) basename((string) $response->headers->get('Location'));
|
||||
}
|
||||
|
||||
function assignmentGroup(string $email, CompanyAddressClass $class, ?CompanyAddressClass $manual = null): void
|
||||
{
|
||||
CompanyEmailGroup::query()->create([
|
||||
'email' => $email, 'classification' => $class, 'manual_classification' => $manual,
|
||||
'companies_count' => 30, 'distinct_names' => 30, 'published_companies_count' => 0, 'threshold' => 3, 'classified_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
test('the same company under the same email is reused instead of created', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = assignmentApiUser();
|
||||
$existing = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster GmbH', 'portal' => 'businessportal24']);
|
||||
|
||||
$id = postApiCompany(['email' => 'Info@Muster.de']);
|
||||
|
||||
expect($id)->toBe($existing->id)
|
||||
->and(Company::withoutGlobalScopes()->where('email', 'like', '%muster.de')->count())->toBe(1)
|
||||
->and($existing->users()->whereKey($user->id)->wherePivot('role', 'member')->exists())->toBeTrue()
|
||||
->and(ActivityEvent::query()->where('subject_id', $existing->id)->where('description', 'like', 'API-Firma%')->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
test('a name that differs only by legal form or a typo still matches', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser();
|
||||
$existing = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster Unternehmensberatung', 'portal' => 'businessportal24']);
|
||||
|
||||
expect(postApiCompany(['name' => 'Muster Unternehmensberatung GmbH & Co. KG']))->toBe($existing->id)
|
||||
->and(postApiCompany(['name' => 'Muster Unternehmensbertung']))->toBe($existing->id);
|
||||
});
|
||||
|
||||
test('a different name under the same address gets its own company as before', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser();
|
||||
Company::factory()->create(['email' => 'presse@agentur.de', 'name' => 'Kunde Eins GmbH', 'portal' => 'businessportal24']);
|
||||
|
||||
$id = postApiCompany(['name' => 'Ganz Anderer Kunde AG', 'email' => 'presse@agentur.de']);
|
||||
|
||||
expect(Company::withoutGlobalScopes()->find($id)->name)->toBe('Ganz Anderer Kunde AG')
|
||||
->and(Company::withoutGlobalScopes()->where('email', 'presse@agentur.de')->count())->toBe(2);
|
||||
});
|
||||
|
||||
test('under a never-published address nothing is ever assigned', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser();
|
||||
assignmentGroup('yahoo@yahoo.com', CompanyAddressClass::NeverPublished);
|
||||
$existing = Company::factory()->create(['email' => 'yahoo@yahoo.com', 'name' => 'Muster GmbH', 'portal' => 'businessportal24']);
|
||||
|
||||
expect(postApiCompany(['email' => 'yahoo@yahoo.com']))->not->toBe($existing->id);
|
||||
});
|
||||
|
||||
test('a manual decision can turn a never-published address back into a normal one', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser();
|
||||
assignmentGroup('presse@pr4you.de', CompanyAddressClass::NeverPublished, manual: CompanyAddressClass::CollectionAddress);
|
||||
$existing = Company::factory()->create(['email' => 'presse@pr4you.de', 'name' => 'Muster GmbH', 'portal' => 'businessportal24']);
|
||||
|
||||
expect(postApiCompany(['email' => 'presse@pr4you.de']))->toBe($existing->id);
|
||||
});
|
||||
|
||||
test('a company on the other portal becomes both and the release lands on the requested portal', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = assignmentApiUser(Portal::Presseecho);
|
||||
$existing = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster GmbH', 'portal' => 'businessportal24']);
|
||||
$category = Category::factory()->create();
|
||||
Queue::fake();
|
||||
|
||||
$id = postApiCompany();
|
||||
|
||||
expect($id)->toBe($existing->id)
|
||||
->and($existing->fresh()->portal)->toBe(Portal::Both);
|
||||
|
||||
$this->postJson('/service/pressreleases', [
|
||||
'press_release' => [
|
||||
'company_id' => $id,
|
||||
'category_id' => $category->legacy_id ?? $category->id,
|
||||
'language' => 'de',
|
||||
'title' => 'Zuordnungs-Test',
|
||||
'text' => 'Inhalt der Test-Pressemitteilung mit genug Text.',
|
||||
],
|
||||
])->assertCreated();
|
||||
|
||||
// `both` ist kein Ziel-Portal für eine Meldung — ohne Parameter und
|
||||
// Domain-Kontext entscheidet das Portal des API-Nutzers.
|
||||
$release = PressRelease::withoutGlobalScopes()->where('title', 'Zuordnungs-Test')->firstOrFail();
|
||||
expect($release->portal)->toBe(Portal::Presseecho)
|
||||
->and($release->company_id)->toBe($existing->id)
|
||||
->and($release->user_id)->toBe($user->id);
|
||||
});
|
||||
|
||||
test('a slug taken on the other portal prevents widening and falls back to a new company', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser(Portal::Presseecho);
|
||||
$existing = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster GmbH', 'portal' => 'businessportal24', 'slug' => 'muster-gmbh']);
|
||||
Company::factory()->create(['email' => 'anders@fremd.de', 'name' => 'Muster GmbH', 'portal' => 'presseecho', 'slug' => 'muster-gmbh']);
|
||||
|
||||
$id = postApiCompany();
|
||||
|
||||
expect($id)->not->toBe($existing->id)
|
||||
->and($existing->fresh()->portal)->toBe(Portal::Businessportal24);
|
||||
});
|
||||
|
||||
test('a merged-away company is not matched, its survivor is', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser(Portal::Presseecho);
|
||||
$survivor = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster GmbH', 'portal' => 'both']);
|
||||
$gone = Company::factory()->create(['email' => 'info@muster.de', 'name' => 'Muster GmbH', 'portal' => 'presseecho']);
|
||||
$gone->delete();
|
||||
|
||||
expect(postApiCompany())->toBe($survivor->id);
|
||||
});
|
||||
|
||||
test('without an email a company is created as before', function () {
|
||||
/** @var TestCase $this */
|
||||
assignmentApiUser();
|
||||
$existing = Company::factory()->create(['email' => null, 'name' => 'Muster GmbH', 'portal' => 'businessportal24']);
|
||||
|
||||
expect(postApiCompany(['email' => null]))->not->toBe($existing->id);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue