diff --git a/app/controllers/admin/bans_controller.rb b/app/controllers/admin/bans_controller.rb index 2456a1bcb..4dc93b30d 100644 --- a/app/controllers/admin/bans_controller.rb +++ b/app/controllers/admin/bans_controller.rb @@ -10,6 +10,8 @@ def create @ban.added_by = current_user if @ban.save + MemberActivityRecorder.record(actor: current_user, key: 'member.banned', + recipient: @ban.member, trackable: @ban) MemberMailer.ban(@ban.member, @ban).deliver_now redirect_to [:admin, @member], notice: t('.success') else diff --git a/app/controllers/admin/chapters/organisers_controller.rb b/app/controllers/admin/chapters/organisers_controller.rb index 23492852a..7aa277722 100644 --- a/app/controllers/admin/chapters/organisers_controller.rb +++ b/app/controllers/admin/chapters/organisers_controller.rb @@ -14,6 +14,8 @@ def create member = Member.find(params[:organiser][:organiser]) member.add_role(:organiser, @chapter) + MemberActivityRecorder.record(actor: current_user, key: 'organiser_role.granted', + recipient: member, trackable: @chapter) redirect_to admin_chapter_organisers_path(@chapter), notice: 'Successfully added organiser.' end @@ -24,6 +26,8 @@ def destroy member = Member.find(params[:id]) member.remove_role(:organiser, @chapter) + MemberActivityRecorder.record(actor: current_user, key: 'organiser_role.revoked', + recipient: member, trackable: @chapter) redirect_to admin_chapter_organisers_path(@chapter), notice: 'Successfully removed organiser.' end diff --git a/app/controllers/admin/invitation_controller.rb b/app/controllers/admin/invitation_controller.rb index a8eff1bb4..2250fa20d 100644 --- a/app/controllers/admin/invitation_controller.rb +++ b/app/controllers/admin/invitation_controller.rb @@ -3,9 +3,12 @@ class Admin::InvitationController < Admin::ApplicationController # event invitations + # rubocop:disable Metrics/AbcSize def update invitation = Invitation.find_by(token: params[:invitation][:id]) invitation.update(attending: true, verified: true, verified_by: current_user, source: Invitation::SOURCE_ADMIN) + MemberActivityRecorder.record(actor: current_user, key: 'invitation.verified', + trackable: invitation, recipient: invitation.member) EventInvitationMailer.attending(invitation.event, invitation.member, invitation).deliver_now @@ -14,10 +17,14 @@ def update notice: "You have verified #{invitation.member.full_name}'s spot at the event!" ) end + # rubocop:enable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize def verify invitation = Invitation.find_by(token: params[:invitation_id]) invitation.update(verified: true, verified_by_id: current_user.id, source: Invitation::SOURCE_ADMIN) + MemberActivityRecorder.record(actor: current_user, key: 'invitation.verified', + trackable: invitation, recipient: invitation.member) EventInvitationMailer.attending(invitation.event, invitation.member, invitation).deliver_now @@ -26,6 +33,7 @@ def verify notice: "You have verified #{invitation.member.full_name}'s spot at the event!" ) end + # rubocop:enable Metrics/AbcSize def cancel invitation = Invitation.find_by(token: params[:invitation_id]) diff --git a/app/controllers/admin/invitations_controller.rb b/app/controllers/admin/invitations_controller.rb index 7054e79fc..bd7076784 100644 --- a/app/controllers/admin/invitations_controller.rb +++ b/app/controllers/admin/invitations_controller.rb @@ -40,6 +40,8 @@ def update_attendance(attending:, attended:) def update_to_attended @invitation.update(attended: true, source: Invitation::SOURCE_ADMIN) + MemberActivityRecorder.record(actor: current_user, key: 'invitation.rsvp_override', + trackable: @invitation, recipient: @invitation.member) end def update_to_unattended @@ -55,6 +57,11 @@ def update_to_attending source: Invitation::SOURCE_ADMIN ) + if update_successful + MemberActivityRecorder.record(actor: current_user, key: 'invitation.rsvp_override', + trackable: @invitation, recipient: @invitation.member) + end + { message: update_successful ? attending_successful : attending_failed, error: !update_successful @@ -74,6 +81,8 @@ def attending_failed def update_to_not_attending @invitation.update!(attending: false, last_overridden_by_id: current_user.id) + MemberActivityRecorder.record(actor: current_user, key: 'invitation.rsvp_override', + trackable: @invitation, recipient: @invitation.member) { message: "You have removed #{@invitation.member.full_name} from the workshop.", diff --git a/app/controllers/admin/meeting_invitations_controller.rb b/app/controllers/admin/meeting_invitations_controller.rb index cea0f419e..2b293070a 100644 --- a/app/controllers/admin/meeting_invitations_controller.rb +++ b/app/controllers/admin/meeting_invitations_controller.rb @@ -6,6 +6,8 @@ def update attended = params.permit(:attended)[:attended] @invitation.update(attending: status, attended:) + MemberActivityRecorder.record(actor: current_user, key: 'meeting_invitation.updated', + trackable: @invitation, recipient: @invitation.member) redirect_to [:admin, @invitation.meeting], notice: t('admin.messages.invitation.update_rsvp', name: @invitation.member.full_name) @@ -25,6 +27,8 @@ def create if invitation.save MeetingInvitationMailer.approve_from_waitlist(meeting, member).deliver_now + MemberActivityRecorder.record(actor: current_user, key: 'meeting_invitation.created', + trackable: invitation, recipient: member) redirect_to [:admin, meeting], notice: t('admin.messages.invitation.rsvp_member', name: member.full_name) else redirect_to [:admin, meeting], notice: t('admin.messages.invitation.rsvp_error', name: member.full_name) diff --git a/app/controllers/admin/member_notes_controller.rb b/app/controllers/admin/member_notes_controller.rb index 5b99f655f..03c03fe6c 100644 --- a/app/controllers/admin/member_notes_controller.rb +++ b/app/controllers/admin/member_notes_controller.rb @@ -4,7 +4,12 @@ def create authorize @note @note.author = current_user - flash[:error] = @note.errors.full_messages unless @note.save + if @note.save + MemberActivityRecorder.record(actor: current_user, key: 'member_note.created', + trackable: @note, recipient: @note.member) + else + flash[:error] = @note.errors.full_messages + end redirect_back fallback_location: root_path end diff --git a/app/controllers/admin/members_controller.rb b/app/controllers/admin/members_controller.rb index 27483b61f..a649f6815 100644 --- a/app/controllers/admin/members_controller.rb +++ b/app/controllers/admin/members_controller.rb @@ -39,11 +39,14 @@ def events def update_subscriptions subscription = @member.subscriptions.find_by!(group_id: params[:group]) + group = subscription.group SubscriptionMailingListService.unsubscribe(subscription) flash[:notice] = t('.unsubscribe', member: @member.full_name, - chapter: subscription.group.chapter.city, - group: subscription.group.name) + chapter: group.chapter.city, + group: group.name) subscription.destroy + MemberActivityRecorder.record(actor: current_user, key: 'subscription.admin_updated', + trackable: group, recipient: @member) redirect_back fallback_location: root_path end diff --git a/app/controllers/check_ins_controller.rb b/app/controllers/check_ins_controller.rb index 7b59e4304..89343201d 100644 --- a/app/controllers/check_ins_controller.rb +++ b/app/controllers/check_ins_controller.rb @@ -117,6 +117,8 @@ def mark_attended(invitation) attrs[:automated_rsvp] = true end invitation.update!(attrs) + MemberActivityRecorder.record(actor: invitation.member, key: 'member.checked_in', + trackable: invitation) end def permitted_role diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index 88bd6043a..f8e3f312e 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -35,6 +35,9 @@ def attend if @invitation.student_spaces? || @invitation.coach_spaces? @invitation.update!(attending: true) + MemberActivityRecorder.record(actor: @invitation.member, key: 'event_invitation.rsvp', + trackable: @invitation) + notice = t('messages.invitations.spot_confirmed', event: @invitation.event.name) unless event.confirmation_required || event.surveys_required @@ -61,6 +64,8 @@ def reject end @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'event_invitation.rejected', + trackable: @invitation) redirect_back( fallback_location: root_path, notice: t('messages.rejected_invitation', name: @invitation.member.name) @@ -74,6 +79,8 @@ def rsvp_meeting meeting = invitation.meeting if invitation.update(attending: true) + MemberActivityRecorder.record(actor: current_user, key: 'meeting_invitation.rsvp', + trackable: invitation) MeetingInvitationMailer.attending(meeting, current_user).deliver_now redirect_to meeting_path(meeting, token: invitation.token), notice: t('messages.invitations.meeting.rsvp') @@ -87,6 +94,9 @@ def cancel_meeting @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'meeting_invitation.cancelled', + trackable: @invitation) + redirect_back fallback_location: root_path, notice: t('messages.invitations.meeting.cancel') end diff --git a/app/controllers/mailing_lists_controller.rb b/app/controllers/mailing_lists_controller.rb index 24032ba83..ddd0e3f59 100644 --- a/app/controllers/mailing_lists_controller.rb +++ b/app/controllers/mailing_lists_controller.rb @@ -4,6 +4,7 @@ class MailingListsController < ApplicationController before_action :require_access def create + MemberActivityRecorder.record(actor: current_user, key: 'mailing_list.subscribe') subscribe_to_newsletter(current_user) flash[:notice] = I18n.t('subscriptions.messages.mailing_list.subscribe') @@ -11,6 +12,7 @@ def create end def destroy + MemberActivityRecorder.record(actor: current_user, key: 'mailing_list.unsubscribe') unsubscribe_from_newsletter(current_user) flash[:notice] = I18n.t('subscriptions.messages.mailing_list.unsubscribe') diff --git a/app/controllers/member/details_controller.rb b/app/controllers/member/details_controller.rb index 6800f420e..f348d67af 100644 --- a/app/controllers/member/details_controller.rb +++ b/app/controllers/member/details_controller.rb @@ -24,6 +24,7 @@ def update return render :edit unless @member.update(attrs) @member.newsletter ? subscribe_to_newsletter(@member) : unsubscribe_from_newsletter(@member) + MemberActivityRecorder.record(actor: @member, key: 'profile.updated') redirect_to step2_member_path end diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index fa659a3ea..79d52f2ac 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -24,6 +24,7 @@ def profile def update if @member.update(member_params) + MemberActivityRecorder.record(actor: current_user, key: 'profile.updated') notice = 'Your details have been updated.' redirect_to profile_path, notice: else diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index 2c86fe90c..535a2ea59 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -7,11 +7,13 @@ def index @member = MemberPresenter.new(current_user) end - def create + def create # rubocop:disable Metrics/MethodLength subscription = Subscription.new(group_id:, member: current_user) if subscription.save SubscriptionMailingListService.subscribe(subscription) + MemberActivityRecorder.record(actor: current_user, key: 'subscription.created', + trackable: subscription.group) send_welcome_email(current_user, subscription) flash[:notice] = I18n.t('subscriptions.messages.group.subscribe', chapter: subscription.group.chapter.city, role: subscription.group.name) @@ -21,14 +23,18 @@ def create redirect_back fallback_location: root_path end - def destroy + def destroy # rubocop:disable Metrics/MethodLength # Don't error if subscription is not found subscription = current_user.subscriptions.find_by(group_id:) SubscriptionMailingListService.unsubscribe(subscription) if subscription subscription&.destroy - # Instead, rely on the group's existence (rather than the subscription) group = Group.find(group_id) + if subscription + MemberActivityRecorder.record(actor: current_user, key: 'subscription.removed', + trackable: group) + end + flash[:notice] = I18n.t('subscriptions.messages.group.unsubscribe', chapter: group.chapter.city, role: group.name) diff --git a/app/controllers/terms_and_conditions_controller.rb b/app/controllers/terms_and_conditions_controller.rb index a0d3be421..036966c28 100644 --- a/app/controllers/terms_and_conditions_controller.rb +++ b/app/controllers/terms_and_conditions_controller.rb @@ -22,6 +22,7 @@ def update member = current_user member.accepted_toc_at = Time.zone.now member.save(validate: false) + MemberActivityRecorder.record(actor: member, key: 'toc.accepted') redirect_to previous_path else flash[notice] = I18n.t('terms_and_conditions.messages.notice') diff --git a/app/controllers/waiting_lists_controller.rb b/app/controllers/waiting_lists_controller.rb index 37df15a16..471def73d 100644 --- a/app/controllers/waiting_lists_controller.rb +++ b/app/controllers/waiting_lists_controller.rb @@ -7,12 +7,14 @@ class WaitingListsController < ApplicationController # FeedbackController#submit (PR #2641, Rollbar #535). skip_forgery_protection only: %i[create destroy] - def create + def create # rubocop:disable Metrics/MethodLength @invitation.assign_attributes(invitation_params) return back_with_message(@invitation.errors.full_messages) unless @invitation.valid?(:waitinglist) @invitation.save && WaitingList.add(@invitation, auto_rsvp) + MemberActivityRecorder.record(actor: @invitation.member, key: 'waiting_list.joined', + trackable: @invitation) message = if auto_rsvp 'You have been added to the waiting list' @@ -25,6 +27,8 @@ def create def destroy WaitingList.find_by(invitation_id: @invitation.id).destroy + MemberActivityRecorder.record(actor: @invitation.member, key: 'waiting_list.left', + trackable: @invitation) redirect_to invitation_path(@invitation), notice: 'You have been removed from the waiting list' end diff --git a/app/controllers/workshop_invitation_controller.rb b/app/controllers/workshop_invitation_controller.rb index e9166437c..6b3369864 100644 --- a/app/controllers/workshop_invitation_controller.rb +++ b/app/controllers/workshop_invitation_controller.rb @@ -45,6 +45,8 @@ def accept return back_with_message(t('messages.no_available_seats')) unless available_spaces?(@workshop, @invitation) if @invitation.update(invitation_params.merge!(attending: true, rsvp_time: Time.zone.now)) + MemberActivityRecorder.record(actor: @invitation.member, key: 'workshop_invitation.rsvp', + trackable: @invitation) @workshop.send_attending_email(@invitation) back_with_message(t('messages.accepted_invitation', name: @invitation.member.name)) else @@ -63,6 +65,8 @@ def reject notice: t('messages.not_attending_already')) else @invitation.update!(attending: false) + MemberActivityRecorder.record(actor: @invitation.member, key: 'workshop_invitation.rejected', + trackable: @invitation) next_spot = WaitingList.next_spot(@invitation.workshop, @invitation.role) diff --git a/spec/controllers/admin/bans_controller_spec.rb b/spec/controllers/admin/bans_controller_spec.rb index 530285a39..1ed30d154 100644 --- a/spec/controllers/admin/bans_controller_spec.rb +++ b/spec/controllers/admin/bans_controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.describe Admin::BansController do let(:member) { Fabricate(:member) } let(:admin) { Fabricate(:member) } @@ -22,4 +24,17 @@ expect(response.body).to include("value=\"#{expected}\"") end end + + describe 'POST #create' do + it 'records member.banned' do + expect do + post :create, params: { member_id: member.id, ban: { reason: 'spam', note: 'banned member', + explanation: 'test', permanent: '1', + expires_at: 1.month.from_now.to_s } } + end.to change { + PublicActivity::Activity.exists?(owner: admin, key: 'member.banned', + recipient: member) + }.from(false).to(true) + end + end end diff --git a/spec/controllers/admin/chapters/organisers_controller_spec.rb b/spec/controllers/admin/chapters/organisers_controller_spec.rb new file mode 100644 index 000000000..d2cf8eff1 --- /dev/null +++ b/spec/controllers/admin/chapters/organisers_controller_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +RSpec.describe Admin::Chapters::OrganisersController, type: :controller do + let(:admin) { Fabricate(:chapter_organiser) } + let(:chapter) { Fabricate(:chapter) } + let(:member) { Fabricate(:member) } + + before do + login_as_admin(admin) + end + + describe 'POST #create' do + it 'records organiser_role.granted' do + post :create, params: { + chapter_id: chapter.id, organiser: { organiser: member.id } + } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'organiser_role.granted', + recipient: member)).to be(true) + end + end + + describe 'DELETE #destroy' do + before { member.add_role(:organiser, chapter) } + + it 'records organiser_role.revoked' do + delete :destroy, params: { chapter_id: chapter.id, id: member.id } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'organiser_role.revoked', + recipient: member)).to be(true) + end + end +end diff --git a/spec/controllers/admin/invitation_controller_spec.rb b/spec/controllers/admin/invitation_controller_spec.rb new file mode 100644 index 000000000..ff0fad8c9 --- /dev/null +++ b/spec/controllers/admin/invitation_controller_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::InvitationController, type: :controller do + describe 'POST #verify' do + let(:invitation) { Fabricate(:invitation, attending: false, verified: nil) } + let(:admin) { Fabricate(:chapter_organiser) } + + before do + admin.add_role(:admin) + login admin + request.env['HTTP_REFERER'] = '/admin/member/3' + end + + it 'records invitation.verified' do + post :verify, params: { event_id: invitation.event.id, invitation_id: invitation.token } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'invitation.verified', + recipient: invitation.member)).to be(true) + end + end +end diff --git a/spec/controllers/admin/invitations_controller_spec.rb b/spec/controllers/admin/invitations_controller_spec.rb index 7157888a3..781f18210 100644 --- a/spec/controllers/admin/invitations_controller_spec.rb +++ b/spec/controllers/admin/invitations_controller_spec.rb @@ -108,4 +108,19 @@ expect(response).to redirect_to(admin_workshop_rsvp_path(workshop, q: 'Zoe', page: 2)) end end + + context 'when recording activity' do + before do + admin.add_role(:organiser, workshop.chapter) + login admin + request.env['HTTP_REFERER'] = '/admin/member/3' + end + + it 'records invitation.rsvp_override when admin forces attending' do + put :update, params: { workshop_id: workshop.id, id: invitation.token, attending: 'true' } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'invitation.rsvp_override', + recipient: invitation.member)).to be(true) + end + end end diff --git a/spec/controllers/admin/member_notes_controller_spec.rb b/spec/controllers/admin/member_notes_controller_spec.rb index 57fafc0a5..05c396749 100644 --- a/spec/controllers/admin/member_notes_controller_spec.rb +++ b/spec/controllers/admin/member_notes_controller_spec.rb @@ -32,5 +32,15 @@ post :create, params: { member_note: { note: ' ', member_id: member.id } } end.not_to(change { MemberNote.all.count }) end + + it 'records member_note.created' do + member = Fabricate(:member) + login admin + request.env['HTTP_REFERER'] = '/admin/member/3' + + post :create, params: { member_note: { member_id: member.id, note: 'context' } } + + expect(PublicActivity::Activity.exists?(key: 'member_note.created', recipient: member)).to be(true) + end end end diff --git a/spec/requests/admin_activity_member_admin_spec.rb b/spec/requests/admin_activity_member_admin_spec.rb new file mode 100644 index 000000000..f32f27f86 --- /dev/null +++ b/spec/requests/admin_activity_member_admin_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin member management activity' do + let(:admin) { Fabricate(:member) } + let(:chapter) { Fabricate(:chapter) } + let(:member) { Fabricate(:member) } + let(:group) { Fabricate(:group) } + + before do + admin.add_role(:admin) + Fabricate(:auth_service, member: admin, provider: 'github', uid: 'admin-uid-1') + mock_auth_hash(provider: 'github', uid: 'admin-uid-1', email: admin.email) + post '/auth/github/callback' + end + + describe 'subscription changes' do + it 'records subscription.admin_updated when admin removes a subscription' do + member.subscriptions.create!(group:) + + get admin_member_update_subscriptions_path(member), params: { group: group.id } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'subscription.admin_updated', + recipient: member)).to be(true) + end + end + + describe 'meeting invitations' do + it 'records meeting_invitation.created on admin invite' do + meeting = Fabricate(:meeting) + + post admin_meeting_invitations_path, + params: { meeting_invitations: { member: member.id, meeting_id: meeting.slug } } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'meeting_invitation.created', + recipient: member)).to be(true) + end + + it 'records meeting_invitation.updated on admin update' do + meeting = Fabricate(:meeting) + invitation = Fabricate(:meeting_invitation, member:, meeting:) + + patch admin_meeting_invitation_path(invitation), + params: { attendance_status: 'true' } + + expect(PublicActivity::Activity.exists?(owner: admin, key: 'meeting_invitation.updated', + recipient: member)).to be(true) + end + end +end diff --git a/spec/requests/member_activity_check_in_spec.rb b/spec/requests/member_activity_check_in_spec.rb new file mode 100644 index 000000000..47a219def --- /dev/null +++ b/spec/requests/member_activity_check_in_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Check-in activity' do + let(:invitation) { Fabricate(:workshop_invitation) } + let(:member) { invitation.member } + let(:workshop) { invitation.workshop } + let(:code) { 'my-check-code' } + + before do + Fabricate(:auth_service, member:, provider: 'github', uid: 'checkin-uid-1') + mock_auth_hash(provider: 'github', uid: 'checkin-uid-1', email: member.email) + post '/auth/github/callback' + workshop.update!(date_and_time: 1.hour.ago, check_in_code: code) + end + + it 'records member.checked_in on valid self-check-in' do + post check_in_w_path(code:), params: { role: 'Student' } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'member.checked_in')).to be(true) + end + + it 'does not record for an invalid role' do + post check_in_w_path(code:), params: { role: 'Invalid' } + + expect(PublicActivity::Activity.exists?(key: 'member.checked_in')).to be(false) + end +end diff --git a/spec/requests/member_activity_profile_spec.rb b/spec/requests/member_activity_profile_spec.rb new file mode 100644 index 000000000..76951385c --- /dev/null +++ b/spec/requests/member_activity_profile_spec.rb @@ -0,0 +1,35 @@ +# spec/requests/member_activity_profile_spec.rb +require 'rails_helper' + +RSpec.describe 'Profile activity' do + let(:member) { Fabricate(:member, email: 'profile@example.com') } + + before do + Fabricate(:auth_service, member:, provider: 'github', uid: 'profile-uid-1') + mock_auth_hash(provider: 'github', uid: 'profile-uid-1', email: member.email) + post '/auth/github/callback' # sign in via real OAuth callback + end + + it 'records profile.updated on MembersController#update' do + put member_path(member), params: { member: { about_you: 'updated bio' } } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'profile.updated')).to be(true) + end + + it 'records profile.updated on Member::DetailsController#update' do + put member_details_path, params: { member: { about_you: 'details bio', how_you_found_us: 'social_media' } } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'profile.updated')).to be(true) + end + + it 'records toc.accepted' do + # Seed session[:previous_request_url] via a GET to root_path — + # accept_terms before_action fires, calls store_path, then redirects + # to terms_and_conditions (which skips accept_terms) + get root_path + + put terms_and_conditions_path, params: { terms_and_conditions_form: { terms: '1' } } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'toc.accepted')).to be(true) + end +end diff --git a/spec/requests/member_activity_rsvps_spec.rb b/spec/requests/member_activity_rsvps_spec.rb new file mode 100644 index 000000000..3daa337c0 --- /dev/null +++ b/spec/requests/member_activity_rsvps_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +RSpec.describe 'Event and meeting RSVP activity' do + let(:member) { Fabricate(:member) } + + before do + ApplicationController.prepend(LoginHelpers::LoginStub) unless ApplicationController < LoginHelpers::LoginStub + LoginHelpers::LoginStub.current_user = member + end + + after { LoginHelpers::LoginStub.current_user = nil } + + describe 'event RSVPs' do + let(:invitation) { Fabricate(:invitation, member:) } + + it 'records event_invitation.rsvp on attend' do + post event_attend_path(invitation.event.id, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'event_invitation.rsvp')).to be(true) + end + + it 'records event_invitation.rejected on reject' do + invitation.update!(attending: true) + post event_reject_path(invitation.event.id, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'event_invitation.rejected')).to be(true) + end + end + + describe 'meeting RSVPs' do + let(:invitation) { Fabricate(:meeting_invitation, member:) } + + it 'records meeting_invitation.rsvp' do + get meeting_invitation_path(invitation.meeting), params: { token: invitation.token } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'meeting_invitation.rsvp')).to be(true) + end + + it 'records meeting_invitation.cancelled' do + invitation.update!(attending: true) + get meeting_cancel_path(invitation.meeting, invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'meeting_invitation.cancelled')).to be(true) + end + end +end diff --git a/spec/requests/member_activity_subscriptions_spec.rb b/spec/requests/member_activity_subscriptions_spec.rb new file mode 100644 index 000000000..695c485f7 --- /dev/null +++ b/spec/requests/member_activity_subscriptions_spec.rb @@ -0,0 +1,31 @@ +# spec/requests/member_activity_subscriptions_spec.rb +require 'rails_helper' + +RSpec.describe 'Subscription and mailing list activity' do + let(:member) { Fabricate(:member) } + let(:group) { Fabricate(:group) } + + before do + Fabricate(:auth_service, member:, provider: 'github', uid: 'subs-uid-1') + mock_auth_hash(provider: 'github', uid: 'subs-uid-1', email: member.email) + post '/auth/github/callback' # sign in via real OAuth callback + end + + it 'records subscription.created and subscription.removed' do + post subscriptions_path, params: { subscription: { group_id: group.id } } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'subscription.created')).to be(true) + + delete destroy_subscriptions_path, params: { subscription: { group_id: group.id } } + + expect(PublicActivity::Activity.exists?(owner: member, key: 'subscription.removed')).to be(true) + end + + it 'records mailing_list.subscribe and unsubscribe' do + post mailing_lists_path + expect(PublicActivity::Activity.exists?(owner: member, key: 'mailing_list.subscribe')).to be(true) + + delete mailing_lists_path + expect(PublicActivity::Activity.exists?(owner: member, key: 'mailing_list.unsubscribe')).to be(true) + end +end diff --git a/spec/requests/member_activity_token_rsvps_spec.rb b/spec/requests/member_activity_token_rsvps_spec.rb new file mode 100644 index 000000000..0533b012d --- /dev/null +++ b/spec/requests/member_activity_token_rsvps_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe 'Token-based workshop RSVP activity' do + let(:invitation) { Fabricate(:workshop_invitation) } + let(:member) { invitation.member } + + it 'records workshop_invitation.rsvp on accept' do + invitation.workshop.update!(date_and_time: 2.days.from_now, rsvp_closes_at: 1.day.from_now) + # Setup the @workshop presenter for available_spaces? — the fabricator's default 10 student seats satisfy + post accept_invitation_path(invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'workshop_invitation.rsvp')).to be(true) + end + + it 'records workshop_invitation.rejected on reject' do + invitation.update!(attending: true) + + get reject_invitation_path(invitation.token) + + expect(PublicActivity::Activity.exists?(owner: member, key: 'workshop_invitation.rejected')).to be(true) + end + + it 'records waiting_list.joined and waiting_list.left' do + # Make workshop full so the invitation can be on waiting list + invitation.workshop.update!(student_spaces: 0) + invitation.update!(attending: false, role: 'Student') + + post invitation_waiting_list_path(invitation) + expect(PublicActivity::Activity.exists?(owner: member, key: 'waiting_list.joined')).to be(true) + + delete invitation_waiting_list_path(invitation) + expect(PublicActivity::Activity.exists?(owner: member, key: 'waiting_list.left')).to be(true) + end +end