小程序登录优化

This commit is contained in:
liu
2026-08-10 08:55:22 +08:00
parent c6f69c5c55
commit df7631c7a9
25 changed files with 954 additions and 549 deletions
+97 -64
View File
@@ -12,7 +12,7 @@ use Symfony\Component\HttpClient\Response\MockResponse;
/**
* 小程序认证:EasyWeChat MockHttpClient 拦截微信调用、
* 登录自动注册、手机号绑定自动匹配、停用拒绝、双端 token 隔离
* 注册绑定门店、登录签发 token、停用拒绝、双端 token 隔离
*/
class MiniAuthTest extends ProcurementTestCase
{
@@ -56,9 +56,11 @@ class MiniAuthTest extends ProcurementTestCase
);
}
/** wx.login code → 自动注册用户签发 token */
public function test_login_creates_user_and_issues_token(): void
/** 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',
@@ -67,14 +69,101 @@ class MiniAuthTest extends ProcurementTestCase
$response = $this->postJson('/mini/auth/login', ['code' => 'wx_code']);
$response->assertOk()
->assertJsonPath('success', true)
->assertJsonStructure(['data' => ['token', 'user' => ['id', 'type', 'store', 'supplier']]]);
->assertJsonStructure(['data' => ['token', 'user' => ['id']]]);
$this->assertNotEmpty($response->json('data.token'));
$this->assertNotNull($user->fresh()->last_login_at);
}
$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);
/** 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());
}
/** 停用账号拒绝登录 */
@@ -105,62 +194,6 @@ class MiniAuthTest extends ProcurementTestCase
$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
{
+30 -4
View File
@@ -34,15 +34,41 @@ class ProductPriceTest extends ProcurementTestCase
$this->assertSame(5.50, (float) $row['price'], '应返回门店所在等级的价格');
}
/** 门店未设置客户等级时拒绝展示价格 */
public function test_store_without_level_is_rejected(): void
/** 门店未设置客户等级:仍可浏览商品,但价格不可见(price=null) */
public function test_store_without_level_sees_products_without_price(): void
{
$store = StoreModel::factory()->create(['level_id' => 0]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->getJson('/mini/product/list')
$response = $this->getJson('/mini/product/list');
$response->assertOk()->assertJsonPath('success', true);
$row = collect($response->json('data.data'))->firstWhere('id', $product->id);
$this->assertNotNull($row, '未设等级的门店仍应能看到商品');
$this->assertNull($row['price'], '未设等级时价格应为 null');
}
/** 未绑定门店:可浏览商品与分类,但价格不可见;加购仍被拦截(购买前置校验不变) */
public function test_unbound_user_can_browse_products_without_price_but_cannot_cart(): void
{
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
$this->actingAsMiniUser(UserModel::factory()->create()); // type=0 待绑定
$list = $this->getJson('/mini/product/list');
$list->assertOk()->assertJsonPath('success', true);
$row = collect($list->json('data.data'))->firstWhere('id', $product->id);
$this->assertNotNull($row, '未绑定门店应能看到商品');
$this->assertNull($row['price'], '未绑定门店不得看到价格');
$this->getJson('/mini/product/categories')
->assertOk()
->assertJsonPath('success', false);
->assertJsonPath('success', true);
$this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1])
->assertJsonPath('success', false)
->assertJsonPath('msg', '尚未绑定门店,请联系客服处理');
}
/** 批量调价:事务写入 + 通知受影响门店用户(不影响无关门店) */