HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/tests/Unit/CampaignServerPoolTest.php
<?php

/**
 * CampaignServerPoolTest — Covers Campaign::getServersPool() 3-branch logic
 * for per-campaign sending-server narrow selection.
 *
 *   A: pivot empty                   → default broadcast (MailList pool)
 *   B: pivot non-empty + active      → RouletteWheel from pivot
 *   C: pivot non-empty + all dead    → throw Exception (fail loud)
 */

use App\Library\RouletteWheel;
use App\Model\Campaign;
use App\Model\CampaignSendingServer;
use App\Model\Contact;
use App\Model\Customer;
use App\Model\Email;
use App\Model\MailList;
use App\Model\Plan;
use App\Model\SendingServer;
use App\Model\Subscription;
use App\Services\Plans\Entitlements\EntitlementKey;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

uses(TestCase::class);

afterEach(function () {
    $testCustomerIds = DB::table('customers')->where('uid', 'like', 'pool_%')->pluck('id');

    $campaignIds = DB::table('campaigns')->whereIn('customer_id', $testCustomerIds)->pluck('id');
    DB::table('campaigns_sending_servers')->whereIn('campaign_id', $campaignIds)->delete();
    DB::table('campaigns_lists_segments')->whereIn('campaign_id', $campaignIds)->delete();
    DB::table('campaigns')->whereIn('id', $campaignIds)->delete();

    DB::table('emails')->whereIn('customer_id', $testCustomerIds)->delete();
    DB::table('mail_lists')->whereIn('customer_id', $testCustomerIds)->delete();
    DB::table('sending_servers')->where('uid', 'like', 'poolsrv_%')->delete();
    DB::table('subscriptions')->whereIn('customer_id', $testCustomerIds)->delete();

    $planIds = DB::table('plans')->where('uid', 'like', 'poolplan_%')->pluck('id');
    DB::table('plan_entitlements')->whereIn('plan_id', $planIds)->delete();
    DB::table('plans')->whereIn('id', $planIds)->delete();

    DB::table('customers')->whereIn('id', $testCustomerIds)->delete();
    DB::table('contacts')->where('uid', 'like', 'poolcontact_%')->delete();
});

function poolFixture(int $serverCount = 2): array
{
    $contact = Contact::forceCreate([
        'uid'        => uniqid('poolcontact_'),
        'first_name' => 'P',
        'last_name'  => 'L',
        'email'      => uniqid() . '@pool-test.invalid',
    ]);
    $customer = Customer::forceCreate([
        'uid'        => uniqid('pool_'),
        'contact_id' => $contact->id,
        'status'     => 'active',
        'timezone'   => 'UTC',
    ]);

    $plan = Plan::forceCreate([
        'uid'              => uniqid('poolplan_'),
        'currency_id'      => 1,
        'name'             => 'Pool Plan',
        'price'            => 0,
        'frequency_amount' => 1,
        'frequency_unit'   => 'month',
        'status'           => 'active',
    ]);
    DB::table('plan_entitlements')->insert([
        'plan_id'         => $plan->id,
        'entitlement_key' => EntitlementKey::HAS_OWN_SENDING_SERVER->value,
        'enabled'         => true,
        'created_at'      => now(),
        'updated_at'      => now(),
    ]);
    Subscription::forceCreate([
        'uid'         => uniqid('poolsub_'),
        'customer_id' => $customer->id,
        'plan_id'     => $plan->id,
        'status'      => Subscription::STATUS_ACTIVE,
    ]);

    $list = MailList::forceCreate([
        'uid'         => uniqid('poollist_'),
        'name'        => 'Pool List',
        'customer_id' => $customer->id,
        'from_email'  => '[email protected]',
        'from_name'   => 'Pool Sender',
    ]);

    // Customer's own active sending servers — `Customer::getSendingServerPool()`
    // surfaces these directly when the HAS_OWN_SENDING_SERVER entitlement
    // is granted (which the fixture above set up).
    $servers = [];
    for ($i = 0; $i < $serverCount; $i++) {
        $servers[] = SendingServer::forceCreate([
            'uid'         => uniqid('poolsrv_'),
            'name'        => "Pool Server {$i}",
            'type'        => 'smtp',
            'status'      => SendingServer::STATUS_ACTIVE,
            'host'        => '127.0.0.1',
            'smtp_port'   => 25,
            'quota_value' => -1,
            'quota_base'  => 1,
            'quota_unit'  => 'day',
            'customer_id' => $customer->id,
        ]);
    }

    $campaign = $customer->local()->newDefaultCampaign();
    $campaign->saveFromArray(['type' => Email::TYPE_REGULAR]);
    $campaign->default_mail_list_id = $list->id;
    $campaign->save();

    return compact('customer', 'list', 'campaign', 'servers');
}

test('Nhánh A — pivot empty falls back to MailList default pool', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    expect($campaign->sendingServersPivot)->toHaveCount(0);

    $pool = $campaign->getServersPool();
    expect($pool)->toBeInstanceOf(RouletteWheel::class);
    expect($pool->count())->toBe(2);
});

test('Nhánh B — pivot non-empty + active servers narrows to selection', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][0]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 70,
    ]);
    $campaign->refresh();

    $pool = $campaign->getServersPool();
    expect($pool->count())->toBe(1);
    $elements = $pool->getElements();
    expect(reset($elements)->id)->toBe($fx['servers'][0]->id);
});

test('Nhánh B partial — pivot with mixed active/inactive yields only active', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    // Both selected, but server 1 marked inactive
    $fx['servers'][1]->status = SendingServer::STATUS_INACTIVE;
    $fx['servers'][1]->save();

    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][0]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 50,
    ]);
    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][1]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 50,
    ]);
    $campaign->refresh();

    $pool = $campaign->getServersPool();
    expect($pool->count())->toBe(1);
});

test('Nhánh C — pivot non-empty with ALL inactive throws (fail loud, not silent fallback)', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    foreach ($fx['servers'] as $s) {
        $s->status = SendingServer::STATUS_INACTIVE;
        $s->save();
    }

    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][0]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 100,
    ]);
    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][1]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 100,
    ]);
    $campaign->refresh();

    expect(fn () => $campaign->getServersPool())->toThrow(\Exception::class, 'all per-campaign sending servers are inactive');
});

test('FK cascade — deleting sending_server clears pivot, drops into nhánh A', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    CampaignSendingServer::forceCreate([
        'campaign_id' => $campaign->id,
        'sending_server_id' => $fx['servers'][0]->id,
        'customer_id' => $fx['customer']->id,
        'fitness' => 100,
    ]);

    // Delete the picked server — FK cascade drops the pivot row
    $fx['servers'][0]->delete();

    $campaign->refresh();
    expect($campaign->sendingServersPivot)->toHaveCount(0);

    // Now nhánh A: falls back to MailList pool (still has server[1])
    $pool = $campaign->getServersPool();
    expect($pool)->toBeInstanceOf(RouletteWheel::class);
    expect($pool->count())->toBe(1);
});

test('availableSendingServerIds returns the eligible pool from defaultMailList', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    $ids = $campaign->availableSendingServerIds();
    expect($ids)->toHaveCount(2);
    expect($ids)->toContain($fx['servers'][0]->id);
    expect($ids)->toContain($fx['servers'][1]->id);
});

test('syncSendingServers replaces pivot rows on each call', function () {
    $fx = poolFixture(serverCount: 2);
    $campaign = $fx['campaign'];

    $campaign->syncSendingServers([
        ['sending_server_id' => $fx['servers'][0]->id, 'fitness' => 30],
        ['sending_server_id' => $fx['servers'][1]->id, 'fitness' => 70],
    ]);
    expect($campaign->sendingServersPivot()->count())->toBe(2);

    // Re-sync with just one entry → previous rows wiped
    $campaign->syncSendingServers([
        ['sending_server_id' => $fx['servers'][0]->id, 'fitness' => 100],
    ]);
    expect($campaign->sendingServersPivot()->count())->toBe(1);

    // Empty array clears all → drops into nhánh A
    $campaign->syncSendingServers([]);
    expect($campaign->sendingServersPivot()->count())->toBe(0);
});