213 lines
7.7 KiB
PHP
213 lines
7.7 KiB
PHP
<?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、停用拒绝、双端 token 隔离
|
||
*/
|
||
class MiniAuthTest extends ProcurementTestCase
|
||
{
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
// 微信配置为 DB 驱动(后台「小程序设置」→ sys_site_config,WechatService 经
|
||
// 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_issues_token_for_registered_user(): void
|
||
{
|
||
$store = StoreModel::factory()->create();
|
||
$user = UserModel::factory()->forStore($store->id)->create(['openid' => 'openid_test_001']);
|
||
$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']]]);
|
||
|
||
$this->assertNotEmpty($response->json('data.token'));
|
||
$this->assertNotNull($user->fresh()->last_login_at);
|
||
}
|
||
|
||
/** openid 未注册 → 拒绝登录 */
|
||
public function test_login_rejects_unregistered_openid(): void
|
||
{
|
||
$this->mockWechat(fn () => new MockResponse((string) json_encode([
|
||
'openid' => 'openid_unknown',
|
||
'session_key' => 'sk',
|
||
])));
|
||
|
||
$this->postJson('/mini/auth/login', ['code' => 'wx_code'])
|
||
->assertOk()
|
||
->assertJsonPath('success', false);
|
||
$this->assertSame(0, UserModel::count());
|
||
}
|
||
|
||
/** 注册:门店编码绑定门店并签发 token */
|
||
public function test_register_binds_store_and_issues_token(): void
|
||
{
|
||
$store = StoreModel::factory()->create(['code' => 'ST001', 'phone' => '13800138000']);
|
||
$this->mockWechat(function (string $method, string $url): MockResponse {
|
||
if (str_contains($url, 'jscode2session')) {
|
||
return new MockResponse((string) json_encode([
|
||
'openid' => 'openid_reg_001',
|
||
'session_key' => 'sk',
|
||
]));
|
||
}
|
||
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'],
|
||
]));
|
||
});
|
||
|
||
$response = $this->postJson('/mini/auth/register', [
|
||
'code' => 'wx_code',
|
||
'phoneCode' => 'phone_code',
|
||
'storeCode' => 'ST001',
|
||
]);
|
||
$response->assertOk()
|
||
->assertJsonPath('success', true)
|
||
->assertJsonStructure(['data' => ['token', 'user' => ['id', 'store_id']]]);
|
||
$this->assertNotEmpty($response->json('data.token'));
|
||
|
||
$user = UserModel::where('openid', 'openid_reg_001')->first();
|
||
$this->assertNotNull($user, '应按 openid 注册用户');
|
||
$this->assertSame($store->id, $user->store_id);
|
||
$this->assertSame('13800138000', $user->phone);
|
||
}
|
||
|
||
/** 注册:门店编码不存在 → 拒绝 */
|
||
public function test_register_rejects_unknown_store(): void
|
||
{
|
||
$this->mockWechat(fn () => new MockResponse((string) json_encode([
|
||
'openid' => 'openid_reg_002',
|
||
'session_key' => 'sk',
|
||
])));
|
||
|
||
$this->postJson('/mini/auth/register', [
|
||
'code' => 'wx_code',
|
||
'phoneCode' => 'phone_code',
|
||
'storeCode' => 'NOT_EXIST',
|
||
])
|
||
->assertOk()
|
||
->assertJsonPath('success', false);
|
||
$this->assertSame(0, UserModel::count());
|
||
}
|
||
|
||
/** 注册:openid 已注册 → 拒绝重复注册 */
|
||
public function test_register_rejects_duplicate_openid(): void
|
||
{
|
||
UserModel::factory()->create(['openid' => 'openid_dup']);
|
||
$this->mockWechat(fn () => new MockResponse((string) json_encode([
|
||
'openid' => 'openid_dup',
|
||
'session_key' => 'sk',
|
||
])));
|
||
|
||
$this->postJson('/mini/auth/register', [
|
||
'code' => 'wx_code',
|
||
'phoneCode' => 'phone_code',
|
||
'storeCode' => 'ST001',
|
||
])
|
||
->assertOk()
|
||
->assertJsonPath('success', false);
|
||
$this->assertSame(1, UserModel::count());
|
||
}
|
||
|
||
/** 停用账号拒绝登录 */
|
||
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());
|
||
}
|
||
|
||
/** 跨端隔离:后台 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);
|
||
}
|
||
}
|