07b5758291
Task 1.1: SalesPeriodResolver.resolve({kind,from,to})→SalesPeriodRange (МСК, Europe/Moscow) + monthsIn() для ступенчатого тарифа. Поддержка this/prev/prev2/custom; неизвестный kind→this; custom from>to→исключение. Тест 10/10, stan 0. Один эскейп на сессию.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
126 lines
5.1 KiB
PHP
126 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Sales\SalesPeriodRange;
|
|
use App\Services\Sales\SalesPeriodResolver;
|
|
use Carbon\CarbonImmutable;
|
|
|
|
// Заморозка времени: 15 июня 2026, 12:00 МСК
|
|
beforeEach(function (): void {
|
|
CarbonImmutable::setTestNow(
|
|
CarbonImmutable::parse('2026-06-15 12:00:00', 'Europe/Moscow'),
|
|
);
|
|
});
|
|
|
|
afterEach(function (): void {
|
|
CarbonImmutable::setTestNow();
|
|
});
|
|
|
|
// ─── kind=this ────────────────────────────────────────────────────────────────
|
|
|
|
it('kind=this returns current month range (June 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
expect($range)->toBeInstanceOf(SalesPeriodRange::class);
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-30 23:59:59');
|
|
});
|
|
|
|
// ─── kind=prev ────────────────────────────────────────────────────────────────
|
|
|
|
it('kind=prev returns previous month range (May 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'prev']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-05-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-31 23:59:59');
|
|
});
|
|
|
|
// ─── kind=prev2 ───────────────────────────────────────────────────────────────
|
|
|
|
it('kind=prev2 returns month-before-previous range (April 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'prev2']);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-04-01 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-04-30 23:59:59');
|
|
});
|
|
|
|
// ─── kind=custom ──────────────────────────────────────────────────────────────
|
|
|
|
it('kind=custom returns exact from/to bounds', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
'to' => '2026-05-20',
|
|
]);
|
|
|
|
expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-03-10 00:00:00');
|
|
expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-20 23:59:59');
|
|
});
|
|
|
|
it('kind=custom: monthsIn returns all three month-starts for Mar-May span', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
'to' => '2026-05-20',
|
|
]);
|
|
|
|
$months = $resolver->monthsIn($range);
|
|
|
|
expect($months)->toHaveCount(3);
|
|
expect($months[0]->format('Y-m-d'))->toBe('2026-03-01');
|
|
expect($months[1]->format('Y-m-d'))->toBe('2026-04-01');
|
|
expect($months[2]->format('Y-m-d'))->toBe('2026-05-01');
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when from > to', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-05-20',
|
|
'to' => '2026-03-10',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when from is missing', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'to' => '2026-05-20',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
it('kind=custom: throws InvalidArgumentException when to is missing', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
expect(fn () => $resolver->resolve([
|
|
'kind' => 'custom',
|
|
'from' => '2026-03-10',
|
|
]))->toThrow(InvalidArgumentException::class);
|
|
});
|
|
|
|
// ─── monthsIn ─────────────────────────────────────────────────────────────────
|
|
|
|
it('monthsIn for single-month "this" range returns one entry (June 2026)', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
$months = $resolver->monthsIn($range);
|
|
|
|
expect($months)->toHaveCount(1);
|
|
expect($months[0]->format('Y-m-d'))->toBe('2026-06-01');
|
|
});
|
|
|
|
// ─── timezone ─────────────────────────────────────────────────────────────────
|
|
|
|
it('range start carries Europe/Moscow timezone', function (): void {
|
|
$resolver = new SalesPeriodResolver;
|
|
$range = $resolver->resolve(['kind' => 'this']);
|
|
|
|
expect($range->start->timezoneName)->toBe('Europe/Moscow');
|
|
});
|