utilikit-1.0.0/tests/src/Functional/UtilikitAjaxTest.php

tests/src/Functional/UtilikitAjaxTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\utilikit\Functional;

use Drupal\utilikit\Service\UtilikitConstants;

/**
 * Tests the UtiliKit AJAX controller functionality.
 *
 * @group utilikit
 */
class UtilikitAjaxTest extends UtilikitFunctionalTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'utilikit',
    'node',
    'field',
    'text',
    'filter',
    'user',
  ];

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * Test user with permissions.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $testUser;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Create article content type.
    $this->drupalCreateContentType(['type' => 'article']);

    // Create test user with permissions.
    $this->testUser = $this->drupalCreateUser([
      'access content',
      'create article content',
      'use utilikit update button',
    ]);

    $this->drupalLogin($this->testUser);
  }

  /**
   * Tests successful CSS update via AJAX in static mode.
   */
  public function testAjaxCssUpdateSuccess() {
    // Enable static mode.
    $this->config('utilikit.settings')
      ->set('rendering_mode', 'static')
      ->save();

    // Prepare test data.
    $classes = ['uk-pd--20', 'uk-mg--t-10', 'uk-bg--ff0000'];
    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');

    // Make AJAX request.
    $response = $this->drupalPostJson('/utilikit/update-css', [
      'classes' => $classes,
      'mode' => 'static',
    ], [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ]);

    $this->assertSession()->statusCodeEquals(200);

    $data = json_decode($response, TRUE);
    $this->assertEquals('success', $data['status']);
    $this->assertStringContainsString('CSS updated successfully', $data['message']);
    $this->assertArrayHasKey('count', $data);
    $this->assertArrayHasKey('total', $data);
    $this->assertArrayHasKey('css', $data);
    $this->assertArrayHasKey('timestamp', $data);

    // Verify CSS contains the classes.
    $this->assertStringContainsString('.uk-pd--20', $data['css']);
    $this->assertStringContainsString('.uk-mg--t-10', $data['css']);
    $this->assertStringContainsString('.uk-bg--ff0000', $data['css']);
  }

  /**
   * Tests AJAX update in inline mode.
   */
  public function testAjaxInlineMode() {
    // Enable inline mode.
    $this->config('utilikit.settings')
      ->set('rendering_mode', 'inline')
      ->save();

    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');

    $response = $this->drupalPostJson('/utilikit/update-css', [
      'classes' => ['uk-pd--20'],
      'mode' => 'inline',
    ], [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ]);

    $this->assertSession()->statusCodeEquals(200);

    $data = json_decode($response, TRUE);
    $this->assertEquals('success', $data['status']);
    $this->assertStringContainsString('Inline mode active', $data['message']);
    $this->assertEquals('inline', $data['mode']);
  }

  /**
   * Tests rate limiting.
   */
  public function testRateLimit() {
    $this->config('utilikit.settings')
      ->set('rendering_mode', 'static')
      ->save();

    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');
    $headers = [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ];

    // Make requests up to the rate limit.
    for ($i = 0; $i < UtilikitConstants::RATE_LIMIT_REQUESTS_PER_MINUTE; $i++) {
      $response = $this->drupalPostJson('/utilikit/update-css', [
        'classes' => ['uk-pd--' . $i],
        'mode' => 'static',
      ], $headers);

      $this->assertSession()->statusCodeEquals(200);
    }

    // Next request should be rate limited.
    $response = $this->drupalPostJson('/utilikit/update-css', [
      'classes' => ['uk-pd--999'],
      'mode' => 'static',
    ], $headers);

    $this->assertSession()->statusCodeEquals(429);

    $data = json_decode($response, TRUE);
    $this->assertEquals('error', $data['status']);
    $this->assertStringContainsString('Rate limit exceeded', $data['message']);
  }

  /**
   * Tests access control.
   */
  public function testAccessControl() {
    // Test without XMLHttpRequest header.
    $this->drupalPost('/utilikit/update-css', [
      'classes' => ['uk-pd--20'],
    ]);
    $this->assertSession()->statusCodeEquals(403);

    // Test with anonymous user.
    $this->drupalLogout();
    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');
    $this->drupalPostJson('/utilikit/update-css', [
      'classes' => ['uk-pd--20'],
    ], [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ]);
    // Should work for anonymous users with 'access content'.
    $this->assertSession()->statusCodeEquals(200);
  }

  /**
   * Tests invalid request data.
   */
  public function testInvalidRequestData() {
    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');
    $headers = [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ];

    // Test with invalid JSON - using Guzzle client directly.
    $client = \Drupal::httpClient();
    $response = $client->request('POST', $this->buildUrl('/utilikit/update-css'), [
      'headers' => array_merge($headers, ['Content-Type' => 'application/json']),
      'body' => 'invalid json',
      'http_errors' => FALSE,
    ]);

    $this->assertEquals(400, $response->getStatusCode());
    $data = json_decode($response->getBody()->getContents(), TRUE);
    $this->assertEquals('error', $data['status']);
    $this->assertStringContainsString('Invalid JSON data', $data['message']);

    // Test with missing classes array.
    $response = $this->drupalPostJson('/utilikit/update-css', [
      'mode' => 'static',
    ], $headers);

    $this->assertSession()->statusCodeEquals(400);
    $data = json_decode($response, TRUE);
    $this->assertEquals('error', $data['status']);
    $this->assertStringContainsString('Missing or invalid classes array', $data['message']);

    // Test with too many classes.
    $tooManyClasses = [];
    for ($i = 0; $i <= UtilikitConstants::MAX_CLASSES_PER_REQUEST; $i++) {
      $tooManyClasses[] = 'uk-pd--' . $i;
    }

    $response = $this->drupalPostJson('/utilikit/update-css', [
      'classes' => $tooManyClasses,
      'mode' => 'static',
    ], $headers);

    $this->assertSession()->statusCodeEquals(400);
    $data = json_decode($response, TRUE);
    $this->assertEquals('error', $data['status']);
    $this->assertStringContainsString('Too many classes', $data['message']);
  }

  /**
   * Tests lock handling and queueing.
   */
  public function testLockHandling() {
    $this->config('utilikit.settings')
      ->set('rendering_mode', 'static')
      ->save();

    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');
    $headers = [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ];

    // Acquire lock manually.
    $lock = \Drupal::lock();
    $lock->acquire(UtilikitConstants::LOCK_CSS_UPDATE);

    try {
      // Make request while locked.
      $response = $this->drupalPostJson('/utilikit/update-css', [
        'classes' => ['uk-pd--20'],
        'mode' => 'static',
      ], $headers);

      $this->assertSession()->statusCodeEquals(200);

      $data = json_decode($response, TRUE);
      $this->assertEquals('locked', $data['status']);
      $this->assertStringContainsString('Another update is in progress', $data['message']);
      $this->assertArrayHasKey('retry_after', $data);

      // Verify item was queued.
      $queue = \Drupal::queue(UtilikitConstants::QUEUE_CSS_PROCESSOR);
      $this->assertGreaterThan(0, $queue->numberOfItems());

    } finally {
      $lock->release(UtilikitConstants::LOCK_CSS_UPDATE);
    }
  }

  /**
   * Tests class validation.
   */
  public function testClassValidation() {
    $this->config('utilikit.settings')
      ->set('rendering_mode', 'static')
      ->save();

    $csrfToken = \Drupal::csrfToken()->get('utilikit-update-css');

    // Mix of valid and invalid classes.
    $response = $this->drupalPostJson('/utilikit/update-css', [
      'classes' => [
    // Valid.
        'uk-pd--20',
    // Invalid - no uk- prefix.
        'invalid-class',
    // Invalid - unknown prefix.
        'uk-xx--20',
    // Valid.
        'uk-mg--t-auto',
    // Invalid - incomplete.
        'uk-',
      ],
      'mode' => 'static',
    ], [
      'X-CSRF-Token' => $csrfToken,
      'X-Requested-With' => 'XMLHttpRequest',
    ]);

    $this->assertSession()->statusCodeEquals(200);

    $data = json_decode($response, TRUE);
    $this->assertEquals('success', $data['status']);

    // Should only process valid classes.
    $this->assertStringContainsString('.uk-pd--20', $data['css']);
    $this->assertStringContainsString('.uk-mg--t-auto', $data['css']);
    $this->assertStringNotContainsString('invalid-class', $data['css']);
    $this->assertStringNotContainsString('.uk-xx--20', $data['css']);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc