Files
ladill-link/tests/Feature/LinkRedirectTest.php
T
isaacclad 86cac606e0
Deploy Ladill Link / deploy (push) Successful in 1m40s
Surface destination edits as the core dynamic short-link feature.
Move destination editing to the top of the link page, clarify that the short URL never changes, prevent caches from pinning old redirects, and test that later destination updates take effect immediately.
2026-07-16 20:12:14 +00:00

365 lines
13 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\ShortLink;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Tests\TestCase;
class LinkRedirectTest extends TestCase
{
use RefreshDatabase;
private function user(): User
{
return User::create([
'public_id' => (string) Str::uuid(),
'name' => 'Test',
'email' => 'test+'.uniqid().'@example.com',
]);
}
public function test_direct_redirect_links_go_to_destination_url(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'direct1',
'source_app' => 'link',
'source_kind' => 'redirect',
'is_managed_here' => true,
'destination_url' => 'https://example.com/target',
'is_active' => true,
]);
$this->get('https://ladl.link/direct1')
->assertRedirect('https://example.com/target');
}
public function test_direct_redirect_forwards_query_string_dynamically(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'promo',
'source_app' => 'link',
'source_kind' => 'redirect',
'is_managed_here' => true,
'destination_url' => 'https://example.com/landing?ref=home',
'is_active' => true,
]);
$this->get('https://ladl.link/promo?utm_source=ig&ref=campaign')
->assertRedirect('https://example.com/landing?ref=campaign&utm_source=ig');
}
public function test_direct_redirect_appends_path_suffix_dynamically(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'docs',
'source_app' => 'link',
'source_kind' => 'redirect',
'is_managed_here' => true,
'destination_url' => 'https://example.com/help',
'is_active' => true,
]);
$this->get('https://ladl.link/docs/getting-started?src=nav')
->assertRedirect('https://example.com/help/getting-started?src=nav');
}
public function test_public_url_uses_config_domain_dynamically(): void
{
config(['link.public_domain' => 'go.example.test']);
$user = $this->user();
$link = ShortLink::create([
'user_id' => $user->id,
'slug' => 'branded',
'source_app' => 'link',
'source_kind' => 'redirect',
'is_managed_here' => true,
'destination_url' => 'https://example.com',
'is_active' => true,
]);
$this->assertSame('https://go.example.test/branded', $link->publicUrl());
$this->assertSame('go.example.test', ShortLink::publicHost());
}
public function test_changing_destination_later_redirects_to_new_url(): void
{
$user = $this->user();
$link = ShortLink::create([
'user_id' => $user->id,
'slug' => 'campaign',
'source_app' => 'link',
'source_kind' => 'redirect',
'is_managed_here' => true,
'destination_url' => 'https://example.com/old-landing',
'is_active' => true,
]);
$this->get('https://ladl.link/campaign')
->assertRedirect('https://example.com/old-landing');
// Destination edit is a first-class Link feature (not frozen at create time).
app(\App\Services\Link\LinkManagerService::class)->update($link, [
'destination_url' => 'https://example.com/new-landing',
'label' => 'Spring campaign',
]);
$link->refresh();
$this->assertSame('https://example.com/new-landing', $link->destination_url);
// Short slug stays put — only the destination changes.
$this->assertSame('campaign', $link->slug);
$this->assertSame('https://ladl.link/campaign', $link->publicUrl());
$response = $this->get('https://ladl.link/campaign');
$response->assertRedirect('https://example.com/new-landing');
$this->assertStringContainsString('no-store', (string) $response->headers->get('Cache-Control'));
}
public function test_catalog_qr_links_proxy_to_platform_instead_of_bad_destination(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'qrpage',
'source_app' => 'qrplus',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/qrpage · Link list page',
'is_active' => true,
]);
Http::fake([
'https://ladill.com/q/qrpage' => Http::response('<html>QR page</html>', 200),
]);
$this->get('https://ladl.link/qrpage')
->assertOk()
->assertSee('QR page');
Http::assertSent(function ($request) {
return $request->url() === 'https://ladill.com/q/qrpage'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
}
public function test_transfer_qr_proxies_to_transfer_app_without_redirect_loop(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'mo4o0v1t',
'source_app' => 'transfer',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/mo4o0v1t',
'is_active' => true,
]);
Http::fake([
'https://ladill.com/q/mo4o0v1t' => Http::response('', 302, [
'Location' => 'https://ladl.link/mo4o0v1t/transfer',
]),
'https://transfer.ladill.com/q/mo4o0v1t/transfer' => Http::response('<html>Transfer files</html>', 200),
]);
$this->get('https://ladl.link/mo4o0v1t')
->assertOk()
->assertSee('Transfer files');
Http::assertSent(function ($request) {
return $request->url() === 'https://transfer.ladill.com/q/mo4o0v1t/transfer'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
}
public function test_event_qr_falls_back_to_events_app_when_platform_returns_404(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'sales-webinar',
'source_app' => 'events',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/sales-webinar',
'is_active' => true,
]);
Http::fake([
'https://ladill.com/q/sales-webinar' => Http::response('Not found', 404),
'https://events.ladill.com/q/sales-webinar' => Http::response('<html>Event page</html>', 200),
]);
$this->get('https://ladl.link/sales-webinar')
->assertOk()
->assertSee('Event page');
Http::assertSent(function ($request) {
return $request->url() === 'https://events.ladill.com/q/sales-webinar'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
}
public function test_event_registration_confirmation_proxies_directly_to_events_app(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'sales-webinar',
'source_app' => 'events',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/sales-webinar',
'is_active' => true,
]);
Http::fake([
'https://events.ladill.com/q/sales-webinar/registered/QRE-TESTREF123456' => Http::response('<html>Registration confirmed</html>', 200),
]);
$this->get('https://ladl.link/sales-webinar/registered/QRE-TESTREF123456')
->assertOk()
->assertSee('Registration confirmed');
Http::assertSent(function ($request) {
return $request->url() === 'https://events.ladill.com/q/sales-webinar/registered/QRE-TESTREF123456'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
Http::assertNotSent(function ($request) {
return str_starts_with($request->url(), 'https://ladill.com/q/sales-webinar/registered/');
});
}
public function test_event_registration_post_forwards_json_body_to_events(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'sales-webinar',
'source_app' => 'events',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/sales-webinar',
'is_active' => true,
]);
Http::fake([
'https://events.ladill.com/q/sales-webinar/register' => function ($request) {
$payload = json_decode($request->body(), true);
return Http::response(json_encode([
'paid' => false,
'success_url' => 'https://ladl.link/sales-webinar/registered/QRE-TESTREF123456',
'badge_code' => 'BADGE123',
]), 200, ['Content-Type' => 'application/json']);
},
]);
$this->postJson('https://ladl.link/sales-webinar/register', [
'tier' => 'General Admission',
'attendee_name' => 'Jane Doe',
'attendee_email' => 'jane@example.com',
'attendee_phone' => '+233200000000',
])
->assertOk()
->assertJsonPath('success_url', 'https://ladl.link/sales-webinar/registered/QRE-TESTREF123456');
Http::assertSent(function ($request) {
if ($request->url() !== 'https://events.ladill.com/q/sales-webinar/register') {
return false;
}
$payload = json_decode($request->body(), true);
return is_array($payload)
&& ($payload['attendee_email'] ?? '') === 'jane@example.com'
&& ($payload['tier'] ?? '') === 'General Admission'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
}
public function test_event_registration_post_does_not_require_link_app_csrf_token(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'sales-webinar',
'source_app' => 'events',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/sales-webinar',
'is_active' => true,
]);
Http::fake([
'https://events.ladill.com/q/sales-webinar/register' => Http::response(json_encode([
'paid' => false,
'success_url' => 'https://ladl.link/sales-webinar/registered/QRE-TESTREF123456',
'badge_code' => 'BADGE123',
]), 200, ['Content-Type' => 'application/json']),
]);
$this->postJson('https://ladl.link/sales-webinar/register', [
'tier' => 'General Admission',
'attendee_name' => 'Jane Doe',
'attendee_email' => 'jane@example.com',
'attendee_phone' => '+233200000000',
])
->assertOk()
->assertJsonPath('badge_code', 'BADGE123');
}
public function test_mini_pay_post_does_not_require_link_app_csrf_token(): void
{
$user = $this->user();
ShortLink::create([
'user_id' => $user->id,
'slug' => 'shop-pay',
'source_app' => 'mini',
'source_kind' => 'qr',
'is_managed_here' => false,
'destination_url' => 'https://ladl.link/shop-pay',
'is_active' => true,
]);
Http::fake([
'https://ladill.com/q/shop-pay/pay' => Http::response(json_encode([
'checkout_url' => 'https://ladill.com/pay/momo/REF123',
'provider' => 'mtn_momo',
]), 200, ['Content-Type' => 'application/json']),
]);
$this->postJson('https://ladl.link/shop-pay/pay', [
'amount' => 12.5,
'customer_phone' => '0241234567',
])
->assertOk()
->assertJsonPath('provider', 'mtn_momo')
->assertJsonPath('checkout_url', 'https://ladill.com/pay/momo/REF123');
Http::assertSent(function ($request) {
if ($request->url() !== 'https://ladill.com/q/shop-pay/pay') {
return false;
}
$payload = json_decode($request->body(), true);
return is_array($payload)
&& (float) ($payload['amount'] ?? 0) === 12.5
&& ($payload['customer_phone'] ?? '') === '0241234567'
&& $request->hasHeader('X-Ladill-Internal', '1');
});
}
}