create(); $hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create()); $response = $this ->actingAs($user, 'sanctum') ->postJson("/api/hunts/{$hunt->id}/participate"); $response ->assertOk() ->assertJsonPath('participation_status.current_step_number', 0) ->assertJsonPath('participation_status.status', ParticipateStatus::Registered->value); $this->assertDatabaseHas('hunt_participants', [ 'user_id' => $user->id, 'hunt_id' => $hunt->id, 'current_step_number' => 0, 'status' => ParticipateStatus::Registered->value, ]); }); it('starts a joined hunt only when the user is inside the start radius', function () { $user = User::factory()->create(); $hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create([ 'latitude' => 44.837789, 'longitude' => -0.57918, 'start_radius_m' => 40, ])); $step = HuntSteps::factory()->create([ 'hunt_id' => $hunt->id, 'step_number' => 1, 'latitude' => 44.838, 'longitude' => -0.579, ]); $hunt->participants()->attach($user->id, [ 'current_step_number' => 0, 'status' => ParticipateStatus::Registered->value, ]); $tooFarResponse = $this ->actingAs($user, 'sanctum') ->postJson("/api/hunts/{$hunt->id}/start", [ 'latitude' => 44.85, 'longitude' => -0.59, ]); $tooFarResponse->assertBadRequest(); $response = $this ->actingAs($user, 'sanctum') ->postJson("/api/hunts/{$hunt->id}/start", [ 'latitude' => 44.837789, 'longitude' => -0.57918, ]); $response ->assertOk() ->assertJsonPath('participation_status.current_step_number', 1) ->assertJsonPath('participation_status.status', ParticipateStatus::InProgress->value) ->assertJsonPath('current_step.id', $step->id) ->assertJsonPath('current_step.step_number', 1); }); it('does not expose a current step before the hunt is started', function () { $user = User::factory()->create(); $hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create()); $hunt->participants()->attach($user->id, [ 'current_step_number' => 0, 'status' => ParticipateStatus::Registered->value, ]); $response = $this ->actingAs($user, 'sanctum') ->getJson("/api/hunts/{$hunt->id}/current-step"); $response ->assertStatus(409) ->assertJsonPath('participation_status.status', ParticipateStatus::Registered->value); }); it('validates the current step and returns the next step target', function () { $user = User::factory()->create(); $hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create()); $firstStep = HuntSteps::factory()->create([ 'hunt_id' => $hunt->id, 'step_number' => 1, 'title' => 'First clue', 'hint_text' => 'Go to the next chest', 'latitude' => 44.837789, 'longitude' => -0.57918, 'radius_m' => 30, ]); $nextStep = HuntSteps::factory()->create([ 'hunt_id' => $hunt->id, 'step_number' => 2, 'latitude' => 44.838, 'longitude' => -0.579, ]); $hunt->participants()->attach($user->id, [ 'current_step_number' => 1, 'status' => ParticipateStatus::InProgress->value, ]); $response = $this ->actingAs($user, 'sanctum') ->postJson("/api/hunts/{$hunt->id}/steps/{$firstStep->id}/validate", [ 'latitude' => 44.837789, 'longitude' => -0.57918, ]); $response ->assertOk() ->assertJsonPath('revealed_hint.hint_text', 'Go to the next chest') ->assertJsonPath('next_step.id', $nextStep->id) ->assertJsonPath('next_step.step_number', 2); $this->assertDatabaseHas('hunt_participants', [ 'user_id' => $user->id, 'hunt_id' => $hunt->id, 'current_step_number' => 2, 'status' => ParticipateStatus::InProgress->value, ]); });