lupus_decoupled-1.x-dev/tests/src/Functional/LupusDecoupledApiResponseTest.php
tests/src/Functional/LupusDecoupledApiResponseTest.php
<?php
namespace Drupal\Tests\lupus_decoupled\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\lupus_decoupled_ce_api\BaseUrlProviderTrait;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\metatag\Entity\MetatagDefaults;
use Drupal\node\Entity\Node;
/**
* Test Lupus Decoupled features.
*
* @group lupus_decoupled
*/
class LupusDecoupledApiResponseTest extends BrowserTestBase {
use BaseUrlProviderTrait;
/**
* The node to use for testing.
*
* @var \Drupal\node\NodeInterface
*/
protected $node;
/**
* API path to created node.
*
* @var string
*/
protected $nodePath;
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'content_translation',
// For admin/content that lists all languages for the node, we need Views:
'views',
'custom_elements',
'lupus_ce_renderer',
'lupus_decoupled',
'lupus_decoupled_ce_api',
'lupus_decoupled_schema_metatag',
'menu_link_content',
'rest_menu_items',
'schema_web_page',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create Basic page node type.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->node = $this->drupalCreateNode(['title' => 'Test node']);
$this->nodePath = 'ce-api/node/' . $this->node->id();
// Make sure $this->getBaseUrlProvider()->getFrontendBaseUrl() always
// returns a value. (It's not always the value set here, though; this is
// just the default.)
$configFactory = \Drupal::configFactory();
$configFactory->getEditable('lupus_decoupled_ce_api.settings')
->set('frontend_base_url', 'https://frontend.example.com')
->save();
$configFactory->getEditable('rest_menu_items.config')
->set('allowed_menus', ['main' => 'main'])
->save();
}
/**
* Tests if 'API output' links in HTML page have correct URL in content admin.
*/
public function testApiOutputLink() {
// Prerequisite: test if admin/API base URLs aren't rewritten.
$drupalBaseUrl = getenv('SIMPLETEST_BASE_URL');
$this->assertSame($drupalBaseUrl, $this->getBaseUrlProvider()
->getAdminBaseUrl(), 'Admin base URL must be equal to Drupal URL.');
$this->assertStringStartsWith($drupalBaseUrl, $this->getBaseUrlProvider()
->getApiBaseUrl(), 'API base URL must contain Drupal URL.');
// Preparation: add German language, then reload node so it re-caches
// available languages, and translate it.
ConfigurableLanguage::createFromLangcode('de')->save();
$this->node = Node::load($this->node->id());
$translated_node = $this->node->addTranslation('de', ['title' => 'DE Test node'] + $this->node->toArray());
$translated_node->save();
$user = $this->drupalCreateUser([
'access content',
'access content overview',
'use api operation link',
]);
$this->drupalLogin($user);
$this->doTestApiOutputLinks('admin/content');
// Test that 'current UI language' context has no effect on links.
$this->doTestApiOutputLinks('de/admin/content');
}
/**
* Tests URLs for API output links (and regular node links) in content admin.
*/
protected function doTestApiOutputLinks(string $path): void {
$this->drupalGet($path);
$domPage = $this->getSession()->getPage();
$frontendBaseUrl = $this->getBaseUrlProvider()->getFrontendBaseUrl();
$domTextNode = $domPage->find('xpath', '//a[text() = "Test node"]');
$this->assertNotEmpty($domTextNode, "Element containing 'Test node' must be visible on $path.");
$linkHtml = $domTextNode->getParent()->getHtml();
$this->assertStringStartsWith("<a href=\"$frontendBaseUrl/", $linkHtml, "'Test node' link on $path must contain the frontend base URL.");
$domTextNode = $domPage->find('xpath', '//a[text() = "DE Test node"]');
$this->assertNotEmpty($domTextNode, "Element containing 'DE Test node' must be visible on $path.");
$linkHtml = $domTextNode->getParent()->getHtml();
$this->assertStringStartsWith("<a href=\"$frontendBaseUrl/de/", $linkHtml, "'DE Test node' link on $path must contain the frontend base URL + langcode.");
$apiBaseUrl = $this->getBaseUrlProvider()->getApiBaseUrl();
$domTextNode = $domPage->find('xpath', '//a[text() = "Test node"]/ancestor::tr')
->find('xpath', '//a[text() = "View API Output"]');
$this->assertNotEmpty($domTextNode, "Element containing 'Test node' -> 'View API Output' must be visible on $path.");
$linkHtml = $domTextNode->getParent()->getHtml();
$this->assertStringStartsWith("<a href=\"$apiBaseUrl/", $linkHtml, "'Test node' -> 'View API Output' link on $path must contain the API base URL.");
$domTextNode = $domPage->find('xpath', '//a[text() = "DE Test node"]/ancestor::tr')
->find('xpath', '//a[text() = "View API Output"]');
$this->assertNotEmpty($domTextNode, "Element containing 'DE Test node' -> 'View API Output' must be visible on $path.");
$linkHtml = $domTextNode->getParent()->getHtml();
$this->assertStringStartsWith("<a href=\"$apiBaseUrl/de/", $linkHtml, "'DE Test node' -> 'View API Output' link on $path must contain the API base URL + langcode.");
}
/**
* Tests if created node's path redirects to frontend.
*/
public function testExistingPageResponse() {
// DriverInterface::getClient() isn't defined but there's no better way
// as of D10.2. (Core tests also do this.)
/* @phpstan-ignore-next-line */
$this->getSession()->getDriver()->getClient()->followRedirects(FALSE);
$this->maximumMetaRefreshCount = 0;
$this->drupalGet('node/' . $this->node->id());
$assert = $this->assertSession();
$assert->statusCodeEquals(302);
$frontendBaseUrl = $this->getBaseUrlProvider()->getFrontendBaseUrl();
$assert->responseHeaderEquals('Location', "$frontendBaseUrl/node/1");
}
/**
* Tests un-existing page access.
*/
public function test404Page() {
$this->drupalGet('i-dont-exist');
$this->assertSession()->statusCodeEquals(404);
}
/**
* Tests if created node is accessible at api endpoint.
*/
public function testExistingPageApiResponse() {
$this->drupalGet($this->nodePath);
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests if 'alternate' link has proper format.
*/
public function testAlternateLink() {
// As long as this is the only test that does translation: add language
// translation here instead of in setUp(). If the number of tests grows:
// reconsider splitting out into translation specific test file.
ConfigurableLanguage::createFromLangcode('de')->save();
// Node needs reload before translation can be added.
$this->node = Node::load($this->node->id());
$this->node->addTranslation('de', ['title' => 'DE translation ' . $this->node->label()] + $this->node->toArray());
$this->node->save();
$response = json_decode($this->drupalGet($this->nodePath), TRUE);
// 'alternate' must be in the 'link' section and point to the front end.
$this->assertStringStartsWith(
rtrim($this->getBaseUrlProvider()->getFrontendBaseUrl(), '/') . '/de/',
$this->findLink($response['metatags']['link'] ?? [], 'alternate', 'de')
);
}
/**
* Finds a specific 'href' value in an array of 'link' metatags.
*
* @param array $link_metatags
* The array of 'link' metatags.
* @param string $rel
* The 'rel' value to search for.
* @param string $hreflang
* The 'hreflang' value to search for.
*
* @return string
* The corresponding 'href' value, or empty string if not found.
*/
private function findLink(array $link_metatags, string $rel, string $hreflang) {
foreach ($link_metatags as $link) {
if (isset($link['href']) && isset($link['rel']) && $link['rel'] === $rel
&& isset($link['hreflang']) && $link['hreflang'] === $hreflang) {
return $link['href'];
}
}
return '';
}
/**
* Tests if created "meta title" is available at api endpoint.
*/
public function testMetaTag() {
$response = json_decode($this->drupalGet($this->nodePath), TRUE);
$metaTitle = $response['metatags']['meta'][0]['content'];
// It contains the site name as well so cannot use assertEquals().
$this->assertStringContainsString($this->node->getTitle(), $metaTitle);
}
/**
* Tests if created "schema description" is present in jsonld section.
*/
public function testSchemaTag() {
$bundle = 'page';
$fieldName = 'field_meta';
// Create storage for metatag field.
$fieldStorage = FieldStorageConfig::create([
'field_name' => $fieldName,
'entity_type' => 'node',
'type' => 'metatag',
]);
$fieldStorage->save();
// Create config for metatag field.
$field = FieldConfig::create([
'field_storage' => $fieldStorage,
'bundle' => $bundle,
]);
$field->save();
// Create a node including schema tag for testing.
$node = $this->drupalCreateNode([
'type' => $bundle,
$fieldName => serialize([
'schema_web_page_description' => 'my schema web page description',
]),
]);
$response = json_decode($this->drupalGet('ce-api/node/' . $node->id()), TRUE);
$description = $response['metatags']['jsonld']['@graph'][0]['description'];
// @todo Getting an error instead of a failure if the key doesn't exist.
$this->assertEquals('my schema web page description', $description);
}
/**
* Tests if breadcrumbs in schema metatags point to the frontend.
*/
public function testSchemaBreadcrumbs() {
MetatagDefaults::create([
'id' => 'node__page',
'tags' => [
'schema_web_page_breadcrumb' => 'Yes',
],
])->save();
$response = json_decode($this->drupalGet($this->nodePath), TRUE);
// The '@graph' element in theory can contain several numbered indices.
// Assume this barebones setup returns one, index 0, containing all tags.
$this->assertTrue(isset($response['metatags']['jsonld']['@graph'][0]['breadcrumb']['itemListElement']), "Breadcrumb must be set in metatags (jsonld > @graph > 0)");
$breadcrumbs = $response['metatags']['jsonld']['@graph'][0]['breadcrumb']['itemListElement'];
// By default, there likely is only one link, containing the home page. If
// the default happens to change, test all.
$frontendBaseUrl = $this->getBaseUrlProvider()->getFrontendBaseUrl();
foreach ($breadcrumbs as $index => $breadcrumb) {
$this->assertStringStartsWith($frontendBaseUrl, $breadcrumb['item'], "Breadcrumb $index must start with the frontend base URL.");
}
}
/**
* Tests links in menu items.
*/
public function testMenu() {
MenuLinkContent::create([
'title' => 'Menu link test title',
'provider' => 'menu_link_content',
'menu_name' => 'main',
'link' => ['uri' => 'internal:/node/1'],
])->save();
$response = json_decode($this->drupalGet('ce-api/api/menu_items/main'), TRUE);
$this->assertSame('Menu link test title', $response[0]['title']);
$this->assertFalse($response[0]['external'], 'Tested menu link must be marked as external:False.');
$this->assertStringStartsWith('/', $response[0]['relative'], "'relative' menu item property must start with a forward slash.");
$frontendBaseUrl = $this->getBaseUrlProvider()->getFrontendBaseUrl();
$this->assertStringStartsWith($frontendBaseUrl, $response[0]['absolute'], "'absolute' menu item property must start with the frontend base URL.");
}
}
