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/SubscriberServiceTest.php
<?php

use App\Events\MailListSubscription;
use App\Model\Contact;
use App\Model\Customer;
use App\Model\Form;
use App\Model\MailList;
use App\Model\MailListTemplate;
use App\Model\Subscriber;
use App\Model\Template;
use App\Model\Timeline;
use App\Services\SubscriberService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

uses(TestCase::class);

beforeEach(function () {
    Config::set('sharding.enabled', false);
});

afterEach(function () {
    $formIds = DB::table('forms')
        ->where('uid', 'like', 'subsvc_%')
        ->pluck('id');

    $mailListIds = DB::table('mail_lists')
        ->where('uid', 'like', 'subsvc_%')
        ->pluck('id');

    $subscriberIds = DB::table('subscribers')
        ->whereIn('mail_list_id', $mailListIds)
        ->pluck('id');

    $templateIds = DB::table('templates')
        ->where('uid', 'like', 'subsvc_%')
        ->pluck('id')
        ->merge(DB::table('mail_list_templates')->whereIn('mail_list_id', $mailListIds)->pluck('template_id'))
        ->merge(DB::table('forms')->whereIn('id', $formIds)->pluck('template_id'))
        ->filter()
        ->unique()
        ->values();

    DB::table('timelines')->whereIn('subscriber_id', $subscriberIds)->delete();
    DB::table('forms')->whereIn('id', $formIds)->delete();
    DB::table('mail_list_templates')->whereIn('mail_list_id', $mailListIds)->delete();
    DB::table('subscribers')->whereIn('id', $subscriberIds)->delete();
    DB::table('fields')->whereIn('mail_list_id', $mailListIds)->delete();
    DB::table('mail_lists')->whereIn('id', $mailListIds)->delete();
    DB::table('templates')->whereIn('id', $templateIds)->delete();

    $customerIds = DB::table('customers')
        ->where('uid', 'like', 'subsvc_%')
        ->pluck('id');

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

function subscriberServiceFixture(array $listOverrides = []): array
{
    $contact = Contact::forceCreate([
        'uid' => uniqid('subsvc_contact_'),
        'first_name' => 'Subscriber',
        'last_name' => 'Service',
        'email' => uniqid('subsvc_') . '@example.test',
    ]);

    $customer = Customer::forceCreate([
        'uid' => uniqid('subsvc_customer_'),
        'contact_id' => $contact->id,
        'status' => 'active',
        'timezone' => 'UTC',
    ]);

    $list = MailList::forceCreate(array_merge([
        'uid' => uniqid('subsvc_list_'),
        'name' => 'Subscriber Service List',
        'customer_id' => $customer->id,
        'from_email' => '[email protected]',
        'from_name' => 'Sender',
        'subscribe_confirmation' => false,
    ], $listOverrides));

    $thankYouTemplate = Template::forceCreate([
        'uid' => uniqid('subsvc_tpl_'),
        'name' => 'Subscriber Service Thank You',
        'content' => '<html><body><p>Thanks for confirming.</p></body></html>',
        'json' => \App\Model\Template::DEFAULT_BUILDER_JSON,
    ]);

    MailListTemplate::forceCreate([
        'uid' => uniqid('subsvc_mltpl_'),
        'mail_list_id' => $list->id,
        'customer_id' => $customer->id,
        'template_id' => $thankYouTemplate->id,
        'alias' => 'sign_up_thankyou_page',
    ]);

    $popupTemplate = Template::forceCreate([
        'uid' => uniqid('subsvc_tpl_'),
        'name' => 'Subscriber Service Popup',
        'content' => '<div builder-wrapper><input type="email" name="EMAIL" /><button type="submit">Join</button></div><message-section style="display:none"><p>Done</p></message-section>',
        'json' => \App\Model\Template::DEFAULT_BUILDER_JSON,
    ]);

    $form = Form::forceCreate([
        'uid' => uniqid('subsvc_form_'),
        'name' => 'Subscriber Service Popup Form',
        'status' => Form::STATUS_PUBLISHED,
        'customer_id' => $customer->id,
        'mail_list_id' => $list->id,
        'template_id' => $popupTemplate->id,
        'metadata' => json_encode([
            'display' => 'immediately',
            'overlay_opacity' => 20,
        ]),
    ]);

    return [
        'contact' => $contact,
        'customer' => $customer,
        'list' => $list->fresh(),
        'form' => $form->fresh(),
        'emailField' => $list->getFieldByTag('EMAIL'),
        'firstNameField' => $list->getFieldByTag('FIRST_NAME'),
    ];
}

function timelineCount(int $subscriberId, string $type): int
{
    return DB::table('timelines')
        ->where('subscriber_id', $subscriberId)
        ->where('type', $type)
        ->count();
}

it('adds a subscriber by customer and records the customer timeline', function () {
    $fixture = subscriberServiceFixture();

    $request = Request::create('/lists/test/subscribers/store', 'POST', [
        'EMAIL' => uniqid('subsvc_added_') . '@example.test',
        'FIRST_NAME' => 'Customer Added',
    ]);

    [$validator, $subscriber] = app(SubscriberService::class)->addByCustomer($fixture['list'], $request, $fixture['customer']);

    expect($validator)->not->toBeNull();
    expect($subscriber)->not->toBeNull();
    expect($subscriber->status)->toBe(Subscriber::STATUS_SUBSCRIBED);
    expect($subscriber->fresh()->getValueByField($fixture['firstNameField']))->toBe('Customer Added');
    expect(timelineCount($subscriber->id, Timeline::TYPE_ADDED_BY_CUSTOMER))->toBe(1);

    $timeline = DB::table('timelines')
        ->where('subscriber_id', $subscriber->id)
        ->where('type', Timeline::TYPE_ADDED_BY_CUSTOMER)
        ->first();

    expect((int) $timeline->customer_id)->toBe($fixture['customer']->id);
});

it('returns validation errors instead of adding duplicate subscribers by customer', function () {
    $fixture = subscriberServiceFixture();
    $email = uniqid('subsvc_added_') . '@example.test';

    Subscriber::forceCreate([
        'uid' => uniqid('subsvc_sub_'),
        'email' => $email,
        'status' => Subscriber::STATUS_SUBSCRIBED,
        'mail_list_id' => $fixture['list']->id,
    ]);

    $request = Request::create('/lists/test/subscribers/store', 'POST', [
        'EMAIL' => $email,
    ]);

    [$validator, $subscriber] = app(SubscriberService::class)->addByCustomer($fixture['list'], $request, $fixture['customer']);

    expect($validator->fails())->toBeTrue();
    expect($subscriber)->toBeNull();
    // Scope timeline check to THIS customer + this email's subscriber.
    // Global count is polluted by other tests + by Stage22 demo seeder (which creates ADDED_BY_CUSTOMER rows).
    $existingSubscriber = Subscriber::where('email', $email)
        ->where('mail_list_id', $fixture['list']->id)
        ->first();
    expect(DB::table('timelines')
        ->where('type', Timeline::TYPE_ADDED_BY_CUSTOMER)
        ->where('customer_id', $fixture['customer']->id)
        ->where('subscriber_id', $existingSubscriber->id)
        ->count())->toBe(0);
});

it('records sign up form opt in without wiping omitted subscriber fields', function () {
    $fixture = subscriberServiceFixture();
    $subscriber = Subscriber::forceCreate([
        'uid' => uniqid('subsvc_sub_'),
        'email' => uniqid('subsvc_web_') . '@example.test',
        'status' => Subscriber::STATUS_UNSUBSCRIBED,
        'mail_list_id' => $fixture['list']->id,
    ]);

    $subscriber->setFieldValue($fixture['firstNameField'], 'Existing Name');
    $subscriber->save();

    $request = Request::create('/frontend/lists/test/sign-up', 'POST', [
        'EMAIL' => $subscriber->email,
    ]);

    [$validator, $updatedSubscriber] = app(SubscriberService::class)->optInFromWeb($fixture['list'], $request);

    expect($validator->fails())->toBeFalse();
    expect($updatedSubscriber->id)->toBe($subscriber->id);
    expect($updatedSubscriber->fresh()->status)->toBe(Subscriber::STATUS_SUBSCRIBED);
    expect($updatedSubscriber->fresh()->getValueByField($fixture['firstNameField']))->toBe('Existing Name');
    expect(timelineCount($updatedSubscriber->id, Timeline::TYPE_SIGN_UP_FORM_OPT_IN))->toBe(1);
});

it('returns validation errors for invalid web opt in requests', function () {
    $fixture = subscriberServiceFixture();
    $timelineCountBefore = DB::table('timelines')
        ->where('type', Timeline::TYPE_SIGN_UP_FORM_OPT_IN)
        ->count();

    $request = Request::create('/frontend/lists/test/sign-up', 'POST', []);

    [$validator, $subscriber] = app(SubscriberService::class)->optInFromWeb($fixture['list'], $request);

    expect($validator->fails())->toBeTrue();
    expect($subscriber)->toBeNull();
    expect(DB::table('timelines')->where('type', Timeline::TYPE_SIGN_UP_FORM_OPT_IN)->count())->toBe($timelineCountBefore);
});

it('records embedded form opt in timelines', function () {
    $fixture = subscriberServiceFixture();

    $request = Request::create('/lists/test/embedded-form-subscribe', 'POST', [
        'EMAIL' => uniqid('subsvc_embed_') . '@example.test',
    ]);

    [$validator, $subscriber] = app(SubscriberService::class)->optInFromEmbeddedForm($fixture['list'], $request);

    expect($validator->fails())->toBeFalse();
    expect($subscriber)->not->toBeNull();
    expect($subscriber->status)->toBe(Subscriber::STATUS_SUBSCRIBED);
    expect(timelineCount($subscriber->id, Timeline::TYPE_EMBEDDED_FORM_OPT_IN))->toBe(1);
});

it('returns validation errors for invalid embedded form requests', function () {
    $fixture = subscriberServiceFixture();

    $request = Request::create('/lists/test/embedded-form-subscribe', 'POST', []);

    [$validator, $subscriber] = app(SubscriberService::class)->optInFromEmbeddedForm($fixture['list'], $request);

    expect($validator->fails())->toBeTrue();
    expect($subscriber)->toBeNull();
    expect(DB::table('timelines')->where('type', Timeline::TYPE_EMBEDDED_FORM_OPT_IN)->count())->toBe(0);
});

it('records popup form opt in timelines with the originating form id', function () {
    $fixture = subscriberServiceFixture();

    $request = Request::create('/frontend/forms/test/save', 'POST', [
        'EMAIL' => uniqid('subsvc_popup_') . '@example.test',
    ]);

    [$validator, $subscriber] = app(SubscriberService::class)->optInFromPopupForm($fixture['form'], $request);

    expect($validator->fails())->toBeFalse();
    expect($subscriber)->not->toBeNull();

    $timeline = DB::table('timelines')
        ->where('subscriber_id', $subscriber->id)
        ->where('type', Timeline::TYPE_POPUP_FORM_OPT_IN)
        ->first();

    expect($timeline)->not->toBeNull();
    expect((int) $timeline->form_id)->toBe($fixture['form']->id);
});

it('returns validation errors for invalid popup form requests', function () {
    $fixture = subscriberServiceFixture();
    $timelineCountBefore = DB::table('timelines')
        ->where('type', Timeline::TYPE_POPUP_FORM_OPT_IN)
        ->count();

    $request = Request::create('/frontend/forms/test/save', 'POST', []);

    [$validator, $subscriber] = app(SubscriberService::class)->optInFromPopupForm($fixture['form'], $request);

    expect($validator->fails())->toBeTrue();
    expect($subscriber)->toBeNull();
    expect(DB::table('timelines')->where('type', Timeline::TYPE_POPUP_FORM_OPT_IN)->count())->toBe($timelineCountBefore);
});

it('generates signed confirmation urls in absolute and relative forms', function () {
    $fixture = subscriberServiceFixture();
    $subscriber = Subscriber::forceCreate([
        'uid' => uniqid('subsvc_sub_'),
        'email' => uniqid('subsvc_confirm_') . '@example.test',
        'status' => Subscriber::STATUS_UNCONFIRMED,
        'mail_list_id' => $fixture['list']->id,
    ]);

    $service = app(SubscriberService::class);
    $absoluteUrl = $service->generateSignedConfirmationUrl($subscriber);
    $relativeUrl = $service->generateSignedConfirmationUrl($subscriber, false);

    expect($absoluteUrl)->toContain('signature=');
    expect($absoluteUrl)->toContain('/frontend/lists/' . $fixture['list']->uid . '/subscribe-confirm/' . $subscriber->id . '/');
    expect($relativeUrl)->toStartWith('/frontend/lists/' . $fixture['list']->uid . '/subscribe-confirm/' . $subscriber->id . '/');
    expect($relativeUrl)->toContain('signature=');
});

it('confirms unconfirmed subscribers once and dispatches the subscription event once', function () {
    Event::fake([MailListSubscription::class]);

    $fixture = subscriberServiceFixture();
    $subscriber = Subscriber::forceCreate([
        'uid' => uniqid('subsvc_sub_'),
        'email' => uniqid('subsvc_confirm_') . '@example.test',
        'status' => Subscriber::STATUS_UNCONFIRMED,
        'mail_list_id' => $fixture['list']->id,
    ]);

    $service = app(SubscriberService::class);

    expect($service->confirmOptIn($subscriber))->toBeTrue();
    expect($subscriber->fresh()->status)->toBe(Subscriber::STATUS_SUBSCRIBED);
    Event::assertDispatchedTimes(MailListSubscription::class, 1);

    expect($service->confirmOptIn($subscriber->fresh()))->toBeFalse();
    Event::assertDispatchedTimes(MailListSubscription::class, 1);
});

it('throws when trying to confirm subscribers outside the unconfirmed or subscribed states', function () {
    $fixture = subscriberServiceFixture();
    $subscriber = Subscriber::forceCreate([
        'uid' => uniqid('subsvc_sub_'),
        'email' => uniqid('subsvc_invalid_') . '@example.test',
        'status' => Subscriber::STATUS_UNSUBSCRIBED,
        'mail_list_id' => $fixture['list']->id,
    ]);

    expect(fn () => app(SubscriberService::class)->confirmOptIn($subscriber))
        ->toThrow(RuntimeException::class, 'Subscriber cannot be confirmed from the current status');
});