Skip to content

Commit

Permalink
[CMSP-1459] Fixes URL normalization process (#138)
Browse files Browse the repository at this point in the history
* add new helper function that parses and normalizes a url from parts

* Update URL normalization function to refactor using __rebuild_url_from_parts

* check if endpoint is defined already

* dont' use anonymous function

* add host if exists

* replace graphql endpoint filter with an action hook to prepopulate
this is a setting that can be changed, so this way we're setting it to what we think it should be without forcing users to use our path

* move the prepopulate action and make it more robust
use dynamic site_url and make the setting conditional based on whether there's already a /wp/ i the siteurl

* add exclusions to site_url filter for graphql

* add playwright tests

* change ci to install

* don't create the site if it already exists

* add git config for the user

* wait for the last action to finish before switching to sftp mode

* set pretty permalinks for playwright test site

* switch the existing site to git mode

* force yes

* check the h2 instead of the site title
that's a theme thing. the canary sites are running a different theme

* test that the resource urls have /wp/ in the path

* missing "

* expect the resource url to be truthy on a request

* append the commit message from the PR to the commit to the test site

* use github token so we can use gh

* add package.json to export-ignore

* move the commit msg var up
so we haven't cd'd into the local copy when we're looking for the commit message

* we don't need `page` at all

* this doesn't change anything other than seeing if we're getting the PR number

* fix the commit message

* Include the git commit message body
so it's not just truncated if it's long

* don't fail if there's nothing to commit

* add caching for dependencies

* fix hello world test

* emdash problems

* fix the locator on the welcome message test

* generate lock files so we can use the cache

* pass default values
so the substr check isn't null

* change test to toContainText

* only generate the npm lock
use the composer.json instead of composer.lock
use the conditional to check for cache

* party pooper 💩

Co-authored-by: Phil Tyler <[email protected]>

* bail early

---------

Co-authored-by: Phil Tyler <[email protected]>
  • Loading branch information
jazzsequence and pwtyler committed Jul 31, 2024
1 parent 86c04a5 commit e72aea5
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/package.json export-ignore
65 changes: 65 additions & 0 deletions .github/tests/wpcm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { test, expect } from "@playwright/test";

const exampleArticle = "Hello world!";
const siteTitle = "WPCM Playwright Tests";
const siteUrl = process.env.SITE_URL || "https://dev-wpcm-playwright-tests.pantheonsite.io";

test("homepage loads and contains example content", async ({ page }) => {
await page.goto(siteUrl);
await expect(page).toHaveTitle(siteTitle);
await expect(page.getByText(exampleArticle)).toHaveText(exampleArticle);
});

test("WP REST API is accessible", async ({ request }) => {
const apiRoot = await request.get(`${siteUrl}/wp-json`);
expect(apiRoot.ok()).toBeTruthy();
});

test("Hello World post is accessible", async ({ page }) => {
await page.goto(`${siteUrl}/hello-world/'`);
await expect(page).toHaveTitle(`${exampleArticle}${siteTitle}`);
// Locate the element containing the desired text
const welcomeText = page.locator('text=Welcome to WordPress');
await expect(welcomeText).toContainText('Welcome to WordPress');
});

test("validate core resource URLs", async ({ request }) => {
const coreResources = [
'wp-includes/js/dist/interactivity.min.js',
'wp-includes/css/dist/editor.min.css',
];

for ( const resource of coreResources ) {
const resourceUrl = `${siteUrl}/wp/${resource}`;
const response = await request.get(resourceUrl);
await expect(response).toBeTruthy();
}
});

test("graphql is able to access hello world post", async ({ request }) => {
const query = `
query {
posts(where: { search: "${exampleArticle}" }) {
edges {
node {
title
}
}
}
}
`;

const response = await request.post(`${siteUrl}/wp/graphql`, {
data: {
query: query
},
headers: {
'Content-Type': 'application/json'
}
});

const responseBody = await response.json();

expect(responseBody.data.posts.edges.length).toBeGreaterThan(0);
expect(responseBody.data.posts.edges[0].node.title).toBe(exampleArticle);
});
113 changes: 113 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: WordPress (Composer Managed) Playwright Tests
on:
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review

permissions:
contents: write

jobs:
playwright:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Generate lock files
run: |
npm install --package-lock-only
- name: Set up cache for dependencies
uses: actions/cache@v4
id: cache
with:
path: |
~/.composer/cache
./vendor
~/.npm
./node_modules
key: ${{ runner.os }}-deps-${{ hashFiles( '**/composer.json', '**/package-lock.json' ) }}
restore-keys: ${{ runner.os }}-deps-

- name: Install Composer dependencies
if: steps.cache.outputs.cache-hit != true
run: composer update --no-progress --prefer-dist --optimize-autoloader

- name: Install NPM dependencies
if: steps.cache.outputs.cache-hit != true
run: npm ci

- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Install SSH keys
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

- name: Get latest Terminus release
uses: pantheon-systems/terminus-github-actions@v1
with:
pantheon-machine-token: ${{ secrets.TERMINUS_TOKEN }}
- name: Validate Pantheon Host Key
run: |
echo "Host *.drush.in HostKeyAlgorithms +ssh-rsa" >> ~/.ssh/config
echo "Host *.drush.in PubkeyAcceptedKeyTypes +ssh-rsa" >> ~/.ssh/config
echo "StrictHostKeyChecking no" >> ~/.ssh/config
- name: Log into Terminus
run: |
terminus auth:login --machine-token=${{ secrets.TERMINUS_TOKEN }}
- name: Create Site
run: |
if terminus site:info wpcm-playwright-tests; then
echo "Test site already exists, skipping site creation."
# If the site exists already, we should switch it to git mode.
terminus connection:set wpcm-playwright-tests.dev git -y
else
terminus site:create wpcm-playwright-tests 'WordPress (Composer Managed) Playwright Test Site' 'WordPress (Composer Managed)' --org=5ae1fa30-8cc4-4894-8ca9-d50628dcba17
fi
- name: Clone the site locally and copy PR updates
env:
GH_TOKEN: ${{ github.token }}
run: |
git config --global user.email "[email protected]"
git config --global user.name "Pantheon WPCM Bot"
PR_NUMBER=$(echo ${{ github.event.pull_request.number }})
echo "Pull Request Number: ${PR_NUMBER}"
COMMIT_MSG=$(gh pr view ${PR_NUMBER} --json commits --jq '.commits[-1] | "\(.messageHeadline) \(.messageBody)"')
echo "Commit Message: ${COMMIT_MSG}"
terminus local:clone wpcm-playwright-tests
cd ~/pantheon-local-copies/wpcm-playwright-tests
rsync -a --exclude='.git' ${{ github.workspace }}/ .
git add -A
git commit -m "Update to latest commit: ${COMMIT_MSG}" || true
git push origin master || true
- name: Status Check
run: terminus wp wpcm-playwright-tests.dev -- cli info

- name: Install WordPress
run: |
terminus wp wpcm-playwright-tests.dev -- core install --title='WPCM Playwright Tests' --admin_user=wpcm [email protected]
terminus wp wpcm-playwright-tests.dev -- option update permalink_structure '/%postname%/'
terminus wp wpcm-playwright-tests.dev -- rewrite flush
terminus wp wpcm-playwright-tests.dev -- cache flush
- name: Install WP GraphQL
run: |
terminus workflow:wait wpcm-playwright-tests.dev
terminus connection:set wpcm-playwright-tests.dev sftp
terminus wp wpcm-playwright-tests.dev -- plugin install --activate wp-graphql
- name: Run Playwright Tests
run: npm run test .github/tests/wpcm.spec.ts

- name: Delete Site
if: success()
run: terminus site:delete wpcm-playwright-tests -y
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@pantheon-systems/wordpress-composer-managed",
"version": "1.31.0",
"description": "Automated testing for WordPress (Composer Managed).",
"scripts": {
"test": "playwright test --trace on",
"report": "playwright show-report"
},
"devDependencies": {
"@playwright/test": "^1.28.0"
}
}
6 changes: 3 additions & 3 deletions upstream-configuration/scripts/ComposerScripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public static function applyComposerJsonUpdates(Event $event)
// is the same as the Pantheon PHP version (which is only major.minor).
// If they do not match, force an update to the platform PHP version. If they
// have the same major.minor version, then
$platformPhpVersion = static::getCurrentPlatformPhp($event);
$pantheonPhpVersion = static::getPantheonPhpVersion($event);
$updatedPlatformPhpVersion = static::bestPhpPatchVersion($pantheonPhpVersion);
$platformPhpVersion = static::getCurrentPlatformPhp($event) ?? '';
$pantheonPhpVersion = static::getPantheonPhpVersion($event) ?? '';
$updatedPlatformPhpVersion = static::bestPhpPatchVersion($pantheonPhpVersion) ?? '';
if ((substr($platformPhpVersion, 0, strlen($pantheonPhpVersion)) != $pantheonPhpVersion) && !empty($updatedPlatformPhpVersion)) {
$io->write("<info>Setting platform.php from '$platformPhpVersion' to '$updatedPlatformPhpVersion' to conform to pantheon php version.</info>");
$composerJson['config']['platform']['php'] = $updatedPlatformPhpVersion;
Expand Down
67 changes: 57 additions & 10 deletions web/app/mu-plugins/filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ function fix_core_resource_urls( string $url ) : string {
}
}

/**
* Prepopulate GraphQL endpoint URL with default value if unset.
* This will ensure that the URL is not changed from /wp/graphql to /graphql by our other filtering unless that's what the user wants.
*
* @since 1.1.0
*/
function prepopulate_graphql_endpoint_url() {
$options = get_option( 'graphql_general_settings' );

// Bail early if options have already been set.
if ( $options ) {
return;
}

$options = [];
$site_path = site_url();
$endpoint = ( ! empty( $site_path ) || strpos( $site_path, 'wp' ) !== false ) ? 'graphql' : 'wp/graphql';
$options['graphql_endpoint'] = $endpoint;
update_option( 'graphql_general_settings', $options );
}
add_action( 'graphql_init', __NAMESPACE__ . '\\prepopulate_graphql_endpoint_url' );

/**
* Drop the /wp, if it exists, from URLs on the main site (single site or multisite).
*
Expand All @@ -106,6 +128,15 @@ function fix_core_resource_urls( string $url ) : string {
* @return string The filtered URL.
*/
function adjust_main_site_urls( string $url ) : string {
if ( doing_action( 'graphql_init' ) ) {
return $url;
}

// Explicit handling for /wp/graphql
if ( strpos( $url, '/graphql' ) !== false ) {
return $url;
}

// If this is the main site, drop the /wp.
if ( is_main_site() && ! __is_login_url( $url ) ) {
$url = str_replace( '/wp/', '/', $url );
Expand Down Expand Up @@ -157,11 +188,11 @@ function __is_login_url( string $url ) : bool {
}

// Check if the URL is a login or admin page
if (strpos($url, 'wp-login') !== false || strpos($url, 'wp-admin') !== false) {
if ( strpos( $url, 'wp-login' ) !== false || strpos($url, 'wp-admin' ) !== false) {
return true;
}

return false
return false;
}

/**
Expand All @@ -172,15 +203,31 @@ function __is_login_url( string $url ) : bool {
* @return string The normalized URL.
*/
function __normalize_wp_url( string $url ): string {
$scheme = parse_url( $url, PHP_URL_SCHEME );
$scheme_with_separator = $scheme ? $scheme . '://' : '';
// Parse the URL into components.
$parts = parse_url( $url );

// Remove the scheme from the URL if it exists.
$remaining_url = $scheme ? substr( $url, strlen($scheme_with_separator ) ) : $url;
// Normalize the URL to remove any double slashes.
if ( isset( $parts['path'] ) ) {
$parts['path'] = preg_replace( '#/+#', '/', $parts['path'] );
}

// Normalize the remaining URL to remove any double slashes.
$normalized_url = str_replace( '//', '/', $remaining_url );
// Rebuild and return the full normalized URL.
return __rebuild_url_from_parts( $parts );
}

// Reconstruct and return the full normalized URL.
return $scheme_with_separator . $normalized_url;
/**
* Rebuild parsed URL from parts.
*
* @since 1.1.0
* @param array $parts URL parts from parse_url.
* @return string Re-parsed URL.
*/
function __rebuild_url_from_parts( array $parts ) : string {
return trailingslashit(
( isset( $parts['scheme'] ) ? "{$parts['scheme']}:" : '' ) .
( isset( $parts['host'] ) ? "{$parts['host']}" : '' ) .
( isset( $parts['path'] ) ? untrailingslashit( "{$parts['path']}" ) : '' ) .
( isset( $parts['query'] ) ? str_replace( '/', '', "?{$parts['query']}" ) : '' ) .
( isset( $parts['fragment'] ) ? str_replace( '/', '', "#{$parts['fragment']}" ) : '' )
);
}

0 comments on commit e72aea5

Please sign in to comment.