fix(http): timestamp validation ±24h для partition guard (Plan 2.6 #iii)
Закрывает CV.11 audit WARN minor #5 (Carbon::createFromTimestamp(time) без range guard → INSERT CRASH "no partition of relation deals found for row" для timestamp вне текущего месячного окна deals_2026_MM). Изменение: SupplierWebhookController::receive — добавлено min/max constraint на 'time' = [now-24h, now+24h] unix-timestamp. Timestamp вне окна → 422 ValidationException. ±24h: покрывает retry-задержки поставщика (network-сбой) + clock-drift серверов; шире окно (±48h+) = риск partition-промаха на стыке месяцев (нужен Plan 5 partition cron). TDD: +3 теста (-2 days → 422; +2 days → 422; -6h → 202). Regression-fix: existing test 'inserts supplier_lead row' использовал hardcoded 'time' => 1703781939 (Dec 28 2023) — теперь out-of-window. Заменено на time(). phpstan-baseline: postJson() count: 8 → 11 (+3 от Task 3 тестов). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,11 +50,19 @@ class SupplierWebhookController extends Controller
|
||||
return response()->json(['message' => 'Not found.'], 404);
|
||||
}
|
||||
|
||||
// Plan 2.6 fix #iii: timestamp partition guard. Партиции deals месячные
|
||||
// (deals_2026_MM); time за пределами текущего месяца → INSERT CRASH
|
||||
// "no partition of relation deals found for row" в RouteSupplierLeadJob.
|
||||
// Окно ±24h защищает от wildly out-of-range значений (старый/будущий
|
||||
// дроп от поставщика); покрывает retry-задержки + clock-drift серверов.
|
||||
$minTime = now()->subDay()->getTimestamp();
|
||||
$maxTime = now()->addDay()->getTimestamp();
|
||||
|
||||
$validated = $request->validate([
|
||||
'vid' => 'required|integer|min:1',
|
||||
'project' => ['required', 'string', 'max:255', 'regex:/^B[123]_.+$/'],
|
||||
'phone' => ['required', 'string', 'regex:/^7\d{10}$/'],
|
||||
'time' => 'required|integer|min:1',
|
||||
'time' => ['required', 'integer', "min:{$minTime}", "max:{$maxTime}"],
|
||||
'tag' => 'nullable|string|max:255',
|
||||
'phones' => 'nullable|array',
|
||||
'phones.*' => 'string|regex:/^7\d{10}$/',
|
||||
|
||||
@@ -597,7 +597,7 @@ parameters:
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 8
|
||||
count: 11
|
||||
path: tests/Feature/Http/Webhook/SupplierWebhookTest.php
|
||||
|
||||
-
|
||||
|
||||
@@ -54,7 +54,7 @@ it('inserts supplier_lead row + dispatches RouteSupplierLeadJob', function () {
|
||||
'tag' => 'Ваш инвестор',
|
||||
'phone' => '79991234567',
|
||||
'phones' => ['79991234567'],
|
||||
'time' => 1703781939,
|
||||
'time' => time(),
|
||||
]);
|
||||
|
||||
$response->assertStatus(202);
|
||||
@@ -122,3 +122,38 @@ it('allows empty IP allowlist в testing env (Plan 2.6 fix #ii — fail-open д
|
||||
|
||||
$response->assertStatus(202);
|
||||
});
|
||||
|
||||
it('rejects timestamp older than 24h (Plan 2.6 fix #iii — partition guard)', function () {
|
||||
$response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [
|
||||
'vid' => 100001,
|
||||
'project' => 'B1_old-time.ru',
|
||||
'phone' => '79991234567',
|
||||
'time' => now()->subDays(2)->getTimestamp(),
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)->assertJsonValidationErrors('time');
|
||||
});
|
||||
|
||||
it('rejects timestamp more than 24h in future (Plan 2.6 fix #iii — partition guard)', function () {
|
||||
$response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [
|
||||
'vid' => 100002,
|
||||
'project' => 'B1_future-time.ru',
|
||||
'phone' => '79991234567',
|
||||
'time' => now()->addDays(2)->getTimestamp(),
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)->assertJsonValidationErrors('time');
|
||||
});
|
||||
|
||||
it('accepts timestamp within ±24h window (Plan 2.6 fix #iii — partition guard)', function () {
|
||||
Bus::fake();
|
||||
|
||||
$response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [
|
||||
'vid' => 100003,
|
||||
'project' => 'B1_valid-time.ru',
|
||||
'phone' => '79991234567',
|
||||
'time' => now()->subHours(6)->getTimestamp(),
|
||||
]);
|
||||
|
||||
$response->assertStatus(202);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user