first version

This commit is contained in:
liu
2026-07-23 20:41:25 +08:00
parent 00a0938a1b
commit 10cff754a4
211 changed files with 11577 additions and 842 deletions
+156
View File
@@ -0,0 +1,156 @@
<?php
namespace Tests\Feature;
use App\Models\StoreModel;
use App\Models\UserModel;
use App\Services\WechatService;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
/**
* 小程序认证:EasyWeChat MockHttpClient 拦截微信调用、
* 登录自动注册、手机号绑定自动匹配、停用拒绝、双端 token 隔离
*/
class MiniAuthTest extends ProcurementTestCase
{
protected function setUp(): void
{
parent::setUp();
// 测试环境注入微信配置(生产由 WECHAT_MINI_APPID/SECRET 提供)
config(['services.wechat.mini' => ['appid' => 'test_appid', 'secret' => 'test_secret']]);
}
/**
* 向 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);
}
}