user = User::factory()->create(); $this->trip = Trip::factory()->create([ 'created_by_user_id' => $this->user->id, 'start_date' => '2024-01-01', 'end_date' => '2024-01-03' ]); $this->token = $this->user->createToken('test-token')->plainTextToken; } public function test_can_create_plannable_item() { $data = [ 'name' => 'Eiffel Tower', 'type' => 'attraction', 'address' => 'Champ de Mars, Paris', 'notes' => 'Visit in the morning', 'metadata' => [ 'opening_hours' => '9:00 AM - 11:00 PM', 'ticket_price' => '25 EUR' ] ]; $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->postJson("/api/trips/{$this->trip->id}/plannables", $data); $response->assertStatus(201) ->assertJsonPath('data.name', 'Eiffel Tower') ->assertJsonPath('data.type', 'attraction') ->assertJsonPath('data.address', 'Champ de Mars, Paris') ->assertJsonPath('data.metadata.opening_hours', '9:00 AM - 11:00 PM'); $this->assertDatabaseHas('plannable_items', [ 'trip_id' => $this->trip->id, 'name' => 'Eiffel Tower', 'type' => 'attraction' ]); } public function test_can_list_plannable_items_for_trip() { PlannableItem::factory()->count(3)->create([ 'trip_id' => $this->trip->id ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->getJson("/api/trips/{$this->trip->id}/plannables"); $response->assertStatus(200) ->assertJsonCount(3, 'data'); } public function test_can_update_plannable_item() { $item = PlannableItem::factory()->create([ 'trip_id' => $this->trip->id, 'name' => 'Old Name' ]); $updateData = [ 'name' => 'Updated Name', 'type' => 'restaurant', 'notes' => 'Updated notes' ]; $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->putJson("/api/plannables/{$item->id}", $updateData); $response->assertStatus(200) ->assertJsonPath('data.name', 'Updated Name') ->assertJsonPath('data.type', 'restaurant'); $this->assertDatabaseHas('plannable_items', [ 'id' => $item->id, 'name' => 'Updated Name', 'type' => 'restaurant' ]); } public function test_can_delete_plannable_item() { $item = PlannableItem::factory()->create([ 'trip_id' => $this->trip->id ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->deleteJson("/api/plannables/{$item->id}"); $response->assertStatus(204); $this->assertDatabaseMissing('plannable_items', [ 'id' => $item->id ]); } public function test_cannot_access_plannable_items_of_other_users_trip() { $otherUser = User::factory()->create(); $otherTrip = Trip::factory()->create([ 'created_by_user_id' => $otherUser->id ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->getJson("/api/trips/{$otherTrip->id}/plannables"); $response->assertStatus(403); } public function test_cannot_update_plannable_item_of_other_users_trip() { $otherUser = User::factory()->create(); $otherTrip = Trip::factory()->create([ 'created_by_user_id' => $otherUser->id ]); $item = PlannableItem::factory()->create([ 'trip_id' => $otherTrip->id ]); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->putJson("/api/plannables/{$item->id}", [ 'name' => 'Hacked Name' ]); $response->assertStatus(403); } public function test_validates_required_fields_when_creating_plannable_item() { $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->postJson("/api/trips/{$this->trip->id}/plannables", []); $response->assertStatus(422) ->assertJsonValidationErrors(['name', 'type']); } public function test_validates_type_enum_values() { $data = [ 'name' => 'Test Item', 'type' => 'invalid_type' ]; $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json' ])->postJson("/api/trips/{$this->trip->id}/plannables", $data); $response->assertStatus(422) ->assertJsonValidationErrors(['type']); } }