use yii\base\InvalidArgumentException;
use yii\db\Exception;
-use yii\helpers\Json;
use yii\web\NotFoundHttpException;
use yii_app\api3\modules\v1\models\Admin;
use yii_app\api3\modules\v1\models\timetable\Timetable;
//убрать после согласования оплаты подработчиков
if (Admin::findOne($admin_id)->group_id === AdminGroup::GROUP_WORKERS && !$data->plan_id) {
- throw new \Exception('Подработчики не могут открыть смены без плана!');
+ throw new InvalidArgumentException('Подработчики не могут открыть смены без плана!');
}
$transaction = \Yii::$app->db->beginTransaction();
$fact->tabel = 1;
$fact->save();
if ($fact->getErrors()) {
- throw new \Exception(Json::encode($fact->getErrors()));
+ throw new InvalidArgumentException(array_values($fact->firstErrors)[0] ?? "");
}
}
}
if ($checkIn->getErrors()) {
- throw new \Exception(Json::encode($checkIn->getErrors()));
+ throw new InvalidArgumentException(array_values($checkIn->firstErrors)[0] ?? "");
}
$transaction->commit();
}
if ($checkIn->getErrors()) {
- throw new \Exception(Json::encode($checkIn->getErrors()));
+ throw new InvalidArgumentException(array_values($checkIn->firstErrors)[0] ?? "");
}
return true;
$checkIn->status = 0;
$checkIn->save();
if ($checkIn->getErrors()) {
- throw new \Exception(Json::encode($checkIn->getErrors()));
+ throw new InvalidArgumentException(array_values($checkIn->firstErrors)[0] ?? "");
}
$transaction->commit();
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace tests\unit\services;
+
+use Codeception\Test\Unit;
+
+/**
+ * Тесты для ERP-241: TimetableService должен бросать InvalidArgumentException,
+ * а не голый \Exception, чтобы EventBehavior мог отобразить понятное сообщение.
+ *
+ * @group services
+ * @group api3
+ * @group regression
+ */
+class TimetableServiceExceptionTest extends Unit
+{
+ private string $serviceFilePath;
+
+ protected function _before(): void
+ {
+ $this->serviceFilePath = dirname(__DIR__, 3) . '/api3/core/services/TimetableService.php';
+ }
+
+ // ========================================================================
+ // ГРУППА 1: Код TimetableService не должен бросать голый \Exception
+ // ========================================================================
+
+ /**
+ * TimetableService не должен содержать throw new \Exception
+ *
+ * EventBehavior обрабатывает только ErrorException, HttpException,
+ * InvalidArgumentException. Голый \Exception приводит к "Произошла
+ * неизвестная ошибка" вместо понятного сообщения.
+ *
+ * @see \yii_app\api3\core\behaviors\EventBehavior::beforeSend()
+ */
+ public function testService_NoGenericExceptionThrows(): void
+ {
+ if (!file_exists($this->serviceFilePath)) {
+ $this->markTestSkipped('TimetableService.php not found');
+ }
+
+ $content = file_get_contents($this->serviceFilePath);
+
+ // throw new \Exception — голый Exception без типизации
+ $pattern = '/throw\s+new\s+\\\\Exception\s*\(/';
+ preg_match_all($pattern, $content, $matches);
+
+ $this->assertEmpty(
+ $matches[0],
+ 'TimetableService не должен бросать голый \\Exception. '
+ . 'Используйте InvalidArgumentException для ошибок валидации. '
+ . 'Найдено ' . count($matches[0]) . ' вхождений.'
+ );
+ }
+
+ /**
+ * TimetableService должен использовать InvalidArgumentException для ошибок валидации
+ */
+ public function testService_UsesInvalidArgumentException(): void
+ {
+ if (!file_exists($this->serviceFilePath)) {
+ $this->markTestSkipped('TimetableService.php not found');
+ }
+
+ $content = file_get_contents($this->serviceFilePath);
+
+ $this->assertStringContainsString(
+ 'use yii\base\InvalidArgumentException',
+ $content,
+ 'TimetableService должен импортировать InvalidArgumentException'
+ );
+
+ $this->assertStringContainsString(
+ 'throw new InvalidArgumentException',
+ $content,
+ 'TimetableService должен использовать InvalidArgumentException для ошибок валидации'
+ );
+ }
+
+ /**
+ * TimetableService должен передавать firstErrors[0] при ошибках save(),
+ * а не Json::encode(getErrors()) — для читаемого сообщения в API
+ */
+ public function testService_UsesFirstErrorsNotJsonEncode(): void
+ {
+ if (!file_exists($this->serviceFilePath)) {
+ $this->markTestSkipped('TimetableService.php not found');
+ }
+
+ $content = file_get_contents($this->serviceFilePath);
+
+ // Не должно быть Json::encode($...->getErrors()) в throw
+ $pattern = '/throw\s+new\s+\w+Exception\s*\(\s*Json::encode\s*\(/';
+ preg_match_all($pattern, $content, $matches);
+
+ $this->assertEmpty(
+ $matches[0],
+ 'TimetableService не должен использовать Json::encode(getErrors()) в исключениях. '
+ . 'Используйте array_values($model->firstErrors)[0] для читаемого сообщения.'
+ );
+ }
+
+ // ========================================================================
+ // ГРУППА 2: EventBehavior корректно обрабатывает InvalidArgumentException
+ // ========================================================================
+
+ /**
+ * EventBehavior должен возвращать type=invalid_request_type_2
+ * для InvalidArgumentException
+ */
+ public function testEventBehavior_HandlesInvalidArgumentException(): void
+ {
+ $behaviorPath = dirname(__DIR__, 3) . '/api3/core/behaviors/EventBehavior.php';
+ if (!file_exists($behaviorPath)) {
+ $this->markTestSkipped('EventBehavior.php not found');
+ }
+
+ $content = file_get_contents($behaviorPath);
+
+ $this->assertStringContainsString(
+ 'InvalidArgumentException',
+ $content,
+ 'EventBehavior должен обрабатывать InvalidArgumentException'
+ );
+
+ $this->assertStringContainsString(
+ 'invalid_request_type_2',
+ $content,
+ 'EventBehavior должен возвращать тип "invalid_request_type_2" для InvalidArgumentException'
+ );
+ }
+}