Files
hado/core/tests/Feature/ClientApiTest.php
2026-09-04 08:28:36 -03:00

135 lines
5.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Enums\EventState;
use App\Models\Event;
use App\Models\Source;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ClientApiTest extends TestCase
{
use RefreshDatabase;
private const TOKEN = 'hado_testtoken';
protected function setUp(): void
{
parent::setUp();
CarbonImmutable::setTestNow('2026-09-01T12:00:00Z');
Source::create(['name' => 'docs', 'token_hash' => hash('sha256', self::TOKEN)]);
}
protected function tearDown(): void
{
CarbonImmutable::setTestNow();
parent::tearDown();
}
private function item(array $over = []): array
{
return array_replace([
'user' => 'nikita', 'source_ref' => 'person:42', 'topic' => 'birthday:2026',
'due_at' => '2026-08-14', 'fire_at' => '2026-08-14', 'after_due' => 'expire',
'payload' => ['title' => 'Сегодня ДР — Вася', 'deep_link' => '/people/42'],
], $over);
}
private function auth(): array
{
return ['Authorization' => 'Bearer '.self::TOKEN];
}
public function test_rejects_missing_or_wrong_token(): void
{
$this->putJson('/api/events', ['events' => [$this->item()]])->assertStatus(401);
$this->putJson('/api/events', ['events' => [$this->item()]], ['Authorization' => 'Bearer nope'])->assertStatus(401);
}
public function test_upserts_batch_and_returns_ids_and_states(): void
{
$this->putJson('/api/events', ['events' => [$this->item()]], $this->auth())
->assertOk()
->assertJsonPath('events.0.source_ref', 'person:42')
->assertJsonPath('events.0.topic', 'birthday:2026')
->assertJsonPath('events.0.state', 'expired');
$this->assertSame(1, Event::count());
}
public function test_validation_rejects_whole_batch_with_index(): void
{
$bad = $this->item(['due_at' => 'tomorrow']);
$this->putJson('/api/events', ['events' => [$this->item(), $bad]], $this->auth())
->assertStatus(422)
->assertJsonValidationErrors(['events.1.due_at']);
$this->assertSame(0, Event::count(), 'ничего не применилось');
}
public function test_validation_rules(): void
{
$this->putJson('/api/events', ['events' => [$this->item(['fire_at' => '2026-08-15'])]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.fire_at']);
$this->putJson('/api/events', ['events' => [$this->item(['after_due' => 'maybe'])]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.after_due']);
$this->putJson('/api/events', ['events' => [$this->item(['payload' => ['subtitle' => 'x']])]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.payload.title']);
$this->putJson('/api/events', ['events' => [$this->item(['user' => ''])]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.user']);
$this->putJson('/api/events', ['events' => array_fill(0, 501, $this->item())], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events']);
}
public function test_deep_link_must_be_relative_or_http(): void
{
$bad = $this->item(['payload' => ['title' => 'x', 'deep_link' => 'javascript:alert(1)']]);
$this->putJson('/api/events', ['events' => [$bad]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.payload.deep_link']);
$protocolRelative = $this->item(['payload' => ['title' => 'x', 'deep_link' => '//evil.com/x']]);
$this->putJson('/api/events', ['events' => [$protocolRelative]], $this->auth())
->assertStatus(422)->assertJsonValidationErrors(['events.0.payload.deep_link']);
$ok1 = $this->item(['payload' => ['title' => 'x', 'deep_link' => '/people/42?x=1']]);
$this->putJson('/api/events', ['events' => [$ok1]], $this->auth())->assertOk();
$ok2 = $this->item(['topic' => 'birthday:2027', 'payload' => ['title' => 'x', 'deep_link' => 'https://docs.example/x']]);
$this->putJson('/api/events', ['events' => [$ok2]], $this->auth())->assertOk();
}
public function test_topic_defaults_to_empty_string(): void
{
$item = $this->item();
unset($item['topic']);
$this->putJson('/api/events', ['events' => [$item]], $this->auth())->assertOk()->assertJsonPath('events.0.topic', '');
}
public function test_get_and_delete_by_identity(): void
{
$this->putJson('/api/events', ['events' => [$this->item(['due_at' => '2026-09-30', 'fire_at' => '2026-08-31', 'after_due' => 'keep'])]], $this->auth());
$this->getJson('/api/events?source_ref=person:42&topic=birthday:2026', $this->auth())
->assertOk()
->assertJsonPath('state', 'preparing')
->assertJsonPath('due_at', '2026-09-30')
->assertJsonPath('topic', 'birthday:2026');
$this->deleteJson('/api/events?source_ref=person:42&topic=birthday:2026', [], $this->auth())->assertNoContent();
$this->assertSame(EventState::Withdrawn, Event::sole()->state);
$this->getJson('/api/events?source_ref=person:42&topic=nope', $this->auth())->assertNotFound();
$this->deleteJson('/api/events?source_ref=person:42&topic=nope', [], $this->auth())->assertNotFound();
}
public function test_source_is_isolated_by_token(): void
{
Source::create(['name' => 'other', 'token_hash' => hash('sha256', 'other-token')]);
$this->putJson('/api/events', ['events' => [$this->item()]], $this->auth());
$this->getJson('/api/events?source_ref=person:42&topic=birthday:2026', ['Authorization' => 'Bearer other-token'])->assertNotFound();
}
}