PromptVault/tests/Feature/Prompts/PromptStoreUpdateTest.php

178 lines
6.3 KiB
PHP
Raw Permalink Normal View History

2026-10-26 20:12:16 +00:00
<?php
namespace Tests\Feature\Prompts;
use App\Models\Prompt;
use App\Models\Tag;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class PromptStoreUpdateTest extends TestCase
{
use RefreshDatabase;
/**
* @return array<string, mixed>
*/
private function validPayload(array $overrides = []): array
{
return array_merge([
'name' => 'Code Reviewer',
'description' => 'Review code written in {{language}}',
'body' => 'Review this {{language}} snippet: {{snippet}}',
'tags' => ['Coding', 'code review'],
], $overrides);
}
public function test_create_page_renders_empty_form_with_suggested_tags(): void
{
Tag::query()->create(['name' => 'writing']);
$this->get('/prompts/create')
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('Prompts/Create')
->where('initial', ['name' => null, 'description' => null, 'body' => null, 'tags' => []])
->where('suggestedTags', ['writing']));
}
public function test_create_page_reflashes_old_input_after_failed_store(): void
{
$name = 'Too long '.str_repeat('n', 121);
// The browser arrives from the create page, so the failed store
// redirects back there with flashed old input.
$this->post('/prompts', ['name' => $name, 'body' => 'b'], ['Referer' => '/prompts/create'])
->assertRedirect('/prompts/create')
->assertSessionHasErrors('name');
$this->get('/prompts/create')
->assertInertia(fn (Assert $page) => $page
->component('Prompts/Create')
->where('initial.name', $name)
->where('initial.body', 'b'));
}
public function test_store_creates_prompt_with_normalized_tags(): void
{
$response = $this->post('/prompts', $this->validPayload([
'tags' => [' Coding ', 'coding', '', ' '],
]));
$prompt = Prompt::query()->sole();
$response->assertRedirect(route('prompts.show', $prompt));
$this->assertSame('Code Reviewer', $prompt->name);
$this->assertSame('Review code written in {{language}}', $prompt->description);
$this->assertSame('coding', $prompt->tags->first()->name);
$this->assertSame(1, Tag::query()->count());
$this->assertFalse($prompt->is_favorite);
$this->assertSame(0, $prompt->usage_count);
}
public function test_store_without_tags_creates_prompt_without_tags(): void
{
$response = $this->post('/prompts', [
'name' => 'Bare',
'body' => 'Hello, world.',
]);
$prompt = Prompt::query()->sole();
$response->assertRedirect(route('prompts.show', $prompt));
$this->assertCount(0, $prompt->tags);
$this->assertNull($prompt->description);
}
public function test_store_reuses_existing_tags_and_dedupes_names(): void
{
Tag::query()->create(['name' => 'alpha']);
$response = $this->post('/prompts', $this->validPayload(['tags' => ['ALPHA', 'alpha', 'beta']]));
$prompt = Prompt::query()->sole();
$response->assertRedirect();
$this->assertSame(2, Tag::query()->count());
$this->assertSame(['alpha', 'beta'], $prompt->tags->pluck('name')->all());
}
public function test_store_validates_payload(): void
{
$this->post('/prompts', ['description' => 'd', 'body' => 'b'])
->assertSessionHasErrors('name');
$this->post('/prompts', $this->validPayload(['name' => str_repeat('n', 121)]))
->assertSessionHasErrors('name');
$this->post('/prompts', $this->validPayload(['description' => str_repeat('d', 501)]))
->assertSessionHasErrors('description');
$this->post('/prompts', ['name' => 'N'])
->assertSessionHasErrors('body');
$this->post('/prompts', $this->validPayload(['tags' => 'nope']))
->assertSessionHasErrors('tags');
$this->post('/prompts', $this->validPayload(['tags' => [123]]))
->assertSessionHasErrors('tags.0');
$this->post('/prompts', $this->validPayload(['tags' => [str_repeat('t', 41)]]))
->assertSessionHasErrors('tags.0');
$this->assertSame(0, Prompt::query()->count());
$this->assertSame(0, Tag::query()->count());
}
public function test_update_changes_fields_and_resyncs_tags(): void
{
$old = Tag::query()->create(['name' => 'old']);
$prompt = Prompt::factory()->create([
'name' => 'Before',
'description' => 'old description',
'body' => 'old {{body}}',
]);
$prompt->tags()->attach($old->id);
$response = $this->put("/prompts/{$prompt->id}", $this->validPayload([
'name' => 'After',
'description' => 'new description',
'body' => 'new body',
'tags' => ['fresh'],
]));
$prompt->refresh();
$response->assertRedirect(route('prompts.show', $prompt));
$this->assertSame('After', $prompt->name);
$this->assertSame('new description', $prompt->description);
$this->assertSame('new body', $prompt->body);
$this->assertSame(['fresh'], $prompt->tags->pluck('name')->all());
// The detached tag row survives; only the pivot is removed.
$this->assertSame(2, Tag::query()->count());
}
public function test_update_without_tags_clears_existing_tags(): void
{
$tag = Tag::query()->create(['name' => 'old']);
$prompt = Prompt::factory()->create(['name' => 'Before', 'body' => 'x']);
$prompt->tags()->attach($tag->id);
$response = $this->put("/prompts/{$prompt->id}", ['name' => 'After', 'body' => 'y']);
$response->assertRedirect();
$this->assertCount(0, $prompt->fresh()->tags);
}
public function test_update_validates_payload(): void
{
$prompt = Prompt::factory()->create();
$this->put("/prompts/{$prompt->id}", ['body' => 'b'])->assertSessionHasErrors('name');
$this->put("/prompts/{$prompt->id}", ['name' => 'N'])->assertSessionHasErrors('body');
$this->put("/prompts/{$prompt->id}", ['name' => 'N', 'body' => 'B', 'tags' => null])
->assertSessionHasNoErrors();
}
}