create(); Sanctum::actingAs($user); $response = $this->postJson('/api/workouts', [ 'external_id' => 'apple-workout-1', 'source' => WorkoutSource::APPLE_HEALTH->value, 'type' => 'running', 'title' => 'Morning run', 'duration_seconds' => 1800, 'calories_burned' => 450, 'distance_meters' => 5200, 'started_at' => '2026-05-19T07:30:00.000Z', ]); $response ->assertCreated() ->assertJsonPath('data.externalId', 'apple-workout-1') ->assertJsonPath('data.source', WorkoutSource::APPLE_HEALTH->value) ->assertJsonPath('data.type', 'running') ->assertJsonPath('data.title', 'Morning run') ->assertJsonPath('data.durationSeconds', 1800) ->assertJsonPath('data.caloriesBurned', 450) ->assertJsonPath('data.distanceMeters', 5200); $this->assertDatabaseHas('workout_sessions', [ 'id' => $response->json('data.id'), 'user_id' => $user->id, 'external_id' => 'apple-workout-1', 'source' => WorkoutSource::APPLE_HEALTH->value, 'type' => 'running', 'title' => 'Morning run', 'duration_seconds' => 1800, 'calories_burned' => 450, 'distance_meters' => 5200, ]); }); it('returns workouts for the authenticated user', function () { $user = User::factory()->create(); $otherUser = User::factory()->create(); $oldWorkoutSession = WorkoutSessions::factory()->for($user, 'user')->create([ 'external_id' => 'manual-1', 'source' => WorkoutSource::MANUAL, 'type' => 'strength', 'title' => 'Upper body', 'duration_seconds' => 2400, 'calories_burned' => 320, 'distance_meters' => 0, 'started_at' => '2026-05-18 10:00:00', ]); $latestWorkoutSession = WorkoutSessions::factory()->for($user, 'user')->create([ 'external_id' => 'apple-1', 'source' => WorkoutSource::APPLE_HEALTH, 'type' => 'running', 'title' => 'Morning run', 'duration_seconds' => 1800, 'calories_burned' => 450, 'distance_meters' => 5200, 'started_at' => '2026-05-19 07:30:00', ]); $otherWorkoutSession = WorkoutSessions::factory()->for($otherUser, 'user')->create([ 'title' => 'Hidden workout', 'started_at' => '2026-05-20 07:30:00', ]); Sanctum::actingAs($user); $response = $this->getJson('/api/workouts'); $response ->assertOk() ->assertJsonCount(2, 'data') ->assertJsonPath('data.0.id', $latestWorkoutSession->id) ->assertJsonPath('data.0.externalId', 'apple-1') ->assertJsonPath('data.0.source', WorkoutSource::APPLE_HEALTH->value) ->assertJsonPath('data.0.type', 'running') ->assertJsonPath('data.0.title', 'Morning run') ->assertJsonPath('data.0.durationSeconds', 1800) ->assertJsonPath('data.0.caloriesBurned', 450) ->assertJsonPath('data.0.distanceMeters', 5200) ->assertJsonPath('data.1.id', $oldWorkoutSession->id) ->assertJsonMissing([ 'id' => $otherWorkoutSession->id, ]); }); it('validates the workouts pagination size', function () { Sanctum::actingAs(User::factory()->create()); $this ->getJson('/api/workouts?per_page=101') ->assertUnprocessable() ->assertJsonValidationErrors(['per_page']); }); it('validates workout creation data', function () { Sanctum::actingAs(User::factory()->create()); $this ->postJson('/api/workouts', [ 'source' => 'invalid', 'type' => '', 'title' => '', 'duration_seconds' => 0, 'calories_burned' => -1, 'distance_meters' => -1, 'started_at' => 'not-a-date', ]) ->assertUnprocessable() ->assertJsonValidationErrors([ 'source', 'type', 'title', 'duration_seconds', 'calories_burned', 'distance_meters', 'started_at', ]); }); it('validates workout external ids per source', function () { $user = User::factory()->create(); WorkoutSessions::factory()->create([ 'external_id' => 'manual-1', 'source' => WorkoutSource::MANUAL, ]); Sanctum::actingAs($user); $this ->postJson('/api/workouts', [ 'external_id' => 'manual-1', 'source' => WorkoutSource::MANUAL->value, 'type' => 'running', 'title' => 'Morning run', 'duration_seconds' => 1800, 'calories_burned' => 450, 'distance_meters' => 5200, ]) ->assertUnprocessable() ->assertJsonValidationErrors(['external_id']); });