diff --git a/src/Admin/UserController.php b/src/Admin/UserController.php index 33d94509..8c3f747c 100644 --- a/src/Admin/UserController.php +++ b/src/Admin/UserController.php @@ -325,6 +325,54 @@ class UserController extends ControllerBase { } + /** + * GET /wechat/status — 微信模板消息配置状态。 + * + * 状态形状与 WeChatNotify::get_status() 对齐(该方法的既定用途即 REST 与 AJAX 复用)。 + */ + public function get_wechat_status(\WP_REST_Request $request): \WP_REST_Response { + if (!class_exists('\AKSRT\Payment\WeChatNotify')) { + return $this->error(__('微信通知组件未加载', 'aksrt'), 500); + } + + return $this->success(['data' => \AKSRT\Payment\WeChatNotify::get_instance()->get_status()]); + } + + /** + * POST /wechat/settings — 更新微信模板消息配置。 + * + * 前端在密钥留空且已配置时发送掩码 ***,此时保留已存密钥; + * 未提供的字段沿用当前值。键白名单由 Integration::update_wechat_settings() 收敛。 + */ + public function update_wechat_settings(\WP_REST_Request $request): \WP_REST_Response { + $params = $this->get_json_params($request); + $current = \AKSRT\Settings\Settings::get_wechat_settings(); + + $enabled = array_key_exists('enabled', $params) ? !empty($params['enabled']) : !empty($current['enabled']); + $app_id = array_key_exists('app_id', $params) + ? sanitize_text_field((string) $params['app_id']) + : (string) ($current['app_id'] ?? ''); + $template_id = array_key_exists('template_id', $params) + ? sanitize_text_field((string) $params['template_id']) + : (string) ($current['template_id'] ?? ''); + + // 掩码 *** 表示「不修改密钥」;仅在显式传入其他值时才覆盖。 + if (array_key_exists('app_secret', $params) && (string) $params['app_secret'] !== '***') { + $app_secret = sanitize_text_field((string) $params['app_secret']); + } else { + $app_secret = (string) ($current['app_secret'] ?? ''); + } + + \AKSRT\Settings\Settings::update_wechat_settings([ + 'enabled' => $enabled, + 'app_id' => $app_id, + 'app_secret' => $app_secret, + 'template_id' => $template_id, + ]); + + return $this->success(['message' => __('微信通知配置已保存', 'aksrt')]); + } + public function rest_handle_credit_settings(\WP_REST_Request $request): \WP_REST_Response { if ($request->get_method() === 'GET') { $config = \AKSRT\Settings\CreditsRuntime::get_credit_settings(); diff --git a/tests/admin/wechat-settings-harness.php b/tests/admin/wechat-settings-harness.php new file mode 100644 index 00000000..e7ac4dfe --- /dev/null +++ b/tests/admin/wechat-settings-harness.php @@ -0,0 +1,151 @@ + prints "WeChat settings harness passed". + */ + +namespace { + define('ABSPATH', __DIR__ . '/'); + + final class WP_REST_Response { + public $data; + public $status; + public function __construct($data = null, int $status = 200) { + $this->data = $data; + $this->status = $status; + } + } + + final class WP_REST_Request { + private array $json; + public function __construct(array $json = []) { $this->json = $json; } + public function get_json_params(): array { return $this->json; } + public function get_method(): string { return 'POST'; } + } + + // ---------- WP function stubs ---------- + function __($s, $d = null) { return (string) $s; } + function sanitize_text_field($s) { return trim(strip_tags((string) $s)); } + function current_user_can($c) { return true; } + function wp_send_json_success($d) { echo (string) json_encode($d); } + function get_transient($k) { return $GLOBALS['wechat_transient'] ?? false; } + function get_option($k, $default = false) { return $GLOBALS['wechat_options'][$k] ?? $default; } + function update_option($k, $v) { $GLOBALS['wechat_options'][$k] = $v; return true; } + + $GLOBALS['wechat_options'] = ['aksrt_wechat_settings' => [ + 'enabled' => true, + 'app_id' => 'wxAPPID', + 'app_secret' => 'SECRET123', + 'template_id' => 'TPL1', + ]]; + $GLOBALS['wechat_transient'] = 'ACCESS_TOKEN'; + + $GLOBALS['wpdb'] = new class { + public $usermeta = 'wp_usermeta'; + public function get_var($sql) { return 7; } + }; +} + +namespace AKSRT\Settings { + // Settings 层仅为薄委托,harness 直接对接真实 Integration。 + class Settings { + public static function get_wechat_settings(): array { return Integration::get_wechat_settings(); } + public static function update_wechat_settings(array $settings): void { Integration::update_wechat_settings($settings); } + } +} + +namespace { + require_once dirname(__DIR__, 2) . '/src/Admin/ControllerBase.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerReportOperations.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerAuthPhoneOperations.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerRestrictionOperations.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerFollowOperations.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerProfileMediaOperations.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/UserController.php'; + require_once dirname(__DIR__, 2) . '/src/Settings/Integration.php'; + require_once dirname(__DIR__, 2) . '/src/Settings/DomainGroups.php'; + require_once dirname(__DIR__, 2) . '/src/Payment/WeChatNotify.php'; + + $failures = []; + $check = static function (string $label, bool $ok) use (&$failures): void { + if (!$ok) { + $failures[] = $label; + } + }; + $stored = static function (): array { + $v = get_option('aksrt_wechat_settings', []); + return is_array($v) ? $v : []; + }; + + $controller = \AKSRT\Admin\UserController::get_instance(); + + // 1) GET /wechat/status 返回完整状态形状(密钥掩码)。 + $response = $controller->get_wechat_status(new WP_REST_Request()); + $data = is_array($response->data) ? ($response->data['data'] ?? []) : []; + $check('status 含 enabled', array_key_exists('enabled', $data) && !empty($data['enabled'])); + $check('status 含 appid', ($data['appid'] ?? '') === 'wxAPPID'); + $check('status configured', !empty($data['configured'])); + $check('status has_token', !empty($data['has_token'])); + $check('status has_template', !empty($data['has_template'])); + $check('status bound_users', ($data['bound_users'] ?? 0) === 7); + $check('status 掩码 app_secret', ($data['app_secret'] ?? '') === '***'); + $check('status allowed_types 非空', !empty($data['allowed_types'])); + + // 2) 完整更新写入全部字段。 + $response = $controller->update_wechat_settings(new WP_REST_Request([ + 'enabled' => true, + 'app_id' => 'wxNEW', + 'app_secret' => 'NEWSECRET', + 'template_id' => 'TPL2', + ])); + $check('更新写入 app_id', ($stored()['app_id'] ?? '') === 'wxNEW'); + $check('更新写入 app_secret', ($stored()['app_secret'] ?? '') === 'NEWSECRET'); + $check('更新写入 template_id', ($stored()['template_id'] ?? '') === 'TPL2'); + $check('更新返回成功消息', str_contains((string) ($response->data['message'] ?? ''), '已保存')); + + // 3) 掩码 *** 保留已存密钥。 + $controller->update_wechat_settings(new WP_REST_Request([ + 'enabled' => true, + 'app_id' => 'wxNEW', + 'app_secret' => '***', + ])); + $check('掩码保留原密钥', ($stored()['app_secret'] ?? '') === 'NEWSECRET'); + + // 4) 缺省字段沿用当前值,开关可关闭。 + $controller->update_wechat_settings(new WP_REST_Request(['enabled' => false])); + $check('缺省字段沿用当前值', ($stored()['app_id'] ?? '') === 'wxNEW' && ($stored()['template_id'] ?? '') === 'TPL2'); + $check('enabled 可关闭', empty($stored()['enabled'])); + + // 5) 输入消毒 + 未知键被白名单丢弃。 + $controller->update_wechat_settings(new WP_REST_Request([ + 'app_id' => 'wxXSS', + 'evil' => 'x', + ])); + $check('app_id 经消毒', ($stored()['app_id'] ?? '') === 'wxXSS'); + $check('未知键被忽略', !array_key_exists('evil', $stored())); + + // 6) 未配置时空密钥可显式清空。 + update_option('aksrt_wechat_settings', ['enabled' => true, 'app_id' => 'a', 'app_secret' => '', 'template_id' => '']); + $controller->update_wechat_settings(new WP_REST_Request(['app_secret' => ''])); + $check('空密钥可清存', ($stored()['app_secret'] ?? '') === ''); + + if ($failures) { + echo "WeChat settings harness FAILED\n"; + foreach ($failures as $failure) { + echo ' - ' . $failure . "\n"; + } + exit(1); + } + + echo "WeChat settings harness passed\n"; +}