get_error_message()); } assert_true($activationOutput === '', 'Plugin activation produced unexpected output: '.$activationOutput); } assert_true(is_plugin_active(TEST_PLUGIN), 'Plugin was not activated.'); update_option('mahak_jwt_secret', TEST_SECRET); update_option('mahak_user_identifier', 'email'); update_option('mahak_login_route', 'mahak/login'); update_option('mahak_after_login_route', 'wp-admin/profile.php'); pass('Plugin activated and configured'); } function base64_url_encode(string $value): string { return rtrim(strtr(base64_encode($value), '+/', '-_'), '='); } function make_jwt(array $payload, string $secret): string { $header = base64_url_encode(json_encode(['typ' => 'JWT', 'alg' => 'HS256'], JSON_THROW_ON_ERROR)); $body = base64_url_encode(json_encode($payload, JSON_THROW_ON_ERROR)); $signature = base64_url_encode(hash_hmac('sha256', $header.'.'.$body, $secret, true)); return $header.'.'.$body.'.'.$signature; } function http_request(string $url, string $cookieHeader = ''): array { $headers = "Connection: close\r\n"; if ($cookieHeader !== '') { $headers .= "Cookie: $cookieHeader\r\n"; } $context = stream_context_create([ 'http' => [ 'ignore_errors' => true, 'timeout' => 20, 'follow_location' => 0, 'max_redirects' => 1, 'header' => $headers, ], ]); $body = @file_get_contents($url, false, $context); $responseHeaders = $http_response_header ?? []; $status = 0; if (isset($responseHeaders[0]) && preg_match('/^HTTP\/\S+\s+(\d+)/', $responseHeaders[0], $matches)) { $status = (int) $matches[1]; } return [ 'status' => $status, 'headers' => $responseHeaders, 'body' => $body === false ? '' : $body, ]; } function wait_for_http(): void { for ($attempt = 1; $attempt <= 60; $attempt++) { $response = http_request(TEST_SITE_URL.'/wp-login.php'); if ($response['status'] > 0) { pass('WordPress HTTP server is reachable'); return; } sleep(1); } fail('WordPress HTTP server did not become reachable.'); } function header_contains(array $headers, string $needle): bool { foreach ($headers as $header) { if (stripos($header, $needle) !== false) { return true; } } return false; } function cookie_header_from_response(array $headers): string { $cookies = []; foreach ($headers as $header) { if (stripos($header, 'Set-Cookie:') !== 0) { continue; } $cookie = trim(substr($header, strlen('Set-Cookie:'))); $cookiePair = explode(';', $cookie, 2)[0] ?? ''; if ($cookiePair !== '') { $cookies[] = $cookiePair; } } return implode('; ', $cookies); } function test_valid_authentication(): void { $token = make_jwt([ 'iat' => time() - 10, 'exp' => time() + 300, 'data' => [ 'name' => TEST_NAME, 'email' => TEST_EMAIL, 'mobile' => '9123456789', 'username' => 'mahak-auth-user', ], ], TEST_SECRET); $response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token)); assert_true($response['status'] === 302, 'Valid authentication request should return HTTP 302.'); assert_true( header_contains($response['headers'], 'Set-Cookie: wordpress_logged_in_'), 'Valid authentication request did not issue a WordPress logged-in cookie.' ); assert_true( header_contains($response['headers'], 'Location: '.TEST_SITE_URL.'/wp-admin/profile.php'), 'Valid authentication request did not redirect to the configured route.' ); $user = get_user_by('email', TEST_EMAIL); assert_true($user !== false, 'Authenticated user was not created.'); assert_true($user->display_name === TEST_NAME, 'Authenticated user display name was not saved.'); $cookieHeader = cookie_header_from_response($response['headers']); $profileResponse = http_request(TEST_SITE_URL.'/wp-admin/profile.php', $cookieHeader); assert_true( $profileResponse['status'] !== 302 || !header_contains($profileResponse['headers'], 'wp-login.php'), 'Issued cookies did not authenticate a follow-up WordPress admin request.' ); pass('Valid JWT creates/logs in user and issued cookies authenticate follow-up request'); } function test_invalid_authentication(): void { $token = make_jwt([ 'iat' => time() - 10, 'exp' => time() + 300, 'data' => [ 'name' => 'Invalid User', 'email' => 'invalid-user@example.test', ], ], TEST_SECRET); $response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token.'tampered')); assert_true( $response['status'] === 500, 'Invalid authentication request should return HTTP 500. Got HTTP '.$response['status'].' with body: '.substr($response['body'], 0, 200) ); assert_true( !header_contains($response['headers'], 'Set-Cookie: wordpress_logged_in_'), 'Invalid authentication request issued a logged-in cookie.' ); assert_true( strpos($response['body'], 'JWT signature is invalid.') === false, 'Debug details were exposed while debug mode was disabled.' ); pass('Invalid JWT is rejected without login cookies or debug details'); } function test_debug_error_details(): void { update_option('mahak_debug_mode', 1); $futureIat = time() + 300; $token = make_jwt([ 'iat' => $futureIat, 'exp' => $futureIat + 600, 'data' => [ 'name' => 'Future User', 'email' => 'future-user@example.test', ], ], TEST_SECRET); $response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token)); assert_true($response['status'] === 500, 'Debug error response should return HTTP 500.'); assert_true( strpos($response['body'], 'JWT cannot be used before iat.') !== false, 'Debug response did not contain the underlying exception message.' ); assert_true( strpos($response['body'], 'UnexpectedValueException') !== false, 'Debug response did not contain the exception class.' ); assert_true( strpos($response['body'], 'WordPress server diagnostics') !== false && strpos($response['body'], 'Unix timestamp') !== false && strpos($response['body'], 'WordPress timezone') !== false, 'Debug response did not contain WordPress server clock and timezone details.' ); assert_true( strpos($response['body'], 'JWT timing claims') !== false && strpos($response['body'], (string) $futureIat) !== false && strpos($response['body'], 'seconds vs server') !== false, 'Debug response did not contain safe JWT timing diagnostics.' ); assert_true( strpos($response['body'], 'Copy Markdown') !== false && strpos($response['body'], '# Mahak Authentication Error') !== false, 'Debug response did not contain a copyable Markdown report.' ); assert_true( strpos($response['body'], TEST_SECRET) === false && strpos($response['body'], $token) === false, 'Debug response exposed the JWT token or signing secret.' ); assert_true( strpos($response['body'], 'Sanitized stack trace') !== false, 'Debug response did not contain the sanitized stack trace.' ); update_option('mahak_debug_mode', 0); pass('Debug report includes clock diagnostics and Markdown without token or secret leakage'); } function test_plugin_update_discovery(): void { $packageUrl = 'https://github.com/dabestaniha/mahak-authenticate-bridge/releases/download/v2.3.0/mahak-authenticate-bridge.zip'; $mockRelease = function ($response, array $request, string $url) use ($packageUrl) { if ($url !== 'https://api.github.com/repos/dabestaniha/mahak-authenticate-bridge/releases/latest') { return $response; } return [ 'headers' => [], 'body' => json_encode([ 'tag_name' => 'v2.3.0', 'html_url' => 'https://github.com/dabestaniha/mahak-authenticate-bridge/releases/tag/v2.3.0', 'body' => 'Test release', 'draft' => false, 'prerelease' => false, 'assets' => [[ 'name' => 'mahak-authenticate-bridge.zip', 'browser_download_url' => $packageUrl, ]], ], JSON_THROW_ON_ERROR), 'response' => ['code' => 200, 'message' => 'OK'], 'cookies' => [], 'filename' => null, ]; }; delete_site_transient('mahak_authenticate_bridge_release'); add_filter('pre_http_request', $mockRelease, 10, 3); $updates = apply_filters('pre_set_site_transient_update_plugins', (object) [ 'checked' => [TEST_PLUGIN => MAHAK_AUTHENTICATE_BRIDGE_VERSION], 'response' => [], ]); remove_filter('pre_http_request', $mockRelease, 10); assert_true(isset($updates->response[TEST_PLUGIN]), 'A newer GitHub release was not offered as a WordPress update.'); assert_true($updates->response[TEST_PLUGIN]->new_version === '2.3.0', 'The offered plugin version was incorrect.'); assert_true($updates->response[TEST_PLUGIN]->package === $packageUrl, 'The release package URL was incorrect.'); pass('Published GitHub releases are discovered by the WordPress updater'); } boot_wordpress(); install_wordpress(); activate_and_configure_plugin(); wait_for_http(); test_valid_authentication(); test_invalid_authentication(); test_debug_error_details(); test_plugin_update_discovery(); echo "[OK] Authentication integration test passed\n";