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

namespace Tests\Unit;

use Tests\TestCase;
use App\Services\PersonalizationTagCatalog;
use App\Model\Campaign;
use App\Model\MailList;
use App\Library\GeneralTagResolver;
use Illuminate\Support\Collection;

class PersonalizationTagCatalogTest extends TestCase
{
    private function catalog(): PersonalizationTagCatalog
    {
        return app(PersonalizationTagCatalog::class);
    }

    public function test_campaign_tags_without_list_returns_campaign_and_general_only()
    {
        $items = $this->catalog()->campaignTags();

        $keys = array_column($items, 'key');

        foreach (['campaign.name', 'campaign.uid', 'campaign.from_email', 'campaign.from_name', 'campaign.subject', 'campaign.reply_to'] as $required) {
            $this->assertContains($required, $keys, "Missing campaign key: {$required}");
        }

        foreach (['current_year', 'current_date', 'current_datetime'] as $required) {
            $this->assertContains($required, $keys, "Missing general key: {$required}");
        }

        $this->assertNotContains('subscriber.email', $keys, 'Without list, subscriber.* must not appear');
        $this->assertNotContains('list.name', $keys, 'Without list, list.* must not appear');
        $this->assertNotContains('unsubscribe_url', $keys, 'Without list, advanced URLs must not appear');
    }

    public function test_items_have_required_shape()
    {
        $items = $this->catalog()->campaignTags();

        $this->assertNotEmpty($items);

        foreach ($items as $item) {
            $this->assertArrayHasKey('key', $item);
            $this->assertArrayHasKey('label', $item);
            $this->assertArrayHasKey('description', $item);
            $this->assertArrayHasKey('snippet', $item);
            $this->assertArrayHasKey('group', $item);

            $this->assertSame('{{' . $item['key'] . '}}', $item['snippet']);
            $this->assertNotSame('', $item['label']);
        }
    }

    public function test_with_list_adds_list_subscriber_and_advanced_scopes()
    {
        $list = $this->buildFakeList(customFields: []);

        $items = $this->catalog()->campaignTags($list);
        $keys = array_column($items, 'key');

        foreach (['list.name', 'list.uid', 'list.from_email', 'list.from_name'] as $required) {
            $this->assertContains($required, $keys, "Missing list key: {$required}");
        }

        foreach (['subscriber.uid', 'subscriber.email', 'subscriber.name', 'subscriber.full_name', 'subscriber.fullname'] as $required) {
            $this->assertContains($required, $keys, "Missing subscriber key: {$required}");
        }

        foreach (['email', 'name', 'full_name', 'first_name', 'last_name'] as $required) {
            $this->assertContains($required, $keys, "Missing global alias: {$required}");
        }

        foreach (['unsubscribe_url', 'update_profile_url', 'web_view_url'] as $required) {
            $this->assertContains($required, $keys, "Missing advanced URL: {$required}");
        }
    }

    public function test_custom_fields_are_included_except_core_tags()
    {
        $list = $this->buildFakeList(customFields: [
            ['tag' => 'FIRST_NAME', 'label' => 'First Name'], // core, should be skipped
            ['tag' => 'EMAIL',      'label' => 'Email'],      // core, should be skipped
            ['tag' => 'COMPANY',    'label' => 'Company'],
            ['tag' => 'BIRTHDAY',   'label' => 'Birthday'],
        ]);

        $items = $this->catalog()->campaignTags($list);
        $keys = array_column($items, 'key');

        $this->assertContains('subscriber.company', $keys);
        $this->assertContains('subscriber.birthday', $keys);

        $firstNameOccurrences = count(array_filter($keys, static fn ($k) => $k === 'subscriber.first_name'));
        $this->assertSame(1, $firstNameOccurrences, 'Core tag FIRST_NAME must not be duplicated from custom fields');
    }

    public function test_snippets_use_double_curly_braces_without_spaces()
    {
        $items = $this->catalog()->campaignTags($this->buildFakeList());

        foreach ($items as $item) {
            $this->assertStringStartsWith('{{', $item['snippet']);
            $this->assertStringEndsWith('}}', $item['snippet']);
        }
    }

    public function test_general_keys_match_general_tag_resolver()
    {
        $items = $this->catalog()->campaignTags();
        $catalogKeys = array_column($items, 'key');

        $resolverKeys = array_keys((new GeneralTagResolver())->getResolvedTags());

        foreach ($resolverKeys as $k) {
            $this->assertContains($k, $catalogKeys, "GeneralTagResolver exposes '{$k}' but catalog does not");
        }
    }

    public function test_campaign_keys_match_campaign_model_getresolvedtags()
    {
        $campaign = new Campaign([
            'name'       => 'Demo',
            'from_email' => '[email protected]',
            'from_name'  => 'Demo Sender',
            'subject'    => 'Hello',
            'reply_to'   => '[email protected]',
        ]);
        $campaign->uid = 'CUID';

        $resolverKeys = $this->flattenLeafKeys($campaign->getResolvedTags());

        $catalogKeys = array_column($this->catalog()->campaignTags(), 'key');

        foreach ($resolverKeys as $k) {
            $this->assertContains($k, $catalogKeys, "Campaign::getResolvedTags exposes '{$k}' but catalog does not");
        }
    }

    public function test_list_keys_match_maillist_getresolvedtags()
    {
        $list = new MailList([
            'name'       => 'Weekly',
            'from_email' => '[email protected]',
            'from_name'  => 'List Sender',
        ]);
        $list->uid = 'LUID';
        // Avoid DB hit on fields relation
        $list->setRelation('fields', new Collection());

        $resolverKeys = $this->flattenLeafKeys($list->getResolvedTags());

        $catalogKeys = array_column($this->catalog()->campaignTags($list), 'key');

        foreach ($resolverKeys as $k) {
            $this->assertContains($k, $catalogKeys, "MailList::getResolvedTags exposes '{$k}' but catalog does not");
        }
    }

    /**
     * Build an in-memory MailList without touching the DB.
     */
    private function buildFakeList(array $customFields = []): MailList
    {
        $list = new MailList([
            'name'       => 'Fake List',
            'from_email' => '[email protected]',
            'from_name'  => 'Fake Sender',
        ]);
        $list->uid = 'LIST-FAKE';

        $fields = collect($customFields)->map(function ($data) {
            $field = new \App\Model\Field([
                'tag'   => $data['tag']   ?? '',
                'label' => $data['label'] ?? '',
                'type'  => $data['type']  ?? 'text',
            ]);
            return $field;
        });

        $list->setRelation('fields', $fields);

        return $list;
    }

    /**
     * Flatten nested resolver output into dot-keys.
     *
     * @param  array<string, mixed>  $tags
     * @return array<int, string>
     */
    private function flattenLeafKeys(array $tags, string $prefix = ''): array
    {
        $out = [];
        foreach ($tags as $k => $v) {
            $full = $prefix !== '' ? $prefix . '.' . $k : (string) $k;
            if (is_array($v)) {
                $out = array_merge($out, $this->flattenLeafKeys($v, $full));
            } else {
                $out[] = $full;
            }
        }
        return $out;
    }
}