Files
xin-procurement/tests/Feature/MiniAuthTest.php
T
2026-08-06 10:49:51 +08:00

180 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\StoreModel;
use App\Models\UserModel;
use App\Services\WechatService;
use Illuminate\Support\Facades\DB;
use Modules\SystemTool\Services\SysSiteConfigService;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
/**
* 小程序认证:EasyWeChat MockHttpClient 拦截微信调用、
* 登录自动注册、手机号绑定自动匹配、停用拒绝、双端 token 隔离
*/
class MiniAuthTest extends ProcurementTestCase
{
protected function setUp(): void
{
parent::setUp();
// 微信配置为 DB 驱动(后台「小程序设置」→ sys_site_configWechatService 经
// site_config('wechatMini') 读取):测试落库并刷新配置缓存
DB::table('sys_site_config_group')->insert([
'id' => 100,
'title' => '小程序设置',
'key' => 'wechatMini',
'remark' => '',
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('sys_site_config_items')->insert([
[
'group_id' => 100, 'key' => 'appid', 'title' => 'APPID', 'describe' => '',
'values' => 'test_appid', 'type' => 'Input', 'options' => null, 'props' => null, 'sort' => 0,
'created_at' => now(), 'updated_at' => now(),
],
[
'group_id' => 100, 'key' => 'secret', 'title' => 'SecretKey', 'describe' => '',
'values' => 'test_secret', 'type' => 'Input', 'options' => null, 'props' => null, 'sort' => 1,
'created_at' => now(), 'updated_at' => now(),
],
]);
SysSiteConfigService::refreshSiteConfig();
}
/**
* 向 WechatService 注入 MockHttpClient,按 URL 分发微信接口 mock 响应
*
* @param callable(string, string): MockResponse $responder
*/
private function mockWechat(callable $responder): void
{
app(WechatService::class)->setHttpClient(
new MockHttpClient($responder, 'https://api.weixin.qq.com')
);
}
/** wx.login code → 自动注册用户并签发 token */
public function test_login_creates_user_and_issues_token(): void
{
$this->mockWechat(fn () => new MockResponse((string) json_encode([
'openid' => 'openid_test_001',
'session_key' => 'session_key_x',
])));
$response = $this->postJson('/mini/auth/login', ['code' => 'wx_code']);
$response->assertOk()
->assertJsonPath('success', true)
->assertJsonStructure(['data' => ['token', 'user' => ['id', 'type', 'store', 'supplier']]]);
$this->assertNotEmpty($response->json('data.token'));
$user = UserModel::where('openid', 'openid_test_001')->first();
$this->assertNotNull($user, '应按 openid 自动创建用户');
$this->assertSame(UserModel::TYPE_PENDING, $user->type);
$this->assertNotNull($user->last_login_at);
}
/** 停用账号拒绝登录 */
public function test_disabled_user_cannot_login(): void
{
UserModel::factory()->disabled()->create(['openid' => 'openid_disabled']);
$this->mockWechat(fn () => new MockResponse((string) json_encode([
'openid' => 'openid_disabled',
'session_key' => 'sk',
])));
$this->postJson('/mini/auth/login', ['code' => 'c'])
->assertOk()
->assertJsonPath('success', false);
}
/** 微信接口报错时登录失败(错误被转译为业务异常,不泄露原始报文结构) */
public function test_login_fails_when_wechat_rejects_code(): void
{
$this->mockWechat(fn () => new MockResponse((string) json_encode([
'errcode' => 40029,
'errmsg' => 'invalid code',
])));
$this->postJson('/mini/auth/login', ['code' => 'bad_code'])
->assertOk()
->assertJsonPath('success', false);
$this->assertSame(0, UserModel::count());
}
/** 手机号绑定:按手机号自动匹配门店 */
public function test_phone_binding_matches_store(): void
{
$store = StoreModel::factory()->create(['phone' => '13800138000']);
$user = UserModel::factory()->create();
$this->actingAsMiniUser($user);
$this->mockWechat(function (string $method, string $url): MockResponse {
if (str_contains($url, '/cgi-bin/token')) {
return new MockResponse((string) json_encode([
'access_token' => 'mock_access_token',
'expires_in' => 7200,
]));
}
return new MockResponse((string) json_encode([
'errcode' => 0,
'phone_info' => ['phoneNumber' => '13800138000'],
]));
});
$this->postJson('/mini/auth/phone', ['phoneCode' => 'phone_code'])
->assertOk()
->assertJsonPath('success', true);
$user = $user->fresh();
$this->assertSame('13800138000', $user->phone);
$this->assertSame(UserModel::TYPE_STORE, $user->type);
$this->assertSame($store->id, $user->store_id);
}
/** 手机号无匹配主体 → 保持待绑定 */
public function test_phone_no_match_stays_pending(): void
{
$user = UserModel::factory()->create();
$this->actingAsMiniUser($user);
$this->mockWechat(function (string $method, string $url): MockResponse {
if (str_contains($url, '/cgi-bin/token')) {
return new MockResponse((string) json_encode([
'access_token' => 'mock_access_token',
'expires_in' => 7200,
]));
}
return new MockResponse((string) json_encode([
'errcode' => 0,
'phone_info' => ['phoneNumber' => '19999999999'],
]));
});
$this->postJson('/mini/auth/phone', ['phoneCode' => 'phone_code'])
->assertJsonPath('success', true);
$this->assertSame(UserModel::TYPE_PENDING, $user->fresh()->type);
}
/** 跨端隔离:后台 token 访问小程序接口 → 401 */
public function test_sys_token_cannot_access_mini(): void
{
$this->actingAsSysUser();
$this->getJson('/mini/auth/info')->assertStatus(401);
}
/** 跨端隔离:小程序 token 访问后台接口 → 401 */
public function test_mini_token_cannot_access_admin_api(): void
{
$this->actingAsMiniUser(UserModel::factory()->create());
$this->getJson('/customer/level')->assertStatus(401);
}
}