diff --git a/.env.dist.testing b/.env.dist.testing new file mode 100644 index 0000000..ccdc27b --- /dev/null +++ b/.env.dist.testing @@ -0,0 +1,20 @@ +TEST_SITE_DB_DSN=mysql:host=localhost;dbname=test +TEST_SITE_DB_HOST=localhost +TEST_SITE_DB_NAME=test +TEST_SITE_DB_USER=root +TEST_SITE_DB_PASSWORD=root +TEST_SITE_TABLE_PREFIX=wp_ +TEST_SITE_ADMIN_USERNAME=admin +TEST_SITE_ADMIN_PASSWORD=password +TEST_SITE_WP_ADMIN_PATH=/wp-admin +WP_ROOT_FOLDER="/home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress" +TEST_DB_NAME=test +TEST_DB_HOST=localhost +TEST_DB_USER=root +TEST_DB_PASSWORD=root +TEST_TABLE_PREFIX=wp_ +TEST_SITE_WP_URL=http://127.0.0.1 +TEST_SITE_WP_DOMAIN=127.0.0.1 +TEST_SITE_ADMIN_EMAIL=wordpress@convertkit.local +TEST_SITE_HTTP_USER_AGENT=HeadlessChrome +TEST_SITE_HTTP_USER_AGENT_MOBILE=HeadlessChromeMobile diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..11823e2 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +TEST_SITE_DB_DSN=mysql:host=localhost;dbname=test +TEST_SITE_DB_HOST=localhost +TEST_SITE_DB_NAME=test +TEST_SITE_DB_USER=root +TEST_SITE_DB_PASSWORD=root +TEST_SITE_TABLE_PREFIX=wp_ +TEST_SITE_ADMIN_USERNAME=admin +TEST_SITE_ADMIN_PASSWORD=password +TEST_SITE_WP_ADMIN_PATH=/wp-admin +WP_ROOT_FOLDER="/Users/tim/Local Sites/convertkit-github/app/public" +TEST_DB_NAME=test +TEST_DB_HOST=localhost +TEST_DB_USER=root +TEST_DB_PASSWORD=root +TEST_TABLE_PREFIX=wp_ +TEST_SITE_WP_URL=http://convertkit.local +TEST_SITE_WP_DOMAIN=convertkit.local +TEST_SITE_ADMIN_EMAIL=wordpress@convertkit.local +TEST_SITE_HTTP_USER_AGENT=HeadlessChrome +TEST_SITE_HTTP_USER_AGENT_MOBILE=HeadlessChromeMobile +CONVERTKIT_API_KEY_NO_DATA= +CONVERTKIT_API_SECRET_NO_DATA= +CONVERTKIT_API_KEY= +CONVERTKIT_API_SECRET= \ No newline at end of file diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index bd998dc..3064255 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -111,6 +111,11 @@ jobs: working-directory: ${{ env.PLUGIN_DIR }} run: composer dump-autoload + # Run Coding Standards on Tests. + - name: Run Coding Standards on Tests + working-directory: ${{ env.PLUGIN_DIR }} + run: php vendor/bin/phpcs -q --standard=phpcs.tests.xml --report=checkstyle ./tests | cs2pr + # Run WordPress Coding Standards on Plugin. - name: Run WordPress Coding Standards working-directory: ${{ env.PLUGIN_DIR }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e8a1f6c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,208 @@ +name: Run Tests + +# When to run tests. +on: + pull_request: + types: + - opened + - synchronize + push: + branches: + - main + +jobs: + tests: + # Name. + name: ${{ matrix.test-groups }} / WordPress ${{ matrix.wp-versions }} / PHP ${{ matrix.php-versions }} + + # Virtual Environment to use. + # @see: https://github.com/actions/virtual-environments + runs-on: ubuntu-20.04 + + # Environment Variables. + # Accessible by using ${{ env.NAME }} + # Use ${{ secrets.NAME }} to include any GitHub Secrets in ${{ env.NAME }} + # The base folder will always be /home/runner/work/github-repo-name/github-repo-name + env: + ROOT_DIR: /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress + PLUGIN_DIR: /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress/wp-content/plugins/convertkit-membermouse + DB_NAME: test + DB_USER: root + DB_PASS: root + DB_HOST: localhost + INSTALL_PLUGINS: "https://hub.membermouse.com/download.php" # Don't include this repository's Plugin here. + CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} # ConvertKit API Key, stored in the repository's Settings > Secrets + CONVERTKIT_API_SECRET: ${{ secrets.CONVERTKIT_API_SECRET }} # ConvertKit API Secret, stored in the repository's Settings > Secrets + CONVERTKIT_API_KEY_NO_DATA: ${{ secrets.CONVERTKIT_API_KEY_NO_DATA }} # ConvertKit API Key for ConvertKit account with no data, stored in the repository's Settings > Secrets + CONVERTKIT_API_SECRET_NO_DATA: ${{ secrets.CONVERTKIT_API_SECRET_NO_DATA }} # ConvertKit API Secret for ConvertKit account with no data, stored in the repository's Settings > Secrets + + # Defines the WordPress and PHP Versions matrix to run tests on + # WooCommerce 5.9.0 requires WordPress 5.6 or greater, so we do not test on earlier versions + # If testing older WordPress versions, ensure they are e.g. 5.7.4, 5.6.6 that have the X3 SSL fix: https://core.trac.wordpress.org/ticket/54207 + # For PHP, make sure that an nginx configuration file exists for the required PHP version in this repository at tests/nginx/php-x.x.conf + strategy: + fail-fast: false + matrix: + wp-versions: [ 'latest' ] #[ 'latest' ] + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] #[ '7.3', '7.4', '8.0', '8.1' ] + + # Folder names within the 'tests' folder to run tests in parallel. + test-groups: [ + 'acceptance/general', + ] + + # Steps to install, configure and run tests + steps: + - name: Define Test Group Name + id: test-group + uses: mad9000/actions-find-and-replace-string@5 + with: + source: ${{ matrix.test-groups }} + find: '/' + replace: '-' + replaceAll: true + + - name: Start MySQL + run: sudo systemctl start mysql.service + + - name: Create MySQL Database + run: | + mysql -e 'CREATE DATABASE test;' -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + mysql -e 'SHOW DATABASES;' -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + + # WordPress won't be able to connect to the DB if we don't perform this step. + - name: Permit MySQL Password Auth for MySQL 8.0 + run: mysql -e "ALTER USER '${{ env.DB_USER }}'@'${{ env.DB_HOST }}' IDENTIFIED WITH mysql_native_password BY '${{ env.DB_PASS }}';" -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + + # Some workflows checkout WordPress from GitHub, but that seems to bring a bunch of uncompiled files with it. + # Instead download from wordpress.org stable. + - name: Download WordPress + run: wget https://wordpress.org/wordpress-${{ matrix.wp-versions }}.tar.gz + + - name: Extract WordPress + run: tar xfz wordpress-${{ matrix.wp-versions }}.tar.gz + + # Checkout (copy) this repository's Plugin to this VM. + - name: Checkout Plugin + uses: actions/checkout@v4 + with: + path: ${{ env.PLUGIN_DIR }} + + # We install WP-CLI, as it provides useful commands to setup and install WordPress through the command line. + - name: Install WP-CLI + run: | + curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + chmod +x wp-cli.phar + sudo mv wp-cli.phar /usr/local/bin/wp-cli + + - name: Setup wp-config.php + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli config create --dbname=${{ env.DB_NAME }} --dbuser=${{ env.DB_USER }} --dbpass=${{ env.DB_PASS }} --dbhost=${{ env.DB_HOST }} --locale=en_DB + + - name: Install WordPress + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli core install --url=127.0.0.1 --title=ConvertKit --admin_user=admin --admin_password=password --admin_email=wordpress@convertkit.local + + # env.INSTALL_PLUGINS is a list of Plugin slugs, space separated e.g. contact-form-7 woocommerce. + - name: Install Free Third Party WordPress Plugins + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli plugin install ${{ env.INSTALL_PLUGINS }} + + # WP_DEBUG = true is required so all PHP errors are output and caught by tests (E_ALL). + - name: Enable WP_DEBUG + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set WP_DEBUG true --raw + + # FS_METHOD = direct is required for WP_Filesystem to operate without suppressed PHP fopen() errors that trip up tests. + - name: Enable FS_METHOD + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set FS_METHOD direct + + # This step is deliberately after WordPress installation and configuration, as enabling PHP 8.x before using WP-CLI results + # in the workflow failing due to incompatibilities between WP-CLI and PHP 8.x. + # By installing PHP at this stage, we can still run our tests against e.g. PHP 8.x. + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: xdebug + + # Make sure that an nginx configuration file exists in this repository at tests/nginx/php-x.x.conf. + # Refer to an existing .conf file in this repository if you need to create a new one e.g. for a new PHP version. + - name: Copy nginx configuration file + run: sudo cp ${{ env.PLUGIN_DIR }}/tests/nginx/php-${{ matrix.php-versions }}.conf /etc/nginx/conf.d/php-${{ matrix.php-versions }}.conf + + - name: Test nginx + run: sudo nginx -t + + - name: Start nginx + run: sudo systemctl start nginx.service + + - name: Install chromedriver + uses: nanasess/setup-chromedriver@master + + - name: Start chromedriver + run: | + export DISPLAY=:99 + chromedriver --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + + # Write any secrets, such as API keys, to the .env.dist.testing file now. + # Make sure your committed .env.dist.testing file ends with a newline. + # The formatting of the contents to include a blank newline is deliberate. + - name: Define GitHub Secrets in .env.dist.testing + uses: DamianReeves/write-file-action@v1.2 + with: + path: ${{ env.PLUGIN_DIR }}/.env.dist.testing + contents: | + + CONVERTKIT_API_KEY=${{ env.CONVERTKIT_API_KEY }} + CONVERTKIT_API_SECRET=${{ env.CONVERTKIT_API_SECRET }} + CONVERTKIT_API_KEY_NO_DATA=${{ env.CONVERTKIT_API_KEY_NO_DATA }} + CONVERTKIT_API_SECRET_NO_DATA=${{ env.CONVERTKIT_API_SECRET_NO_DATA }} + write-mode: append + + # Installs wp-browser, Codeception, PHP CodeSniffer and anything else needed to run tests. + - name: Run Composer + working-directory: ${{ env.PLUGIN_DIR }} + run: composer update + + - name: Build PHP Autoloader + working-directory: ${{ env.PLUGIN_DIR }} + run: composer dump-autoload + + # This ensures the Plugin's log file can be written to. + # We don't recursively do this, as it'll prevent Codeception from writing to the /tests/_output directory. + - name: Set Permissions for Plugin Directory + run: | + sudo chmod g+w ${{ env.PLUGIN_DIR }} + sudo chown www-data:www-data ${{ env.PLUGIN_DIR }} + + # Build Codeception Tests. + - name: Build Tests + working-directory: ${{ env.PLUGIN_DIR }} + run: php vendor/bin/codecept build + + # Run Codeception Acceptance Tests. + - name: Run tests/${{ matrix.test-groups }} + working-directory: ${{ env.PLUGIN_DIR }} + run: php vendor/bin/codecept run tests/${{ matrix.test-groups }} --fail-fast + + # Artifacts are data generated by this workflow that we want to access, such as log files, screenshots, HTML output. + # The if: failure() directive means that this will run when the workflow fails e.g. if a test fails, which is needed + # because we want to see why a test failed. + - name: Upload Test Results to Artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }} + path: ${{ env.PLUGIN_DIR }}/tests/_output/ + + - name: Upload Plugin Log File to Artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: log-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}.txt + path: ${{ env.PLUGIN_DIR }}/log/log.txt \ No newline at end of file diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..e05dd29 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,22 @@ +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +settings: + error_level: E_ALL & ~E_STRICT & ~E_DEPRECATED +actor_suffix: Tester +extensions: + enabled: + - Codeception\Extension\RunFailed + commands: + - Codeception\Command\GenerateWPUnit + - Codeception\Command\GenerateWPRestApi + - Codeception\Command\GenerateWPRestController + - Codeception\Command\GenerateWPRestPostTypeController + - Codeception\Command\GenerateWPAjax + - Codeception\Command\GenerateWPCanonical + - Codeception\Command\GenerateWPXMLRPC +params: + - .env.dist.testing \ No newline at end of file diff --git a/composer.json b/composer.json index 7f34bcc..62fc3a7 100644 --- a/composer.json +++ b/composer.json @@ -3,9 +3,6 @@ "description": "ConvertKit WordPress Plugin", "type": "project", "license": "GPLv3", - "require": { - "convertkit/convertkit-wordpress-libraries": "1.4.2" - }, "require-dev": { "lucatume/wp-browser": "<3.5", "codeception/module-asserts": "^1.3", diff --git a/phpcs.tests.xml b/phpcs.tests.xml new file mode 100644 index 0000000..881d54b --- /dev/null +++ b/phpcs.tests.xml @@ -0,0 +1,58 @@ + + + Coding Standards for Tests + + + tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/_data/.gitkeep b/tests/_data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_data/dump.sql b/tests/_data/dump.sql new file mode 100644 index 0000000..90a00fe --- /dev/null +++ b/tests/_data/dump.sql @@ -0,0 +1,364 @@ +-- Adminer 4.8.1 MySQL 8.0.16 dump + +SET NAMES utf8; +SET time_zone = '+00:00'; +SET foreign_key_checks = 0; +SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; + +SET NAMES utf8mb4; + +DROP TABLE IF EXISTS `wp_commentmeta`; +CREATE TABLE `wp_commentmeta` ( + `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `comment_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `comment_id` (`comment_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_comments`; +CREATE TABLE `wp_comments` ( + `comment_ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `comment_post_ID` bigint(20) unsigned NOT NULL DEFAULT '0', + `comment_author` tinytext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `comment_author_email` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_author_url` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_author_IP` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `comment_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `comment_content` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `comment_karma` int(11) NOT NULL DEFAULT '0', + `comment_approved` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '1', + `comment_agent` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'comment', + `comment_parent` bigint(20) unsigned NOT NULL DEFAULT '0', + `user_id` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`comment_ID`), + KEY `comment_post_ID` (`comment_post_ID`), + KEY `comment_approved_date_gmt` (`comment_approved`,`comment_date_gmt`), + KEY `comment_date_gmt` (`comment_date_gmt`), + KEY `comment_parent` (`comment_parent`), + KEY `comment_author_email` (`comment_author_email`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_comments` (`comment_ID`, `comment_post_ID`, `comment_author`, `comment_author_email`, `comment_author_url`, `comment_author_IP`, `comment_date`, `comment_date_gmt`, `comment_content`, `comment_karma`, `comment_approved`, `comment_agent`, `comment_type`, `comment_parent`, `user_id`) VALUES +(1, 1, 'A WordPress Commenter', 'wapuu@wordpress.example', 'https://wordpress.org/', '', '2023-07-03 13:38:12', '2023-07-03 13:38:12', 'Hi, this is a comment.\nTo get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.\nCommenter avatars come from Gravatar.', 0, '1', '', 'comment', 0, 0); + +DROP TABLE IF EXISTS `wp_links`; +CREATE TABLE `wp_links` ( + `link_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `link_url` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_name` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_image` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_target` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_description` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_visible` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'Y', + `link_owner` bigint(20) unsigned NOT NULL DEFAULT '1', + `link_rating` int(11) NOT NULL DEFAULT '0', + `link_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `link_rel` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_notes` mediumtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `link_rss` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + PRIMARY KEY (`link_id`), + KEY `link_visible` (`link_visible`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_options`; +CREATE TABLE `wp_options` ( + `option_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `option_name` varchar(191) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `option_value` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `autoload` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'yes', + PRIMARY KEY (`option_id`), + UNIQUE KEY `option_name` (`option_name`), + KEY `autoload` (`autoload`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_options` (`option_id`, `option_name`, `option_value`, `autoload`) VALUES +(1, 'siteurl', 'http://convertkit.local', 'yes'), +(2, 'home', 'http://convertkit.local', 'yes'), +(3, 'blogname', 'convertkit', 'yes'), +(4, 'blogdescription', 'Just another WordPress site', 'yes'), +(5, 'users_can_register', '0', 'yes'), +(6, 'admin_email', 'dev-email@flywheel.local', 'yes'), +(7, 'start_of_week', '1', 'yes'), +(8, 'use_balanceTags', '0', 'yes'), +(9, 'use_smilies', '1', 'yes'), +(10, 'require_name_email', '1', 'yes'), +(11, 'comments_notify', '1', 'yes'), +(12, 'posts_per_rss', '10', 'yes'), +(13, 'rss_use_excerpt', '0', 'yes'), +(14, 'mailserver_url', 'mail.example.com', 'yes'), +(15, 'mailserver_login', 'login@example.com', 'yes'), +(16, 'mailserver_pass', 'password', 'yes'), +(17, 'mailserver_port', '110', 'yes'), +(18, 'default_category', '1', 'yes'), +(19, 'default_comment_status', 'open', 'yes'), +(20, 'default_ping_status', 'open', 'yes'), +(21, 'default_pingback_flag', '1', 'yes'), +(22, 'posts_per_page', '10', 'yes'), +(23, 'date_format', 'F j, Y', 'yes'), +(24, 'time_format', 'g:i a', 'yes'), +(25, 'links_updated_date_format', 'F j, Y g:i a', 'yes'), +(26, 'comment_moderation', '0', 'yes'), +(27, 'moderation_notify', '1', 'yes'), +(28, 'permalink_structure', '/%postname%/', 'yes'), +(29, 'rewrite_rules', 'a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}', 'yes'), +(30, 'hack_file', '0', 'yes'), +(31, 'blog_charset', 'UTF-8', 'yes'), +(32, 'moderation_keys', '', 'no'), +(33, 'active_plugins', 'a:0:{}', 'yes'), +(34, 'category_base', '', 'yes'), +(35, 'ping_sites', 'http://rpc.pingomatic.com/', 'yes'), +(36, 'comment_max_links', '2', 'yes'), +(37, 'gmt_offset', '0', 'yes'), +(38, 'default_email_category', '1', 'yes'), +(39, 'recently_edited', '', 'no'), +(40, 'template', 'twentytwentythree', 'yes'), +(41, 'stylesheet', 'twentytwentythree', 'yes'), +(42, 'comment_registration', '0', 'yes'), +(43, 'html_type', 'text/html', 'yes'), +(44, 'use_trackback', '0', 'yes'), +(45, 'default_role', 'subscriber', 'yes'), +(46, 'db_version', '57155', 'yes'), +(47, 'uploads_use_yearmonth_folders', '1', 'yes'), +(48, 'upload_path', '', 'yes'), +(49, 'blog_public', '1', 'yes'), +(50, 'default_link_category', '2', 'yes'), +(51, 'show_on_front', 'posts', 'yes'), +(52, 'tag_base', '', 'yes'), +(53, 'show_avatars', '1', 'yes'), +(54, 'avatar_rating', 'G', 'yes'), +(55, 'upload_url_path', '', 'yes'), +(56, 'thumbnail_size_w', '150', 'yes'), +(57, 'thumbnail_size_h', '150', 'yes'), +(58, 'thumbnail_crop', '1', 'yes'), +(59, 'medium_size_w', '300', 'yes'), +(60, 'medium_size_h', '300', 'yes'), +(61, 'avatar_default', 'mystery', 'yes'), +(62, 'large_size_w', '1024', 'yes'), +(63, 'large_size_h', '1024', 'yes'), +(64, 'image_default_link_type', 'none', 'yes'), +(65, 'image_default_size', '', 'yes'), +(66, 'image_default_align', '', 'yes'), +(67, 'close_comments_for_old_posts', '0', 'yes'), +(68, 'close_comments_days_old', '14', 'yes'), +(69, 'thread_comments', '1', 'yes'), +(70, 'thread_comments_depth', '5', 'yes'), +(71, 'page_comments', '0', 'yes'), +(72, 'comments_per_page', '50', 'yes'), +(73, 'default_comments_page', 'newest', 'yes'), +(74, 'comment_order', 'asc', 'yes'), +(75, 'sticky_posts', 'a:0:{}', 'yes'), +(76, 'widget_categories', 'a:0:{}', 'yes'), +(77, 'widget_text', 'a:0:{}', 'yes'), +(78, 'widget_rss', 'a:0:{}', 'yes'), +(79, 'uninstall_plugins', 'a:0:{}', 'no'), +(80, 'timezone_string', '', 'yes'), +(81, 'page_for_posts', '0', 'yes'), +(82, 'page_on_front', '0', 'yes'), +(83, 'default_post_format', '0', 'yes'), +(84, 'link_manager_enabled', '0', 'yes'), +(85, 'finished_splitting_shared_terms', '1', 'yes'), +(86, 'site_icon', '0', 'yes'), +(87, 'medium_large_size_w', '768', 'yes'), +(88, 'medium_large_size_h', '0', 'yes'), +(89, 'wp_page_for_privacy_policy', '3', 'yes'), +(90, 'show_comments_cookies_opt_in', '1', 'yes'), +(91, 'admin_email_lifespan', '1712414097', 'yes'), +(92, 'disallowed_keys', '', 'no'), +(93, 'comment_previously_approved', '1', 'yes'), +(94, 'auto_plugin_theme_update_emails', 'a:0:{}', 'no'), +(95, 'auto_update_core_dev', 'enabled', 'yes'), +(96, 'auto_update_core_minor', 'enabled', 'yes'), +(97, 'auto_update_core_major', 'enabled', 'yes'), +(98, 'wp_force_deactivated_plugins', 'a:0:{}', 'yes'), +(99, 'initial_db_version', '57155', 'yes'), +(100, 'wp_user_roles', 'a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}', 'yes'), +(101, 'fresh_site', '1', 'yes'), +(102, 'user_count', '1', 'no'), +(103, 'widget_block', 'a:6:{i:2;a:1:{s:7:\"content\";s:19:\"\";}i:3;a:1:{s:7:\"content\";s:154:\"

Recent Posts

\";}i:4;a:1:{s:7:\"content\";s:227:\"

Recent Comments

\";}i:5;a:1:{s:7:\"content\";s:146:\"

Archives

\";}i:6;a:1:{s:7:\"content\";s:150:\"

Categories

\";}s:12:\"_multiwidget\";i:1;}', 'yes'), +(104, 'sidebars_widgets', 'a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:7:\"block-2\";i:1;s:7:\"block-3\";i:2;s:7:\"block-4\";}s:9:\"sidebar-2\";a:2:{i:0;s:7:\"block-5\";i:1;s:7:\"block-6\";}s:13:\"array_version\";i:3;}', 'yes'), +(105, 'cron', 'a:9:{i:1696862175;a:1:{s:28:\"wp_update_comment_type_batch\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1696865698;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1696905298;a:3:{s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1696905315;a:1:{s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1696948498;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1696948515;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1696948516;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1697466926;a:1:{s:30:\"wp_delete_temp_updater_backups\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}}s:7:\"version\";i:2;}', 'yes'), +(106, 'widget_pages', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(107, 'widget_calendar', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(108, 'widget_archives', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(109, 'widget_media_audio', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(110, 'widget_media_image', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(111, 'widget_media_gallery', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(112, 'widget_media_video', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(113, 'widget_meta', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(114, 'widget_search', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(115, 'widget_recent-posts', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(116, 'widget_recent-comments', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(117, 'widget_tag_cloud', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(118, 'widget_nav_menu', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(119, 'widget_custom_html', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(120, 'nonce_key', ':@mS9DsY,vFI=iAKiwy;g$hm^l d4nE(_%Bd+>v2K`d;Km2Me;mO9[f_-Q Zh=[Q', 'no'), +(121, 'nonce_salt', '/?#2ig*c_e_}-[mel-$?%U;;@}oC9TF%|l /Df%)V@kV~|$Lo[bKcL,W{y-]4%S>', 'no'), +(122, 'recovery_keys', 'a:0:{}', 'yes'), +(123, 'theme_mods_twentytwentythree', 'a:1:{s:18:\"custom_css_post_id\";i:-1;}', 'yes'), +(124, 'db_upgraded', '', 'yes'), +(125, 'can_compress_scripts', '1', 'yes'), +(126, 'WishListMemberOptions_Migrated', '1', 'yes'), +(127, 'widget_wishlistwidget', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(128, 'WishListMemberOptions_MigrateLevelData', '1', 'yes'), +(129, 'WishListMemberOptions_MigrateContentLevelData', '1', 'yes'); + +DROP TABLE IF EXISTS `wp_postmeta`; +CREATE TABLE `wp_postmeta` ( + `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `post_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `post_id` (`post_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +DROP TABLE IF EXISTS `wp_posts`; +CREATE TABLE `wp_posts` ( + `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `post_author` bigint(20) unsigned NOT NULL DEFAULT '0', + `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_content` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_title` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_excerpt` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'publish', + `comment_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', + `ping_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', + `post_password` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `post_name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `to_ping` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `pinged` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_content_filtered` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_parent` bigint(20) unsigned NOT NULL DEFAULT '0', + `guid` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `menu_order` int(11) NOT NULL DEFAULT '0', + `post_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'post', + `post_mime_type` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_count` bigint(20) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`), + KEY `post_name` (`post_name`(191)), + KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`), + KEY `post_parent` (`post_parent`), + KEY `post_author` (`post_author`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +DROP TABLE IF EXISTS `wp_term_relationships`; +CREATE TABLE `wp_term_relationships` ( + `object_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `term_taxonomy_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `term_order` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`object_id`,`term_taxonomy_id`), + KEY `term_taxonomy_id` (`term_taxonomy_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_term_relationships` (`object_id`, `term_taxonomy_id`, `term_order`) VALUES +(1, 1, 0); + +DROP TABLE IF EXISTS `wp_term_taxonomy`; +CREATE TABLE `wp_term_taxonomy` ( + `term_taxonomy_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `term_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `taxonomy` varchar(32) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `description` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `parent` bigint(20) unsigned NOT NULL DEFAULT '0', + `count` bigint(20) NOT NULL DEFAULT '0', + PRIMARY KEY (`term_taxonomy_id`), + UNIQUE KEY `term_id_taxonomy` (`term_id`,`taxonomy`), + KEY `taxonomy` (`taxonomy`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_term_taxonomy` (`term_taxonomy_id`, `term_id`, `taxonomy`, `description`, `parent`, `count`) VALUES +(1, 1, 'category', '', 0, 1); + +DROP TABLE IF EXISTS `wp_termmeta`; +CREATE TABLE `wp_termmeta` ( + `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `term_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `term_id` (`term_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_terms`; +CREATE TABLE `wp_terms` ( + `term_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `slug` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `term_group` bigint(10) NOT NULL DEFAULT '0', + PRIMARY KEY (`term_id`), + KEY `slug` (`slug`(191)), + KEY `name` (`name`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_terms` (`term_id`, `name`, `slug`, `term_group`) VALUES +(1, 'Uncategorized', 'uncategorized', 0); + +DROP TABLE IF EXISTS `wp_usermeta`; +CREATE TABLE `wp_usermeta` ( + `umeta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`umeta_id`), + KEY `user_id` (`user_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES +(1, 1, 'nickname', 'admin'), +(2, 1, 'first_name', ''), +(3, 1, 'last_name', ''), +(4, 1, 'description', ''), +(5, 1, 'rich_editing', 'true'), +(6, 1, 'syntax_highlighting', 'true'), +(7, 1, 'comment_shortcuts', 'false'), +(8, 1, 'admin_color', 'fresh'), +(9, 1, 'use_ssl', '0'), +(10, 1, 'show_admin_bar_front', 'true'), +(11, 1, 'locale', ''), +(12, 1, 'wp_capabilities', 'a:1:{s:13:\"administrator\";b:1;}'), +(13, 1, 'wp_user_level', '10'), +(14, 1, 'dismissed_wp_pointers', ''), +(15, 1, 'show_welcome_panel', '1'), +(16, 1, 'session_tokens', 'a:1:{s:64:\"d1edb8c7d17dc41fa6de9833631a6381dca0306f20dfd4b64947e6b8818dd16e\";a:4:{s:10:\"expiration\";i:1676810217;s:2:\"ip\";s:9:\"127.0.0.1\";s:2:\"ua\";s:117:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36\";s:5:\"login\";i:1676637417;}}'), +(17, 1, 'wp_user-settings', 'unfold=1&ampmfold=o&ampeditor=html&amplibraryContent=browse&ampsiteorigin_panels_setting_tab=widgets&libraryContent=browse&editor=tinymce&libraryContent=browse&editor=tinymce&siteorigin_panels_setting_tab=welcome'), +(18, 1, 'wp_user-settings-time', '1676637417'), +(19, 1, 'wp_dashboard_quick_press_last_post_id', '1'), +(20, 1, 'edit_page_per_page', '100'), +(21, 1, 'edit_post_per_page', '100'); + +DROP TABLE IF EXISTS `wp_users`; +CREATE TABLE `wp_users` ( + `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `user_login` varchar(60) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_pass` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_nicename` varchar(50) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_email` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_url` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_registered` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `user_activation_key` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_status` int(11) NOT NULL DEFAULT '0', + `display_name` varchar(250) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + PRIMARY KEY (`ID`), + KEY `user_login_key` (`user_login`), + KEY `user_nicename` (`user_nicename`), + KEY `user_email` (`user_email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_users` (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES +(1, 'admin', '$P$BPKHO1xSCwu6j57sJB/p7JndeBdRVd.', 'admin', 'dev-email@flywheel.local', 'http://convertkit.local', '2023-07-03 13:38:12', '', 0, 'admin'); + +-- 2023-02-17 12:37:57 \ No newline at end of file diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100644 index 0000000..a17e0fd --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,27 @@ +{yourFunctionName}. + * + * @since 1.2.0 + */ +class ConvertKitAPI extends \Codeception\Module +{ + /** + * Check the given email address exists as a subscriber, and optionally + * checks that the first name and custom fields contain the expected data. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $emailAddress Email Address. + * @param bool|string $firstName First Name. + * @param bool|array $customFields Custom Fields. + */ + public function apiCheckSubscriberExists($I, $emailAddress, $firstName = false, $customFields = false) + { + // Run request. + $results = $this->apiRequest( + 'subscribers', + 'GET', + [ + 'email_address' => $emailAddress, + 'include_total_count' => true, + + // Some test email addresses might bounce, so we want to check all subscriber states. + 'status' => 'all', + ] + ); + + // Check at least one subscriber was returned and it matches the email address. + $I->assertGreaterThan(0, $results['pagination']['total_count']); + $I->assertEquals($emailAddress, $results['subscribers'][0]['email_address']); + + // If a first name was provided, check it matches. + if ($firstName) { + $I->assertEquals($firstName, $results['subscribers'][0]['first_name']); + } + + // If custom fields are provided, check they exist. + if ($customFields) { + foreach ($customFields as $customField => $customFieldValue) { + $I->assertEquals($results['subscribers'][0]['fields'][ $customField ], $customFieldValue); + } + } + } + + /** + * Check the given email address does not exists as a subscriber. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $emailAddress Email Address. + */ + public function apiCheckSubscriberDoesNotExist($I, $emailAddress) + { + // Run request. + $results = $this->apiRequest( + 'subscribers', + 'GET', + [ + 'email_address' => $emailAddress, + 'include_total_count' => true, + + // Some test email addresses might bounce, so we want to check all subscriber states. + 'status' => 'all', + ] + ); + + // Check no subscribers are returned by this request. + $I->assertEquals(0, $results['pagination']['total_count']); + } + + /** + * Checks if the given email address has the given tag. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $emailAddress Email Address. + * @param string $tagID Tag ID. + */ + public function apiCheckSubscriberHasTag($I, $emailAddress, $tagID) + { + // Get subscriber ID by email. + $subscriberID = $this->apiGetSubscriberIDByEmail($emailAddress); + + // Get subscriber tags. + $subscriberTags = $this->apiGetSubscriberTags($subscriberID); + + $subscriberTagged = false; + foreach ($subscriberTags as $tag) { + if ( (int) $tag['id'] === (int) $tagID) { + $subscriberTagged = true; + break; + } + } + + // Check that the Subscriber is tagged. + $I->assertTrue($subscriberTagged); + } + + /** + * Checks if the given email address does not have the given tag. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $emailAddress Email Address. + * @param string $tagID Tag ID. + */ + public function apiCheckSubscriberDoesNotHaveTag($I, $emailAddress, $tagID) + { + // Get subscriber ID by email. + $subscriberID = $this->apiGetSubscriberIDByEmail($emailAddress); + + // Get subscriber tags. + $subscriberTags = $this->apiGetSubscriberTags($subscriberID); + + $subscriberTagged = false; + foreach ($subscriberTags as $tag) { + if ( (int) $tag['id'] === (int) $tagID) { + $subscriberTagged = true; + break; + } + } + + // Check that the Subscriber is not tagged. + $I->assertFalse($subscriberTagged); + } + + /** + * Checks if the given email address has no tags in ConvertKit. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $emailAddress Email Address. + */ + public function apiCheckSubscriberHasNoTags($I, $emailAddress) + { + // Get subscriber ID by email. + $subscriberID = $this->apiGetSubscriberIDByEmail($emailAddress); + + // Get subscriber tags. + $subscriberTags = $this->apiGetSubscriberTags($subscriberID); + + // Confirm no tags exist. + $I->assertCount(0, $subscriberTags); + } + + /** + * Returns the subscriber ID for the given email address from the API. + * + * @since 1.2.0 + * + * @param string $emailAddress Subscriber Email Address. + * @return array + */ + public function apiGetSubscriberIDByEmail($emailAddress) + { + $subscriber = $this->apiRequest( + 'subscribers', + 'GET', + [ + 'email_address' => $emailAddress, + 'include_total_count' => true, + + // Some test email addresses might bounce, so we want to check all subscriber states. + 'status' => 'all', + ] + ); + + return $subscriber['subscribers'][0]['id']; + } + + /** + * Returns all tags for the given subscriber ID from the API. + * + * @since 1.2.0 + * + * @param int $subscriberID Subscriber ID. + * @return array + */ + public function apiGetSubscriberTags($subscriberID) + { + $tags = $this->apiRequest('subscribers/' . $subscriberID . '/tags'); + return $tags['tags']; + } + + /** + * Sends a request to the ConvertKit API, typically used to read an endpoint to confirm + * that data in an Acceptance Test was added/edited/deleted successfully. + * + * @since 1.2.0 + * + * @param string $endpoint Endpoint. + * @param string $method Method (GET|POST|PUT). + * @param array $params Endpoint Parameters. + */ + public function apiRequest($endpoint, $method = 'GET', $params = array()) + { + // Send request. + $client = new \GuzzleHttp\Client(); + switch ($method) { + case 'GET': + $result = $client->request( + $method, + 'https://api.convertkit.com/v4/' . $endpoint . '?' . http_build_query($params), + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'timeout' => 5, + ], + ] + ); + break; + + default: + $result = $client->request( + $method, + 'https://api.convertkit.com/v4/' . $endpoint, + [ + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + 'Authorization' => 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'timeout' => 5, + ], + 'body' => (string) json_encode($params), // phpcs:ignore WordPress.WP.AlternativeFunctions + ] + ); + break; + } + + // Return JSON decoded response. + return json_decode($result->getBody()->getContents(), true); + } +} diff --git a/tests/_support/Helper/Acceptance/Email.php b/tests/_support/Helper/Acceptance/Email.php new file mode 100644 index 0000000..444896c --- /dev/null +++ b/tests/_support/Helper/Acceptance/Email.php @@ -0,0 +1,25 @@ +{yourFunctionName}. + * + * @since 1.2.0 + */ +class Email extends \Codeception\Module +{ + /** + * Generates a unique email address for use in a test, comprising of a prefix, + * date + time and PHP version number. + * + * This ensures that if tests are run in parallel, the same email address + * isn't used for two tests across parallel testing runs. + * + * @since 1.2.0 + */ + public function generateEmailAddress() + { + return 'wordpress-' . uniqid() . '-' . date( 'Y-m-d-H-i-s' ) . '-php-' . PHP_VERSION_ID . '@n7studios.com'; + } +} diff --git a/tests/_support/Helper/Acceptance/Plugin.php b/tests/_support/Helper/Acceptance/Plugin.php new file mode 100644 index 0000000..e62fb89 --- /dev/null +++ b/tests/_support/Helper/Acceptance/Plugin.php @@ -0,0 +1,37 @@ +{yourFunctionName}. + * + * @since 1.2.0 + */ +class Plugin extends \Codeception\Module +{ + /** + * Helper method to activate the ConvertKit Plugin, checking + * it activated and no errors were output. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + */ + public function activateConvertKitPlugin($I) + { + $I->activateThirdPartyPlugin($I, 'convertkit-membermouse'); + } + + /** + * Helper method to deactivate the ConvertKit Plugin, checking + * it activated and no errors were output. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + */ + public function deactivateConvertKitPlugin($I) + { + $I->deactivateThirdPartyPlugin($I, 'convertkit-membermouse'); + } +} diff --git a/tests/_support/Helper/Acceptance/ThirdPartyPlugin.php b/tests/_support/Helper/Acceptance/ThirdPartyPlugin.php new file mode 100644 index 0000000..5e35dda --- /dev/null +++ b/tests/_support/Helper/Acceptance/ThirdPartyPlugin.php @@ -0,0 +1,66 @@ +{yourFunctionName}. + * + * @since 1.9.6 + */ +class ThirdPartyPlugin extends \Codeception\Module +{ + /** + * Helper method to activate a third party Plugin, checking + * it activated and no errors were output. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param string $name Plugin Slug. + */ + public function activateThirdPartyPlugin($I, $name) + { + // Login as the Administrator. + $I->loginAsAdmin(); + + // Go to the Plugins screen in the WordPress Administration interface. + $I->amOnPluginsPage(); + + // Activate the Plugin. + $I->activatePlugin($name); + + // Go to the Plugins screen again; this prevents any Plugin that loads a wizard-style screen from + // causing seePluginActivated() to fail. + $I->amOnPluginsPage(); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + } + + /** + * Helper method to activate a third party Plugin, checking + * it activated and no errors were output. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I Acceptance Tester. + * @param string $name Plugin Slug. + */ + public function deactivateThirdPartyPlugin($I, $name) + { + // Login as the Administrator. + $I->loginAsAdmin(); + + // Go to the Plugins screen in the WordPress Administration interface. + $I->amOnPluginsPage(); + + // Deactivate the Plugin. + $I->deactivatePlugin($name); + + // Wait for notice to display. + $I->waitForElementVisible('div.updated'); + + // Check that the Plugin deactivated successfully. + $I->seePluginDeactivated($name); + } +} diff --git a/tests/_support/Helper/Acceptance/Xdebug.php b/tests/_support/Helper/Acceptance/Xdebug.php new file mode 100644 index 0000000..53f0e94 --- /dev/null +++ b/tests/_support/Helper/Acceptance/Xdebug.php @@ -0,0 +1,25 @@ +{yourFunctionName}. + * + * @since 1.2.0 + */ +class Xdebug extends \Codeception\Module +{ + /** + * Helper method to assert that there are non PHP errors, warnings or notices output + * + * @since 1.2.0 + * + * @param AcceptanceTester $I Acceptance Tester. + */ + public function checkNoWarningsAndNoticesOnScreen($I) + { + // Check that no Xdebug errors exist. + $I->dontSeeElement('.xdebug-error'); + $I->dontSeeElement('.xe-notice'); + } +} diff --git a/tests/_support/Helper/Functional.php b/tests/_support/Helper/Functional.php new file mode 100644 index 0000000..2e4e491 --- /dev/null +++ b/tests/_support/Helper/Functional.php @@ -0,0 +1,11 @@ +activateConvertKitPlugin($I); + $I->activateThirdPartyPlugin($I, 'membermouse-platform'); + + // Go to the Plugin's Settings > General Screen. + $I->amOnAdminPage('options-general.php?page=convertkit-mm'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + $I->deactivateConvertKitPlugin($I); + $I->deactivateThirdPartyPlugin($I, 'membermouse-platform'); + } + + /** + * Test that activating the Plugin, without activating the MemberMouse Plugin, works + * with no errors. + * + * @since 1.2.0 + * + * @param AcceptanceTester $I Tester. + */ + public function testPluginActivationDeactivationWithoutMemberMouse(AcceptanceTester $I) + { + $I->activateConvertKitPlugin($I); + + // Go to the Plugin's Settings > General Screen. + $I->amOnAdminPage('options-general.php?page=convertkit-mm'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + $I->deactivateConvertKitPlugin($I); + } +} diff --git a/tests/functional.suite.yml b/tests/functional.suite.yml new file mode 100644 index 0000000..140252e --- /dev/null +++ b/tests/functional.suite.yml @@ -0,0 +1,40 @@ +# Codeception Test Suite Configuration +# +# Suite for functional tests +# Emulate web requests and make WordPress process them + +actor: FunctionalTester +modules: + enabled: + - WPDb + - WPBrowser + # - WPFilesystem + - Asserts + - \Helper\Functional + config: + WPDb: + dsn: '%TEST_SITE_DB_DSN%' + user: '%TEST_SITE_DB_USER%' + password: '%TEST_SITE_DB_PASSWORD%' + dump: 'tests/_data/dump.sql' + populate: true + cleanup: true + waitlock: 10 + url: '%TEST_SITE_WP_URL%' + urlReplacement: true + tablePrefix: '%TEST_SITE_TABLE_PREFIX%' + WPBrowser: + url: '%TEST_SITE_WP_URL%' + adminUsername: '%TEST_SITE_ADMIN_USERNAME%' + adminPassword: '%TEST_SITE_ADMIN_PASSWORD%' + adminPath: '%TEST_SITE_WP_ADMIN_PATH%' + headers: + X_TEST_REQUEST: 1 + X_WPBROWSER_REQUEST: 1 + + WPFilesystem: + wpRootFolder: '%WP_ROOT_FOLDER%' + plugins: '/wp-content/plugins' + mu-plugins: '/wp-content/mu-plugins' + themes: '/wp-content/themes' + uploads: '/wp-content/uploads' \ No newline at end of file diff --git a/tests/nginx/php-7.4.conf b/tests/nginx/php-7.4.conf new file mode 100644 index 0000000..94ec299 --- /dev/null +++ b/tests/nginx/php-7.4.conf @@ -0,0 +1,13 @@ +server { + listen 80; + root /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress; + server_name 127.0.0.1; + index index.php; + location / { + try_files $uri $uri/ /index.php?$args; + } + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; + } +} \ No newline at end of file diff --git a/tests/nginx/php-8.0.conf b/tests/nginx/php-8.0.conf new file mode 100644 index 0000000..e56ef39 --- /dev/null +++ b/tests/nginx/php-8.0.conf @@ -0,0 +1,13 @@ +server { + listen 80; + root /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress; + server_name 127.0.0.1; + index index.php; + location / { + try_files $uri $uri/ /index.php?$args; + } + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; + } +} \ No newline at end of file diff --git a/tests/nginx/php-8.1.conf b/tests/nginx/php-8.1.conf new file mode 100644 index 0000000..3a33728 --- /dev/null +++ b/tests/nginx/php-8.1.conf @@ -0,0 +1,13 @@ +server { + listen 80; + root /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress; + server_name 127.0.0.1; + index index.php; + location / { + try_files $uri $uri/ /index.php?$args; + } + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; + } +} \ No newline at end of file diff --git a/tests/nginx/php-8.2.conf b/tests/nginx/php-8.2.conf new file mode 100644 index 0000000..f071665 --- /dev/null +++ b/tests/nginx/php-8.2.conf @@ -0,0 +1,13 @@ +server { + listen 80; + root /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress; + server_name 127.0.0.1; + index index.php; + location / { + try_files $uri $uri/ /index.php?$args; + } + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + } +} \ No newline at end of file diff --git a/tests/nginx/php-8.3.conf b/tests/nginx/php-8.3.conf new file mode 100644 index 0000000..3e38366 --- /dev/null +++ b/tests/nginx/php-8.3.conf @@ -0,0 +1,13 @@ +server { + listen 80; + root /home/runner/work/convertkit-membermouse/convertkit-membermouse/wordpress; + server_name 127.0.0.1; + index index.php; + location / { + try_files $uri $uri/ /index.php?$args; + } + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; + } +} \ No newline at end of file diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100644 index 0000000..46a00eb --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,10 @@ +# Codeception Test Suite Configuration +# +# Suite for unit tests not relying WordPress code. + +actor: UnitTester +modules: + enabled: + - Asserts + - \Helper\Unit + step_decorators: ~ \ No newline at end of file diff --git a/tests/wpunit.suite.yml b/tests/wpunit.suite.yml new file mode 100644 index 0000000..fd88d3e --- /dev/null +++ b/tests/wpunit.suite.yml @@ -0,0 +1,21 @@ +# Codeception Test Suite Configuration +# +# Suite for unit or integration tests that require WordPress functions and classes. + +actor: WpunitTester +modules: + enabled: + - WPLoader + - \Helper\Wpunit + config: + WPLoader: + wpRootFolder: "%WP_ROOT_FOLDER%" + dbName: "%TEST_DB_NAME%" + dbHost: "%TEST_DB_HOST%" + dbUser: "%TEST_DB_USER%" + dbPassword: "%TEST_DB_PASSWORD%" + tablePrefix: "%TEST_TABLE_PREFIX%" + domain: "%TEST_SITE_WP_DOMAIN%" + adminEmail: "%TEST_SITE_ADMIN_EMAIL%" + title: "Test" + plugins: ['convertkit-membermouse/convertkit-membermouse.php'] # Change to the repository Plugin that we're testing. \ No newline at end of file