From 0a36afbfdce4a78e6d05909b51ed9d559802d4ac Mon Sep 17 00:00:00 2001 From: root Date: Mon, 10 Aug 2026 11:47:51 +0300 Subject: [PATCH] Add feature test coverage for the core domain Only Breeze's default auth/profile tests existed - nothing covered Workouts, Sessions, Exercises, Yoga, Favorites, Blog, or the Admin panel, which is the actual product. Adds factories for the domain models and 45 new feature tests covering the main flows plus authorization boundaries (owner-only access on workouts/sessions, admin-only access on /admin, drafts hidden from the public blog). Co-Authored-By: Claude Sonnet 5 --- app/Models/Exercise.php | 3 + app/Models/Post.php | 3 + database/factories/ExerciseFactory.php | 25 +++ database/factories/WorkoutExerciseFactory.php | 27 ++++ database/factories/WorkoutFactory.php | 22 +++ database/factories/WorkoutSessionFactory.php | 23 +++ database/factories/YogaCategoryFactory.php | 20 +++ database/factories/YogaPoseFactory.php | 33 ++++ tests/Feature/Admin/AdminAccessTest.php | 39 +++++ tests/Feature/Admin/ExerciseCrudTest.php | 73 +++++++++ tests/Feature/Admin/PostCrudTest.php | 89 +++++++++++ tests/Feature/BlogTest.php | 67 ++++++++ tests/Feature/ExerciseTest.php | 76 +++++++++ tests/Feature/FavoriteTest.php | 47 ++++++ tests/Feature/WorkoutSessionTest.php | 107 +++++++++++++ tests/Feature/WorkoutTest.php | 149 ++++++++++++++++++ tests/Feature/YogaPoseTest.php | 58 +++++++ 17 files changed, 861 insertions(+) create mode 100644 database/factories/ExerciseFactory.php create mode 100644 database/factories/WorkoutExerciseFactory.php create mode 100644 database/factories/WorkoutFactory.php create mode 100644 database/factories/WorkoutSessionFactory.php create mode 100644 database/factories/YogaCategoryFactory.php create mode 100644 database/factories/YogaPoseFactory.php create mode 100644 tests/Feature/Admin/AdminAccessTest.php create mode 100644 tests/Feature/Admin/ExerciseCrudTest.php create mode 100644 tests/Feature/Admin/PostCrudTest.php create mode 100644 tests/Feature/BlogTest.php create mode 100644 tests/Feature/ExerciseTest.php create mode 100644 tests/Feature/FavoriteTest.php create mode 100644 tests/Feature/WorkoutSessionTest.php create mode 100644 tests/Feature/WorkoutTest.php create mode 100644 tests/Feature/YogaPoseTest.php diff --git a/app/Models/Exercise.php b/app/Models/Exercise.php index 08f989d..77a214c 100644 --- a/app/Models/Exercise.php +++ b/app/Models/Exercise.php @@ -2,12 +2,15 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; class Exercise extends Model { + use HasFactory; + protected $fillable = [ 'name', 'slug', diff --git a/app/Models/Post.php b/app/Models/Post.php index 8c83000..d8d3d45 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -3,12 +3,15 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\Storage; class Post extends Model { + use HasFactory; + protected $fillable = [ 'title', 'slug', diff --git a/database/factories/ExerciseFactory.php b/database/factories/ExerciseFactory.php new file mode 100644 index 0000000..0d5bb8f --- /dev/null +++ b/database/factories/ExerciseFactory.php @@ -0,0 +1,25 @@ + + */ +class ExerciseFactory extends Factory +{ + public function definition(): array + { + $name = ucfirst(fake()->unique()->words(3, true)); + + return [ + 'name' => $name, + 'slug' => Str::slug($name), + 'exercise_db_id' => (string) fake()->unique()->numberBetween(1, 999999), + 'gif_url' => 'https://example.com/exercise.gif', + 'instructions' => implode("\n", fake()->sentences(3)), + ]; + } +} diff --git a/database/factories/WorkoutExerciseFactory.php b/database/factories/WorkoutExerciseFactory.php new file mode 100644 index 0000000..7a71420 --- /dev/null +++ b/database/factories/WorkoutExerciseFactory.php @@ -0,0 +1,27 @@ + + */ +class WorkoutExerciseFactory extends Factory +{ + public function definition(): array + { + return [ + 'workout_id' => Workout::factory(), + 'exercise_id' => Exercise::factory(), + 'position' => 1, + 'sets' => 3, + 'reps' => 10, + 'weight' => null, + 'rest_seconds' => 60, + 'notes' => null, + ]; + } +} diff --git a/database/factories/WorkoutFactory.php b/database/factories/WorkoutFactory.php new file mode 100644 index 0000000..00c2796 --- /dev/null +++ b/database/factories/WorkoutFactory.php @@ -0,0 +1,22 @@ + + */ +class WorkoutFactory extends Factory +{ + public function definition(): array + { + return [ + 'user_id' => User::factory(), + 'title' => ucfirst(fake()->words(3, true)) . ' Workout', + 'notes' => fake()->sentence(), + 'estimated_minutes' => fake()->numberBetween(15, 90), + ]; + } +} diff --git a/database/factories/WorkoutSessionFactory.php b/database/factories/WorkoutSessionFactory.php new file mode 100644 index 0000000..e3ee8f1 --- /dev/null +++ b/database/factories/WorkoutSessionFactory.php @@ -0,0 +1,23 @@ + + */ +class WorkoutSessionFactory extends Factory +{ + public function definition(): array + { + return [ + 'user_id' => User::factory(), + 'workout_id' => Workout::factory(), + 'started_at' => now(), + 'completed_at' => null, + ]; + } +} diff --git a/database/factories/YogaCategoryFactory.php b/database/factories/YogaCategoryFactory.php new file mode 100644 index 0000000..a0cba5b --- /dev/null +++ b/database/factories/YogaCategoryFactory.php @@ -0,0 +1,20 @@ + + */ +class YogaCategoryFactory extends Factory +{ + public function definition(): array + { + return [ + 'external_id' => fake()->unique()->numberBetween(1, 999999), + 'name' => ucfirst(fake()->unique()->word()) . ' Yoga', + 'description' => fake()->sentence(), + ]; + } +} diff --git a/database/factories/YogaPoseFactory.php b/database/factories/YogaPoseFactory.php new file mode 100644 index 0000000..f371561 --- /dev/null +++ b/database/factories/YogaPoseFactory.php @@ -0,0 +1,33 @@ + + */ +class YogaPoseFactory extends Factory +{ + public function definition(): array + { + $name = ucfirst(fake()->unique()->words(2, true)) . ' Pose'; + + return [ + 'external_id' => fake()->unique()->numberBetween(1, 999999), + 'category_id' => YogaCategory::factory(), + 'slug' => Str::slug($name), + 'english_name' => $name, + 'sanskrit_name_adapted' => fake()->word(), + 'sanskrit_name' => fake()->word(), + 'translation_name' => fake()->word(), + 'description' => fake()->sentence(), + 'benefits' => fake()->sentence(), + 'url_svg' => null, + 'url_png' => null, + 'url_svg_alt' => null, + ]; + } +} diff --git a/tests/Feature/Admin/AdminAccessTest.php b/tests/Feature/Admin/AdminAccessTest.php new file mode 100644 index 0000000..fdfdea7 --- /dev/null +++ b/tests/Feature/Admin/AdminAccessTest.php @@ -0,0 +1,39 @@ +get(route('admin.dashboard')); + + $response->assertRedirect(route('login')); + } + + public function test_regular_user_is_forbidden(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('admin.dashboard')); + + $response->assertForbidden(); + } + + public function test_admin_can_view_dashboard(): void + { + $admin = User::factory()->create(); + $admin->is_admin = true; + $admin->save(); + + $response = $this->actingAs($admin)->get(route('admin.dashboard')); + + $response->assertOk(); + } +} diff --git a/tests/Feature/Admin/ExerciseCrudTest.php b/tests/Feature/Admin/ExerciseCrudTest.php new file mode 100644 index 0000000..fa77d61 --- /dev/null +++ b/tests/Feature/Admin/ExerciseCrudTest.php @@ -0,0 +1,73 @@ +create(); + $admin->is_admin = true; + $admin->save(); + + return $admin; + } + + public function test_regular_user_cannot_access_exercise_admin(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('admin.exercises.index')); + + $response->assertForbidden(); + } + + public function test_admin_can_create_an_exercise(): void + { + $response = $this->actingAs($this->admin())->post(route('admin.exercises.store'), [ + 'name' => 'Barbell Deadlift', + 'gif_url' => 'https://example.com/deadlift.gif', + 'instructions' => 'Lift the bar.', + ]); + + $response->assertRedirect(route('admin.exercises.index')); + $this->assertDatabaseHas('exercises', [ + 'name' => 'Barbell Deadlift', + 'slug' => 'barbell-deadlift', + ]); + } + + public function test_admin_can_update_an_exercise(): void + { + $exercise = Exercise::factory()->create(['name' => 'Old Name']); + + $response = $this->actingAs($this->admin())->patch(route('admin.exercises.update', $exercise), [ + 'name' => 'New Name', + 'gif_url' => $exercise->gif_url, + 'instructions' => $exercise->instructions, + ]); + + $response->assertRedirect(route('admin.exercises.index')); + $this->assertDatabaseHas('exercises', [ + 'id' => $exercise->id, + 'name' => 'New Name', + ]); + } + + public function test_admin_can_delete_an_exercise(): void + { + $exercise = Exercise::factory()->create(); + + $response = $this->actingAs($this->admin())->delete(route('admin.exercises.destroy', $exercise)); + + $response->assertRedirect(route('admin.exercises.index')); + $this->assertDatabaseMissing('exercises', ['id' => $exercise->id]); + } +} diff --git a/tests/Feature/Admin/PostCrudTest.php b/tests/Feature/Admin/PostCrudTest.php new file mode 100644 index 0000000..47f2644 --- /dev/null +++ b/tests/Feature/Admin/PostCrudTest.php @@ -0,0 +1,89 @@ +create(); + $admin->is_admin = true; + $admin->save(); + + return $admin; + } + + public function test_regular_user_cannot_access_post_admin(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('admin.posts.index')); + + $response->assertForbidden(); + } + + public function test_admin_can_create_a_published_post(): void + { + $admin = $this->admin(); + + $response = $this->actingAs($admin)->post(route('admin.posts.store'), [ + 'title' => 'New Training Tips', + 'excerpt' => 'Short summary', + 'body' => 'First paragraph.', + 'published' => '1', + ]); + + $response->assertRedirect(route('admin.posts.index')); + $this->assertDatabaseHas('posts', [ + 'title' => 'New Training Tips', + 'slug' => 'new-training-tips', + 'author_id' => $admin->id, + ]); + $post = Post::where('slug', 'new-training-tips')->firstOrFail(); + $this->assertTrue($post->is_published); + } + + public function test_admin_can_create_a_draft_post(): void + { + $this->actingAs($this->admin())->post(route('admin.posts.store'), [ + 'title' => 'Unfinished Draft', + 'body' => 'Work in progress.', + ]); + + $post = Post::where('slug', 'unfinished-draft')->firstOrFail(); + $this->assertFalse($post->is_published); + } + + public function test_admin_can_update_a_post(): void + { + $post = Post::factory()->create(['title' => 'Old Title', 'published_at' => null]); + + $response = $this->actingAs($this->admin())->patch(route('admin.posts.update', $post), [ + 'title' => 'Updated Title', + 'body' => $post->body, + 'published' => '1', + ]); + + $response->assertRedirect(route('admin.posts.index')); + $post->refresh(); + $this->assertEquals('Updated Title', $post->title); + $this->assertTrue($post->is_published); + } + + public function test_admin_can_delete_a_post(): void + { + $post = Post::factory()->create(); + + $response = $this->actingAs($this->admin())->delete(route('admin.posts.destroy', $post)); + + $response->assertRedirect(route('admin.posts.index')); + $this->assertDatabaseMissing('posts', ['id' => $post->id]); + } +} diff --git a/tests/Feature/BlogTest.php b/tests/Feature/BlogTest.php new file mode 100644 index 0000000..2b10149 --- /dev/null +++ b/tests/Feature/BlogTest.php @@ -0,0 +1,67 @@ +create(['published_at' => now()->subDay()]); + Post::factory()->draft()->create(); + + $response = $this->get(route('blog.index')); + + $response->assertOk(); + $response->assertSee($published->title); + $response->assertViewHas('posts', function ($posts) { + return $posts->total() === 1; + }); + } + + public function test_index_excludes_future_scheduled_posts(): void + { + Post::factory()->create(['published_at' => now()->addDay()]); + + $response = $this->get(route('blog.index')); + + $response->assertOk(); + $response->assertViewHas('posts', function ($posts) { + return $posts->total() === 0; + }); + } + + public function test_show_displays_a_published_post(): void + { + $post = Post::factory()->create([ + 'title' => 'How to Train Consistently', + 'published_at' => now()->subDay(), + ]); + + $response = $this->get(route('blog.show', $post->slug)); + + $response->assertOk(); + $response->assertSee('How to Train Consistently'); + } + + public function test_show_returns_404_for_a_draft_post(): void + { + $post = Post::factory()->draft()->create(); + + $response = $this->get(route('blog.show', $post->slug)); + + $response->assertNotFound(); + } + + public function test_show_returns_404_for_unknown_slug(): void + { + $response = $this->get('/blog/does-not-exist'); + + $response->assertNotFound(); + } +} diff --git a/tests/Feature/ExerciseTest.php b/tests/Feature/ExerciseTest.php new file mode 100644 index 0000000..55c38c8 --- /dev/null +++ b/tests/Feature/ExerciseTest.php @@ -0,0 +1,76 @@ +count(3)->create(); + + $response = $this->get(route('exercises.index')); + + $response->assertOk(); + $response->assertViewHas('exercises', function ($exercises) { + return $exercises->total() === 3; + }); + } + + public function test_index_can_filter_by_search_query(): void + { + Exercise::factory()->create(['name' => 'Barbell Bench Press']); + Exercise::factory()->create(['name' => 'Dumbbell Curl']); + + $response = $this->get(route('exercises.index', ['q' => 'Bench'])); + + $response->assertOk(); + $response->assertViewHas('exercises', function ($exercises) { + return $exercises->total() === 1 + && $exercises->first()->name === 'Barbell Bench Press'; + }); + } + + public function test_index_can_filter_by_body_part(): void + { + $chest = BodyPart::create(['name' => 'Chest', 'slug' => 'chest']); + $back = BodyPart::create(['name' => 'Back', 'slug' => 'back']); + + $chestExercise = Exercise::factory()->create(); + $chestExercise->bodyParts()->attach($chest->id); + + $backExercise = Exercise::factory()->create(); + $backExercise->bodyParts()->attach($back->id); + + $response = $this->get(route('exercises.index', ['bodypart' => 'chest'])); + + $response->assertOk(); + $response->assertViewHas('exercises', function ($exercises) use ($chestExercise) { + return $exercises->total() === 1 + && $exercises->first()->id === $chestExercise->id; + }); + } + + public function test_show_displays_exercise(): void + { + $exercise = Exercise::factory()->create(['name' => 'Barbell Squat']); + + $response = $this->get(route('exercises.show', $exercise->slug)); + + $response->assertOk(); + $response->assertSee('Barbell Squat'); + } + + public function test_show_returns_404_for_unknown_slug(): void + { + $response = $this->get('/exercises/does-not-exist'); + + $response->assertNotFound(); + } +} diff --git a/tests/Feature/FavoriteTest.php b/tests/Feature/FavoriteTest.php new file mode 100644 index 0000000..368be0c --- /dev/null +++ b/tests/Feature/FavoriteTest.php @@ -0,0 +1,47 @@ +create(); + + $response = $this->post(route('exercises.favorite', $exercise)); + + $response->assertRedirect(route('login')); + } + + public function test_user_can_toggle_exercise_favorite(): void + { + $user = User::factory()->create(); + $exercise = Exercise::factory()->create(); + + $this->actingAs($user)->post(route('exercises.favorite', $exercise)); + $this->assertTrue($user->favoriteExercises()->where('exercise_id', $exercise->id)->exists()); + + $this->actingAs($user)->post(route('exercises.favorite', $exercise)); + $this->assertFalse($user->favoriteExercises()->where('exercise_id', $exercise->id)->exists()); + } + + public function test_user_can_toggle_workout_favorite(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(); + + $this->actingAs($user)->post(route('workouts.favorite', $workout)); + $this->assertTrue($user->favoriteWorkouts()->where('workout_id', $workout->id)->exists()); + + $this->actingAs($user)->post(route('workouts.favorite', $workout)); + $this->assertFalse($user->favoriteWorkouts()->where('workout_id', $workout->id)->exists()); + } +} diff --git a/tests/Feature/WorkoutSessionTest.php b/tests/Feature/WorkoutSessionTest.php new file mode 100644 index 0000000..370246f --- /dev/null +++ b/tests/Feature/WorkoutSessionTest.php @@ -0,0 +1,107 @@ +create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $item1 = WorkoutExercise::factory()->create(['workout_id' => $workout->id, 'position' => 1]); + $item2 = WorkoutExercise::factory()->create(['workout_id' => $workout->id, 'position' => 2]); + + $response = $this->actingAs($user)->post(route('sessions.store', $workout)); + + $session = WorkoutSession::where('workout_id', $workout->id)->firstOrFail(); + $response->assertRedirect(route('sessions.show', $session)); + $this->assertDatabaseHas('workout_session_items', [ + 'session_id' => $session->id, + 'workout_exercise_id' => $item1->id, + ]); + $this->assertDatabaseHas('workout_session_items', [ + 'session_id' => $session->id, + 'workout_exercise_id' => $item2->id, + ]); + } + + public function test_other_user_cannot_start_a_session_for_someone_elses_workout(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $owner->id]); + + $response = $this->actingAs($other)->post(route('sessions.store', $workout)); + + $response->assertForbidden(); + } + + public function test_owner_can_view_their_session(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $session = WorkoutSession::factory()->create(['user_id' => $user->id, 'workout_id' => $workout->id]); + + $response = $this->actingAs($user)->get(route('sessions.show', $session)); + + $response->assertOk(); + } + + public function test_other_user_cannot_view_session(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $owner->id]); + $session = WorkoutSession::factory()->create(['user_id' => $owner->id, 'workout_id' => $workout->id]); + + $response = $this->actingAs($other)->get(route('sessions.show', $session)); + + $response->assertForbidden(); + } + + public function test_owner_can_toggle_session_item(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $item = WorkoutExercise::factory()->create(['workout_id' => $workout->id]); + $session = WorkoutSession::factory()->create(['user_id' => $user->id, 'workout_id' => $workout->id]); + + $response = $this->actingAs($user)->patch(route('sessions.items.toggle', [$session, $item])); + + $response->assertRedirect(); + $this->assertDatabaseHas('workout_session_items', [ + 'session_id' => $session->id, + 'workout_exercise_id' => $item->id, + 'done' => true, + ]); + + // Toggling again flips it back off + $this->actingAs($user)->patch(route('sessions.items.toggle', [$session, $item])); + $this->assertDatabaseHas('workout_session_items', [ + 'session_id' => $session->id, + 'workout_exercise_id' => $item->id, + 'done' => false, + ]); + } + + public function test_owner_can_complete_a_session(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $session = WorkoutSession::factory()->create(['user_id' => $user->id, 'workout_id' => $workout->id]); + + $response = $this->actingAs($user)->post(route('sessions.complete', $session)); + + $response->assertRedirect(route('workouts.show', $workout)); + $this->assertNotNull($session->fresh()->completed_at); + } +} diff --git a/tests/Feature/WorkoutTest.php b/tests/Feature/WorkoutTest.php new file mode 100644 index 0000000..771b473 --- /dev/null +++ b/tests/Feature/WorkoutTest.php @@ -0,0 +1,149 @@ +get(route('workouts.index')); + + $response->assertRedirect(route('login')); + } + + public function test_user_can_create_a_workout(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post(route('workouts.store'), [ + 'title' => 'Leg Day', + 'notes' => 'Focus on form', + 'estimated_minutes' => 45, + ]); + + $this->assertDatabaseHas('workouts', [ + 'user_id' => $user->id, + 'title' => 'Leg Day', + ]); + $workout = Workout::where('title', 'Leg Day')->firstOrFail(); + $response->assertRedirect(route('workouts.show', $workout)); + } + + public function test_owner_can_view_their_workout(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user)->get(route('workouts.show', $workout)); + + $response->assertOk(); + $response->assertSee($workout->title); + } + + public function test_other_user_cannot_view_workout(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $owner->id]); + + $response = $this->actingAs($other)->get(route('workouts.show', $workout)); + + $response->assertForbidden(); + } + + public function test_owner_can_add_exercise_to_workout(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $exercise = Exercise::factory()->create(); + + $response = $this->actingAs($user)->post(route('workouts.exercises.add', $workout), [ + 'exercise_id' => $exercise->id, + 'sets' => 4, + 'reps' => 8, + ]); + + $response->assertRedirect(); + $this->assertDatabaseHas('workout_exercises', [ + 'workout_id' => $workout->id, + 'exercise_id' => $exercise->id, + 'sets' => 4, + 'reps' => 8, + ]); + } + + public function test_other_user_cannot_add_exercise_to_workout(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $owner->id]); + $exercise = Exercise::factory()->create(); + + $response = $this->actingAs($other)->post(route('workouts.exercises.add', $workout), [ + 'exercise_id' => $exercise->id, + ]); + + $response->assertForbidden(); + } + + public function test_owner_can_update_workout_exercise(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $item = WorkoutExercise::factory()->create(['workout_id' => $workout->id, 'sets' => 3]); + + $response = $this->actingAs($user)->patch(route('workouts.exercises.update', [$workout, $item]), [ + 'sets' => 5, + ]); + + $response->assertRedirect(); + $this->assertDatabaseHas('workout_exercises', [ + 'id' => $item->id, + 'sets' => 5, + ]); + } + + public function test_owner_can_remove_workout_exercise(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + $item = WorkoutExercise::factory()->create(['workout_id' => $workout->id]); + + $response = $this->actingAs($user)->delete(route('workouts.exercises.remove', [$workout, $item])); + + $response->assertRedirect(); + $this->assertDatabaseMissing('workout_exercises', ['id' => $item->id]); + } + + public function test_owner_can_delete_workout(): void + { + $user = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user)->delete(route('workouts.destroy', $workout)); + + $response->assertRedirect(route('workouts.index')); + $this->assertDatabaseMissing('workouts', ['id' => $workout->id]); + } + + public function test_other_user_cannot_delete_workout(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + $workout = Workout::factory()->create(['user_id' => $owner->id]); + + $response = $this->actingAs($other)->delete(route('workouts.destroy', $workout)); + + $response->assertForbidden(); + $this->assertDatabaseHas('workouts', ['id' => $workout->id]); + } +} diff --git a/tests/Feature/YogaPoseTest.php b/tests/Feature/YogaPoseTest.php new file mode 100644 index 0000000..ca31c4f --- /dev/null +++ b/tests/Feature/YogaPoseTest.php @@ -0,0 +1,58 @@ +count(3)->create(); + + $response = $this->get(route('yoga.index')); + + $response->assertOk(); + $response->assertViewHas('poses', function ($poses) { + return $poses->total() === 3; + }); + } + + public function test_index_can_filter_by_category(): void + { + $core = YogaCategory::factory()->create(['name' => 'Core Yoga']); + $balance = YogaCategory::factory()->create(['name' => 'Balance Yoga']); + + $corePose = YogaPose::factory()->create(['category_id' => $core->id]); + YogaPose::factory()->create(['category_id' => $balance->id]); + + $response = $this->get(route('yoga.index', ['category' => $core->id])); + + $response->assertOk(); + $response->assertViewHas('poses', function ($poses) use ($corePose) { + return $poses->total() === 1 && $poses->first()->id === $corePose->id; + }); + } + + public function test_show_displays_pose(): void + { + $pose = YogaPose::factory()->create(['english_name' => 'Downward Dog']); + + $response = $this->get(route('yoga.show', $pose->slug)); + + $response->assertOk(); + $response->assertSee('Downward Dog'); + } + + public function test_show_returns_404_for_unknown_slug(): void + { + $response = $this->get('/yoga/does-not-exist'); + + $response->assertNotFound(); + } +}