app = new Application(dirname(__DIR__, 2)); $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']])); $this->app->instance('cache', new CacheRepository(new ArrayStore())); $translator = new Translator(new ArrayLoader(), 'zh'); $this->app->instance('translator', $translator); $this->app->instance('validator', new Factory($translator, $this->app)); $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory()); Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); }); Facade::setFacadeApplication($this->app); $db = new Manager($this->app); $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']); $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager()); $db->schema()->create('users', function (Blueprint $t) { $t->id(); $t->string('member_id'); $t->string('password'); $t->string('payment_password'); $t->string('language')->default('zh'); $t->integer('recharge_channel_group_id')->default(1); $t->timestamps(); }); $db->schema()->create('user_session', function (Blueprint $t) { $t->id(); $t->string('user_id'); $t->string('token'); $t->integer('expire_time'); }); DB::table('users')->insert([ 'member_id' => '100001', 'password' => create_password('old-login'), 'payment_password' => password_hash('old-funds', PASSWORD_DEFAULT), ]); foreach (['100001' => 'target-token', '100002' => 'other-token'] as $member => $token) { DB::table('user_session')->insert(['user_id' => (string) $member, 'token' => $token, 'expire_time' => time() + 3600]); Cache::put('token_user_' . $token, ['member_id' => (string) $member]); } } protected function tearDown(): void { DB::disconnect(); Facade::clearResolvedInstances(); Model::clearBootedModels(); parent::tearDown(); } private function reset(array $params): array { $this->app->instance('request', Request::create('/admin/user/setPassword', 'POST', $params + ['member_id' => '100001'])); return (new UserController())->setPassword()->getData(true); } private function credentials(): array { return (array) DB::table('users')->where('member_id', '100001')->first(); } public function test_invalid_passwords_are_rejected_before_either_credential_or_session_is_changed(): void { $before = $this->credentials(); foreach (['password', 'payment_password'] as $field) { foreach (['12345', str_repeat('a', 17), str_repeat('密', 17), 123456, array_fill(0, 6, 'a'), false] as $invalid) { $params = ['password' => 'valid-login', 'payment_password' => 'valid-funds']; $params[$field] = $invalid; $body = $this->reset($params); $this->assertSame(-3, $body['code']); $this->assertSame($before, $this->credentials()); $this->assertSame(2, DB::table('user_session')->count()); $this->assertTrue(Cache::has('token_user_target-token')); } } } public static function validPasswords(): array { return [ 'minimum' => ['abc123'], 'maximum' => [str_repeat('a', 16)], 'leading zeroes' => ['000000'], 'symbols' => ['abc!@#123'], 'unicode character limit' => [str_repeat('密', 16)], ]; } #[DataProvider('validPasswords')] public function test_valid_boundaries_preserve_hashing_and_login_session_invalidation(string $password): void { $this->assertSame(0, $this->reset(['password' => $password, 'payment_password' => $password])['code']); $row = $this->credentials(); $this->assertSame(create_password($password), $row['password']); $this->assertTrue(password_verify($password, $row['payment_password'])); $this->assertSame(0, DB::table('user_session')->where('user_id', '100001')->count()); $this->assertFalse(Cache::has('token_user_target-token')); $this->assertSame(1, DB::table('user_session')->where('user_id', '100002')->count()); $this->assertTrue(Cache::has('token_user_other-token')); } public function test_resetting_only_funds_password_preserves_login_password_and_session(): void { $this->assertSame(0, $this->reset(['payment_password' => 'new-funds'])['code']); $row = $this->credentials(); $this->assertSame(create_password('old-login'), $row['password']); $this->assertTrue(password_verify('new-funds', $row['payment_password'])); $this->assertSame(2, DB::table('user_session')->count()); $this->assertTrue(Cache::has('token_user_target-token')); } public function test_omitted_or_empty_passwords_remain_unchanged(): void { $before = $this->credentials(); foreach ([[], ['password' => null, 'payment_password' => null], ['password' => '', 'payment_password' => '']] as $params) { $this->assertSame(0, $this->reset($params)['code']); $this->assertSame($before, $this->credentials()); $this->assertSame(2, DB::table('user_session')->count()); $this->assertTrue(Cache::has('token_user_target-token')); } } }