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); } }