提交
This commit is contained in:
25
tests/customer_center_action_dispatch_self_check.php
Normal file
25
tests/customer_center_action_dispatch_self_check.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$controller = file_get_contents(__DIR__ . '/../app/miniapi/controller/CustomerCenter.php');
|
||||
if (!is_string($controller)) {
|
||||
throw new RuntimeException('Unable to read customer center controller');
|
||||
}
|
||||
if (!str_contains($controller, "param('_action', '')")) {
|
||||
throw new RuntimeException('Applicant endpoint must inspect the explicit action parameter');
|
||||
}
|
||||
if (!str_contains($controller, "\$action === 'save'")) {
|
||||
throw new RuntimeException('Applicant endpoint must dispatch the save action');
|
||||
}
|
||||
if (!str_contains($controller, "\$action === 'remove'")) {
|
||||
throw new RuntimeException('Applicant endpoint must dispatch the remove action');
|
||||
}
|
||||
|
||||
foreach (['miniprogram', 'miniprogram-bj'] as $miniapp) {
|
||||
$profiles = file_get_contents(__DIR__ . '/../../' . $miniapp . '/pages/profiles/index.js');
|
||||
if (!is_string($profiles) || !str_contains($profiles, '/miniapi/customer-center/applicant?_action=remove')) {
|
||||
throw new RuntimeException($miniapp . ' must call the explicit remove action');
|
||||
}
|
||||
}
|
||||
|
||||
echo "Customer center action dispatch self-check passed\n";
|
||||
22
tests/customer_center_route_order_self_check.php
Normal file
22
tests/customer_center_route_order_self_check.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$routes = file_get_contents(__DIR__ . '/../route/app.php');
|
||||
if (!is_string($routes)) {
|
||||
throw new RuntimeException('Unable to read route configuration');
|
||||
}
|
||||
|
||||
$detail = strpos($routes, "Route::post('customer-center/applicant',");
|
||||
$save = strpos($routes, "Route::post('customer-center/applicant/save',");
|
||||
$remove = strpos($routes, "Route::post('customer-center/applicant/remove',");
|
||||
if ($detail === false || $save === false || $remove === false) {
|
||||
throw new RuntimeException('Customer center applicant routes are incomplete');
|
||||
}
|
||||
if ($save > $detail || $remove > $detail) {
|
||||
throw new RuntimeException('Specific applicant routes must be registered before the detail route');
|
||||
}
|
||||
if (!preg_match("/Route::post\('customer-center\\/applicant',\s*'CustomerCenter\\/applicant'\)->completeMatch\(\);/", $routes)) {
|
||||
throw new RuntimeException('Applicant detail route must use complete matching');
|
||||
}
|
||||
|
||||
echo "Customer center route order self-check passed\n";
|
||||
51
tests/evus_correction_route_self_check.php
Normal file
51
tests/evus_correction_route_self_check.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use app\miniapi\service\EvusCorrection\EvusCorrectionService;
|
||||
|
||||
function miniCorrectionAssertSame(mixed $expected, mixed $actual, string $message): void
|
||||
{
|
||||
if ($expected !== $actual) {
|
||||
throw new RuntimeException($message . PHP_EOL
|
||||
. 'Expected: ' . var_export($expected, true) . PHP_EOL
|
||||
. 'Actual: ' . var_export($actual, true));
|
||||
}
|
||||
}
|
||||
|
||||
$service = new EvusCorrectionService();
|
||||
miniCorrectionAssertSame(
|
||||
'https://user.example.com/home/evus_correction/detail',
|
||||
$service->endpoint('https://user.example.com/home/users/login', 'detail'),
|
||||
'Detail must use the existing user URL host and isolated upstream controller'
|
||||
);
|
||||
miniCorrectionAssertSame(
|
||||
'https://user.example.com/home/evus_correction/submit',
|
||||
$service->endpoint('https://user.example.com/home/users/login?source=mini', 'submit'),
|
||||
'Submit must remove the existing users action and query string'
|
||||
);
|
||||
miniCorrectionAssertSame('', $service->endpoint('https://user.example.com/home/users/login', 'delete'), 'Unknown upstream actions must be rejected');
|
||||
miniCorrectionAssertSame(
|
||||
['user_id' => 88, 'order_sn' => 'EVUS-1'],
|
||||
$service->payload(88, ['user_id' => 999, 'order_sn' => 'EVUS-1']),
|
||||
'Authenticated user ID must override an untrusted client value'
|
||||
);
|
||||
|
||||
$routes = file_get_contents(__DIR__ . '/../route/app.php');
|
||||
if (!is_string($routes)
|
||||
|| !str_contains($routes, "Route::get('evus/correction', 'EvusCorrection/detail');")
|
||||
|| !str_contains($routes, "Route::post('evus/correction/submit', 'EvusCorrection/submit');")
|
||||
) {
|
||||
throw new RuntimeException('The miniapi must register explicit correction detail and submit routes');
|
||||
}
|
||||
|
||||
$upload = file_get_contents(__DIR__ . '/../app/miniapi/controller/Upload.php');
|
||||
if (!is_string($upload)
|
||||
|| !str_contains($upload, "'evus_correction'")
|
||||
|| !str_contains($upload, "'evus-correction/' . \$contactUserId . '/' . \$fileName")
|
||||
) {
|
||||
throw new RuntimeException('The correction upload scene must require login and use the account-scoped OSS prefix');
|
||||
}
|
||||
|
||||
echo "Miniapi EVUS correction route self-check passed\n";
|
||||
40
tests/legal_app_config_self_check.php
Normal file
40
tests/legal_app_config_self_check.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$config = require dirname(__DIR__) . '/config/mini_legal.php';
|
||||
$app = $config['businesses']['evus']['apps']['evus_jn_app'] ?? [];
|
||||
|
||||
if (($app['company_code'] ?? '') !== 'jn') {
|
||||
fwrite(STDERR, "evus_jn_app must use the Jinan company.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
foreach (['terms', 'privacy'] as $type) {
|
||||
$intro = (string)($app['documents'][$type]['intro_template'] ?? '');
|
||||
if (strpos($intro, '移动应用') === false) {
|
||||
fwrite(STDERR, "$type must identify the Android mobile app.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$privacyAppendix = (string)($app['documents']['privacy']['append_content'] ?? '');
|
||||
foreach (['支付宝', '微信 OpenSDK', 'OCR', '阿里云 OSS'] as $service) {
|
||||
if (strpos($privacyAppendix, $service) === false) {
|
||||
fwrite(STDERR, "privacy disclosure is missing $service.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$privacyContent = (string)($app['documents']['privacy']['content'] ?? '');
|
||||
foreach (['手机号码', '护照', '美国签证', '订单', '支付结果', '保存期限'] as $topic) {
|
||||
if (strpos($privacyContent, $topic) === false) {
|
||||
fwrite(STDERR, "Android privacy policy is missing $topic.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (stripos($privacyContent, 'cookie') !== false || strpos($privacyContent, '计算机') !== false) {
|
||||
fwrite(STDERR, "Android privacy policy must not contain legacy website-only collection claims.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "legal app config self-check passed\n";
|
||||
34
tests/order_source_self_check.php
Normal file
34
tests/order_source_self_check.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('env')) {
|
||||
function env(string $name, $default = null)
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
$config = require __DIR__ . '/../config/miniapp.php';
|
||||
$apps = (array)($config['apps'] ?? []);
|
||||
|
||||
if (($apps['evus_jn_app']['order_source'] ?? '') !== 'jnandroid') {
|
||||
throw new RuntimeException('Android EVUS orders must use source jnandroid.');
|
||||
}
|
||||
|
||||
if (($apps['jiuzhouqian-android']['order_source'] ?? '') !== 'jnandroid') {
|
||||
throw new RuntimeException('Legacy Android clients must use source jnandroid.');
|
||||
}
|
||||
|
||||
foreach (['evus_jn_mp', 'evus_bj_mp'] as $miniAppCode) {
|
||||
if (($apps[$miniAppCode]['order_source'] ?? '') !== 'qlwxmini') {
|
||||
throw new RuntimeException($miniAppCode . ' must keep source qlwxmini.');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['Evus.php', 'Esta.php'] as $controllerFile) {
|
||||
$controller = file_get_contents(__DIR__ . '/../app/miniapi/controller/' . $controllerFile);
|
||||
if (!is_string($controller) || !str_contains($controller, "['order_source'] ?? 'qlwxmini'")) {
|
||||
throw new RuntimeException($controllerFile . ' must read source from the current mini app config.');
|
||||
}
|
||||
}
|
||||
|
||||
echo "order source self-check passed\n";
|
||||
53
tests/unit/AppUpdateServiceTest.php
Normal file
53
tests/unit/AppUpdateServiceTest.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\unit;
|
||||
|
||||
use app\miniapi\service\AppUpdateService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class AppUpdateServiceTest extends TestCase
|
||||
{
|
||||
public function testBootstrapUsesStableGatewayForRequestedStoreChannel(): void
|
||||
{
|
||||
$result = (new AppUpdateService())->bootstrap([
|
||||
'latest_version_code' => 21,
|
||||
'latest_version_name' => '1.0.19',
|
||||
'gateway_url' => 'https://miniapi.jzvisa.cn/miniapi/app/update',
|
||||
], 'huawei');
|
||||
|
||||
self::assertSame(21, $result['latest_version_code']);
|
||||
self::assertSame('https://miniapi.jzvisa.cn/miniapi/app/update?channel=huawei', $result['download_url']);
|
||||
}
|
||||
|
||||
public function testUnknownChannelFallsBackToOfficial(): void
|
||||
{
|
||||
$service = new AppUpdateService();
|
||||
|
||||
self::assertSame('official', $service->normalizeChannel('unknown-store'));
|
||||
self::assertSame('official', $service->normalizeChannel(''));
|
||||
}
|
||||
|
||||
public function testTextFalseDoesNotEnableForcedUpdate(): void
|
||||
{
|
||||
$result = (new AppUpdateService())->bootstrap([
|
||||
'force_update' => 'false',
|
||||
], 'official');
|
||||
|
||||
self::assertFalse($result['force_update']);
|
||||
}
|
||||
|
||||
public function testStoreTargetFallsBackToOfficialAndRejectsUnsafeUrls(): void
|
||||
{
|
||||
$service = new AppUpdateService();
|
||||
$config = [
|
||||
'store_urls' => [
|
||||
'official' => 'https://jzvisa.cn/evus',
|
||||
'huawei' => 'javascript:alert(1)',
|
||||
],
|
||||
];
|
||||
|
||||
self::assertSame('https://jzvisa.cn/evus', $service->storeTarget($config, 'huawei'));
|
||||
self::assertSame('https://jzvisa.cn/evus', $service->storeTarget($config, 'xiaomi'));
|
||||
}
|
||||
}
|
||||
20
tests/upload_transport_self_check.php
Normal file
20
tests/upload_transport_self_check.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$source = file_get_contents(__DIR__ . '/../app/service/SwooleService.php');
|
||||
if (!is_string($source)) {
|
||||
throw new RuntimeException('Unable to read upload transport');
|
||||
}
|
||||
if (!str_contains($source, "private string \$host = 'ossup.jzvisa.cn';")) {
|
||||
throw new RuntimeException('Upload transport must use ossup.jzvisa.cn');
|
||||
}
|
||||
if (!str_contains($source, 'private int $port = 19501;')) {
|
||||
throw new RuntimeException('Upload transport must keep port 19501');
|
||||
}
|
||||
|
||||
$controller = file_get_contents(__DIR__ . '/../app/miniapi/controller/Upload.php');
|
||||
if (!is_string($controller) || !str_contains($controller, "'json_encode_param' => 0")) {
|
||||
throw new RuntimeException('Upload responses must escape Unicode for wx.uploadFile compatibility');
|
||||
}
|
||||
|
||||
echo "Upload transport self-check passed\n";
|
||||
97
tests/wechat_phone_token_retry_self_check.php
Normal file
97
tests/wechat_phone_token_retry_self_check.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api {
|
||||
final class Httpcurl
|
||||
{
|
||||
public static array $requests = [];
|
||||
public static array $responses = [];
|
||||
|
||||
public static function request($url, $type, $data = false, $header = [], $timeout = 0): array
|
||||
{
|
||||
self::$requests[] = compact('url', 'type', 'data', 'header', 'timeout');
|
||||
$body = array_shift(self::$responses);
|
||||
return [json_encode($body, JSON_UNESCAPED_UNICODE), '', ['http_code' => 200], 0, ''];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace think\facade {
|
||||
final class Cache
|
||||
{
|
||||
public static array $items = [];
|
||||
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
return self::$items[$key] ?? $default;
|
||||
}
|
||||
|
||||
public static function set(string $key, $value, $ttl = null): bool
|
||||
{
|
||||
self::$items[$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete(string $key): bool
|
||||
{
|
||||
unset(self::$items[$key]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final class Log
|
||||
{
|
||||
public static array $warnings = [];
|
||||
|
||||
public static function warning(string $message): void
|
||||
{
|
||||
self::$warnings[] = $message;
|
||||
}
|
||||
|
||||
public static function error(string $message): void
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require __DIR__ . '/../app/service/MiniProgramWechatService.php';
|
||||
|
||||
use api\Httpcurl;
|
||||
use app\service\MiniProgramWechatService;
|
||||
use think\facade\Cache;
|
||||
|
||||
$appid = 'wx-test-appid';
|
||||
$cacheKey = 'miniapp_stable_access_token_' . md5('evus_bj_mp|' . $appid);
|
||||
Cache::$items[$cacheKey] = 'stale-token';
|
||||
Httpcurl::$responses = [
|
||||
['errcode' => 40001, 'errmsg' => 'invalid credential'],
|
||||
['access_token' => 'fresh-stable-token', 'expires_in' => 7200],
|
||||
['errcode' => 0, 'phone_info' => ['purePhoneNumber' => '18668962632']],
|
||||
];
|
||||
|
||||
$service = new MiniProgramWechatService('evus_bj_mp', [
|
||||
'wechat_appid' => $appid,
|
||||
'wechat_secret' => 'test-secret',
|
||||
]);
|
||||
$mobile = $service->mobileByPhoneCode('single-use-phone-code');
|
||||
|
||||
if ($mobile !== '18668962632') {
|
||||
throw new RuntimeException('Phone lookup must retry once after an invalid access token');
|
||||
}
|
||||
if (count(Httpcurl::$requests) !== 3) {
|
||||
throw new RuntimeException('Phone lookup must make exactly one retry');
|
||||
}
|
||||
if (!str_contains((string)Httpcurl::$requests[1]['url'], '/cgi-bin/stable_token')) {
|
||||
throw new RuntimeException('Token refresh must use the stable access token endpoint');
|
||||
}
|
||||
$refreshBody = json_decode((string)Httpcurl::$requests[1]['data'], true);
|
||||
if (($refreshBody['force_refresh'] ?? null) !== true) {
|
||||
throw new RuntimeException('Invalid access token retry must force one stable token refresh');
|
||||
}
|
||||
if ((Cache::$items[$cacheKey] ?? '') !== 'fresh-stable-token') {
|
||||
throw new RuntimeException('Refreshed stable access token must be cached');
|
||||
}
|
||||
|
||||
echo "Wechat phone token retry self-check passed\n";
|
||||
}
|
||||
Reference in New Issue
Block a user