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_call_is_opened_with_its_stack(): void { $this->actingWithRole('editor'); $technology = Technology::factory()->create(); $this->postJson('/api/v1/calls?lang=es', [ 'title' => ['es' => 'Backend en Laravel', 'en' => 'Laravel backend'], 'slug' => ['es' => 'backend-laravel', 'en' => 'laravel-backend'], 'hours_per_week' => 4, 'duration_weeks' => 8, 'seniority' => 'mid', 'closes_at' => '2026-12-01', 'technology_ids' => [$technology->id], ]) ->assertCreated() ->assertJsonPath('data.title', 'Backend en Laravel') ->assertJsonPath('data.status', 'open') ->assertJsonPath('data.technology_ids', [$technology->id]) ->assertJsonPath('data.applications_count', 0); $this->postJson('/api/v1/calls', [ 'title' => ['es' => 'Otra'], 'slug' => ['es' => 'backend-laravel'], 'hours_per_week' => 60, 'seniority' => 'guru', ]) ->assertUnprocessable() ->assertJsonValidationErrors(['slug', 'hours_per_week', 'seniority']); } #[Test] public function a_call_is_closed_and_then_deleted(): void { $this->actingWithRole('editor'); $call = Call::factory()->create(); $this->patchJson("/api/v1/calls/{$call->id}", ['status' => 'filled']) ->assertOk() ->assertJsonPath('data.status', 'filled'); $this->getJson('/api/v1/calls?status=open')->assertJsonCount(0, 'data'); $this->deleteJson("/api/v1/calls/{$call->id}")->assertNoContent(); $this->assertModelMissing($call); } #[Test] public function the_inbox_leaves_spam_out_unless_asked(): void { $this->actingWithRole('editor'); Application::factory()->create(['name' => 'Ada']); Application::factory()->create(['status' => ApplicationStatus::Spam]); $this->getJson('/api/v1/applications') ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.name', 'Ada') ->assertJsonMissingPath('data.0.ip_hash'); $this->getJson('/api/v1/applications?status=spam')->assertJsonCount(1, 'data'); $this->getJson('/api/v1/applications?search=ada')->assertJsonCount(1, 'data'); } #[Test] public function handling_an_application_records_who_did_it(): void { $editor = $this->actingWithRole('editor'); $application = Application::factory()->create(); $this->patchJson("/api/v1/applications/{$application->id}", [ 'status' => 'contacted', 'notes' => 'Llamada el jueves.', ]) ->assertOk() ->assertJsonPath('data.status', 'contacted') ->assertJsonPath('data.notes', 'Llamada el jueves.') ->assertJsonPath('data.handled_by.id', $editor->id); $this->assertNotNull($application->fresh()->handled_at); $this->patchJson("/api/v1/applications/{$application->id}", ['status' => 'new']) ->assertJsonPath('data.handled_by', null) ->assertJsonPath('data.handled_at', null); } #[Test] public function what_the_sender_wrote_cannot_be_changed(): void { $this->actingWithRole('editor'); $application = Application::factory()->create(['email' => 'real@example.org']); $this->patchJson("/api/v1/applications/{$application->id}", ['email' => 'fake@example.org', 'status' => 'accepted']) ->assertOk(); $this->assertSame('real@example.org', $application->fresh()->email); } #[Test] public function nobody_creates_an_application_here(): void { $this->actingWithRole('admin'); $this->postJson('/api/v1/applications', ['name' => 'X'])->assertMethodNotAllowed(); } #[Test] public function a_writer_cannot_read_the_inbox(): void { $this->actingWithRole('writer'); $application = Application::factory()->create(); $this->getJson('/api/v1/applications')->assertForbidden(); $this->getJson("/api/v1/applications/{$application->id}")->assertForbidden(); // Calls are theirs to read, like on the panel. $this->getJson('/api/v1/calls')->assertOk(); } #[Test] public function a_deleted_application_can_be_brought_back(): void { $this->actingWithRole('editor'); $application = Application::factory()->create(); $this->deleteJson("/api/v1/applications/{$application->id}")->assertNoContent(); $this->assertSoftDeleted($application); $this->postJson("/api/v1/applications/{$application->id}/restore")->assertOk(); $this->assertNotSoftDeleted($application); } }