}
$msg = 'Failed to acquire advisory lock within ' . $this->getLockTimeoutSec() . ' seconds';
- Yii::error($msg, 'stock-history');
+ try {
+ Yii::error($msg, 'stock-history');
+ } catch (\Throwable $e) {
+ // Логирование не должно маскировать основную ошибку
+ }
$this->sendTelegram("CRITICAL: Stock History ETL — $msg");
throw new \RuntimeException($msg);
private function logResult(string $date, string $time, int $rowCount, DqResult $dqResult): void
{
$status = $dqResult->allPassed() ? 'SUCCESS' : 'DQ_FAILURES';
- Yii::info("Stock History ETL [{$date} {$time}]: {$status}, rows={$rowCount}", 'stock-history');
+ try {
+ Yii::info("Stock History ETL [{$date} {$time}]: {$status}, rows={$rowCount}", 'stock-history');
+ } catch (\Throwable $e) {
+ // Логирование не должно прерывать ETL
+ }
}
private function sendAlerts(DqResult $dqResult, string $date, string $time): void
$messages = [];
foreach ($criticals as $f) {
$messages[] = "CRITICAL [{$f['code']}]: {$f['message']}";
- Yii::error("DQ {$f['code']}: {$f['message']}", 'stock-history');
}
foreach ($majors as $f) {
$messages[] = "MAJOR [{$f['code']}]: {$f['message']}";
- Yii::warning("DQ {$f['code']}: {$f['message']}", 'stock-history');
+ }
+
+ try {
+ foreach ($criticals as $f) {
+ Yii::error("DQ {$f['code']}: {$f['message']}", 'stock-history');
+ }
+ foreach ($majors as $f) {
+ Yii::warning("DQ {$f['code']}: {$f['message']}", 'stock-history');
+ }
+ } catch (\Throwable $e) {
+ // Логирование не должно прерывать ETL
}
$text = "Stock History DQ [{$date} {$time}]:\n" . implode("\n", $messages);
/**
* @return array{0: \yii\db\Connection, 1: \yii\db\Command}
*/
+ /** No-op callback для подавления Telegram/Yii в тестах */
+ private static function noopTelegramCallback(): callable
+ {
+ return static function (string $message): void {};
+ }
+
private function createMockDbAndCommand(): array
{
$db = $this->createMock(\yii\db\Connection::class);
);
$command->method('execute')->willReturn(10);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->collect('08:00');
$this->assertTrue($result->isSuccess());
$this->expectExceptionMessage('advisory lock');
// Анонимный класс с минимальными таймаутами для быстрого теста
- $service = new class($db) extends StockHistoryService {
+ $noopCallback = self::noopTelegramCallback();
+ $service = new class($db, $noopCallback) extends StockHistoryService {
protected function getLockTimeoutSec(): int { return 1; }
protected function getLockRetrySleepUs(): int { return 100000; } // 0.1 sec
};
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('snapshotTime');
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->collect('invalid');
}
$command->method('queryAll')->willReturn([]);
$command->method('execute')->willReturn(0);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->collect('08:00');
$this->assertTrue($result->isSuccess());
]);
$command->method('execute')->willReturn(1);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->collect('08:00');
$hasOnConflict = false;
$command->method('queryScalar')
->willReturnOnConsecutiveCalls(5, 5, 0, 0, 0, 100, 95);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->runDqAssertions('2026-02-21', '08:00');
$this->assertTrue($result->allPassed());
$command->method('queryScalar')
->willReturnOnConsecutiveCalls(24, 20, 0, 0, 0, 100, 100);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->runDqAssertions('2026-02-21', '08:00');
$this->assertFalse($result->allPassed());
$command->method('queryScalar')
->willReturnOnConsecutiveCalls(5, 5, 0, 3, 0, 100, 100);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->runDqAssertions('2026-02-21', '08:00');
$majors = $result->getMajorFailures();
$command->method('queryScalar')
->willReturnOnConsecutiveCalls(5, 5, 0, 0, 0, 100, 50);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->runDqAssertions('2026-02-21', '08:00');
$majors = $result->getMajorFailures();
$command->method('queryScalar')
->willReturnOnConsecutiveCalls(5, 0, 0, 0, 0, 0, 100);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$result = $service->runDqAssertions('2026-02-21', '08:00');
$this->assertFalse($result->allPassed());
);
$command->method('execute')->willReturn(0);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->createPartition('2026-04');
$sql = implode(' ', $executedSql);
$this->expectException(\InvalidArgumentException::class);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->createPartition('invalid');
}
);
$command->method('execute')->willReturn(0);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->ensurePartitions();
$partitionSql = array_filter($executedSql, fn($s) => stripos($s, 'PARTITION OF') !== false);
);
$command->method('execute')->willReturn(0);
- $service = new StockHistoryService($db);
+ $service = new StockHistoryService($db, self::noopTelegramCallback());
$service->dropOldPartitions(24);
$this->assertCount(2, $droppedSql, 'Должны быть удалены 2 старые партиции (2023_11 и 2023_12)');