seed(RolePermissionSeeder::class); } protected function actingWithRole(string $role): User { $user = User::factory()->create(); $user->syncRoles([$role]); $this->withHeaders($this->apiHeadersFor($user)); return $user; } #[Test] public function a_client_is_created_with_its_sector_in_several_languages(): void { $this->actingWithRole('editor'); $this->postJson('/api/v1/clients?lang=en&with=translations', [ 'name' => 'Asociación Vecinal', 'slug' => 'asociacion-vecinal', 'sector' => ['es' => 'Tercer sector', 'en' => 'Non-profit'], 'url' => 'https://vecinos.example.org', 'is_public' => true, ]) ->assertCreated() ->assertJsonPath('data.sector', 'Non-profit') ->assertJsonPath('data.translations.sector.es', 'Tercer sector') ->assertJsonPath('data.projects_count', 0); $this->postJson('/api/v1/clients', ['name' => 'Otra', 'slug' => 'asociacion-vecinal']) ->assertUnprocessable() ->assertJsonValidationErrors('slug'); } #[Test] public function a_deleted_client_can_be_brought_back(): void { $this->actingWithRole('editor'); $client = Client::factory()->create(); $this->deleteJson("/api/v1/clients/{$client->id}")->assertNoContent(); $this->assertSoftDeleted($client); $this->postJson("/api/v1/clients/{$client->id}/restore")->assertOk(); $this->assertNotSoftDeleted($client); } #[Test] public function a_testimonial_keeps_the_language_it_was_given_in(): void { $this->actingWithRole('editor'); $client = Client::factory()->create(); $this->postJson('/api/v1/testimonials?lang=es', [ 'quote' => ['en' => 'They listened first.', 'es' => 'Primero escucharon.'], 'original_locale' => 'en', 'author_name' => 'Grace Hopper', 'author_role' => ['es' => 'Coordinadora'], 'client_id' => $client->id, 'given_at' => '2026-05-01', ]) ->assertCreated() ->assertJsonPath('data.quote', 'Primero escucharon.') ->assertJsonPath('data.original_locale', 'en') ->assertJsonPath('data.client_id', $client->id) ->assertJsonPath('data.given_at', '2026-05-01'); $this->assertSame('They listened first.', Testimonial::query()->sole()->getTranslation('quote', 'en')); $this->postJson('/api/v1/testimonials', [ 'quote' => ['es' => 'x'], 'original_locale' => 'tlh', 'author_name' => 'Worf', ]) ->assertUnprocessable() ->assertJsonValidationErrors('original_locale'); } #[Test] public function a_talk_is_filtered_by_who_gave_it_and_deleted_for_good(): void { $this->actingWithRole('editor'); $member = Member::factory()->create(); $id = $this->postJson('/api/v1/talks', [ 'title' => 'Angular sin miedo', 'language' => 'es', 'summary' => ['es' => 'Señales y SSR.', 'en' => 'Signals and SSR.'], 'event' => 'Commit Conf', 'held_at' => '2026-04-20', 'member_id' => $member->id, 'video_url' => 'https://video.example.org/1', ]) ->assertCreated() ->assertJsonPath('data.title', 'Angular sin miedo') ->json('data.id'); Talk::factory()->create(); $this->getJson("/api/v1/talks?member_id={$member->id}&lang=en") ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.summary', 'Signals and SSR.'); $this->deleteJson("/api/v1/talks/{$id}")->assertNoContent(); $this->assertDatabaseMissing('talks', ['id' => $id]); } #[Test] public function a_writer_can_read_them_but_not_write_them(): void { $this->actingWithRole('writer'); $client = Client::factory()->create(); $this->getJson('/api/v1/clients')->assertOk(); $this->getJson('/api/v1/testimonials')->assertOk(); $this->getJson('/api/v1/talks')->assertOk(); $this->patchJson("/api/v1/clients/{$client->id}", ['name' => 'Otro'])->assertForbidden(); $this->postJson('/api/v1/talks', ['title' => 'X', 'language' => 'es', 'event' => 'Y'])->assertForbidden(); } #[Test] public function a_logo_is_uploaded_for_a_client(): void { $this->actingWithRole('writer'); // Uploading for a client takes permission to write clients. $this->postJson('/api/v1/media', ['url' => 'https://example.org/a.png', 'for' => 'clients']) ->assertForbidden(); } }