diff --git a/app/Events/UserConfirmedEvent.php b/app/Events/UserConfirmedEvent.php new file mode 100644 index 0000000000..f2f1195147 --- /dev/null +++ b/app/Events/UserConfirmedEvent.php @@ -0,0 +1,31 @@ +idevents = $idevents; + $this->iduser = $iduser; + } +} diff --git a/app/Events/UserLeftEvent.php b/app/Events/UserLeftEvent.php new file mode 100644 index 0000000000..c1a968973b --- /dev/null +++ b/app/Events/UserLeftEvent.php @@ -0,0 +1,31 @@ +idevents = $idevents; + $this->iduser = $iduser; + } +} diff --git a/app/EventsUsers.php b/app/EventsUsers.php index 569923b1f7..23546bc0bd 100644 --- a/app/EventsUsers.php +++ b/app/EventsUsers.php @@ -38,6 +38,11 @@ public function volunteer() return $this->hasOne(\App\User::class, 'id', 'user'); } + public function event() + { + return $this->hasOne(\App\Party::class, 'id', 'event'); + } + public function getFullName() { if (! is_null($this->full_name)) { diff --git a/app/Http/Controllers/API/EventController.php b/app/Http/Controllers/API/EventController.php index 12ef1170fc..655bba5d45 100644 --- a/app/Http/Controllers/API/EventController.php +++ b/app/Http/Controllers/API/EventController.php @@ -161,9 +161,18 @@ public function addVolunteer(Request $request, $idevents) $full_name = null; } - // User is null, this volunteer is either anonymous or no user exists. + $eventRole = Role::RESTARTER; + if ($request->has('user') && $request->input('user') !== 'not-registered') { + // User is null, this volunteer is either anonymous or no user exists. $user = $request->input('user'); + + if ($user) { + $u = User::find($user); + + // A host of the group who is added to an event becomes a host of the event. + $eventRole = $u && Fixometer::userIsHostOfGroup($party->group, $user) ? Role::HOST : Role::RESTARTER; + } } else { $user = null; } @@ -173,7 +182,7 @@ public function addVolunteer(Request $request, $idevents) ->where('user', $user) ->where('status', '<>', 1) ->whereNotNull('status') - ->where('role', 4); + ->where('role', $eventRole); $userWasInvited = $invitedUserQuery->count() == 1; if ($userWasInvited) { @@ -186,13 +195,11 @@ public function addVolunteer(Request $request, $idevents) 'event' => $idevents, 'user' => $user, 'status' => 1, - 'role' => 4, + 'role' => $eventRole, 'full_name' => $full_name, ]); } - $party->increment('volunteers'); - if (!is_null($volunteer_email_address)) { // Send email. $from = User::find(Auth::user()->id); @@ -491,8 +498,6 @@ public function createEventv2(Request $request) 'role' => Role::HOST, ]); - $party->increment('volunteers'); - // Notify relevant users. $usersToNotify = Fixometer::usersWhoHavePreference('admin-moderate-event'); foreach ($party->associatedNetworkCoordinators() as $coordinator) { diff --git a/app/Http/Controllers/PartyController.php b/app/Http/Controllers/PartyController.php index 54f1fa38e4..cef20dbe14 100644 --- a/app/Http/Controllers/PartyController.php +++ b/app/Http/Controllers/PartyController.php @@ -418,15 +418,12 @@ public function getJoinEvent($event_id) ]); - $event->increment('volunteers'); - $flashData = []; if (! Auth::user()->isInGroup($event->theGroup->idgroups)) { $flashData['prompt-follow-group'] = true; } $this->notifyHostsOfRsvp($user_event, $event_id); - $this->addToDiscourseThread($event, Auth::user()); return redirect()->back()->with($flashData); } @@ -473,30 +470,6 @@ public function notifyHostsOfRsvp($user_event, $event_id) } } - public function addToDiscourseThread($event, $user) - { - if ($event->discourse_thread) { - // We want a host of the event to add the user to the thread. - try { - $hosts = User::join('events_users', 'events_users.user', '=', 'users.id') - ->where('events_users.event', $event->idevents) - ->where('events_users.role', 3) - ->select('users.*') - ->get(); - } catch (\Exception $e) { - $hosts = null; - } - - if (! is_null($hosts) && count($hosts)) { - $this->discourseService->addUserToPrivateMessage( - $event->discourse_thread, - $hosts[0]->username, - $user->username - ); - } - } - } - public static function stats($id) { $event = Party::where('idevents', $id)->first(); @@ -605,11 +578,6 @@ public function removeVolunteer(Request $request) $delete_user = $volunteer->delete(); if ($delete_user == 1) { - //If the user accepted the invitation, we decrement - if ($volunteer->status == 1) { - Party::find($event_id)->decrement('volunteers'); - } - //Return JSON $return = [ 'success' => true, @@ -750,10 +718,8 @@ public function confirmInvite($event_id, $hash) // Increment volunteers column to include latest invite $event = Party::find($event_id); - $event->increment('volunteers'); $this->notifyHostsOfRsvp($user_event, $event_id); - $this->addToDiscourseThread($event, Auth::user()); return redirect('/party/view/'.$user_event->event); } @@ -764,7 +730,10 @@ public function confirmInvite($event_id, $hash) public function cancelInvite($event_id) { - EventsUsers::where('user', Auth::user()->id)->where('event', $event_id)->delete(); + // We have to do a loop to avoid the gotcha where bulk delete operations don't invoke observers. + foreach (EventsUsers::where('user', Auth::user()->id)->where('event', $event_id)->get() as $delete) { + $delete->delete(); + }; return redirect('/party/view/'.intval($event_id))->with('success', __('events.invite_cancelled')); } @@ -865,7 +834,12 @@ public function deleteEvent($id) Audits::where('auditable_type', \App\Party::class)->where('auditable_id', $id)->delete(); Device::where('event', $id)->delete(); - EventsUsers::where('event', $id)->delete(); + + // We have to do a loop to avoid the gotcha where bulk delete operations don't invoke observers. + foreach (EventsUsers::where('event', $id)->get() as $delete) { + $delete->delete(); + }; + $event->delete(); event(new EventDeleted($event)); diff --git a/app/Listeners/AddUserToDiscourseThreadForEvent.php b/app/Listeners/AddUserToDiscourseThreadForEvent.php new file mode 100644 index 0000000000..1571d48b4e --- /dev/null +++ b/app/Listeners/AddUserToDiscourseThreadForEvent.php @@ -0,0 +1,50 @@ +discourseService = $discourseService; + } + + private function getHost($idevents) { + $hosts = User::join('events_users', 'events_users.user', '=', 'users.id') + ->where('events_users.event', $idevents) + ->where('events_users.role', Role::HOST) + ->select('users.*') + ->get(); + + return $hosts->count() ? $hosts[0] : null; + } + + public function handle(UserConfirmedEvent $e) { + if ($e->iduser) { + $event = Party::find($e->idevents); + $user = User::find($e->iduser); + + // Might not exist - timing windows. + if ($event && $user && $event->discourse_thread) { + // We need a host of the event to add the user to the thread. + $host = $this->getHost($event->idevents); + + if ($host) { + $this->discourseService->addUserToPrivateMessage( + $event->discourse_thread, + $host->username, + $user->username + ); + } + } + } + } +} \ No newline at end of file diff --git a/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php b/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php new file mode 100644 index 0000000000..d19f093be5 --- /dev/null +++ b/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php @@ -0,0 +1,50 @@ +discourseService = $discourseService; + } + + private function getHost($idevents) { + $hosts = User::join('events_users', 'events_users.user', '=', 'users.id') + ->where('events_users.event', $idevents) + ->where('events_users.role', Role::HOST) + ->select('users.*') + ->get(); + + return $hosts->count() ? $hosts[0] : null; + } + + public function handle(UserLeftEvent $e) { + if ($e->iduser) { + $event = Party::find($e->idevents); + $user = User::find($e->iduser); + + // Might not exist - timing windows. + if ($event && $user && $event->discourse_thread) { + // We need a host of the event to add the user to the thread. + $host = $this->getHost($event->idevents); + + if ($host) { + $this->discourseService->removeUserFromPrivateMessage( + $event->discourse_thread, + $host->username, + $user->username + ); + } + } + } + } +} \ No newline at end of file diff --git a/app/Observers/EventsUsersObserver.php b/app/Observers/EventsUsersObserver.php new file mode 100644 index 0000000000..1e15ca3efd --- /dev/null +++ b/app/Observers/EventsUsersObserver.php @@ -0,0 +1,108 @@ +discourseService = $discourseService; + } + + /** + * Listen to the created event. + * + * @param \App\EventsUsers $eu + * @return void + */ + public function created(EventsUsers $eu) + { + $idevents = $eu->event; + $event = \App\Party::find($idevents); + $iduser = $eu->user; + $user = $iduser ? User::find($iduser) : null; + + if ($eu->status == 1) { + // Confirmed. Make sure they are on the thread. + $this->confirmed($event, $user); + } else { + // Not confirmed. Make sure they are not on the thread. + $this->removed($event, $user); + } + } + + /** + * Listen to the updated event. + * + * @param \App\EventsUsers $eu + * @return void + */ + public function updated(EventsUsers $eu) { + $idevents = $eu->event; + $event = \App\Party::find($idevents); + $iduser = $eu->user; + $user = $iduser ? User::find($iduser) : null; + + if ($eu->status == 1) { + // Confirmed. Make sure they are on the thread. + $this->confirmed($event, $user); + } else { + // Not confirmed. Make sure they are not on the thread. + $this->removed($event, $user); + } + } + + /** + * Listen to the deleted event. + * + * @param \App\EventsUsers $eu + * @return void + */ + public function deleted(EventsUsers $eu) + { + $idevents = $eu->event; + $event = \App\Party::find($idevents); + $iduser = $eu->user; + $user = $iduser ? User::find($iduser) : null; + + // Make sure they are not on the thread. + $this->removed($event, $user); + } + + /** + * @param Party $event + * @param User $user + * @return void + */ + private function confirmed($event, $user): void + { + $event->increment('volunteers'); + event(new UserConfirmedEvent($event->idevents, $user ? $user->id : null)); + } + + /** + * @param Party $event + * @param User $user + * @return void + */ + private function removed($event, $user): void + { + $event->decrement('volunteers'); + event(new UserLeftEvent($event->idevents, $user ? $user->id : null)); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ae260b2643..2c73288359 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,8 +2,10 @@ namespace App\Providers; +use App\EventsUsers; use App\Helpers\Geocoder; use App\Helpers\RobustTranslator; +use App\Observers\EventsUsersObserver; use Auth; use Cache; use Illuminate\Support\ServiceProvider; @@ -36,6 +38,8 @@ public function boot() }); \Illuminate\Pagination\Paginator::useBootstrapThree(); + + EventsUsers::observe(EventsUsersObserver::class); } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 3f9788ea88..74df5d645d 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -5,13 +5,17 @@ use App\Events\DeviceCreatedOrUpdated; use App\Events\EventDeleted; use App\Events\EventImagesUploaded; +use App\Events\UserConfirmedEvent; use App\Events\UserDeleted; use App\Events\UserEmailUpdated; use App\Events\UserFollowedGroup; use App\Events\UserLanguageUpdated; +use App\Events\UserLeftEvent; use App\Events\UserRegistered; use App\Events\UserUpdated; use App\Listeners\AddUserToDiscourseGroup; +use App\Listeners\AddUserToDiscourseThreadForEvent; +use App\Listeners\RemoveUserFromDiscourseThreadForEvent; use App\Listeners\AnonymiseSoftDeletedUser; use App\Listeners\DeleteEventFromWordPress; use App\Listeners\DeviceUpdatedAt; @@ -87,6 +91,14 @@ class EventServiceProvider extends ServiceProvider DeviceCreatedOrUpdated::class => [ DeviceUpdatedAt::class, ], + + UserConfirmedEvent::class => [ + AddUserToDiscourseThreadForEvent::class + ], + + UserLeftEvent::class => [ + RemoveUserFromDiscourseThreadForEvent::class + ] ]; /** diff --git a/app/Services/DiscourseService.php b/app/Services/DiscourseService.php index a25c4bdfb6..b5f47e97fd 100644 --- a/app/Services/DiscourseService.php +++ b/app/Services/DiscourseService.php @@ -136,6 +136,40 @@ public function addUserToPrivateMessage($threadid, $addBy, $addUser) } } + public function removeUserFromPrivateMessage($threadid, $removeBy, $removeUser) { + if (! config('restarters.features.discourse_integration')) { + return; + } + + Log::info("Remove user from private message $threadid, $removeBy, $removeUser"); + + $client = app('discourse-client', [ + 'username' => $removeBy, + ]); + + $params = [ + 'username' => $removeUser, + ]; + + $endpoint = "/t/$threadid/remove-allowed-user"; + + Log::info('Removing from private message: '.json_encode($params)); + $response = $client->request( + 'PUT', + $endpoint, + [ + 'form_params' => $params, + ] + ); + + Log::info('Response status: '.$response->getStatusCode()); + Log::info('Response body: '.$response->getBody()); + + if (! $response->getStatusCode() === 200) { + Log::error("Could not remove from private message ($threadid, $removeBy, $removeUser:".$response->getReasonPhrase()); + } + } + public function getAllUsers() { if (! config('restarters.features.discourse_integration')) { diff --git a/tests/Feature/Events/AddRemoveVolunteerTest.php b/tests/Feature/Events/AddRemoveVolunteerTest.php index 5a047e91bd..558a108d6c 100644 --- a/tests/Feature/Events/AddRemoveVolunteerTest.php +++ b/tests/Feature/Events/AddRemoveVolunteerTest.php @@ -4,7 +4,9 @@ use App\EventsUsers; use App\Group; +use App\Helpers\Fixometer; use App\Helpers\Geocoder; +use App\Listeners\RemoveUserFromDiscourseThreadForEvent; use App\Network; use App\Notifications\AdminModerationEvent; use App\Notifications\NotifyRestartersOfNewEvent; @@ -16,6 +18,7 @@ use Illuminate\Support\Facades\Notification; use Symfony\Component\DomCrawler\Crawler; use Tests\TestCase; +use Illuminate\Support\Facades\Queue; class AddRemoveVolunteerTest extends TestCase { @@ -23,9 +26,10 @@ class AddRemoveVolunteerTest extends TestCase * @dataProvider roleProvider */ - public function testAddRemove($role) + public function testAddRemove($role, $addrole, $shouldBeHost) { $this->withoutExceptionHandling(); + Queue::fake(); $group = Group::factory()->create(); $network = Network::factory()->create(); @@ -53,7 +57,28 @@ public function testAddRemove($role) $this->actingAs($host); - $restarter = User::factory()->restarter()->create(); + switch ($addrole) { + case 'Administrator': + $restarter = User::factory()->administrator()->create(); + break; + case 'NetworkCoordinator': + $restarter = User::factory()->networkCoordinator()->create(); + break; + case 'HostThis': + $restarter = User::factory()->host()->create(); + $group->addVolunteer($restarter); + $group->makeMemberAHost($restarter); + break; + case 'HostOther': + $restarter = User::factory()->host()->create(); + $group2 = Group::factory()->create(); + $group2->addVolunteer($restarter); + $group2->makeMemberAHost($restarter); + break; + case 'Restarter': + $restarter = User::factory()->restarter()->create(); + } + // Add an existing user $response = $this->put('/api/events/' . $event->idevents . '/volunteers', [ @@ -79,6 +104,15 @@ public function testAddRemove($role) ] ]); + // Check they are/are not a host. + $hostFor = Party::hostFor([$restarter->id])->get(); + + if ($shouldBeHost) { + $this->assertTrue($hostFor->contains($event)); + } else { + $this->assertFalse($hostFor->contains($event)); + } + // Remove them $volunteer = EventsUsers::where('user', $restarter->id)->first(); $this->post('/party/remove-volunteer/', [ @@ -97,6 +131,12 @@ public function testAddRemove($role) ] ]); + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($event, $restarter) { + if ($job->class == RemoveUserFromDiscourseThreadForEvent::class) { + return true; + } + }); + // Add an invited user $restarter = User::factory()->restarter()->create(); $response = $this->post('/party/invite', [ @@ -154,8 +194,10 @@ public function testAddRemove($role) public function roleProvider() { return [ - [ 'Administrator' ], - [ 'NetworkCoordinator' ], + [ 'Administrator', 'Restarter', false ], + [ 'NetworkCoordinator', 'HostThis', true ], + [ 'NetworkCoordinator', 'HostOther', false ], + [ 'NetworkCoordinator', 'Administrator', false ], ]; } diff --git a/tests/Feature/Events/CreateEventTest.php b/tests/Feature/Events/CreateEventTest.php index b291fab64c..7f897d59f2 100644 --- a/tests/Feature/Events/CreateEventTest.php +++ b/tests/Feature/Events/CreateEventTest.php @@ -2,15 +2,20 @@ namespace Tests\Feature; +use App\Events\UserConfirmedEvent; +use App\Events\UserLeftEvent; use App\EventsUsers; use App\Group; use App\Helpers\Geocoder; use App\Helpers\RepairNetworkService; +use App\Listeners\AddUserToDiscourseThreadForEvent; +use App\Listeners\RemoveUserFromDiscourseThreadForEvent; use App\Network; use App\Notifications\AdminModerationEvent; use App\Notifications\NotifyRestartersOfNewEvent; use App\Party; use App\Role; +use App\Services\DiscourseService; use App\User; use Carbon\Carbon; use DB; @@ -453,13 +458,17 @@ public function emails_sent_to_coordinators_when_event_created() public function a_host_can_be_added_later() { $this->withoutExceptionHandling(); + Queue::fake(); $host = User::factory()->host()->create(); + $host2 = User::factory()->host()->create(); $this->actingAs($host); $group = Group::factory()->create(); $group->addVolunteer($host); $group->makeMemberAHost($host); + $group->addVolunteer($host2); + $group->makeMemberAHost($host2); // Create the event $eventAttributes = Party::factory()->raw(['group' => $group->idgroups, 'event_date' => '2000-01-01', 'approved' => true]); @@ -469,12 +478,27 @@ public function a_host_can_be_added_later() // Find the event id $party = $group->parties()->latest()->first(); + // Add the second host. + $response = $this->put('/api/events/' . $party->idevents . '/volunteers?api_token=' . $host->api_token, [ + 'volunteer_email_address' => $host2->email, + 'full_name' => $host2->name, + 'user' => $host2->id, + ]); + + $response->assertSuccessful(); + // Remove the host from the event $volunteer = EventsUsers::where('user', $host->id)->first(); $this->post('/party/remove-volunteer/', [ 'id' => $volunteer->idevents_users, ])->assertSee('true'); + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($party, $host) { + if ($job->class == RemoveUserFromDiscourseThreadForEvent::class) { + return true; + } + }); + // Assert that we see the host in the list of volunteers to add to the event. $response = $this->get('/api/groups/'. $group->idgroups . '/volunteers?api_token=' . $host->api_token); $response->assertJson([ @@ -486,7 +510,10 @@ public function a_host_can_be_added_later() ]); // Assert we can add them back in. - $response = $this->put('/api/events/' . $party->idevents . '/volunteers', [ + $party->discourse_thread = 123; + $party->save(); + + $response = $this->put('/api/events/' . $party->idevents . '/volunteers?api_token=' . $host->api_token, [ 'volunteer_email_address' => $host->email, 'full_name' => $host->name, 'user' => $host->id, @@ -495,6 +522,30 @@ public function a_host_can_be_added_later() $response->assertSuccessful(); $rsp = json_decode($response->getContent(), TRUE); $this->assertEquals('success', $rsp['success']); + + // Check the listener was queued to be called in the background. + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($party, $host) { + if ($job->class == AddUserToDiscourseThreadForEvent::class) { + return true; + } + }); + + // Manually call the listener for coverage and check it calls through to Discourse. + $this->instance( + DiscourseService::class, + \Mockery::mock(DiscourseService::class, function ($mock) { + $mock->shouldReceive('addUserToPrivateMessage')->once(); + $mock->shouldReceive('removeUserFromPrivateMessage')->once(); + }) + ); + + $listener = app()->make(AddUserToDiscourseThreadForEvent::class); + $event = new UserConfirmedEvent($party->idevents, $host->id); + $listener->handle($event); + + $listener = app()->make(RemoveUserFromDiscourseThreadForEvent::class); + $event = new UserLeftEvent($party->idevents, $host->id); + $listener->handle($event); } public function provider() @@ -634,6 +685,8 @@ public function no_notification_after_leaving() { /** @test */ public function notifications_are_queued_as_expected() { + Notification::fake(); + // At the moment we are queueing (backgrounding) admin notifications but not user notifications. // // Don't call Notification::fake() - we want real notifications. @@ -641,6 +694,7 @@ public function notifications_are_queued_as_expected() // Create an admin $admin = User::factory()->administrator()->create(); + // Create a network with a group. $network = Network::factory()->create(); $group = Group::factory()->create(); @@ -655,47 +709,23 @@ public function notifications_are_queued_as_expected() $group->makeMemberAHost($host); $this->actingAs($host); - // Clear any jobs queued in earlier tests. - $max = 1000; - do { - $job = Queue::pop('database'); - - if ($job) { - try { - $job->fail('removed in UT'); - } catch (\Exception $e) {} - } - - $max--; - } - while (Queue::size() > 0 && $max > 0); - // Create an event. - $initialQueueSize = \Illuminate\Support\Facades\Queue::size('database'); $event = Party::factory()->raw(); $event['group'] = $group->idgroups; $response = $this->post('/api/v2/events?api_token=' . $host->api_token, $this->eventAttributesToAPI($event)); $response->assertSuccessful(); - // Should have queued AdminModerationEvent. - $queueSize = Queue::size(); - self::assertGreaterThan($initialQueueSize, $queueSize); - - // Fail it. - $job = Queue::pop(); - self::assertNotNull($job); - self::assertStringContainsString('AdminModerationEvent', $job->getRawBody()); - try { - $job->fail('removed in UT'); - } catch (\Exception $e) {} - self::assertEquals(0, Queue::size('database')); + Notification::assertSentTo( + [$admin], AdminModerationEvent::class + ); - // Approval should generate a notification to the host which is also queued. + // Approval should generate a notification to the host. $event = Party::latest()->first(); $event->approve(); - # Should have queued ApproveEvent. - self::assertEquals(0, Queue::size('database')); + Notification::assertSentTo( + [$host], EventConfirmed::class + ); } /** @test */ diff --git a/tests/Feature/Events/DeleteEventTest.php b/tests/Feature/Events/DeleteEventTest.php index 17c8b243dc..71a1918fa1 100644 --- a/tests/Feature/Events/DeleteEventTest.php +++ b/tests/Feature/Events/DeleteEventTest.php @@ -61,7 +61,7 @@ public function an_admin_can_delete_an_event() $this->actingAs($admin); $response = $this->get("/api/group/{$group->idgroups}/stats?api_token=1234"); $stats = json_decode($response->getContent(), true); - $this->assertEquals(9, $stats['num_hours_volunteered']); + $this->assertEquals(21, $stats['num_hours_volunteered']); // Now delete the event. $response = $this->post('/party/delete/'.$event->idevents); diff --git a/tests/Feature/Events/EventRequestReviewEmailTest.php b/tests/Feature/Events/EventRequestReviewEmailTest.php index cb42445d4b..8ce0e817c5 100644 --- a/tests/Feature/Events/EventRequestReviewEmailTest.php +++ b/tests/Feature/Events/EventRequestReviewEmailTest.php @@ -75,8 +75,6 @@ protected function init_event_and_dependencies() 'full_name' => null, ]); - $this->event->increment('volunteers'); - $this->assertTrue($this->group->isVolunteer($this->volunteer->getKey())); $this->assertTrue($this->event->isVolunteer($this->volunteer->getKey())); } diff --git a/tests/Feature/Events/ExportTest.php b/tests/Feature/Events/ExportTest.php index 84fdac5e7d..5d53fc3ed2 100644 --- a/tests/Feature/Events/ExportTest.php +++ b/tests/Feature/Events/ExportTest.php @@ -76,6 +76,8 @@ public function testExport($role) $group3->approved = false; $group3->save(); + $this->artisan("queue:work --stop-when-empty"); + $this->actingAs($user); // Create an event on each and approve it. diff --git a/tests/Feature/Events/InviteEventTest.php b/tests/Feature/Events/InviteEventTest.php index 390f5e18f1..5652443607 100644 --- a/tests/Feature/Events/InviteEventTest.php +++ b/tests/Feature/Events/InviteEventTest.php @@ -2,12 +2,10 @@ namespace Tests\Feature; -use App\Events\ApproveEvent; use App\EventsUsers; use App\Group; use App\Helpers\Fixometer; -use App\Listeners\CreateDiscourseThreadForEvent; -use App\Listeners\CreateWordpressPostForEvent; +use App\Listeners\AddUserToDiscourseThreadForEvent; use App\Notifications\RSVPEvent; use App\Party; use App\Role; @@ -17,6 +15,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Tests\TestCase; use App\Notifications\JoinEvent; +use Illuminate\Support\Facades\Queue; class InviteEventTest extends TestCase { @@ -264,17 +263,26 @@ public function testInvitableUserPOV() public function testInvitableNotifications() { + Queue::fake(); Notification::fake(); $this->withoutExceptionHandling(); - $group = Group::factory()->create(); + $user = User::factory()->administrator()->create([ + 'api_token' => '1234', + ]); + $this->actingAs($user); + + $idgroups = $this->createGroup('Test Group', 'https://therestartproject.org', 'London', 'Some text.', true, true); + $idevents = $this->createEvent($idgroups, 'tomorrow'); + + // Joining should trigger adding to the Discourse thread. Fake one. + $event = \App\Party::find($idevents); + $event->discourse_thread = 123; + $event->save(); + + $group = Group::find($idgroups); $host = User::factory()->host()->create(); - $event = Party::factory()->create([ - 'group' => $group, - 'event_start_utc' => '2130-01-01T12:13:00+00:00', - 'event_end_utc' => '2130-01-01T13:14:00+00:00', - 'user_id' => $host->id - ]); + $event = Party::find($idevents); EventsUsers::create([ 'event' => $event->getKey(), 'user' => $host->getKey(), @@ -327,7 +335,7 @@ public function testInvitableNotifications() $this->get('/logout'); $this->actingAs($user); - // Now accept the invitation. + // Now accept the invitation, which should trigger adding to the Discourse thread. $eu = EventsUsers::where('user', '=', $user->id)->first(); $invitation = '/party/accept-invite/' . $event->idevents . '/' . $eu->status; @@ -359,6 +367,12 @@ function ($notification, $channels, $host) use ($user, $event) { $response9 = $this->get('/party/get-group-emails-with-names/'.$event->idevents); $members = json_decode($response9->getContent()); $this->assertEquals([], $members); + + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($event, $user) { + if ($job->class == AddUserToDiscourseThreadForEvent::class) { + return true; + } + }); } public function testInviteViaLink() { diff --git a/tests/Feature/Events/JoinEventTest.php b/tests/Feature/Events/JoinEventTest.php index 6c32aa7b57..3787c884b9 100644 --- a/tests/Feature/Events/JoinEventTest.php +++ b/tests/Feature/Events/JoinEventTest.php @@ -2,13 +2,10 @@ namespace Tests\Feature; +use App\Listeners\RemoveUserFromDiscourseThreadForEvent; +use Illuminate\Support\Facades\Queue; use App\EventsUsers; -use App\Group; -use App\Helpers\Geocoder; -use App\Network; -use App\Notifications\AdminModerationEvent; -use App\Notifications\NotifyRestartersOfNewEvent; -use App\Party; +use App\Listeners\AddUserToDiscourseThreadForEvent; use App\User; use DB; use Illuminate\Support\Facades\Notification; @@ -18,17 +15,32 @@ class JoinEventTest extends TestCase { public function testJoin() { - $this->withoutExceptionHandling(); + Queue::fake(); - $group = Group::factory()->create([ - 'approved' => true - ]); - $event = Party::factory()->create(['group' => $group->idgroups]); + $this->withoutExceptionHandling(); - $user = User::factory()->restarter()->create(); + $user = User::factory()->administrator()->create([ + 'api_token' => '1234', + ]); $this->actingAs($user); + $idgroups = $this->createGroup('Test Group', 'https://therestartproject.org', 'London', 'Some text.', true, true); + $idevents = $this->createEvent($idgroups, 'tomorrow'); + + // Joining should trigger adding to the Discourse thread. Fake one. + $event = \App\Party::find($idevents); + $event->discourse_thread = 123; + $event->save(); + + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($event, $user) { + if ($job->class == AddUserToDiscourseThreadForEvent::class) { + return true; + } + }); + // Join. Should get redirected, and also prompted to follow the group (which we haven't). + $user = User::factory()->restarter()->create(); + $this->actingAs($user); $response = $this->get('/party/join/'.$event->idevents); $this->assertTrue($response->isRedirection()); $response->assertSessionHas('prompt-follow-group'); @@ -53,6 +65,12 @@ public function testJoin() ':is-attending' => 'false', ], ]); + + Queue::assertPushed(\Illuminate\Events\CallQueuedListener::class, function ($job) use ($event, $user) { + if ($job->class == RemoveUserFromDiscourseThreadForEvent::class) { + return true; + } + }); } public function testJoinInvalid() { diff --git a/tests/Feature/Events/ModerationEventPhotosNotificationTest.php b/tests/Feature/Events/ModerationEventPhotosNotificationTest.php index 3fa6c1b0e4..dfe59d32d6 100644 --- a/tests/Feature/Events/ModerationEventPhotosNotificationTest.php +++ b/tests/Feature/Events/ModerationEventPhotosNotificationTest.php @@ -131,8 +131,6 @@ protected function init_event_and_dependencies() 'full_name' => null, ]); - $this->event->increment('volunteers'); - $this->assertTrue($this->group->isVolunteer($this->restarter->getKey())); $this->assertTrue($this->event->isVolunteer($this->restarter->getKey())); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 4712dcdc49..8421d208ed 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -118,6 +118,20 @@ protected function setUp(): void $this->processQueuedNotifications(); $this->OpenAPIValidator = ValidatorBuilder::fromJson(storage_path('api-docs/api-docs.json'))->getValidator(); + // Clear any jobs queued in earlier tests. + $max = 1000; + do { + $job = Queue::pop('database'); + + if ($job) { + try { + $job->fail('removed in UT'); + } catch (\Exception $e) {} + } + + $max--; + } + while (Queue::size() > 0 && $max > 0); } public function userAttributes()