feat(admin): 密码校验统一与后台 API/设置增强
- 管理端账号/代理/站点等密码最短长度统一为 6 位 - LotterySettings、审计日志展示、币种与报表接口等小修复 - 补充设置批量更新、注单、校验错误等相关测试
This commit is contained in:
@@ -30,9 +30,10 @@ test('audit log presenter maps business action and entity target', function ():
|
||||
|
||||
expect($payload['operator_label'])->toBe('管理员 #1')
|
||||
->and($payload['operator_subtitle'])->toBeNull()
|
||||
->and($payload['module_label'])->toBe('代理')
|
||||
->and($payload['module_label'])->toBe('代理管理')
|
||||
->and($payload['action_label'])->toBe('同步代理角色权限')
|
||||
->and($payload['target_label'])->toBe('角色 #5');
|
||||
->and($payload['target_label'])->toBe('角色 #5')
|
||||
->and($payload['summary_label'])->toBe('代理管理 · 同步代理角色权限(角色 #5)');
|
||||
});
|
||||
|
||||
test('audit log presenter uses admin api resource name for middleware style rows', function (): void {
|
||||
@@ -55,7 +56,8 @@ test('audit log presenter uses admin api resource name for middleware style rows
|
||||
$payload = AuditLogApiPresenter::row($row, $resourceNames);
|
||||
|
||||
expect($payload['action_label'])->toBe($resourceName)
|
||||
->and($payload['target_label'])->toBe($resourceName.' #5');
|
||||
->and($payload['target_label'])->toBe('代理角色 #5')
|
||||
->and($payload['summary_label'])->toBe('代理管理 · '.$resourceName.'(代理角色 #5)');
|
||||
});
|
||||
|
||||
test('agent role permission sync records one business audit and skips middleware duplicate', function (): void {
|
||||
@@ -133,7 +135,8 @@ test('audit log index returns chinese display labels', function (): void {
|
||||
->assertJsonPath('code', ErrorCode::Success->value)
|
||||
->assertJsonPath('data.items.0.operator_label', '超管甲')
|
||||
->assertJsonPath('data.items.0.operator_subtitle', '管理员 #'.$admin->id)
|
||||
->assertJsonPath('data.items.0.module_label', '代理')
|
||||
->assertJsonPath('data.items.0.module_label', '代理管理')
|
||||
->assertJsonPath('data.items.0.action_label', '同步代理角色权限')
|
||||
->assertJsonPath('data.items.0.target_label', '角色 #9');
|
||||
->assertJsonPath('data.items.0.target_label', '角色 #9')
|
||||
->assertJsonPath('data.items.0.summary_label', '代理管理 · 同步代理角色权限(角色 #9)');
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ test('admin login returns bearer token when captcha passes validation', function
|
||||
->assertJsonPath('data.admin.navigation.0.nav_group', 'overview')
|
||||
->assertJsonPath('data.admin.navigation.1.segment', 'agents')
|
||||
->assertJsonPath('data.admin.navigation.1.nav_group', 'agent')
|
||||
->assertJsonPath('data.admin.navigation.2.segment', 'agent_list')
|
||||
->assertJsonPath('data.admin.navigation.2.segment', 'settlement_center')
|
||||
->assertJsonStructure(['data' => ['token', 'token_type', 'admin' => ['id', 'username', 'nickname', 'email', 'permissions', 'operational_permissions', 'navigation']]]);
|
||||
|
||||
$token = $resp->json('data.token');
|
||||
|
||||
@@ -47,12 +47,12 @@ test('admin can create currency and normalize code', function (): void {
|
||||
->postJson('/api/v1/admin/currencies', [
|
||||
'code' => 'eur',
|
||||
'name' => 'Euro',
|
||||
'decimal_places' => 2,
|
||||
'is_enabled' => true,
|
||||
'is_bettable' => true,
|
||||
])
|
||||
->assertStatus(201)
|
||||
->assertJsonPath('data.code', 'EUR')
|
||||
->assertJsonPath('data.decimal_places', 2)
|
||||
->assertJsonPath('data.is_bettable', true);
|
||||
|
||||
$this->assertDatabaseHas('currencies', [
|
||||
|
||||
@@ -71,8 +71,28 @@ function settingsReadOnlyToken(): string
|
||||
}
|
||||
|
||||
test('admin can batch update settings in one request', function (): void {
|
||||
LotterySettings::put('draw.interval_minutes', 5, 'draw');
|
||||
LotterySettings::put('draw.cooldown_minutes', 15, 'draw');
|
||||
LotterySettings::put('draw.require_manual_review', true, 'draw');
|
||||
|
||||
$token = settingsAdminToken();
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/admin/settings/batch', [
|
||||
'items' => [
|
||||
['key' => 'draw.cooldown_minutes', 'value' => 20],
|
||||
['key' => 'draw.require_manual_review', 'value' => false],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.key', 'draw.cooldown_minutes');
|
||||
|
||||
expect(LotterySetting::query()->where('setting_key', 'draw.cooldown_minutes')->value('value_json'))->toBe(20)
|
||||
->and(LotterySetting::query()->where('setting_key', 'draw.require_manual_review')->value('value_json'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin cannot batch update env locked deployment settings', function (): void {
|
||||
config(['lottery.draw.interval_minutes' => 5]);
|
||||
LotterySettings::put('draw.interval_minutes', 5, 'draw');
|
||||
|
||||
$token = settingsAdminToken();
|
||||
|
||||
@@ -80,14 +100,28 @@ test('admin can batch update settings in one request', function (): void {
|
||||
->putJson('/api/v1/admin/settings/batch', [
|
||||
'items' => [
|
||||
['key' => 'draw.interval_minutes', 'value' => 10],
|
||||
['key' => 'draw.cooldown_minutes', 'value' => 20],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.key', 'draw.cooldown_minutes');
|
||||
->assertForbidden();
|
||||
|
||||
expect(LotterySetting::query()->where('setting_key', 'draw.interval_minutes')->value('value_json'))->toBe(10)
|
||||
->and(LotterySetting::query()->where('setting_key', 'draw.cooldown_minutes')->value('value_json'))->toBe(20);
|
||||
expect(LotterySettings::drawIntervalMinutes())->toBe(5);
|
||||
});
|
||||
|
||||
test('admin settings deployment returns read only snapshot', function (): void {
|
||||
config([
|
||||
'lottery.default_currency' => 'USD',
|
||||
'lottery.draw.interval_minutes' => 7,
|
||||
]);
|
||||
|
||||
$token = settingsAdminToken();
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/settings/deployment')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.key', 'currency.default_code')
|
||||
->assertJsonPath('data.items.0.value', 'USD')
|
||||
->assertJsonPath('data.items.0.env_var', 'LOTTERY_DEFAULT_CURRENCY')
|
||||
->assertJsonFragment(['key' => 'draw.interval_minutes', 'value' => 7]);
|
||||
});
|
||||
|
||||
test('admin settings batch update rejects empty items', function (): void {
|
||||
@@ -199,13 +233,13 @@ test('wallet max limit cannot be less than current min limit', function (): void
|
||||
});
|
||||
|
||||
test('settings updates require permission for their setting group', function (): void {
|
||||
LotterySettings::put('draw.interval_minutes', 5, 'draw');
|
||||
LotterySettings::put('draw.cooldown_minutes', 15, 'draw');
|
||||
LotterySettings::put('frontend.play_rules_html_zh', '<div>old</div>', 'frontend');
|
||||
|
||||
$token = settingsReadOnlyToken();
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/admin/settings/draw.interval_minutes', [
|
||||
->putJson('/api/v1/admin/settings/draw.cooldown_minutes', [
|
||||
'value' => 10,
|
||||
])
|
||||
->assertForbidden();
|
||||
@@ -216,6 +250,6 @@ test('settings updates require permission for their setting group', function ():
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
expect(LotterySetting::query()->where('setting_key', 'draw.interval_minutes')->value('value_json'))->toBe(5)
|
||||
expect(LotterySetting::query()->where('setting_key', 'draw.cooldown_minutes')->value('value_json'))->toBe(15)
|
||||
->and(LotterySetting::query()->where('setting_key', 'frontend.play_rules_html_zh')->value('value_json'))->toBe('<div>new</div>');
|
||||
});
|
||||
|
||||
@@ -260,7 +260,7 @@ test('permission catalog groups permissions by admin navigation order', function
|
||||
expect($groups[2]['key'])->toBe('draws');
|
||||
|
||||
$groupsByKey = collect($groups)->keyBy('key');
|
||||
expect($groupsByKey['admin_users']['label'])->toBe('管理列表');
|
||||
expect($groupsByKey['admin_users']['label'])->toBe('平台账号');
|
||||
expect(array_column($groupsByKey['admin_users']['permissions'], 'slug'))->toBe(['prd.admin_user.manage']);
|
||||
expect($groupsByKey['admin_roles']['label'])->toBe('角色管理');
|
||||
expect(array_column($groupsByKey['admin_roles']['permissions'], 'slug'))->toBe(['prd.admin_role.manage']);
|
||||
|
||||
@@ -13,6 +13,13 @@ test('lottery settings get returns seeded value', function (): void {
|
||||
expect(LotterySettings::get('fixture.flag'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('env locked draw settings ignore database overrides', function (): void {
|
||||
config(['lottery.draw.interval_minutes' => 9]);
|
||||
LotterySettings::put('draw.interval_minutes', 99, 'draw');
|
||||
|
||||
expect(LotterySettings::drawIntervalMinutes())->toBe(9);
|
||||
});
|
||||
|
||||
test('lottery settings misses are not cached', function (): void {
|
||||
Cache::flush();
|
||||
|
||||
|
||||
@@ -271,6 +271,15 @@ test('ticket items index filters by status number and date range', function ():
|
||||
->assertOk()
|
||||
->assertJsonPath('data.total', 1)
|
||||
->assertJsonPath('data.items.0.draw_no', '20260512-780');
|
||||
|
||||
$orderNo = TicketOrder::query()->where('draw_id', $draw2->id)->value('order_no');
|
||||
expect($orderNo)->not->toBeEmpty();
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->getJson('/api/v1/ticket/items?order_no='.urlencode((string) $orderNo))
|
||||
->assertOk()
|
||||
->assertJsonPath('data.total', 1)
|
||||
->assertJsonPath('data.items.0.order_no', $orderNo);
|
||||
});
|
||||
|
||||
test('ticket item show returns match result and timeline', function (): void {
|
||||
|
||||
@@ -47,11 +47,11 @@ test('keeps already localized chinese messages', function (): void {
|
||||
|
||||
test('normalizes english min length for password in zh', function (): void {
|
||||
$errors = ApiValidationErrors::normalize(
|
||||
['password' => ['The password field must be at least 8 characters.']],
|
||||
['password' => ['The password field must be at least 6 characters.']],
|
||||
'zh',
|
||||
);
|
||||
|
||||
expect($errors['password'][0])->toBe('密码至少需要 8 个字符。');
|
||||
expect($errors['password'][0])->toBe('密码至少需要 6 个字符。');
|
||||
});
|
||||
|
||||
test('normalizes exact draw items message in zh', function (): void {
|
||||
|
||||
Reference in New Issue
Block a user