fix(settings): honor per-day trading hours and refresh SuperAdmin overlay

Use trading_hours_by_day for close_price gates, prefer public SuperAdmin fetch, and add refresh endpoint/command so stale last-good cache no longer keeps switches like enable_automation off.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 21:22:44 +03:30
co-authored by Cursor
parent d2469dc253
commit 7e8979d78d
9 changed files with 437 additions and 27 deletions
@@ -0,0 +1,125 @@
<?php
namespace Tests\Unit;
use App\Services\ConfigService;
use Carbon\Carbon;
use Mockery;
use Tests\TestCase;
class ConfigServiceTradingHoursTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
/** @param array<string, mixed> $values */
private function cfg(array $values): ConfigService
{
$cfg = Mockery::mock(ConfigService::class)->makePartial();
$cfg->shouldReceive('get')->andReturnUsing(function (string $key, $default = null) use ($values) {
return array_key_exists($key, $values) ? $values[$key] : $default;
});
$cfg->shouldReceive('getString')->andReturnUsing(function (string $key, string $default = '') use ($values) {
$value = $values[$key] ?? $default;
return trim((string) $value);
});
$cfg->shouldReceive('getBool')->andReturnUsing(function (string $key, bool $default = false) use ($values) {
if (! array_key_exists($key, $values)) {
return $default;
}
return filter_var($values[$key], FILTER_VALIDATE_BOOLEAN);
});
return $cfg;
}
public function test_per_day_schedule_closes_before_global_end_day(): void
{
$weekly = json_encode([
'mode' => 'per_day',
'days' => [
'sat' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
'sun' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
'mon' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:00:00'],
'tue' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
'wed' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
'thu' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
'fri' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:30:00'],
],
], JSON_UNESCAPED_UNICODE);
$cfg = $this->cfg([
'close_price' => 'true',
'startDay' => '08:30',
'endDay' => '21:30',
'trading_hours_by_day' => $weekly,
]);
$monday2115 = Carbon::create(2026, 8, 24, 21, 15, 0);
$monday2059 = Carbon::create(2026, 8, 24, 20, 59, 0);
$this->assertTrue($cfg->isOutsideTradingHours($monday2115));
$this->assertFalse($cfg->isOutsideTradingHours($monday2059));
}
public function test_disabled_day_is_closed_all_day(): void
{
$weekly = json_encode([
'mode' => 'per_day',
'days' => [
'fri' => ['enabled' => false, 'start' => '08:30:00', 'end' => '21:30:00'],
],
], JSON_UNESCAPED_UNICODE);
$cfg = $this->cfg([
'close_price' => 'true',
'startDay' => '08:30',
'endDay' => '21:30',
'trading_hours_by_day' => $weekly,
]);
$fridayNoon = Carbon::create(2026, 8, 28, 12, 0, 0);
$this->assertTrue($cfg->isOutsideTradingHours($fridayNoon));
}
public function test_apply_global_trading_hours_zeros_payload_when_closed(): void
{
$weekly = json_encode([
'mode' => 'per_day',
'days' => [
'mon' => ['enabled' => true, 'start' => '08:30:00', 'end' => '21:00:00'],
],
], JSON_UNESCAPED_UNICODE);
$cfg = $this->cfg([
'close_price' => 'true',
'show_home_price_when_closed' => 'false',
'startDay' => '08:30',
'endDay' => '21:30',
'trading_hours_by_day' => $weekly,
]);
$now = Carbon::create(2026, 8, 24, 21, 15, 0);
Carbon::setTestNow($now);
$result = $cfg->applyGlobalTradingHoursToPayload([
'sell' => 100,
'buy' => 110,
'price' => 105,
'closed' => false,
'status' => 'open',
]);
$this->assertTrue($result['closed']);
$this->assertSame('closed', $result['status']);
$this->assertSame(0, $result['price']);
Carbon::setTestNow();
}
}