test: Add certificate generation tests and deployment tools

- Add E2E test for certificate generation functionality
- Create deployment configuration script for staging server
- Add certificate email testing script
- Update composer.lock with TCPDF dependencies

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bengizmo 2025-05-20 15:32:29 -03:00
parent 964d5f75a8
commit 42138339cb
4 changed files with 393 additions and 28 deletions

View file

@ -0,0 +1,10 @@
#!/bin/bash
# Deployment Configuration
PROJECT_ROOT="/Users/ben/dev/upskill-event-manager"
REMOTE_HOST="146.190.76.204"
REMOTE_USER="roodev"
REMOTE_PATH_BASE="/home/974670.cloudwaysapps.com/uberrxmprk/public_html"
PLUGIN_SLUG="hvac-community-events"
REMOTE_PLUGIN_PATH="/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events"
LOCAL_PLUGIN_PATH="/Users/ben/dev/upskill-event-manager/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events"

View file

@ -0,0 +1,149 @@
#!/bin/bash
# Script to test certificate email functionality on staging server
# Load configuration
source bin/deploy-config.sh
# Set email address to test with
TEST_EMAIL="ben@tealmaker.com"
echo "=== Testing Certificate Email Functionality ==="
echo "Remote host: $REMOTE_HOST"
echo "Remote user: $REMOTE_USER"
echo "WordPress path: $REMOTE_PATH_BASE"
echo "Test email: $TEST_EMAIL"
echo "==============================="
# Connect to the remote server and run a WP-CLI command to create a test event, attendee, and certificate
ssh $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_PATH_BASE && \
wp eval '
// Create a test event if needed
\$event_exists = get_posts(array(
\"post_type\" => \"tribe_events\",
\"meta_key\" => \"_test_certificate_event\",
\"meta_value\" => \"yes\",
\"posts_per_page\" => 1
));
if (empty(\$event_exists)) {
\$event_id = wp_insert_post(array(
\"post_title\" => \"Test Certificate Event\",
\"post_content\" => \"This is a test event for certificate generation\",
\"post_status\" => \"publish\",
\"post_type\" => \"tribe_events\"
));
update_post_meta(\$event_id, \"_test_certificate_event\", \"yes\");
echo \"Created test event with ID: {\$event_id}\\n\";
} else {
\$event_id = \$event_exists[0]->ID;
echo \"Using existing test event with ID: {\$event_id}\\n\";
}
// Create a test attendee
\$attendee_exists = get_posts(array(
\"post_type\" => \"tribe_rsvp_attendees\",
\"meta_key\" => \"_tribe_tickets_email\",
\"meta_value\" => \"$TEST_EMAIL\",
\"posts_per_page\" => 1
));
if (empty(\$attendee_exists)) {
\$attendee_id = wp_insert_post(array(
\"post_title\" => \"Test Attendee\",
\"post_status\" => \"publish\",
\"post_type\" => \"tribe_rsvp_attendees\"
));
update_post_meta(\$attendee_id, \"_tribe_tickets_email\", \"$TEST_EMAIL\");
update_post_meta(\$attendee_id, \"_tribe_tickets_full_name\", \"Test Attendee\");
update_post_meta(\$attendee_id, \"_tribe_rsvp_event\", \$event_id);
update_post_meta(\$attendee_id, \"_tribe_rsvp_status\", \"yes\");
update_post_meta(\$attendee_id, \"_tribe_tickets_attendee_status\", \"yes\");
update_post_meta(\$attendee_id, \"check_in\", \"1\");
echo \"Created test attendee with ID: {\$attendee_id}\\n\";
} else {
\$attendee_id = \$attendee_exists[0]->ID;
echo \"Using existing attendee with ID: {\$attendee_id}\\n\";
}
// Load certificate manager
require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-manager.php\";
require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-generator.php\";
\$certificate_manager = HVAC_Certificate_Manager::instance();
\$certificate_generator = HVAC_Certificate_Generator::instance();
// Generate certificate
\$certificate_exists = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id);
if (!\$certificate_exists) {
\$result = \$certificate_generator->generate_certificate(\$event_id, \$attendee_id);
if (\$result) {
\$certificate = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id);
echo \"Generated certificate with ID: {\$certificate->certificate_id}\\n\";
} else {
echo \"Failed to generate certificate\\n\";
}
} else {
echo \"Using existing certificate with ID: {\$certificate_exists->certificate_id}\\n\";
}
// Get existing certificate
\$certificate = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id);
if (!\$certificate) {
echo \"No certificate found\\n\";
exit;
}
// Email certificate
require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-security.php\";
\$certificate_security = HVAC_Certificate_Security::instance();
// Generate secure download URL
\$event = get_post(\$certificate->event_id);
\$attendee_name = get_post_meta(\$certificate->attendee_id, \"_tribe_tickets_full_name\", true);
\$certificate_data = array(
\"file_path\" => \$certificate->file_path,
\"event_name\" => \$event->post_title,
\"attendee_name\" => \$attendee_name
);
\$download_url = \$certificate_security->generate_download_token(\$certificate->certificate_id, \$certificate_data, 7 * DAY_IN_SECONDS);
if (!\$download_url) {
echo \"Failed to generate download URL\\n\";
exit;
}
// Email subject
\$subject = sprintf(
\"Your Certificate for %s\",
\$event->post_title
);
// Email body
\$message = sprintf(
\"Hello %s,\\n\\nThank you for attending %s.\\n\\nYour certificate of completion is now available. Please click the link below to download your certificate:\\n\\n%s\\n\\nThis link will expire in 7 days.\\n\\nRegards,\\nTest System\",
\$attendee_name,
\$event->post_title,
\$download_url
);
// Send email
\$headers = array(\"Content-Type: text/plain; charset=UTF-8\");
\$sent = wp_mail(\"$TEST_EMAIL\", \$subject, \$message, \$headers);
if (\$sent) {
// Record email sent
\$certificate_manager->mark_certificate_emailed(\$certificate->certificate_id);
echo \"Certificate email sent successfully to $TEST_EMAIL\\n\";
} else {
echo \"Failed to send certificate email\\n\";
}
'
"
echo "Email verification complete. Please check $TEST_EMAIL for the certificate email."

View file

@ -0,0 +1,127 @@
import { test, expect } from '@playwright/test';
/**
* Certificate Generation End-to-End Test
*
* This test verifies the certificate generation functionality:
* - Login as a trainer
* - Navigate to an event summary page
* - Generate certificates for attendees
* - View certificate
* - Email certificate to a test email
* - Revoke certificate
*/
test.describe('Certificate Generation Tests', () => {
const stagingUrl = 'https://wordpress-974670-5399585.cloudwaysapps.com/';
const loginUrl = `${stagingUrl}community-login/`;
const dashboardUrl = `${stagingUrl}hvac-dashboard/`;
const testEmail = 'ben@tealmaker.com';
test.beforeEach(async ({ page }) => {
// Login as trainer
await page.goto(loginUrl);
await page.fill('input[name="log"]', 'test_trainer');
await page.fill('input[name="pwd"]', 'Test123!');
await page.click('input[type="submit"]');
// Verify login was successful by checking for dashboard
await expect(page).toHaveURL(dashboardUrl);
});
test('Generate and manage certificates for an event', async ({ page }) => {
// Navigate to the dashboard to find an event
await page.goto(dashboardUrl);
// Click on the first event summary link
await page.click('.hvac-event-title a');
// Wait for the event summary page to load
await expect(page.locator('h1')).toContainText('Summary');
// Check if we have attendees
const hasAttendees = await page.locator('.hvac-transactions-table').isVisible();
if (hasAttendees) {
// Check if any attendee doesn't have a certificate yet
const generateButtonExists = await page.locator('.hvac-cert-action:text("Generate")').isVisible();
if (generateButtonExists) {
// Generate a certificate for an attendee
await page.click('.hvac-cert-action:text("Generate")');
// Navigate to the generate certificates page
await expect(page.locator('h1')).toContainText('Generate Certificates');
// Check the 'checked_in_only' checkbox
await page.check('#checked-in-only-checkbox');
// Generate certificates
await page.click('button[type="submit"]');
// Wait for success message
await expect(page.locator('.hvac-success-message')).toBeVisible();
// Go back to the event summary
await page.click('a:text("Back to Event Summary")');
}
// Check if there are any generated certificates
const viewButtonExists = await page.locator('.hvac-cert-action:text("View")').isVisible();
if (viewButtonExists) {
// View a certificate
await page.click('.hvac-cert-action:text("View")', { force: true });
// Wait for the certificate modal to appear
await expect(page.locator('#hvac-certificate-modal')).toBeVisible();
// Wait for the certificate iframe to load
await page.waitForSelector('#hvac-certificate-preview[src^="http"]');
// Close the modal
await page.click('.hvac-modal-close');
// Email a certificate
await page.click('.hvac-cert-action:text("Email")', { force: true });
// Confirm the email dialog
await page.once('dialog', dialog => dialog.accept());
// Wait for success message
await page.waitForTimeout(2000); // Wait for alert to appear and dismiss
// Revoke a certificate
await page.click('.hvac-cert-action:text("Revoke")', { force: true });
// Enter reason in the prompt
await page.once('dialog', dialog => dialog.accept('Revocation test'));
// Wait for status to update to Revoked
await expect(page.locator('td:has-text("Revoked")')).toBeVisible();
} else {
console.log('No generated certificates available to test with');
}
} else {
console.log('No attendees found for the event');
}
});
test('Navigate to Certificate Reports page', async ({ page }) => {
// Navigate to certificate reports page
await page.goto(`${stagingUrl}certificate-reports/`);
// Verify the page loaded successfully
await expect(page.locator('h1')).toContainText('Certificate Reports');
// Try filtering certificates
await page.selectOption('select[name="filter_status"]', 'all');
await page.click('button[type="submit"]');
// Check for certificate data or empty message
const hasReports = await page.locator('.hvac-certificate-table').isVisible();
if (!hasReports) {
await expect(page.locator('.hvac-no-certificates')).toBeVisible();
}
});
});

View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d03d5891db11eabba74e5b9558c9c7b9", "content-hash": "fabbbe60cdbad1e1d198c566db326a4c",
"packages": [ "packages": [
{ {
"name": "composer/installers", "name": "composer/installers",
@ -156,6 +156,77 @@
} }
], ],
"time": "2021-09-13T08:19:44+00:00" "time": "2021-09-13T08:19:44+00:00"
},
{
"name": "tecnickcom/tcpdf",
"version": "6.9.4",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
"reference": "c838d7f7babb0d35763acfb9ecf78c3f45966f83"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/c838d7f7babb0d35763acfb9ecf78c3f45966f83",
"reference": "c838d7f7babb0d35763acfb9ecf78c3f45966f83",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=7.1.0"
},
"type": "library",
"autoload": {
"classmap": [
"config",
"include",
"tcpdf.php",
"tcpdf_barcodes_1d.php",
"tcpdf_barcodes_2d.php",
"include/tcpdf_colors.php",
"include/tcpdf_filters.php",
"include/tcpdf_font_data.php",
"include/tcpdf_fonts.php",
"include/tcpdf_images.php",
"include/tcpdf_static.php",
"include/barcodes/datamatrix.php",
"include/barcodes/pdf417.php",
"include/barcodes/qrcode.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
"role": "lead"
}
],
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
"homepage": "http://www.tcpdf.org/",
"keywords": [
"PDFD32000-2008",
"TCPDF",
"barcodes",
"datamatrix",
"pdf",
"pdf417",
"qrcode"
],
"support": {
"issues": "https://github.com/tecnickcom/TCPDF/issues",
"source": "https://github.com/tecnickcom/TCPDF/tree/6.9.4"
},
"funding": [
{
"url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ",
"type": "custom"
}
],
"time": "2025-05-13T11:34:35+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -349,20 +420,20 @@
}, },
{ {
"name": "hamcrest/hamcrest-php", "name": "hamcrest/hamcrest-php",
"version": "v2.0.1", "version": "v2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git", "url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.3|^7.0|^8.0" "php": "^7.4|^8.0"
}, },
"replace": { "replace": {
"cordoval/hamcrest-php": "*", "cordoval/hamcrest-php": "*",
@ -370,8 +441,8 @@
"kodova/hamcrest-php": "*" "kodova/hamcrest-php": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/php-file-iterator": "^1.4 || ^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -394,9 +465,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/hamcrest/hamcrest-php/issues", "issues": "https://github.com/hamcrest/hamcrest-php/issues",
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1"
}, },
"time": "2020-07-09T08:09:16+00:00" "time": "2025-04-30T06:54:44+00:00"
}, },
{ {
"name": "mockery/mockery", "name": "mockery/mockery",
@ -483,16 +554,16 @@
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.13.0", "version": "1.13.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414" "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414", "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -531,7 +602,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/myclabs/DeepCopy/issues", "issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
}, },
"funding": [ "funding": [
{ {
@ -539,7 +610,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-02-12T12:17:51+00:00" "time": "2025-04-29T12:36:36+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
@ -1038,16 +1109,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.22", "version": "9.6.23",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1058,7 +1129,7 @@
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1", "myclabs/deep-copy": "^1.13.1",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.2.1",
"php": ">=7.3", "php": ">=7.3",
@ -1121,7 +1192,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23"
}, },
"funding": [ "funding": [
{ {
@ -1132,12 +1203,20 @@
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "type": "github"
}, },
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{ {
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-05T13:48:26+00:00" "time": "2025-05-02T06:40:34+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -2154,16 +2233,16 @@
}, },
{ {
"name": "wp-phpunit/wp-phpunit", "name": "wp-phpunit/wp-phpunit",
"version": "6.7.2", "version": "6.8.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/wp-phpunit/wp-phpunit.git", "url": "https://github.com/wp-phpunit/wp-phpunit.git",
"reference": "e2bb06bacc92a8e9e405e83f56989e8ed9359db1" "reference": "a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/e2bb06bacc92a8e9e405e83f56989e8ed9359db1", "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f",
"reference": "e2bb06bacc92a8e9e405e83f56989e8ed9359db1", "reference": "a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -2198,7 +2277,7 @@
"issues": "https://github.com/wp-phpunit/issues", "issues": "https://github.com/wp-phpunit/issues",
"source": "https://github.com/wp-phpunit/wp-phpunit" "source": "https://github.com/wp-phpunit/wp-phpunit"
}, },
"time": "2025-02-12T01:22:52+00:00" "time": "2025-04-16T01:40:54+00:00"
}, },
{ {
"name": "yoast/phpunit-polyfills", "name": "yoast/phpunit-polyfills",