小程序用户改用门店登录

This commit is contained in:
liu
2026-08-20 13:46:04 +08:00
parent 6d9f02d831
commit c1d490134e
251 changed files with 689 additions and 2727 deletions
+120 -173
View File
@@ -3,195 +3,142 @@
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 隔离
* 小程序认证:门店 账号 + 密码 登录(不再使用微信能力)
* 停用拒绝、修改密码、双端 token 隔离
*/
class MiniAuthTest extends ProcurementTestCase
{
protected function setUp(): void
/** 正确账号密码 → 签发 token,回传门店信息(含等级),刷新最后登录时间 */
public function test_login_issues_token_for_valid_credentials(): 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(),
$level = \App\Models\CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->withPassword('abc12345')->create([
'username' => 'store_login_1',
'level_id' => $level->id,
]);
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(),
],
$response = $this->postJson('/mini/auth/login', [
'username' => 'store_login_1',
'password' => 'abc12345',
]);
SysSiteConfigService::refreshSiteConfig();
$response->assertOk()
->assertJsonPath('success', true)
->assertJsonStructure(['data' => ['token', 'user' => ['id', 'name', 'code', 'level']]]);
$this->assertNotEmpty($response->json('data.token'));
$this->assertSame($store->id, $response->json('data.user.id'));
// 密码永不回显
$this->assertArrayNotHasKey('password', $response->json('data.user'));
$this->assertNotNull($store->refresh()->last_login_at);
}
/**
* 向 WechatService 注入 MockHttpClient,按 URL 分发微信接口 mock 响应
*
* @param callable(string, string): MockResponse $responder
*/
private function mockWechat(callable $responder): void
/** 密码错误 → 拒绝 */
public function test_login_rejects_wrong_password(): void
{
app(WechatService::class)->setHttpClient(
new MockHttpClient($responder, 'https://api.weixin.qq.com')
);
StoreModel::factory()->withPassword('abc12345')->create(['username' => 'store_login_2']);
$this->postJson('/mini/auth/login', [
'username' => 'store_login_2',
'password' => 'wrong_pwd',
])->assertOk()
->assertJsonPath('success', false);
}
/** wx.login code → 已注册用户签发 token */
public function test_login_issues_token_for_registered_user(): void
/** 账号不存在 → 拒绝 */
public function test_login_rejects_unknown_username(): void
{
$this->postJson('/mini/auth/login', [
'username' => 'not_exist',
'password' => 'abc12345',
])->assertOk()
->assertJsonPath('success', false);
}
/** 停用门店拒绝登录 */
public function test_disabled_store_cannot_login(): void
{
StoreModel::factory()->disabled()->withPassword('abc12345')->create(['username' => 'store_login_3']);
$this->postJson('/mini/auth/login', [
'username' => 'store_login_3',
'password' => 'abc12345',
])->assertOk()
->assertJsonPath('success', false);
}
/** 登录参数缺失 → 校验失败 */
public function test_login_validates_required_params(): void
{
$this->postJson('/mini/auth/login', [])
->assertOk()
->assertJsonPath('success', false);
}
/** 登录签发的 token 可访问受保护接口,auth/info 返回当前门店 */
public function test_login_token_accesses_protected_routes(): void
{
$store = StoreModel::factory()->withPassword('abc12345')->create(['username' => 'store_login_4']);
$login = $this->postJson('/mini/auth/login', [
'username' => 'store_login_4',
'password' => 'abc12345',
]);
$token = $login->json('data.token');
$this->withToken($token)->getJson('/mini/auth/info')
->assertOk()
->assertJsonPath('success', true)
->assertJsonPath('data.id', $store->id);
}
/** 修改密码:原密码正确 → 新密码可登录 */
public function test_set_password_succeeds(): void
{
$store = StoreModel::factory()->withPassword('old_pwd_1')->create();
$this->actingAsMiniStore($store);
$this->putJson('/mini/auth/password', [
'oldPassword' => 'old_pwd_1',
'newPassword' => 'new_pwd_1',
'rePassword' => 'new_pwd_1',
])->assertOk()
->assertJsonPath('success', true);
$this->postJson('/mini/auth/login', [
'username' => $store->username,
'password' => 'new_pwd_1',
])->assertOk()
->assertJsonPath('success', true);
}
/** 修改密码:原密码错误 → 拒绝 */
public function test_set_password_rejects_wrong_old_password(): void
{
$store = StoreModel::factory()->withPassword('old_pwd_2')->create();
$this->actingAsMiniStore($store);
$this->putJson('/mini/auth/password', [
'oldPassword' => 'bad_old',
'newPassword' => 'new_pwd_2',
'rePassword' => 'new_pwd_2',
])->assertOk()
->assertJsonPath('success', false);
}
/** 修改密码:两次输入不一致 → 校验失败 */
public function test_set_password_validates_confirmation(): 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',
])));
$this->actingAsMiniStore($store);
$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()
$this->putJson('/mini/auth/password', [
'oldPassword' => '123456',
'newPassword' => 'new_pwd_3',
'rePassword' => 'different',
])->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 */
@@ -205,7 +152,7 @@ class MiniAuthTest extends ProcurementTestCase
/** 跨端隔离:小程序 token 访问后台接口 → 401 */
public function test_mini_token_cannot_access_admin_api(): void
{
$this->actingAsMiniUser(UserModel::factory()->create());
$this->actingAsMiniStore(StoreModel::factory()->create());
$this->getJson('/customer/level')->assertStatus(401);
}