$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} نباید سوپرادمین-اونر باشد" ); } } }