Files
boursecu_server/tests/Unit/ConfigServiceTradingHoursTest.php
mohmdsdgandCursor b21b136711 fix(settings): honor last-price switch and overlay only SuperAdmin access flags
Stored DB prices were treated as live quotes, and SuperAdmin overlay overwrote tenant keys like trading hours. Keep overlay to the 14 access flags and hide fallback prices when the panel says so.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 03:01:07 +03:30

302 lines
9.9 KiB
PHP

<?php
namespace Tests\Unit;
use App\Services\ConfigService;
use App\Services\SuperAdminProgramSettingsOverlay;
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_marks_closed_without_hiding_status_when_home_price_off(): 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',
'show_last_price' => '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->applyHomePriceDisplayState([
'sell' => 100,
'buy' => 110,
'price' => 105,
'closed' => false,
'status' => 'open',
]);
$this->assertTrue($result['closed']);
$this->assertSame('closed', $result['status']);
$this->assertSame(0, $result['buy']);
$this->assertSame(0, $result['sell']);
$this->assertSame(0, $result['price']);
Carbon::setTestNow();
}
public function test_outside_hours_shows_last_price_when_home_price_when_closed_is_on(): void
{
$cfg = $this->cfg([
'close_price' => 'true',
'show_home_price_when_closed' => 'true',
'show_last_price' => 'false',
'startDay' => '11:00',
'endDay' => '20:30',
]);
$now = Carbon::create(2026, 8, 25, 2, 3, 0);
Carbon::setTestNow($now);
$result = $cfg->applyHomePriceDisplayState([
'sell' => 168000000,
'buy' => 168798112,
'price' => 168798112,
'closed' => false,
'status' => 'open',
]);
$this->assertTrue($result['closed']);
$this->assertSame('open', $result['status']);
$this->assertSame(168798112, $result['buy']);
Carbon::setTestNow();
}
public function test_close_price_off_with_no_quote_is_no_quote(): void
{
$cfg = $this->cfg([
'close_price' => 'false',
'show_last_price' => 'false',
'show_home_price_when_closed' => 'false',
'startDay' => '11:00',
'endDay' => '20:30',
]);
$result = $cfg->applyHomePriceDisplayState([
'sell' => 0,
'buy' => 0,
'price' => 0,
'closed' => false,
]);
$this->assertFalse($result['closed']);
$this->assertSame('no_quote', $result['status']);
}
public function test_inside_hours_with_quote_is_open(): void
{
$cfg = $this->cfg([
'close_price' => 'true',
'show_last_price' => 'false',
'show_home_price_when_closed' => 'false',
'startDay' => '11:00',
'endDay' => '20:30',
]);
$now = Carbon::create(2026, 8, 25, 12, 0, 0);
Carbon::setTestNow($now);
$result = $cfg->applyHomePriceDisplayState([
'sell' => 100,
'buy' => 110,
'price' => 105,
'price_source' => 'montzar',
'montzar' => 105,
]);
$this->assertFalse($result['closed']);
$this->assertSame('open', $result['status']);
$this->assertSame(110, $result['buy']);
Carbon::setTestNow();
}
/**
* سناریوی گزارش‌شده: مظنه زنده قطع است و قیمت از آخرین ردیف prices آمده،
* ولی «نمایش آخرین قیمت ذخیره‌شده» خاموش است → نباید قیمت نشان داده شود.
*/
public function test_stored_price_is_hidden_when_show_last_price_is_off(): void
{
$cfg = $this->cfg([
'close_price' => 'false',
'show_last_price' => 'false',
'show_home_price_when_closed' => 'false',
]);
$result = $cfg->applyHomePriceDisplayState([
'sell' => 168336410,
'buy' => 168798112,
'price' => 168798112,
'price_last' => 168798112,
'price_source' => 'database_last',
'montzar' => 0,
'zaarbox' => 0,
'main_product' => 0,
]);
$this->assertFalse($result['closed']);
$this->assertSame('no_quote', $result['status']);
$this->assertSame(0, $result['buy']);
$this->assertSame(0, $result['sell']);
$this->assertSame(0, $result['price']);
$this->assertSame(0, $result['price_last']);
}
public function test_live_quote_detection_ignores_database_fallback(): void
{
$cfg = $this->cfg([]);
$this->assertTrue($cfg->payloadHasLiveQuote([
'buy' => 110,
'price_source' => 'montzar',
]));
$this->assertFalse($cfg->payloadHasLiveQuote([
'buy' => 110,
'price_source' => 'database_last',
]));
// منابع زندهٔ دیگر مثل tala_ir نباید «ذخیره‌شده» حساب شوند
$this->assertTrue($cfg->payloadHasLiveQuote([
'buy' => 110,
'price_source' => 'tala_ir',
]));
// بدون برچسب منبع، عددِ مرجع زنده تصمیم می‌گیرد
$this->assertTrue($cfg->payloadHasLiveQuote([
'buy' => 110,
'zaarbox' => 105,
]));
$this->assertFalse($cfg->payloadHasLiveQuote([
'buy' => 110,
'montzar' => 0,
'zaarbox' => 0,
'main_product' => 0,
]));
$this->assertFalse($cfg->payloadHasLiveQuote([
'buy' => 0,
'price_source' => 'montzar',
]));
}
public function test_trading_hours_setting_keys(): void
{
$cfg = new ConfigService();
$this->assertTrue($cfg->isTradingHoursSettingKey('close_price'));
$this->assertTrue($cfg->isTradingHoursSettingKey('trading_hours_by_day'));
$this->assertTrue($cfg->isTradingHoursSettingKey('show_home_price_when_closed'));
$this->assertFalse($cfg->isTradingHoursSettingKey('name_fa'));
}
/** کلیدهای ساعت کاری باید مالکیت tenant داشته باشند وگرنه پنل اثری روی قیمت ندارد */
public function test_trading_hours_keys_are_tenant_owned(): void
{
foreach (ConfigService::TRADING_HOURS_CLIENT_KEYS as $key) {
$this->assertTrue(
SuperAdminProgramSettingsOverlay::isLocalOwned($key),
"{$key} باید local-owned باشد"
);
$this->assertFalse(
SuperAdminProgramSettingsOverlay::isSuperAdminOwned($key),
"{$key} نباید سوپرادمین-اونر باشد"
);
}
}
}