message; } public function get_error_data(): array { return $this->data; } } final class WP_REST_Response { public function __construct(public $data = null, public int $status = 200) {} } final class WP_REST_Request { public function __construct(private array $json = []) {} public function get_json_params(): array { return $this->json; } } function __($text, $domain = '') { return $text; } function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); } function sanitize_key($value): string { return strtolower(preg_replace('/[^a-z0-9_-]/', '', (string) $value)); } function is_wp_error($value): bool { return $value instanceof WP_Error; } function get_option($key, $default = false) { return $GLOBALS['wechat_options'][$key] ?? $default; } function update_option($key, $value): bool { $GLOBALS['wechat_options'][$key] = $value; return true; } function get_current_user_id(): int { return 9; } function get_user_meta($user_id, $key, $single = false) { return $key === 'jio_weixingzh_id' && $user_id === 9 ? ($GLOBALS['wechat_openid'] ?? '') : ''; } function home_url($path = ''): string { return 'https://example.test' . $path; } function esc_url_raw($url): string { return (string) $url; } function current_time($format): string { return '2026-11-10 10:00:00'; } function wp_json_encode($value): string { return json_encode($value, JSON_UNESCAPED_UNICODE); } function wp_remote_post($url, $args) { $GLOBALS['wechat_last_post'] = [$url, $args]; return $GLOBALS['wechat_remote_response']; } function wp_remote_retrieve_body($response): string { return (string) ($response['body'] ?? ''); } function wp_remote_retrieve_response_code($response): int { return (int) ($response['response']['code'] ?? 0); } function get_userdata($user_id) { return null; } function get_users($args) { return []; } function add_action($hook, $callback, $priority = 10, $accepted_args = 1): void {} $GLOBALS['wpdb'] = new class { public string $usermeta = 'wp_usermeta'; public function get_var($query): int { return 1; } }; $GLOBALS['wechat_options'] = ['jio_wechat_settings' => []]; $GLOBALS['wechat_openid'] = 'OPENID9'; $GLOBALS['wechat_remote_response'] = ['response' => ['code' => 200], 'body' => '{"errcode":0,"errmsg":"ok"}']; } namespace Jio\Auth { final class WeixinGzh { public static function is_enabled(): bool { return true; } public static function get_instance(): self { return new self(); } public function access_token(): string { return 'ACCESS_TOKEN'; } } } namespace Jio\Settings { class Settings { public static function get_wechat_settings(): array { return Integration::get_wechat_settings(); } public static function update_wechat_settings(array $config): void { Integration::update_wechat_settings($config); } public static function get_weixingzh_settings(): array { return ['gzh_type' => 'service', 'appid' => 'wxAPP', 'appsecret' => 'secret']; } } } namespace { $root = dirname(__DIR__, 2); foreach ([ 'src/Admin/ControllerBase.php', 'src/Admin/UserControllerReportOperations.php', 'src/Admin/UserControllerAuthPhoneOperations.php', 'src/Admin/UserControllerRestrictionOperations.php', 'src/Admin/UserControllerFollowOperations.php', 'src/Admin/UserControllerProfileMediaOperations.php', 'src/Admin/UserController.php', 'src/Settings/WechatTemplates.php', 'src/Settings/Integration.php', 'src/Payment/WeChatNotifyEvents.php', 'src/Payment/WeChatNotify.php', ] as $file) { require_once $root . '/' . $file; } $check = static function (bool $condition, string $name): void { if (!$condition) { fwrite(STDERR, "FAILED: {$name}\n"); exit(1); } }; $controller = \Jio\Admin\UserController::get_instance(); $sender = \Jio\Payment\WeChatNotify::get_instance(); $status = $controller->get_wechat_status(new WP_REST_Request())->data['data']; $check(count($status['types']) === 14, 'available types'); $check($status['bound_users'] === 1 && $status['current_user_bound'], 'real binding key'); $check($status['oauth_ready'], 'oauth status'); $config = ['enabled' => true, 'templates' => [ 'comment_to_parent' => ['enabled' => true, 'id' => 'TPL_REPLY', 'keys' => ['name' => 'thing1', 'time' => 'time2', 'content' => '']], ]]; $saved = $controller->update_wechat_settings(new WP_REST_Request($config)); $check($saved->status === 200, 'save accepted'); $stored = get_option('jio_wechat_settings'); $check($stored['templates']['comment_to_parent']['id'] === 'TPL_REPLY', 'template persisted'); $check(count($stored['templates']) === 14, 'all types normalized'); $check(!isset($stored['app_id']) && !isset($stored['app_secret']) && !isset($stored['template_id']), 'obsolete settings removed'); $check($controller->get_wechat_status(new WP_REST_Request())->data['data']['config'] === $stored, 'round trip'); $invalid = $controller->update_wechat_settings(new WP_REST_Request(['enabled' => true, 'templates' => [ 'comment_to_parent' => ['enabled' => true, 'id' => 'TPL', 'keys' => ['name' => 'bad key']], ]])); $check($invalid->status === 400 && get_option('jio_wechat_settings') === $stored, 'invalid mapping rejected without write'); $invalid = $controller->update_wechat_settings(new WP_REST_Request(['enabled' => true, 'templates' => ['unknown' => ['enabled' => true, 'id' => 'X']]])); $check($invalid->status === 400, 'unknown type rejected'); $invalid = $controller->update_wechat_settings(new WP_REST_Request(['enabled' => true, 'templates' => [ 'comment_to_parent' => ['enabled' => true, 'id' => 'TPL', 'keys' => ['name' => 'keyword3']], ]])); $check($invalid->status === 400 && get_option('jio_wechat_settings') === $stored, 'fallback mapping collision rejected'); $draft = $controller->update_wechat_settings(new WP_REST_Request(['enabled' => true, 'templates' => [ 'comment_to_parent' => ['enabled' => true, 'id' => '', 'keys' => []], ]])); $check($draft->status === 200, 'incomplete template can be saved as draft'); $blocked = $controller->test_wechat_template(new WP_REST_Request(['type' => 'comment_to_parent'])); $check($blocked->status === 400, 'incomplete template cannot be tested'); $controller->update_wechat_settings(new WP_REST_Request($config)); $sent = $sender->send_event(9, 'comment_to_parent', ['name' => str_repeat('中', 25), 'time' => '2026-11-10', 'content' => '测试回复'], 'https://example.test/comment/1'); $body = json_decode($GLOBALS['wechat_last_post'][1]['body'], true); $check($sent, 'send succeeds'); $check($body['touser'] === 'OPENID9' && $body['template_id'] === 'TPL_REPLY', 'recipient and template'); $check($body['url'] === 'https://example.test/comment/1', 'deep link'); $check($body['data']['thing1']['value'] === str_repeat('中', 16) . '...', 'thing truncation'); $check($body['data']['time2']['value'] === '2026-11-10' && $body['data']['keyword3']['value'] === '测试回复', 'mapping and fallback'); $tested = $controller->test_wechat_template(new WP_REST_Request(['type' => 'comment_to_parent'])); $check($tested->status === 200, 'test endpoint sends'); $GLOBALS['wechat_remote_response'] = ['response' => ['code' => 200], 'body' => '{"errcode":40013,"errmsg":"invalid appid"}']; $tested = $controller->test_wechat_template(new WP_REST_Request(['type' => 'comment_to_parent'])); $check($tested->status === 502 && str_contains($tested->data['message'], '40013'), 'api failure exposed'); $GLOBALS['wechat_openid'] = ''; $tested = $controller->test_wechat_template(new WP_REST_Request(['type' => 'comment_to_parent'])); $check($tested->status === 400, 'unbound user rejected'); echo "WeChat settings harness passed\n"; }