From 6a80b89b4b2ca44645d161afd7b1d208690e990d Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Sun, 28 Jul 2024 19:43:00 -0400 Subject: [PATCH] Upgraded rubocop. --- .rubocop.yml | 5 + .rubocop_todo.yml | 194 ++++++++++++++++-- Gemfile | 1 + Gemfile.lock | 38 ++-- .../api/endpoints/requests/command.rb | 2 +- slack-strava/api/endpoints/requests/event.rb | 2 +- slack-strava/api/helpers/sort_helpers.rb | 3 +- slack-strava/models/map.rb | 2 +- slack-strava/models/team.rb | 2 +- spec/api/cors_spec.rb | 2 + spec/api/documentation_spec.rb | 2 + .../endpoints/credit_cards_endpoint_spec.rb | 7 +- spec/api/endpoints/maps_endpoint_spec.rb | 27 ++- spec/api/endpoints/root_endpoint_spec.rb | 4 +- spec/api/endpoints/slack_endpoint_spec.rb | 52 ++++- spec/api/endpoints/status_endpoint_spec.rb | 8 +- spec/api/endpoints/strava_endpoint_spec.rb | 49 ++++- .../endpoints/subscriptions_endpoint_spec.rb | 14 +- spec/api/endpoints/teams_endpoint_spec.rb | 23 ++- spec/api/endpoints/users_endpoint_spec.rb | 7 +- spec/initializers/array_spec.rb | 12 +- spec/integration/homepage_spec.rb | 5 +- spec/integration/subscribe_spec.rb | 28 ++- spec/integration/team_spec.rb | 6 +- spec/integration/teams_spec.rb | 6 +- spec/integration/update_cc_spec.rb | 18 +- spec/models/activity_fields_spec.rb | 14 +- spec/models/activity_spec.rb | 66 ++++-- spec/models/activity_summary_spec.rb | 4 +- spec/models/club_activity_spec.rb | 56 +++-- spec/models/club_spec.rb | 46 ++++- spec/models/map_types_spec.rb | 7 +- spec/models/system_stats_spec.rb | 17 +- spec/models/team_leaderboard_spec.rb | 27 ++- spec/models/team_spec.rb | 80 ++++++-- spec/models/team_stats_spec.rb | 34 ++- spec/models/user_activity_spec.rb | 109 +++++++++- spec/models/user_spec.rb | 194 +++++++++++++----- spec/slack-strava/app_spec.rb | 29 ++- spec/slack-strava/commands/connect_spec.rb | 5 + spec/slack-strava/commands/disconnect_spec.rb | 12 +- spec/slack-strava/commands/help_spec.rb | 6 + spec/slack-strava/commands/info_spec.rb | 6 + .../slack-strava/commands/leaderboard_spec.rb | 5 + .../slack-strava/commands/resubscribe_spec.rb | 23 ++- spec/slack-strava/commands/set_spec.rb | 50 +++++ spec/slack-strava/commands/stats_spec.rb | 4 + .../commands/subscription_spec.rb | 17 +- .../slack-strava/commands/unsubscribe_spec.rb | 25 ++- spec/slack-strava/server_spec.rb | 12 +- spec/slack-strava/service_spec.rb | 9 +- spec/slack-strava/version_spec.rb | 2 +- .../endpoints/it_behaves_like_a_cursor_api.rb | 3 +- spec/support/database_cleaner.rb | 2 +- spec/support/stripe.rb | 3 +- 55 files changed, 1125 insertions(+), 261 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5a7f1c5..87b20f8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,3 +35,8 @@ Lint/StructNewOverride: Enabled: true inherit_from: .rubocop_todo.yml + +require: + - rubocop-capybara + - rubocop-rake + - rubocop-rspec diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3ebd463..f8dc214 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,11 +1,24 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-07-13 18:23:29 UTC using RuboCop version 1.31.2. +# on 2024-07-28 23:34:28 UTC using RuboCop version 1.65.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 2 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: link_or_button, strict +Capybara/ClickLinkOrButtonStyle: + Exclude: + - 'spec/integration/update_cc_spec.rb' + +# Offense count: 3 +Capybara/SpecificActions: + Exclude: + - 'spec/integration/subscribe_spec.rb' + - 'spec/integration/update_cc_spec.rb' + # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleAlignWith, Severity. @@ -32,11 +45,12 @@ Lint/EmptyBlock: # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS Naming/FileName: Exclude: + - 'Rakefile.rb' - 'slack-strava.rb' # Offense count: 4 # Configuration parameters: ForbiddenDelimiters. -# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +# ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'slack-strava/commands/help.rb' @@ -45,7 +59,7 @@ Naming/HeredocDelimiterNaming: # Offense count: 15 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to +# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'slack-strava/app.rb' @@ -56,33 +70,177 @@ Naming/MethodParameterName: - 'slack-strava/models/team.rb' - 'slack-strava/models/user.rb' -# Offense count: 13 +# Offense count: 14 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer -# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 Naming/VariableNumber: Exclude: - 'spec/models/team_leaderboard_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 1 +# Offense count: 138 +RSpec/AnyInstance: + Enabled: false + +# Offense count: 185 +# Configuration parameters: Prefixes, AllowedPatterns. +# Prefixes: when, with, without +RSpec/ContextWording: + Enabled: false + +# Offense count: 115 # This cop supports unsafe autocorrection (--autocorrect-all). -Style/CaseLikeIf: +# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. +# SupportedStyles: described_class, explicit +RSpec/DescribedClass: Exclude: - - 'slack-strava/models/user_activity.rb' + - 'spec/models/activity_fields_spec.rb' + - 'spec/models/activity_spec.rb' + - 'spec/models/club_activity_spec.rb' + - 'spec/models/map_types_spec.rb' + - 'spec/models/system_stats_spec.rb' + - 'spec/models/team_leaderboard_spec.rb' + - 'spec/models/team_spec.rb' + - 'spec/models/user_activity_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/slack-strava/app_spec.rb' + - 'spec/slack-strava/server_spec.rb' + - 'spec/slack-strava/service_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). -Style/GlobalStdStream: +# Configuration parameters: AutoCorrect. +RSpec/EmptyExampleGroup: Exclude: - - 'slack-strava/api/middleware.rb' + - 'spec/models/athlete_spec.rb' + +# Offense count: 160 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 32 + +# Offense count: 9 +RSpec/ExpectInHook: + Exclude: + - 'spec/api/endpoints/credit_cards_endpoint_spec.rb' + - 'spec/models/team_spec.rb' + - 'spec/models/user_activity_spec.rb' + - 'spec/slack-strava/commands/resubscribe_spec.rb' + - 'spec/slack-strava/commands/unsubscribe_spec.rb' + +# Offense count: 1 +RSpec/IdenticalEqualityAssertion: + Exclude: + - 'spec/models/activity_spec.rb' + +# Offense count: 30 +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +RSpec/IndexedLet: + Exclude: + - 'spec/api/endpoints/status_endpoint_spec.rb' + - 'spec/api/endpoints/teams_endpoint_spec.rb' + - 'spec/models/system_stats_spec.rb' + - 'spec/models/team_leaderboard_spec.rb' + - 'spec/models/team_spec.rb' + - 'spec/models/team_stats_spec.rb' # Offense count: 2 -# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. -Style/GuardClause: +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: Exclude: - - 'slack-strava/models/activity_fields.rb' - - 'slack-strava/models/user_activity.rb' + - 'spec/slack-strava/service_spec.rb' + - 'spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb' + +# Offense count: 33 +RSpec/LetSetup: + Enabled: false + +# Offense count: 4 +RSpec/MessageChain: + Exclude: + - 'spec/api/endpoints/slack_endpoint_spec.rb' + - 'spec/api/endpoints/status_endpoint_spec.rb' + - 'spec/models/activity_spec.rb' + - 'spec/models/user_activity_spec.rb' + +# Offense count: 93 +# Configuration parameters: . +# SupportedStyles: have_received, receive +RSpec/MessageSpies: + EnforcedStyle: receive + +# Offense count: 198 +RSpec/MultipleExpectations: + Max: 11 + +# Offense count: 32 +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 14 + +# Offense count: 14 +# Configuration parameters: EnforcedStyle, IgnoreSharedExamples. +# SupportedStyles: always, named_only +RSpec/NamedSubject: + Exclude: + - 'spec/api/documentation_spec.rb' + - 'spec/api/endpoints/teams_endpoint_spec.rb' + - 'spec/slack-strava/app_spec.rb' + +# Offense count: 97 +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 7 + +# Offense count: 2 +RSpec/RepeatedExample: + Exclude: + - 'spec/slack-strava/commands/set_spec.rb' + +# Offense count: 18 +# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. +# Include: **/*_spec.rb +RSpec/SpecFilePathFormat: + Enabled: false + +# Offense count: 34 +RSpec/StubbedMock: + Exclude: + - 'spec/api/endpoints/slack_endpoint_spec.rb' + - 'spec/api/endpoints/subscriptions_endpoint_spec.rb' + - 'spec/api/endpoints/teams_endpoint_spec.rb' + - 'spec/integration/subscribe_spec.rb' + - 'spec/integration/update_cc_spec.rb' + - 'spec/models/club_activity_spec.rb' + - 'spec/models/club_spec.rb' + - 'spec/models/user_activity_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/slack-strava/app_spec.rb' + - 'spec/slack-strava/commands/connect_spec.rb' + - 'spec/slack-strava/commands/disconnect_spec.rb' + - 'spec/slack-strava/commands/resubscribe_spec.rb' + - 'spec/slack-strava/commands/unsubscribe_spec.rb' + - 'spec/slack-strava/server_spec.rb' + +# Offense count: 5 +# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Exclude: + - 'spec/api/endpoints/subscriptions_endpoint_spec.rb' + - 'spec/api/endpoints/teams_endpoint_spec.rb' + - 'spec/models/user_spec.rb' + +# Offense count: 1 +RSpec/VoidExpect: + Exclude: + - 'spec/integration/homepage_spec.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/GlobalStdStream: + Exclude: + - 'slack-strava/api/middleware.rb' # Offense count: 1 # Configuration parameters: MinBranchesCount. @@ -139,7 +297,7 @@ Style/NestedTernaryOperator: # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, IgnoredMethods. +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: @@ -152,7 +310,7 @@ Style/OpenStructUse: Exclude: - 'slack-strava/api/endpoints/status_endpoint.rb' -# Offense count: 1 +# Offense count: 2 # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: @@ -182,9 +340,9 @@ Style/StringConcatenation: - 'slack-strava/api/helpers/error_helpers.rb' - 'slack-strava/models/team.rb' -# Offense count: 165 +# Offense count: 185 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https Layout/LineLength: Max: 507 diff --git a/Gemfile b/Gemfile index 74394fe..aefd210 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ group :development, :test do gem 'foreman' gem 'rake', '~> 12.3' gem 'rubocop' + gem 'rubocop-capybara' gem 'rubocop-rake' gem 'rubocop-rspec' end diff --git a/Gemfile.lock b/Gemfile.lock index 12d1b44..e462204 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -104,11 +104,12 @@ GEM io-console (0.5.6) irb (1.2.4) reline (>= 0.0.1) - json (2.6.2) + json (2.7.2) kaminari-core (1.2.1) kaminari-grape (1.0.1) grape kaminari-core (~> 1.0) + language_server-protocol (3.17.0.3) mailchimp_api_v3 (0.2.18) rest-client (~> 2) matrix (0.4.2) @@ -158,9 +159,10 @@ GEM faraday (>= 1.0.0) faraday_middleware hashie - parallel (1.22.1) - parser (3.1.2.0) + parallel (1.25.1) + parser (3.3.4.0) ast (~> 2.4.1) + racc polylines (0.3.0) public_suffix (5.1.1) puma (6.4.2) @@ -195,8 +197,8 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.3.2) + strscan roar (1.1.0) representable (~> 3.0.0) rspec (3.9.0) @@ -212,25 +214,28 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-support (3.9.3) - rubocop (1.31.2) + rubocop (1.65.0) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) + regexp_parser (>= 2.4, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.18.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.19.1) - parser (>= 3.1.1.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-capybara (2.21.0) + rubocop (~> 1.41) rubocop-rake (0.6.0) rubocop (~> 1.0) - rubocop-rspec (2.12.1) - rubocop (~> 1.31) + rubocop-rspec (3.0.3) + rubocop (~> 1.61) ruby-enum (0.8.0) i18n - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) safe_yaml (1.0.5) @@ -283,7 +288,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) - unicode-display_width (2.2.0) + unicode-display_width (2.5.0) vcr (6.0.0) wannabe_bool (0.7.1) webmock (3.8.3) @@ -331,6 +336,7 @@ DEPENDENCIES rake (~> 12.3) rspec rubocop + rubocop-capybara rubocop-rake rubocop-rspec ruby-enum diff --git a/slack-strava/api/endpoints/requests/command.rb b/slack-strava/api/endpoints/requests/command.rb index 3dd2cec..5c65dbf 100644 --- a/slack-strava/api/endpoints/requests/command.rb +++ b/slack-strava/api/endpoints/requests/command.rb @@ -5,7 +5,7 @@ class Command < Request attr_reader :action, :arg, :type, :channel_id, :channel_name, :user_id, :team_id, :text, :image_url, :response_url, :trigger_id, :submission, :message_ts def initialize(params) - super(params) + super if params.key?(:payload) payload = params[:payload] @action = payload[:callback_id] diff --git a/slack-strava/api/endpoints/requests/event.rb b/slack-strava/api/endpoints/requests/event.rb index 0ac7fc8..9fd1e38 100644 --- a/slack-strava/api/endpoints/requests/event.rb +++ b/slack-strava/api/endpoints/requests/event.rb @@ -5,7 +5,7 @@ class Event < Request attr_reader :challenge, :type, :team_id, :api_app_id, :event def initialize(params) - super(params) + super @challenge = params[:challenge] if params.key?(:event) @event = Hashie::Mash.new(params[:event]) diff --git a/slack-strava/api/helpers/sort_helpers.rb b/slack-strava/api/helpers/sort_helpers.rb index 9bb77df..ee58d58 100644 --- a/slack-strava/api/helpers/sort_helpers.rb +++ b/slack-strava/api/helpers/sort_helpers.rb @@ -48,7 +48,8 @@ def sort(coll, options = {}) error!("Cannot sort #{coll.class.name}", 500) end end - coll = coll.is_a?(Module) && coll.respond_to?(:all) ? coll.all : coll + coll = coll.all if coll.is_a?(Module) && coll.respond_to?(:all) + coll end end end diff --git a/slack-strava/models/map.rb b/slack-strava/models/map.rb index d0135f5..2d6d697 100644 --- a/slack-strava/models/map.rb +++ b/slack-strava/models/map.rb @@ -49,7 +49,7 @@ def proxy_image_url end def png_size - return png.data.size if png&.data + png.data.size if png&.data end def to_s diff --git a/slack-strava/models/team.rb b/slack-strava/models/team.rb index bc5e7fe..706b151 100644 --- a/slack-strava/models/team.rb +++ b/slack-strava/models/team.rb @@ -377,7 +377,7 @@ def activated_text <<~EOS Welcome to Slava! Invite #{bot_mention} to a channel to publish activities to it. - Type \"*connect*\" to connect your Strava account." + Type "*connect*" to connect your Strava account." EOS end diff --git a/spec/api/cors_spec.rb b/spec/api/cors_spec.rb index c28b888..0f27b71 100644 --- a/spec/api/cors_spec.rb +++ b/spec/api/cors_spec.rb @@ -14,11 +14,13 @@ expect(last_response.headers['Access-Control-Allow-Origin']).to eq '*' expect(last_response.headers['Access-Control-Expose-Headers']).to eq '' end + it 'includes Access-Control-Allow-Origin in the response' do get '/api', {}, 'HTTP_ORIGIN' => '*' expect(last_response.status).to eq 200 expect(last_response.headers['Access-Control-Allow-Origin']).to eq '*' end + it 'includes Access-Control-Allow-Origin in errors' do get '/api/invalid', {}, 'HTTP_ORIGIN' => '*' expect(last_response.status).to eq 404 diff --git a/spec/api/documentation_spec.rb b/spec/api/documentation_spec.rb index b496793..14f938f 100644 --- a/spec/api/documentation_spec.rb +++ b/spec/api/documentation_spec.rb @@ -8,6 +8,7 @@ get '/api/swagger_doc' JSON.parse(last_response.body) end + it 'documents root level apis' do expect(subject['paths'].keys).to eq [ '/api/status', @@ -30,6 +31,7 @@ get '/api/swagger_doc/teams' JSON.parse(last_response.body) end + it 'documents teams apis' do expect(subject['paths'].keys).to eq ['/api/teams', '/api/teams/{id}'] end diff --git a/spec/api/endpoints/credit_cards_endpoint_spec.rb b/spec/api/endpoints/credit_cards_endpoint_spec.rb index 98f24e6..e69ba83 100644 --- a/spec/api/endpoints/credit_cards_endpoint_spec.rb +++ b/spec/api/endpoints/credit_cards_endpoint_spec.rb @@ -11,8 +11,10 @@ expect(json['type']).to eq 'param_error' end end + context 'premium team without a stripe customer id' do let!(:team) { Fabricate(:team, subscribed: true, stripe_customer_id: nil) } + it 'fails to update credit_card' do expect { client.credit_cards._post( @@ -25,9 +27,11 @@ end end end + context 'existing premium team' do - include_context :stripe_mock + include_context 'stripe mock' let!(:team) { Fabricate(:team) } + before do stripe_helper.create_plan(id: 'slava-yearly', amount: 999) customer = Stripe::Customer.create( @@ -39,6 +43,7 @@ expect_any_instance_of(Team).to receive(:inform_admin!).once team.update_attributes!(subscribed: true, stripe_customer_id: customer['id']) end + it 'updates a credit card' do new_source = stripe_helper.generate_card_token client.credit_cards._post( diff --git a/spec/api/endpoints/maps_endpoint_spec.rb b/spec/api/endpoints/maps_endpoint_spec.rb index 901ab7d..1eeec57 100644 --- a/spec/api/endpoints/maps_endpoint_spec.rb +++ b/spec/api/endpoints/maps_endpoint_spec.rb @@ -11,80 +11,95 @@ expect(JSON.parse(last_response.body)).to eq('error' => 'Not Found') end end + context 'with proxy maps on' do let(:team) { Fabricate(:team, proxy_maps: true) } let(:user) { Fabricate(:user, team: team) } + context 'with an activity' do let(:activity) { Fabricate(:user_activity, user: user) } + it 'returns map and updates map timestamp', vcr: { cassette_name: 'strava/map' } do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 expect(last_response.headers['Content-Type']).to eq 'image/png' - expect(activity.reload.map.png_retrieved_at).to_not be_nil + expect(activity.reload.map.png_retrieved_at).not_to be_nil end + it 'handles if-none-match', vcr: { cassette_name: 'strava/map' } do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 - expect(last_response.headers['ETag']).to_not be nil + expect(last_response.headers['ETag']).not_to be_nil get "/api/maps/#{activity.map.id}.png", {}, 'HTTP_IF_NONE_MATCH' => last_response.headers['ETag'] expect(last_response.status).to eq 304 end + it 'returns no map data' do allow_any_instance_of(Map).to receive(:update_png!) get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 404 end + it 'refetches map if needed', vcr: { cassette_name: 'strava/map', allow_playback_repeats: true } do - expect(activity.map.png).to_not be_nil + expect(activity.map.png).not_to be_nil activity.map.delete_png! expect(activity.reload.map.png).to be_nil get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 expect(last_response.headers['Content-Type']).to eq 'image/png' - expect(activity.reload.map.png).to_not be_nil + expect(activity.reload.map.png).not_to be_nil end + context 'with map', vcr: { cassette_name: 'strava/map' } do it 'updates map timestamp' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 - expect(activity.reload.map.png_retrieved_at).to_not be_nil + expect(activity.reload.map.png_retrieved_at).not_to be_nil end + it 'returns content-type' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 expect(last_response.headers['Content-Type']).to eq 'image/png' end + it 'returns content-length' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 expect(last_response.headers['Content-Length']).to eq last_response.body.size.to_s end + it 'handles if-none-match' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 200 - expect(last_response.headers['ETag']).to_not be nil + expect(last_response.headers['ETag']).not_to be_nil get "/api/maps/#{activity.map.id}.png", {}, 'HTTP_IF_NONE_MATCH' => last_response.headers['ETag'] expect(last_response.status).to eq 304 end end end end + context 'with defaults' do let(:team) { Fabricate(:team) } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } + before do team.update_attributes!(proxy_maps: false) end + it 'redirects to Google map' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 302 expect(last_response.headers['Location']).to start_with 'https://maps.googleapis.com' end end + context 'with a private activity', vcr: { cassette_name: 'strava/map' } do let(:user) { Fabricate(:user, private_activities: false) } let(:activity) { Fabricate(:user_activity, private: true, user: user) } + it 'does not return map' do get "/api/maps/#{activity.map.id}.png" expect(last_response.status).to eq 403 diff --git a/spec/api/endpoints/root_endpoint_spec.rb b/spec/api/endpoints/root_endpoint_spec.rb index 8b50550..762b767 100644 --- a/spec/api/endpoints/root_endpoint_spec.rb +++ b/spec/api/endpoints/root_endpoint_spec.rb @@ -9,6 +9,7 @@ links = JSON.parse(last_response.body)['_links'] expect(links.keys.sort).to eq(%w[self status subscriptions credit_cards team teams user users].sort) end + it 'follows all links' do get '/api' expect(last_response.status).to eq 200 @@ -22,9 +23,10 @@ get href.gsub('http://example.org', '') expect(last_response.status).to eq 200 - expect(JSON.parse(last_response.body)).to_not eq({}) + expect(JSON.parse(last_response.body)).not_to eq({}) end end + it 'rewrites encoded HAL links to make them clickable' do get '/api/teams/%7B?cursor,size%7D' expect(last_response.status).to eq 302 diff --git a/spec/api/endpoints/slack_endpoint_spec.rb b/spec/api/endpoints/slack_endpoint_spec.rb index dc5ef5f..a4f6fdc 100644 --- a/spec/api/endpoints/slack_endpoint_spec.rb +++ b/spec/api/endpoints/slack_endpoint_spec.rb @@ -6,11 +6,18 @@ context 'with a SLACK_VERIFICATION_TOKEN' do let(:token) { 'slack-verification-token' } let(:team) { Fabricate(:team) } + before do ENV['SLACK_VERIFICATION_TOKEN'] = token end + + after do + ENV.delete('SLACK_VERIFICATION_TOKEN') + end + context 'interactive buttons' do let(:user) { Fabricate(:user, team: team, access_token: 'token', token_expires_at: Time.now + 1.day) } + context 'without a club' do let(:club) do Club.new( @@ -24,6 +31,7 @@ logo: 'https://dgalywyr863hv.cloudfront.net/pictures/clubs/43749/1121181/4/medium.jpg' ) end + it 'connects club', vcr: { cassette_name: 'strava/retrieve_a_club' } do expect { expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( @@ -49,8 +57,10 @@ }.to change(Club, :count).by(1) end end + context 'with a club' do let!(:club) { Fabricate(:club, team: team) } + it 'disconnects club' do expect { expect_any_instance_of(Strava::Api::Client).to receive(:paginate) @@ -76,6 +86,7 @@ }.to change(Club, :count).by(-1) end end + it 'returns an error with a non-matching verification token' do post '/api/slack/action', payload: { actions: [{ name: 'strava_id', value: '43749' }], @@ -89,6 +100,7 @@ response = JSON.parse(last_response.body) expect(response['error']).to eq 'Message token is not coming from Slack.' end + it 'returns invalid callback id' do post '/api/slack/action', payload: { actions: [{ name: 'strava_id', value: 'id' }], @@ -103,8 +115,10 @@ expect(response['error']).to eq 'Callback invalid-callback is not supported.' end end + context 'slash commands' do let(:user) { Fabricate(:user, team: team) } + context 'invalid command' do it 'fails with an error' do post '/api/slack/command', @@ -124,6 +138,7 @@ ) end end + context 'stats' do it 'returns team stats' do post '/api/slack/command', @@ -142,6 +157,7 @@ 'channel' => 'channel' ) end + it 'calls stats with channel' do expect_any_instance_of(Team).to receive(:stats).with(channel_id: 'channel_id') post '/api/slack/command', @@ -153,6 +169,7 @@ team_id: team.team_id, token: token end + it 'calls stats without channel on a DM' do expect_any_instance_of(Team).to receive(:stats).with({}) post '/api/slack/command', @@ -165,13 +182,16 @@ token: token end end + context 'in channel' do before do allow_any_instance_of(Team).to receive(:bot_in_channel?).and_return(true) end + context 'disconnected user' do let!(:club_in_another_channel) { Fabricate(:club, team: team, channel_id: 'another') } let!(:club) { Fabricate(:club, team: team, channel_id: 'channel') } + it 'lists clubs connected to this channel' do post '/api/slack/command', command: '/slava', @@ -191,6 +211,7 @@ ) end end + context 'connected user' do let(:user) { Fabricate(:user, team: team, access_token: 'token', token_expires_at: Time.now + 1.day) } let(:nyrr_club) do @@ -205,6 +226,7 @@ logo: 'https://dgalywyr863hv.cloudfront.net/pictures/clubs/108605/8433029/1/medium.jpg' ) end + it 'lists clubs a user is a member of', vcr: { cassette_name: 'strava/list_athlete_clubs' } do post '/api/slack/command', command: '/slava', @@ -219,9 +241,11 @@ expect(response['attachments'].count).to eq 5 expect(response['attachments'][0]['title']).to eq nyrr_club.name end + context 'with another connected club in the channel' do let!(:club_in_another_channel) { Fabricate(:club, team: team, channel_id: 'another') } let!(:club) { Fabricate(:club, team: team, channel_id: 'channel') } + it 'lists both clubs a user is a member of and the connected club', vcr: { cassette_name: 'strava/list_athlete_clubs' } do post '/api/slack/command', command: '/slava', @@ -237,6 +261,7 @@ expect(response['attachments'][1]['title']).to eq club.name end end + context 'leaderboard' do it 'returns team leaderboard' do post '/api/slack/command', @@ -256,6 +281,7 @@ ) end end + context 'leaderboard with an arg' do it 'returns team leaderboard' do post '/api/slack/command', @@ -276,11 +302,14 @@ end end end + context 'out of channel' do before do allow_any_instance_of(Team).to receive(:bot_in_channel?).and_return(false) end + let!(:club) { Fabricate(:club, team: team, channel_id: 'channel') } + it 'requires the bot to be a member' do post '/api/slack/command', command: '/slava', @@ -294,6 +323,7 @@ expect(response['text']).to eq 'Please invite <@slava> to this channel before connecting a club.' end end + context 'DMs' do it 'says no clubs are connected in a DM' do post '/api/slack/command', @@ -313,8 +343,10 @@ 'user' => user.user_id ) end + context 'with a connected club' do let!(:club) { Fabricate(:club, team: team) } + it 'lists connected clubs in a DM' do post '/api/slack/command', command: '/slava', @@ -341,6 +373,7 @@ end end end + it 'returns an error with a non-matching verification token' do post '/api/slack/command', command: '/slava', @@ -354,6 +387,7 @@ response = JSON.parse(last_response.body) expect(response['error']).to eq 'Message token is not coming from Slack.' end + it 'provides a connect link' do post '/api/slack/command', command: '/slava', @@ -379,6 +413,7 @@ channel: 'channel' }.to_json) end + it 'attempts to disconnect' do post '/api/slack/command', command: '/slava', @@ -396,8 +431,10 @@ }.to_json) end end + context 'slack events' do let(:user) { Fabricate(:user, team: team) } + it 'returns an error with a non-matching verification token' do post '/api/slack/event', type: 'url_verification', @@ -407,6 +444,7 @@ response = JSON.parse(last_response.body) expect(response['error']).to eq 'Message token is not coming from Slack.' end + it 'performs event challenge' do post '/api/slack/event', type: 'url_verification', @@ -416,11 +454,14 @@ response = JSON.parse(last_response.body) expect(response).to eq('challenge' => 'challenge') end + context 'with an activity' do let(:activity) { Fabricate(:user_activity, user: user) } + before do allow(HTTParty).to receive_message_chain(:get, :body).and_return('PNG') end + it 'unfurls a strava URL' do expect_any_instance_of(User).to receive(:sync_strava_activity!) .with(activity.strava_id) @@ -454,25 +495,27 @@ authed_users: ['U04KB5WQR'] expect(last_response.status).to eq 201 - expect(activity.reload.bragged_at).to_not be nil + expect(activity.reload.bragged_at).not_to be_nil end end end - after do - ENV.delete('SLACK_VERIFICATION_TOKEN') - end end + context 'with a dev slack verification token' do let(:token) { 'slack-verification-token' } let(:team) { Fabricate(:team) } + before do ENV['SLACK_VERIFICATION_TOKEN_DEV'] = token end + after do ENV.delete('SLACK_VERIFICATION_TOKEN_DEV') end + context 'slack events' do let(:user) { Fabricate(:user, team: team) } + it 'returns an error with a non-matching verification token' do post '/api/slack/event', type: 'url_verification', @@ -482,6 +525,7 @@ response = JSON.parse(last_response.body) expect(response['error']).to eq 'Message token is not coming from Slack.' end + it 'performs event challenge' do post '/api/slack/event', type: 'url_verification', diff --git a/spec/api/endpoints/status_endpoint_spec.rb b/spec/api/endpoints/status_endpoint_spec.rb index a9c36cb..1e7685d 100644 --- a/spec/api/endpoints/status_endpoint_spec.rb +++ b/spec/api/endpoints/status_endpoint_spec.rb @@ -18,15 +18,17 @@ context 'with an inactive team' do let!(:team) { Fabricate(:team, active: false) } + it 'returns a status with ping' do status = client.status expect(status.teams_count).to eq 1 - expect(status).to_not respond_to(:ping) + expect(status).not_to respond_to(:ping) end end context 'with an active team' do let!(:team) { Fabricate(:team) } + it 'returns a status with ping' do status = client.status expect(status.teams_count).to eq 1 @@ -39,16 +41,20 @@ let!(:team) { Fabricate(:team) } let!(:user) { Fabricate(:user, team: team) } let!(:connected_user) { Fabricate(:user, team: team, access_token: 'xyz') } + before do allow(HTTParty).to receive_message_chain(:get, :body).and_return('PNG') end + it 'returns a status with distance and users' do status = client.status expect(status.connected_users_count).to eq 1 end + context 'with activities' do let!(:activity1) { Fabricate(:user_activity, user: connected_user, distance: 12_345_678_100) } let!(:activity2) { Fabricate(:user_activity, user: connected_user, distance: 54_321_678_100) } + it 'returns a status with distance and users' do status = client.status expect(status.total_distance_in_miles_s).to eq '41425095.12 miles' diff --git a/spec/api/endpoints/strava_endpoint_spec.rb b/spec/api/endpoints/strava_endpoint_spec.rb index 333cfd9..3bfc3d6 100644 --- a/spec/api/endpoints/strava_endpoint_spec.rb +++ b/spec/api/endpoints/strava_endpoint_spec.rb @@ -6,9 +6,15 @@ context 'with a STRAVA_CLIENT_SECRET' do let(:token) { 'strava-verification-token' } let(:challenge) { 'unique-challenge' } + before do ENV['STRAVA_CLIENT_SECRET'] = token end + + after do + ENV.delete('STRAVA_CLIENT_SECRET') + end + context 'webhook challenge' do it 'responds to a valid challenge' do get "/api/strava/event?hub.verify_token=#{StravaWebhook.instance.verify_token}&hub.challenge=#{challenge}&hub.mode=subscribe" @@ -16,11 +22,13 @@ response = JSON.parse(last_response.body) expect(response['hub.challenge']).to eq(challenge) end + it 'returns access denied on an invalid challenge' do get "/api/strava/event?hub.verify_token=invalid&hub.challenge=#{challenge}&hub.mode=subscribe" expect(last_response.status).to eq 403 end end + context 'webhook event' do let(:event_data) do { @@ -33,6 +41,7 @@ updates: {} } end + it 'responds to a valid event' do post '/api/strava/event', JSON.dump(event_data), @@ -41,8 +50,10 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + context 'with a connected user' do let!(:user) { Fabricate(:user, access_token: 'token') } + it 'syncs user' do expect_any_instance_of(Logger).to receive(:info).with(/Syncing activity/).and_call_original expect_any_instance_of(User).to receive(:sync_activity_and_brag!).with(event_data[:object_id]).once @@ -58,12 +69,14 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + context 'with an expired subscription' do let(:team) { Fabricate(:team, created_at: 3.weeks.ago) } let!(:user) { Fabricate(:user, access_token: 'token', team: team) } + it 'does not sync user' do expect_any_instance_of(Logger).to receive(:info).with(/expired/).and_call_original - expect_any_instance_of(User).to_not receive(:sync_and_brag!) + expect_any_instance_of(User).not_to receive(:sync_and_brag!) post '/api/strava/event', JSON.dump( event_data.merge( @@ -77,12 +90,14 @@ expect(response['ok']).to be true end end + context 'with an inactive team' do let(:team) { Fabricate(:team, active: false) } let!(:user) { Fabricate(:user, access_token: 'token', team: team) } + it 'does not sync user' do expect_any_instance_of(Logger).to receive(:info).with(/inactive/).and_call_original - expect_any_instance_of(User).to_not receive(:sync_and_brag!) + expect_any_instance_of(User).not_to receive(:sync_and_brag!) post '/api/strava/event', JSON.dump( event_data.merge( @@ -96,6 +111,7 @@ expect(response['ok']).to be true end end + context 'with multiple connected users with the same athlete id' do let!(:team2) { Fabricate(:team) } let!(:user2) do @@ -106,6 +122,7 @@ access_token: 'token' ) end + it 'syncs both users' do users = [] allow_any_instance_of(User).to receive(:sync_activity_and_brag!) do |a_user, _args| @@ -124,10 +141,12 @@ expect(response['ok']).to be true expect(users).to eq([user, user2]) end + context 'with an inactive team' do before do team2.update_attributes!(active: false) end + it 'skips over' do users = [] allow_any_instance_of(User).to receive(:sync_activity_and_brag!) do |a_user, _args| @@ -147,6 +166,7 @@ expect(users).to eq([user]) end end + it 'skips over failures' do users = [] allow_any_instance_of(User).to receive(:sync_activity_and_brag!) do |a_user, _args| @@ -168,8 +188,10 @@ expect(users).to eq([user2]) end end + context 'with an existing activity' do let!(:activity) { Fabricate(:user_activity, user: user, map: nil) } + it 'rebrags the existing activity' do expect_any_instance_of(Logger).to receive(:info).with(/Updating activity/).and_call_original expect_any_instance_of(User).to receive(:rebrag_activity!) do |u, a| @@ -189,6 +211,7 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + it 'unbrags an existing activity' do expect_any_instance_of(Logger).to receive(:info).with(/Deleting activity/).and_call_original expect_any_instance_of(UserActivity).to receive(:unbrag!) @@ -205,9 +228,10 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + it 'ignores non-existent activities' do expect_any_instance_of(Logger).to receive(:info).with(/Ignoring activity/).and_call_original - expect_any_instance_of(User).to_not receive(:rebrag_activity!) + expect_any_instance_of(User).not_to receive(:rebrag_activity!) post '/api/strava/event', JSON.dump( event_data.merge( @@ -221,9 +245,10 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + it 'skips other object types' do expect_any_instance_of(Logger).to receive(:warn).with(/Ignoring object type 'other'/).and_call_original - expect_any_instance_of(User).to_not receive(:rebrag_activity!) + expect_any_instance_of(User).not_to receive(:rebrag_activity!) post '/api/strava/event', JSON.dump( event_data.merge( @@ -238,6 +263,7 @@ response = JSON.parse(last_response.body) expect(response['ok']).to be true end + context 'with multiple connected users with the same athlete id' do let!(:team2) { Fabricate(:team) } let!(:user2) do @@ -249,6 +275,7 @@ ) end let!(:activity2) { Fabricate(:user_activity, user: user2, strava_id: activity.strava_id, map: nil) } + it 'rebrags both activities' do users = [] allow_any_instance_of(User).to receive(:rebrag_activity!) do |a_user, _args| @@ -268,6 +295,7 @@ expect(response['ok']).to be true expect(users).to eq([user, user2]) end + it 'unbrags both activities' do activities = [] allow_any_instance_of(UserActivity).to receive(:unbrag!) do |a_activity, _args| @@ -287,6 +315,7 @@ expect(response['ok']).to be true expect(activities).to eq([activity, activity2]) end + it 'skips over failures' do users = [] allow_any_instance_of(User).to receive(:rebrag_activity!) do |a_user, _args| @@ -308,10 +337,12 @@ expect(response['ok']).to be true expect(users).to eq([user2]) end + context 'with an inactive team' do before do activity.team.update_attributes!(active: false) end + it 'skips for inactive teams' do users = [] allow_any_instance_of(User).to receive(:rebrag_activity!) do |a_user, _args| @@ -333,11 +364,12 @@ end end end + it 'ignores unknown aspects' do expect_any_instance_of(Logger).to receive(:info).with(/Ignoring aspect type 'unknown'/).and_call_original - expect_any_instance_of(User).to_not receive(:sync_and_brag!) - expect_any_instance_of(User).to_not receive(:rebrag!) - expect_any_instance_of(User).to_not receive(:unbrag!) + expect_any_instance_of(User).not_to receive(:sync_and_brag!) + expect_any_instance_of(User).not_to receive(:rebrag!) + expect_any_instance_of(User).not_to receive(:unbrag!) post '/api/strava/event', JSON.dump( event_data.merge( @@ -354,8 +386,5 @@ end end end - after do - ENV.delete('STRAVA_CLIENT_SECRET') - end end end diff --git a/spec/api/endpoints/subscriptions_endpoint_spec.rb b/spec/api/endpoints/subscriptions_endpoint_spec.rb index 07e23f9..960e307 100644 --- a/spec/api/endpoints/subscriptions_endpoint_spec.rb +++ b/spec/api/endpoints/subscriptions_endpoint_spec.rb @@ -11,8 +11,10 @@ expect(json['type']).to eq 'param_error' end end + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + it 'fails to create a subscription' do expect { client.subscriptions._post( @@ -27,18 +29,22 @@ end end end + context 'team with a canceled subscription' do let!(:team) { Fabricate(:team, subscribed: false, stripe_customer_id: 'customer_id') } let(:stripe_customer) { double(Stripe::Customer) } + before do allow(Stripe::Customer).to receive(:retrieve).with(team.stripe_customer_id).and_return(stripe_customer) end + context 'with an active subscription' do before do allow(stripe_customer).to receive(:subscriptions).and_return([ double(Stripe::Subscription) ]) end + it 'fails to create a subscription' do expect { client.subscriptions._post( @@ -53,10 +59,12 @@ end end end + context 'without no active subscription' do before do allow(stripe_customer).to receive(:subscriptions).and_return([]) end + it 'updates a subscription' do expect(Stripe::Customer).to receive(:update).with( team.stripe_customer_id, @@ -82,13 +90,15 @@ ) team.reload expect(team.subscribed).to be true - expect(team.subscribed_at).to_not be nil + expect(team.subscribed_at).not_to be_nil expect(team.stripe_customer_id).to eq 'customer_id' end end end + context 'existing team' do let!(:team) { Fabricate(:team) } + it 'creates a subscription' do expect(Stripe::Customer).to receive(:create).with( source: 'token', @@ -111,7 +121,7 @@ ) team.reload expect(team.subscribed).to be true - expect(team.subscribed_at).to_not be nil + expect(team.subscribed_at).not_to be_nil expect(team.stripe_customer_id).to eq 'customer_id' end end diff --git a/spec/api/endpoints/teams_endpoint_spec.rb b/spec/api/endpoints/teams_endpoint_spec.rb index 086a34d..9c164e4 100644 --- a/spec/api/endpoints/teams_endpoint_spec.rb +++ b/spec/api/endpoints/teams_endpoint_spec.rb @@ -7,12 +7,15 @@ subject do client.teams end + it 'lists no teams' do expect(subject.to_a.size).to eq 0 end + context 'with teams' do let!(:team1) { Fabricate(:team, api: false) } let!(:team2) { Fabricate(:team, api: true) } + it 'lists teams with api enabled' do expect(subject.to_a.size).to eq 1 expect(subject.first.id).to eq team2.id.to_s @@ -58,10 +61,12 @@ ) ).and_return(oauth_access) end + after do ENV.delete('SLACK_CLIENT_ID') ENV.delete('SLACK_CLIENT_SECRET') end + it 'creates a team' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( text: "Welcome to Slava!\nInvite <@bot_user_id> to a channel to publish activities to it.\nType \"*connect*\" to connect your Strava account.\"\n", @@ -79,6 +84,7 @@ expect(team.activated_user_id).to eq 'activated_user_id' }.to change(Team, :count).by(1) end + it 'reactivates a deactivated team' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( text: "Welcome to Slava!\nInvite <@bot_user_id> to a channel to publish activities to it.\nType \"*connect*\" to connect your Strava account.\"\n", @@ -97,8 +103,9 @@ expect(team.active).to be true expect(team.bot_user_id).to eq 'bot_user_id' expect(team.activated_user_id).to eq 'activated_user_id' - }.to_not change(Team, :count) + }.not_to change(Team, :count) end + it 'reactivates a team deactivated on slack' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( text: "Welcome to Slava!\nInvite <@bot_user_id> to a channel to publish activities to it.\nType \"*connect*\" to connect your Strava account.\"\n", @@ -118,8 +125,9 @@ expect(team.active).to be true expect(team.bot_user_id).to eq 'bot_user_id' expect(team.activated_user_id).to eq 'activated_user_id' - }.to_not change(Team, :count) + }.not_to change(Team, :count) end + it 'returns a useful error when team already exists' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( text: "Welcome to Slava!\nInvite <@bot_user_id> to a channel to publish activities to it.\nType \"*connect*\" to connect your Strava account.\"\n", @@ -133,6 +141,7 @@ expect(json['message']).to eq "Team #{existing_team.name} is already registered." end end + it 'reactivates a deactivated team with a different code' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( text: "Welcome to Slava!\nInvite <@bot_user_id> to a channel to publish activities to it.\nType \"*connect*\" to connect your Strava account.\"\n", @@ -151,8 +160,9 @@ expect(team.active).to be true expect(team.bot_user_id).to eq 'bot_user_id' expect(team.activated_user_id).to eq 'activated_user_id' - }.to_not change(Team, :count) + }.not_to change(Team, :count) end + context 'with mailchimp settings' do before do SlackRubyBotServer::Mailchimp.configure do |config| @@ -160,8 +170,11 @@ config.mailchimp_list_id = 'list-id' end end + after do SlackRubyBotServer::Mailchimp.config.reset! + ENV.delete('MAILCHIMP_API_KEY') + ENV.delete('MAILCHIMP_LIST_ID') end let(:list) { double(Mailchimp::List, members: double(Mailchimp::List::Members)) } @@ -206,10 +219,6 @@ client.teams._post(code: 'code') end - after do - ENV.delete('MAILCHIMP_API_KEY') - ENV.delete('MAILCHIMP_LIST_ID') - end end end end diff --git a/spec/api/endpoints/users_endpoint_spec.rb b/spec/api/endpoints/users_endpoint_spec.rb index 00c5bab..1e04bcb 100644 --- a/spec/api/endpoints/users_endpoint_spec.rb +++ b/spec/api/endpoints/users_endpoint_spec.rb @@ -5,6 +5,7 @@ context 'users' do let(:user) { Fabricate(:user) } + it 'connects a user to their Strava account', vcr: { cassette_name: 'strava/retrieve_access' } do expect_any_instance_of(User).to receive(:dm!).with( text: "Your Strava account has been successfully connected.\nI won't post any private activities, DM me `set private on` to toggle that and `help` for other options." @@ -19,10 +20,11 @@ user.reload expect(user.access_token).to eq 'token' - expect(user.connected_to_strava_at).to_not be nil + expect(user.connected_to_strava_at).not_to be_nil expect(user.token_type).to eq 'Bearer' expect(user.athlete.athlete_id).to eq '12345' end + context 'with prior activities' do before do allow_any_instance_of(Map).to receive(:update_png!) @@ -31,6 +33,7 @@ user.brag! user.disconnect_from_strava end + it 'resets all activities', vcr: { cassette_name: 'strava/retrieve_access' } do expect { expect { @@ -47,7 +50,7 @@ user.reload expect(user.access_token).to eq 'token' - expect(user.connected_to_strava_at).to_not be nil + expect(user.connected_to_strava_at).not_to be_nil expect(user.token_type).to eq 'Bearer' expect(user.athlete.athlete_id).to eq '12345' }.to change(user.activities, :count).by(-2) diff --git a/spec/initializers/array_spec.rb b/spec/initializers/array_spec.rb index 347b5d7..6ac0aa6 100644 --- a/spec/initializers/array_spec.rb +++ b/spec/initializers/array_spec.rb @@ -1,33 +1,41 @@ require 'spec_helper' describe Array do - context '.and' do + describe '.and' do it 'one' do expect(['foo'].and).to eq 'foo' end + it 'two' do expect(%w[foo bar].and).to eq 'foo and bar' end + it 'three' do expect(%w[foo bar baz].and).to eq 'foo, bar and baz' end + it 'one with block' do expect(['foo'].and { |i| "#{i}" }).to eq 'foo' end + it 'two with block' do expect(%w[foo bar].and { |i| "#{i}" }).to eq 'foo and bar' end + it 'three with block' do expect(%w[foo bar baz].and { |i| "#{i}" }).to eq 'foo, bar and baz' end end - context '.or' do + + describe '.or' do it 'one' do expect(['foo'].or).to eq 'foo' end + it 'two' do expect(%w[foo bar].or).to eq 'foo or bar' end + it 'three' do expect(%w[foo bar baz].or).to eq 'foo, bar or baz' end diff --git a/spec/integration/homepage_spec.rb b/spec/integration/homepage_spec.rb index 799e9c1..410c514 100644 --- a/spec/integration/homepage_spec.rb +++ b/spec/integration/homepage_spec.rb @@ -1,20 +1,23 @@ require 'spec_helper' -describe 'Homepage', js: true, type: :feature do +describe 'Homepage', :js, type: :feature do before do ENV['SLACK_CLIENT_ID'] = 'client_id' ENV['SLACK_CLIENT_SECRET'] = 'client_secret' ENV['SLACK_OAUTH_SCOPE'] = 'scope:read' visit '/' end + after do ENV.delete 'SLACK_CLIENT_ID' ENV.delete 'SLACK_CLIENT_SECRET' ENV.delete 'SLACK_OAUTH_SCOPE' end + it 'displays index.html page' do expect(title).to eq('Slava: Strava integration with Slack') end + it 'includes a link to add to slack with the client id' do expect(find("a[href='https://slack.com/oauth/authorize?scope=scope:read&client_id=client_id']")) end diff --git a/spec/integration/subscribe_spec.rb b/spec/integration/subscribe_spec.rb index 1def640..c72887a 100644 --- a/spec/integration/subscribe_spec.rb +++ b/spec/integration/subscribe_spec.rb @@ -1,44 +1,52 @@ require 'spec_helper' -describe 'Subscribe', js: true, type: :feature do +describe 'Subscribe', :js, type: :feature do context 'without team_id' do before do visit '/subscribe' end + it 'requires a team' do - expect(find('#messages')).to have_text('Missing or invalid team ID.') - find('#subscribe', visible: false) + expect(find_by_id('messages')).to have_text('Missing or invalid team ID.') + find_by_id('subscribe', visible: false) end end + context 'for a subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + before do visit "/subscribe?team_id=#{team.team_id}" end + it 'displays an error' do - expect(find('#messages')).to have_text("Team #{team.name} is already subscribed, thank you for your support.") - find('#subscribe', visible: false) + expect(find_by_id('messages')).to have_text("Team #{team.name} is already subscribed, thank you for your support.") + find_by_id('subscribe', visible: false) end end + context 'for a team' do let!(:team) { Fabricate(:team) } + before do ENV['STRIPE_API_PUBLISHABLE_KEY'] = 'pk_test_804U1vUeVeTxBl8znwriXskf' end + after do ENV.delete 'STRIPE_API_PUBLISHABLE_KEY' end + it 'subscribes team' do visit "/subscribe?team_id=#{team.team_id}" - expect(find('#messages')).to have_text("Subscribe team #{team.name} for $9.99/yr.") + expect(find_by_id('messages')).to have_text("Subscribe team #{team.name} for $9.99/yr.") allow_any_instance_of(Team).to receive(:inform_everyone!) - find('#subscribe', visible: true) + find_by_id('subscribe', visible: true) expect(Stripe::Customer).to receive(:create).and_return('id' => 'customer_id') - find('#subscribeButton').click + find_by_id('subscribeButton').click sleep 1 @@ -53,8 +61,8 @@ sleep 5 - find('#subscribe', visible: false) - expect(find('#messages')).to have_text("Team #{team.name} successfully subscribed.\n\nThank you for your support!") + find_by_id('subscribe', visible: false) + expect(find_by_id('messages')).to have_text("Team #{team.name} successfully subscribed.\n\nThank you for your support!") team.reload expect(team.subscribed).to be true diff --git a/spec/integration/team_spec.rb b/spec/integration/team_spec.rb index 048c341..026c908 100644 --- a/spec/integration/team_spec.rb +++ b/spec/integration/team_spec.rb @@ -1,16 +1,18 @@ require 'spec_helper' -describe 'Teams', js: true, type: :feature do +describe 'Teams', :js, type: :feature do before do ENV['SLACK_CLIENT_ID'] = 'client_id' ENV['SLACK_CLIENT_SECRET'] = 'client_secret' ENV['SLACK_OAUTH_SCOPE'] = 'bot,commands,links:read,links:write' end + after do ENV.delete 'SLACK_CLIENT_ID' ENV.delete 'SLACK_CLIENT_SECRET' ENV.delete 'SLACK_OAUTH_SCOPE' end + context 'oauth', vcr: { cassette_name: 'auth_test' } do it 'registers a team' do allow_any_instance_of(Team).to receive(:ping!).and_return(ok: true) @@ -19,7 +21,7 @@ allow_any_instance_of(Slack::Web::Client).to receive(:oauth_access).with(hash_including(code: 'code')).and_return(oauth_access) expect { visit '/?code=code' - expect(page.find('#messages')).to have_content 'Team successfully registered!' + expect(page.find_by_id('messages')).to have_content 'Team successfully registered!' }.to change(Team, :count).by(1) end end diff --git a/spec/integration/teams_spec.rb b/spec/integration/teams_spec.rb index 8d30584..399079a 100644 --- a/spec/integration/teams_spec.rb +++ b/spec/integration/teams_spec.rb @@ -1,14 +1,16 @@ require 'spec_helper' -describe 'Teams', js: true, type: :feature do +describe 'Teams', :js, type: :feature do before do ENV['SLACK_CLIENT_ID'] = 'client_id' ENV['SLACK_CLIENT_SECRET'] = 'client_secret' end + after do ENV.delete 'SLACK_CLIENT_ID' ENV.delete 'SLACK_CLIENT_SECRET' end + context 'oauth', vcr: { cassette_name: 'auth_test' } do it 'registers a team' do allow_any_instance_of(Team).to receive(:inform!).with('x') @@ -18,7 +20,7 @@ allow_any_instance_of(Slack::Web::Client).to receive(:oauth_access).with(hash_including(code: 'code')).and_return(oauth_access) expect { visit '/?code=code' - expect(page.find('#messages')).to have_content "Team successfully registered!\n\nInvite @slava to a channel." + expect(page.find_by_id('messages')).to have_content "Team successfully registered!\n\nInvite @slava to a channel." }.to change(Team, :count).by(1) end end diff --git a/spec/integration/update_cc_spec.rb b/spec/integration/update_cc_spec.rb index 9c80235..c8adadc 100644 --- a/spec/integration/update_cc_spec.rb +++ b/spec/integration/update_cc_spec.rb @@ -1,21 +1,25 @@ require 'spec_helper' -describe 'Update cc', js: true, type: :feature do +describe 'Update cc', :js, type: :feature do context 'with a stripe key' do before do ENV['STRIPE_API_PUBLISHABLE_KEY'] = 'pk_test_804U1vUeVeTxBl8znwriXskf' end + after do ENV.delete 'STRIPE_API_PUBLISHABLE_KEY' end + context 'with an invalid team ID' do it 'displays error' do visit '/update_cc?team_id=invalid' - expect(find('#messages')).to have_text('Team not found.') + expect(find_by_id('messages')).to have_text('Team not found.') end end + context 'a team with a stripe customer ID' do let!(:team) { Fabricate(:team, stripe_customer_id: 'stripe_customer_id') } + it 'updates cc' do visit "/update_cc?team_id=#{team.team_id}" expect(find('h1')).to have_text('Slava: Update Credit Card Info') @@ -34,12 +38,14 @@ find('button[type="submit"]').click end sleep 5 - find('#update_cc', visible: false) - expect(find('#messages')).to have_text("Successfully updated team #{team.name} credit card.\n\nThank you for your support!") + find_by_id('update_cc', visible: false) + expect(find_by_id('messages')).to have_text("Successfully updated team #{team.name} credit card.\n\nThank you for your support!") end end + context 'a team without a stripe customer ID' do let!(:team) { Fabricate(:team, stripe_customer_id: nil) } + it 'displays error' do visit "/update_cc?team_id=#{team.team_id}" expect(find('h1')).to have_text('Slava: Update Credit Card Info') @@ -54,8 +60,8 @@ find('button[type="submit"]').click end sleep 5 - find('#update_cc', visible: false) - expect(find('#messages')).to have_text('Not a Subscriber') + find_by_id('update_cc', visible: false) + expect(find_by_id('messages')).to have_text('Not a Subscriber') end end end diff --git a/spec/models/activity_fields_spec.rb b/spec/models/activity_fields_spec.rb index a819c61..a1ee248 100644 --- a/spec/models/activity_fields_spec.rb +++ b/spec/models/activity_fields_spec.rb @@ -1,37 +1,47 @@ require 'spec_helper' describe ActivityFields do - context '#parse_s' do + describe '#parse_s' do it 'returns nil for nil' do - expect(ActivityFields.parse_s(nil)).to be nil + expect(ActivityFields.parse_s(nil)).to be_nil end + it 'returns an empty set for an empty string' do expect(ActivityFields.parse_s('')).to eq([]) end + it 'returns None' do expect(ActivityFields.parse_s('none')).to eq([ActivityFields::NONE]) end + it 'returns Time' do expect(ActivityFields.parse_s('time')).to eq([ActivityFields::TIME]) end + it 'returns Elapsed Time' do expect(ActivityFields.parse_s('elapsed time')).to eq([ActivityFields::ELAPSED_TIME]) end + it 'returns Time and Elapsed Time' do expect(ActivityFields.parse_s('time, elapsed time')).to eq([ActivityFields::TIME, ActivityFields::ELAPSED_TIME]) end + it 'cannot combine None with other values' do expect { ActivityFields.parse_s('None, elapsed time') }.to raise_error SlackStrava::Error, 'None cannot be used with other fields.' end + it 'cannot combine All with other values' do expect { ActivityFields.parse_s('All, elapsed time') }.to raise_error SlackStrava::Error, 'All cannot be used with other fields.' end + it 'removes duplicates' do expect(ActivityFields.parse_s('elapsed time, Elapsed Time, Time')).to eq([ActivityFields::ELAPSED_TIME, ActivityFields::TIME]) end + it 'raise an error on an invalid value' do expect { ActivityFields.parse_s('invalid, elapsed time') }.to raise_error SlackStrava::Error, 'Invalid field: invalid, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' end + it 'raise an error on invalid fields' do expect { ActivityFields.parse_s('invalid, elapsed time, whatever') }.to raise_error SlackStrava::Error, 'Invalid fields: invalid and whatever, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' end diff --git a/spec/models/activity_spec.rb b/spec/models/activity_spec.rb index d078d78..5d36a46 100644 --- a/spec/models/activity_spec.rb +++ b/spec/models/activity_spec.rb @@ -1,23 +1,27 @@ require 'spec_helper' describe Activity do - context '#pace_per_mile_s' do + describe '#pace_per_mile_s' do it 'rounds up 60 seconds' do expect(Activity.new(average_speed: 3.354).pace_per_mile_s).to eq '8m00s/mi' end end - context '#==' do + + describe '#==' do let(:club_activity) { Fabricate(:club_activity) } let(:swim_activity) { Fabricate(:swim_activity) } + it 'same object' do expect(club_activity).to eq club_activity end + it 'different instance' do expect(club_activity).to eq Fabricate(:club_activity) expect(club_activity).to eq club_activity.dup - expect(swim_activity).to_not eq club_activity - expect(club_activity).to_not eq swim_activity + expect(swim_activity).not_to eq club_activity + expect(club_activity).not_to eq swim_activity end + context 'same data' do let(:data) do { @@ -28,23 +32,27 @@ } end let(:activity) { Activity.new(data) } + it 'equals for different instances' do expect(Activity.new(data)).to eq activity expect(ClubActivity.new(data)).to eq activity expect(activity).to eq Activity.new(data) expect(activity).to eq ClubActivity.new(data) end + it 'is different with different data' do - expect(Activity.new(data.merge(distance: 0))).to_not eq activity - expect(Activity.new(data.merge(moving_time: 0))).to_not eq activity - expect(Activity.new(data.merge(elapsed_time: 0))).to_not eq activity - expect(Activity.new(data.merge(total_elevation_gain: 0))).to_not eq activity + expect(Activity.new(data.merge(distance: 0))).not_to eq activity + expect(Activity.new(data.merge(moving_time: 0))).not_to eq activity + expect(Activity.new(data.merge(elapsed_time: 0))).not_to eq activity + expect(Activity.new(data.merge(total_elevation_gain: 0))).not_to eq activity end end + it 'different object' do - expect(swim_activity).to_not eq Object.new + expect(swim_activity).not_to eq Object.new end end + context 'bragged_in?' do let!(:user_activity) do Fabricate(:user_activity, @@ -64,23 +72,29 @@ team: user_activity.team } end + it 'finds a similar user activity' do expect(Activity.new(user_activity_data).bragged_in?('channel1')).to be true end + it 'does not find a similar user activity bragged in a different channel' do expect(Activity.new(user_activity_data).bragged_in?('another')).to be false end + it 'does not find a dissimilar activity' do expect(Activity.new(user_activity_data.merge(distance: -1)).bragged_in?('channel2')).to be false end + it 'does not find a similar user activity bragged a long time ago' do user_activity.set(bragged_at: 1.week.ago) expect(Activity.new(user_activity_data).bragged_in?('channel1')).to be false end + it 'does not find a similar user activity bragged in a different team' do expect(Activity.new(user_activity_data.merge(team: Fabricate(:team))).bragged_in?('channel1')).to be false end end + context 'privately_bragged?' do context 'private activity' do let!(:user_activity) do @@ -100,28 +114,35 @@ team: user_activity.team } end + it 'finds a similar user activity' do expect(Activity.new(user_activity_data).privately_bragged?).to be true end + it 'does not find a similar public user activity' do user_activity.set(private: false) expect(Activity.new(user_activity_data).privately_bragged?).to be false end + it 'finds a similar user activity with everyone visibility' do user_activity.set(visibility: 'everyone') expect(Activity.new(user_activity_data).privately_bragged?).to be true end + it 'does not find a dissimilar private activity' do expect(Activity.new(user_activity_data.merge(distance: -1)).privately_bragged?).to be false end + it 'does not find a similar private user activity bragged a long time ago' do user_activity.set(bragged_at: 1.week.ago) expect(Activity.new(user_activity_data).privately_bragged?).to be false end + it 'does not find a similar private user activity in a different team' do expect(Activity.new(user_activity_data.merge(team: Fabricate(:team))).privately_bragged?).to be false end end + context 'visibility' do let!(:user_activity) do Fabricate(:user_activity, @@ -138,80 +159,99 @@ team: user_activity.team } end + it 'finds a similar only_me user activity' do user_activity.set(visibility: 'only_me') expect(Activity.new(user_activity_data).privately_bragged?).to be true end + it 'finds a similar followers_only user activity' do user_activity.set(visibility: 'followers_only') expect(Activity.new(user_activity_data).privately_bragged?).to be true end + it 'does not find a similar everyone user activity' do user_activity.set(visibility: 'everyone') expect(Activity.new(user_activity_data).privately_bragged?).to be false end + it 'does not find a dissimilar private activity' do user_activity.set(visibility: 'only_me') expect(Activity.new(user_activity_data.merge(distance: -1)).privately_bragged?).to be false end + it 'does not find a similar private user activity bragged a long time ago' do user_activity.set(visibility: 'only_me') user_activity.set(bragged_at: 1.week.ago) expect(Activity.new(user_activity_data).privately_bragged?).to be false end + it 'does not find a similar private user activity in a different team' do user_activity.set(visibility: 'only_me') expect(Activity.new(user_activity_data.merge(team: Fabricate(:team))).privately_bragged?).to be false end end end + context 'access changes' do before do allow(HTTParty).to receive_message_chain(:get, :body).and_return('PNG') end + describe 'privacy changes' do context 'a private, bragged activity that was not posted to any channels' do let!(:activity) { Fabricate(:user_activity, private: true, bragged_at: Time.now.utc) } + it 'resets bragged_at' do activity.update_attributes!(private: false) expect(activity.reload.bragged_at).to be_nil end end + context 'a private, bragged activity a long time ago that was not posted to any channels' do let!(:activity) { Fabricate(:user_activity, private: true, bragged_at: 1.week.ago) } + it 'does not reset bragged_at' do activity.update_attributes!(private: false) - expect(activity.reload.bragged_at).to_not be_nil + expect(activity.reload.bragged_at).not_to be_nil end end + context 'a private, bragged activity that was posted to a channel' do let!(:activity) { Fabricate(:user_activity, private: true, bragged_at: Time.now.utc, channel_messages: [ChannelMessage.new(channel: 'c1')]) } + it 'does not reset bragged_at' do activity.update_attributes!(private: false) - expect(activity.reload.bragged_at).to_not be_nil + expect(activity.reload.bragged_at).not_to be_nil end end end + describe 'visibility changes' do context 'bragged activity that was not posted to any channels' do let!(:activity) { Fabricate(:user_activity, visibility: 'only_me', bragged_at: Time.now.utc) } + it 'resets bragged_at' do activity.update_attributes!(visibility: 'everyone') expect(activity.reload.bragged_at).to be_nil end end + context 'bragged activity a long time ago that was not posted to any channels' do let!(:activity) { Fabricate(:user_activity, visibility: 'only_me', bragged_at: 1.week.ago) } + it 'does not reset bragged_at' do activity.update_attributes!(visibility: 'everyone') - expect(activity.reload.bragged_at).to_not be_nil + expect(activity.reload.bragged_at).not_to be_nil end end + context 'bragged activity that was posted to a channel' do let!(:activity) { Fabricate(:user_activity, visibility: 'only_me', bragged_at: Time.now.utc, channel_messages: [ChannelMessage.new(channel: 'c1')]) } + it 'does not reset bragged_at' do activity.update_attributes!(visibility: 'everyone') - expect(activity.reload.bragged_at).to_not be_nil + expect(activity.reload.bragged_at).not_to be_nil end end end diff --git a/spec/models/activity_summary_spec.rb b/spec/models/activity_summary_spec.rb index 6941bf6..00c4ca1 100644 --- a/spec/models/activity_summary_spec.rb +++ b/spec/models/activity_summary_spec.rb @@ -2,8 +2,10 @@ describe ActivitySummary do let(:activity_summary) { Fabricate(:activity_summary) } - context '#to_slack_attachment' do + + describe '#to_slack_attachment' do let(:slack_attachment) { activity_summary.to_slack_attachment } + it 'returns a slack attachment' do expect(slack_attachment).to eq( { diff --git a/spec/models/club_activity_spec.rb b/spec/models/club_activity_spec.rb index 60b9f5f..97ddcef 100644 --- a/spec/models/club_activity_spec.rb +++ b/spec/models/club_activity_spec.rb @@ -4,15 +4,18 @@ context 'hidden?' do context 'default' do let(:activity) { Fabricate(:club_activity) } + it 'is not hidden' do expect(activity.hidden?).to be false end end end + context 'brag!' do let(:team) { Fabricate(:team) } let(:club) { Fabricate(:club, team: team) } let!(:activity) { Fabricate(:club_activity, club: club) } + it 'sends a message to the subscribed channel' do expect(club.team.slack_client).to receive(:chat_postMessage).with( activity.to_slack.merge( @@ -22,15 +25,17 @@ ).and_return('ts' => 1) expect(activity.brag!).to eq([ts: 1, channel: club.channel_id]) end + it 'warns if the bot leaves the channel' do expect { expect_any_instance_of(Logger).to receive(:warn).with(/not_in_channel/) expect(club.team.slack_client).to receive(:chat_postMessage) { raise Slack::Web::Api::Errors::SlackError, 'not_in_channel' } - expect(activity.brag!).to be nil - }.to_not change(Club, :count) + expect(activity.brag!).to be_nil + }.not_to change(Club, :count) end + it 'warns if the account goes inactive' do expect { expect { @@ -38,10 +43,11 @@ expect(club.team.slack_client).to receive(:chat_postMessage) { raise Slack::Web::Api::Errors::SlackError, 'account_inactive' } - expect(activity.brag!).to be nil - }.to_not change(Club, :count) - }.to_not change(ClubActivity, :count) + expect(activity.brag!).to be_nil + }.not_to change(Club, :count) + }.not_to change(ClubActivity, :count) end + it 'informs admin on restricted_action' do expect { expect_any_instance_of(Logger).to receive(:warn).with(/restricted_action/) @@ -49,9 +55,10 @@ expect(club.team.slack_client).to receive(:chat_postMessage) { raise Slack::Web::Api::Errors::SlackError, 'restricted_action' } - expect(activity.brag!).to be nil - }.to_not change(Club, :count) + expect(activity.brag!).to be_nil + }.not_to change(Club, :count) end + it 'informs admin on is_archived channel' do expect { expect_any_instance_of(Logger).to receive(:warn).with(/is_archived/) @@ -59,10 +66,11 @@ expect(club.team.slack_client).to receive(:chat_postMessage) { raise Slack::Web::Api::Errors::SlackError, 'is_archived' } - expect(activity.brag!).to be nil - }.to_not change(Club, :count) + expect(activity.brag!).to be_nil + }.not_to change(Club, :count) expect(club.reload.sync_activities).to be false end + context 'having already bragged a user activity in the channel' do let!(:user_activity) do Fabricate(:user_activity, @@ -77,14 +85,16 @@ ChannelMessage.new(channel: club.channel_id) ]) end + it 'does not re-brag the activity' do - expect(club.team.slack_client).to_not receive(:chat_postMessage) + expect(club.team.slack_client).not_to receive(:chat_postMessage) expect { - expect(activity.brag!).to be nil + expect(activity.brag!).to be_nil }.to change(club.activities.unbragged, :count).by(-1) - expect(activity.bragged_at).to_not be_nil + expect(activity.bragged_at).not_to be_nil end end + context 'having a private user activity' do let!(:user_activity) do Fabricate(:user_activity, @@ -96,6 +106,7 @@ map: nil, private: true) end + context 'unbragged' do it 'rebrags the activity' do expect(club.team.slack_client).to receive(:chat_postMessage).with( @@ -107,22 +118,26 @@ expect(activity.brag!).to eq([ts: 1, channel: club.channel_id]) end end + context 'bragged recently' do before do user_activity.set(bragged_at: Time.now.utc) end + it 'does not rebrag the activity' do - expect(club.team.slack_client).to_not receive(:chat_postMessage) + expect(club.team.slack_client).not_to receive(:chat_postMessage) expect { - expect(activity.brag!).to be nil + expect(activity.brag!).to be_nil }.to change(club.activities.unbragged, :count).by(-1) - expect(activity.bragged_at).to_not be_nil + expect(activity.bragged_at).not_to be_nil end end + context 'bragged a long time ago' do before do user_activity.set(bragged_at: Time.now.utc - 1.month) end + it 'rebrags the activity' do expect(club.team.slack_client).to receive(:chat_postMessage).with( activity.to_slack.merge( @@ -135,10 +150,12 @@ end end end + context 'miles' do let(:team) { Fabricate(:team, units: 'mi') } let(:club) { Fabricate(:club, team: team) } let(:activity) { Fabricate(:club_activity, club: club) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -162,10 +179,12 @@ ) end end + context 'km' do let(:team) { Fabricate(:team, units: 'km') } let(:club) { Fabricate(:club, team: team) } let(:activity) { Fabricate(:club_activity, club: club) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -189,10 +208,12 @@ ) end end + context 'both' do let(:team) { Fabricate(:team, units: 'both') } let(:club) { Fabricate(:club, team: team) } let(:activity) { Fabricate(:club_activity, club: club) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -216,11 +237,14 @@ ) end end + context 'fields' do let(:club) { Fabricate(:club, team: team) } let(:activity) { Fabricate(:club_activity, club: club) } + context 'none' do let(:team) { Fabricate(:team, activity_fields: ['None']) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -235,8 +259,10 @@ ) end end + context 'some' do let(:team) { Fabricate(:team, activity_fields: %w[Pace Elevation Type]) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ diff --git a/spec/models/club_spec.rb b/spec/models/club_spec.rb index 9cfde97..00d5797 100644 --- a/spec/models/club_spec.rb +++ b/spec/models/club_spec.rb @@ -3,6 +3,7 @@ describe Club do let(:team) { Fabricate(:team) } let!(:club) { Fabricate(:club, team: team, strava_id: '43749', access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + context 'sync_last_strava_activity!', vcr: { allow_playback_repeats: true, cassette_name: 'strava/club_sync_last_strava_activity' } do it 'retrieves the last activity' do expect { @@ -12,16 +13,19 @@ expect(activity.strava_id).to eq 'b1cbe401792d703084b56eb0bb9ac455' expect(activity.name).to eq 'Hard as fuck run home — tired + lots of aches ' end + it 'only saves the last activity once' do expect { 2.times { club.sync_last_strava_activity! } }.to change(club.activities, :count).by(1) end + it 'retrieves an incremental set of activities', vcr: { cassette_name: 'strava/club_sync_new_strava_activities' } do expect { club.sync_new_strava_activities! }.to change(club.activities, :count).by(8) end + it 'retrieves an incremental set of activities skipping duplicates', vcr: { cassette_name: 'strava/club_sync_new_strava_activities' } do # first activity from the cassette club.activities.create!(team: club.team, strava_id: 'b1cbe401792d703084b56eb0bb9ac455') @@ -29,15 +33,18 @@ club.sync_new_strava_activities! }.to change(club.activities, :count).by(7) end + it 'updates the existing duplicate', vcr: { cassette_name: 'strava/club_sync_new_strava_activities' } do activity = club.activities.create!(team: club.team, strava_id: 'b1cbe401792d703084b56eb0bb9ac455') tt = activity.reload.updated_at.utc Timecop.travel(Time.now + 1.hour) club.sync_new_strava_activities! - expect(activity.reload.updated_at.utc.to_i).to_not eq(tt.to_i) + expect(activity.reload.updated_at.utc.to_i).not_to eq(tt.to_i) end + context 'with two club channels' do let!(:club2) { Fabricate(:club, team: team, strava_id: '43749', channel_id: '1HNTD0CW', channel_name: 'testing', access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'retrieves the last activity and stores it twice' do expect { club.sync_last_strava_activity! @@ -48,6 +55,7 @@ expect(club.activities.count).to eq(1) expect(club2.activities.count).to eq(1) end + it 'only saves the last activity once per club' do expect { 2.times { club.sync_last_strava_activity! } @@ -58,6 +66,7 @@ expect(club.activities.count).to eq(1) expect(club2.activities.count).to eq(1) end + it 'retrieves an incremental set of activities', vcr: { cassette_name: 'strava/club_sync_new_strava_activities', allow_playback_repeats: true } do expect { club.sync_new_strava_activities! @@ -66,6 +75,7 @@ club2.sync_new_strava_activities! }.to change(club2.activities, :count).by(8) end + it 'retrieves an incremental set of activities skipping duplicates', vcr: { cassette_name: 'strava/club_sync_new_strava_activities', allow_playback_repeats: true } do # first activity from the cassette club.activities.create!(team: club.team, strava_id: 'b1cbe401792d703084b56eb0bb9ac455') @@ -77,6 +87,7 @@ club2.sync_new_strava_activities! }.to change(club2.activities, :count).by(7) end + it 'updates the existing duplicates', vcr: { cassette_name: 'strava/club_sync_new_strava_activities', allow_playback_repeats: true } do activity = club.activities.create!(team: club.team, strava_id: 'b1cbe401792d703084b56eb0bb9ac455') tt = activity.reload.updated_at.utc @@ -84,10 +95,11 @@ tt2 = activity.reload.updated_at.utc Timecop.travel(Time.now + 1.hour) club.sync_new_strava_activities! - expect(activity.reload.updated_at.utc.to_i).to_not eq(tt.to_i) + expect(activity.reload.updated_at.utc.to_i).not_to eq(tt.to_i) expect(activity2.reload.updated_at.utc.to_i).to eq(tt2.to_i) end end + ['Authorization Error', 'Forbidden'].each do |message| it 'disconnects club on auth failure' do allow(club.strava_client).to receive(:club_activities).and_raise( @@ -101,10 +113,10 @@ ) ).and_return('ts' => 1) expect { club.sync_last_strava_activity! }.to raise_error Strava::Errors::Fault - expect(club.access_token).to be nil - expect(club.token_type).to be nil - expect(club.refresh_token).to be nil - expect(club.token_expires_at).to be nil + expect(club.access_token).to be_nil + expect(club.token_type).to be_nil + expect(club.refresh_token).to be_nil + expect(club.token_expires_at).to be_nil end end it 'disables sync on 404' do @@ -122,33 +134,39 @@ expect { club.sync_last_strava_activity! }.to raise_error Faraday::ResourceNotFound expect(club.sync_activities?).to be false end + context 'without a refresh token (until October 2019)', vcr: { cassette_name: 'strava/refresh_access_token' } do before do club.update_attributes!(refresh_token: nil, token_expires_at: nil) end + it 'refreshes access token using access token' do club.send(:strava_client) expect(club.refresh_token).to eq 'updated-refresh-token' expect(club.access_token).to eq 'updated-access-token' - expect(club.token_expires_at).to_not be_nil + expect(club.token_expires_at).not_to be_nil expect(club.token_type).to eq 'Bearer' end end + context 'with an expired refresh token', vcr: { cassette_name: 'strava/refresh_access_token' } do before do club.update_attributes!(refresh_token: 'refresh_token', token_expires_at: nil) end + it 'refreshes access token' do club.send(:strava_client) expect(club.refresh_token).to eq 'updated-refresh-token' expect(club.access_token).to eq 'updated-access-token' - expect(club.token_expires_at).to_not be_nil + expect(club.token_expires_at).not_to be_nil expect(club.token_type).to eq 'Bearer' end end end + context 'brag!' do let!(:activity) { Fabricate(:club_activity, club: club) } + it 'brags the last unbragged activity' do expect_any_instance_of(ClubActivity).to receive(:brag!).and_return( [ @@ -166,39 +184,47 @@ ) end end + context 'sync_and_brag!', vcr: { cassette_name: 'strava/club_sync_new_strava_activities', allow_playback_repeats: true } do context 'upon creation' do it 'syncs but does not brag' do - expect_any_instance_of(Slack::Web::Client).to_not receive(:chat_postMessage) + expect_any_instance_of(Slack::Web::Client).not_to receive(:chat_postMessage) club.sync_and_brag! end end + context 'after an initial sync' do before do club.sync_and_brag! end + context 'with a new activity' do before do club.activities.desc(:_id).first.destroy end + it 'syncs and brags' do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).once club.sync_and_brag! end end end + it 'warns on error' do expect_any_instance_of(Logger).to receive(:warn).with(/unexpected error/) allow(club).to receive(:sync_new_strava_activities!).and_raise 'unexpected error' - expect { club.sync_and_brag! }.to_not raise_error + expect { club.sync_and_brag! }.not_to raise_error end + context 'rate limit exceeded' do let(:rate_limit_exceeded_error) { Strava::Errors::Fault.new(429, body: { 'message' => 'Rate Limit Exceeded', 'errors' => [{ 'resource' => 'Application', 'field' => 'rate limit', 'code' => 'exceeded' }] }) } + it 'raises an exception' do allow(club).to receive(:sync_new_strava_activities!).and_raise rate_limit_exceeded_error expect { club.sync_and_brag! }.to raise_error(Strava::Errors::Fault, /Rate Limit Exceeded/) end end + pending 'uses a lock' end end diff --git a/spec/models/map_types_spec.rb b/spec/models/map_types_spec.rb index 22426bd..050d011 100644 --- a/spec/models/map_types_spec.rb +++ b/spec/models/map_types_spec.rb @@ -1,16 +1,19 @@ require 'spec_helper' describe MapTypes do - context '#parse_s' do + describe '#parse_s' do it 'returns nil for nil' do - expect(MapTypes.parse_s(nil)).to be nil + expect(MapTypes.parse_s(nil)).to be_nil end + it 'returns off' do expect(MapTypes.parse_s('off')).to eq(MapTypes::OFF) end + it 'returns full' do expect(MapTypes.parse_s('full')).to eq(MapTypes::FULL) end + it 'raise an error on an invalid value' do expect { MapTypes.parse_s('invalid') }.to raise_error SlackStrava::Error, 'Invalid value: invalid, possible values are full, off and thumb.' end diff --git a/spec/models/system_stats_spec.rb b/spec/models/system_stats_spec.rb index 047e00a..6423b74 100644 --- a/spec/models/system_stats_spec.rb +++ b/spec/models/system_stats_spec.rb @@ -2,47 +2,55 @@ describe SystemStats do let(:stats) { SystemStats.aggregate! } + before do allow_any_instance_of(Map).to receive(:update_png!) end + context 'aggregate!' do it 'creates a record' do expect { 2.times { SystemStats.aggregate! } }.to change(SystemStats, :count).by(2) end end + context 'latest_or_aggregate!' do it 'creates one record' do expect { 2.times { SystemStats.latest_or_aggregate! } }.to change(SystemStats, :count).by(1) end + it 'creates another record 24 hours later' do SystemStats.aggregate! Timecop.travel(Time.now + 1.day) do expect { SystemStats.latest_or_aggregate! }.to change(SystemStats, :count).by(1) end Timecop.travel(Time.now + 1.day + 1.hour) do - expect { SystemStats.latest_or_aggregate! }.to_not change(SystemStats, :count) + expect { SystemStats.latest_or_aggregate! }.not_to change(SystemStats, :count) end end end + context 'with no teams' do it 'zero' do expect(stats.teams_count).to eq 0 expect(stats.active_teams_count).to eq 0 expect(stats.connected_users_count).to eq 0 expect(stats.total_distance_in_miles).to eq 0.0 - expect(stats.total_distance_in_miles_s).to be nil + expect(stats.total_distance_in_miles_s).to be_nil end end + context 'with a team' do let!(:team) { Fabricate(:team) } + it 'one team' do expect(stats.teams_count).to eq 1 expect(stats.active_teams_count).to eq 1 expect(stats.connected_users_count).to eq 0 expect(stats.total_distance_in_miles).to eq 0.0 - expect(stats.total_distance_in_miles_s).to be nil + expect(stats.total_distance_in_miles_s).to be_nil end end + context 'with activities' do let!(:team) { Fabricate(:team) } let!(:user1) { Fabricate(:user, team: team) } @@ -55,11 +63,12 @@ let!(:activity1) { Fabricate(:user_activity, user: user1) } let!(:activity2) { Fabricate(:user_activity, user: user1) } let!(:activity3) { Fabricate(:user_activity, user: user2) } + it 'aggregates stats' do expect(stats.teams_count).to eq 1 expect(stats.active_teams_count).to eq 1 expect(stats.connected_users_count).to eq 0 - expect(stats.total_distance_in_miles).to_not eq 0 + expect(stats.total_distance_in_miles).not_to eq 0 expect(stats.total_distance_in_miles_s).to match(/\d+.\d+ miles/) end end diff --git a/spec/models/team_leaderboard_spec.rb b/spec/models/team_leaderboard_spec.rb index 51e593f..5d76b72 100644 --- a/spec/models/team_leaderboard_spec.rb +++ b/spec/models/team_leaderboard_spec.rb @@ -2,33 +2,42 @@ describe TeamLeaderboard do let!(:team) { Fabricate(:team) } + before do allow_any_instance_of(Map).to receive(:update_png!) end + context 'initialize' do it 'errors on no metric' do expect { TeamLeaderboard.new(team, metric: nil).aggregate! }.to raise_error SlackStrava::Error, "Missing value. Expected one of #{TeamLeaderboard::MEASURABLE_VALUES.or}." end + it 'errors on empty metric' do expect { TeamLeaderboard.new(team, metric: '').aggregate! }.to raise_error SlackStrava::Error, "Missing value. Expected one of #{TeamLeaderboard::MEASURABLE_VALUES.or}." end + it 'errors on invalid' do expect { TeamLeaderboard.new(team, metric: 'invalid').aggregate! }.to raise_error SlackStrava::Error, "Invalid value: invalid. Expected one of #{TeamLeaderboard::MEASURABLE_VALUES.or}." end + it 'errors on multiple metrics' do expect { TeamLeaderboard.new(team, metric: 'Distance, Speed').aggregate! }.to raise_error SlackStrava::Error, "Invalid value: Distance, Speed. Expected one of #{TeamLeaderboard::MEASURABLE_VALUES.or}." end + it 'errors on one invalid metric' do expect { TeamLeaderboard.new(team, metric: 'Distance, invalid').aggregate! }.to raise_error SlackStrava::Error, "Invalid value: Distance, invalid. Expected one of #{TeamLeaderboard::MEASURABLE_VALUES.or}." end + it 'is case insensitive' do - expect { TeamLeaderboard.new(team, metric: 'distance').aggregate! }.to_not raise_error - expect { TeamLeaderboard.new(team, metric: 'pR CouNT').aggregate! }.to_not raise_error + expect { TeamLeaderboard.new(team, metric: 'distance').aggregate! }.not_to raise_error + expect { TeamLeaderboard.new(team, metric: 'pR CouNT').aggregate! }.not_to raise_error end end + TeamLeaderboard::MEASURABLE_VALUES.each do |metric| context metric do let(:leaderboard) { TeamLeaderboard.new(team, metric: metric) } + it 'returns no activities by default' do expect(leaderboard.to_s).to eq "There are no activities with #{metric} in this channel." end @@ -44,16 +53,19 @@ let!(:user1_swim_activity_2) { Fabricate(:swim_activity, user: user1, team: team) } let!(:user2_activity_1) { Fabricate(:user_activity, user: user2, team: team) } let!(:another_activity) { Fabricate(:user_activity, user: Fabricate(:user, team: Fabricate(:team))) } + TeamLeaderboard::MEASURABLE_VALUES.each do |metric| context metric do let(:leaderboard) { TeamLeaderboard.new(team, metric: metric) } + it 'returns no activities by default' do - expect(leaderboard.to_s).to_not be_blank + expect(leaderboard.to_s).not_to be_blank end end end context 'distance leaderboard' do let(:leaderboard) { team.leaderboard(metric: 'Distance') } + it 'aggregate!' do expect(leaderboard.aggregate!.to_a).to eq( [ @@ -63,6 +75,7 @@ ] ) end + it 'to_s' do expect(leaderboard.to_s).to eq( [ @@ -73,8 +86,10 @@ ) end end + context 'count leaderboard' do let(:leaderboard) { team.leaderboard(metric: 'Count') } + it 'aggregate!' do expect(leaderboard.aggregate!.to_a).to eq( [ @@ -84,6 +99,7 @@ ] ) end + it 'to_s' do expect(leaderboard.to_s).to eq( [ @@ -94,6 +110,7 @@ ) end end + context 'channel leaderboard' do before do user1_activity_1.update_attributes!( @@ -110,8 +127,10 @@ ] }) end + context 'channel1' do let(:leaderboard) { team.leaderboard(metric: 'Distance', channel_id: 'channel1') } + it 'aggregate!' do expect(leaderboard.aggregate!.to_a).to eq( [ @@ -120,8 +139,10 @@ ) end end + context 'channel2' do let(:leaderboard) { team.leaderboard(metric: 'Distance', channel_id: 'channel2') } + it 'aggregate!' do expect(leaderboard.aggregate!.to_a).to eq( [ diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb index bcbfe22..0d20ba2 100644 --- a/spec/models/team_spec.rb +++ b/spec/models/team_spec.rb @@ -1,12 +1,13 @@ require 'spec_helper' describe Team do - context '#purge!' do + describe '#purge!' do let!(:active_team) { Fabricate(:team) } let!(:inactive_team) { Fabricate(:team, active: false) } let!(:inactive_team_one_week_ago) { Fabricate(:team, updated_at: 1.week.ago, active: false) } let!(:inactive_team_two_weeks_ago) { Fabricate(:team, updated_at: 2.weeks.ago, active: false) } let!(:inactive_team_a_month_ago) { Fabricate(:team, updated_at: 1.month.ago, active: false) } + it 'destroys teams inactive for two weeks' do expect { Team.purge! @@ -14,32 +15,37 @@ expect(Team.find(active_team.id)).to eq active_team expect(Team.find(inactive_team.id)).to eq inactive_team expect(Team.find(inactive_team_one_week_ago.id)).to eq inactive_team_one_week_ago - expect(Team.find(inactive_team_two_weeks_ago.id)).to be nil - expect(Team.find(inactive_team_a_month_ago.id)).to be nil + expect(Team.find(inactive_team_two_weeks_ago.id)).to be_nil + expect(Team.find(inactive_team_a_month_ago.id)).to be_nil end + context 'with a subscribed team' do before do inactive_team_a_month_ago.set(subscribed: true) end + it 'does not destroy team the subscribed team' do expect { Team.purge! }.to change(Team, :count).by(-1) - expect(Team.find(inactive_team_two_weeks_ago.id)).to be nil - expect(Team.find(inactive_team_a_month_ago.id)).to_not be nil + expect(Team.find(inactive_team_two_weeks_ago.id)).to be_nil + expect(Team.find(inactive_team_a_month_ago.id)).not_to be_nil end end end - context '#admins' do + + describe '#admins' do let!(:team) { Fabricate(:team) } let!(:activated_user) { Fabricate(:user, team: team) } let!(:random_user) { Fabricate(:user, team: team) } let!(:another_team_user) { Fabricate(:user, team: Fabricate(:team)) } let!(:admin_user) { Fabricate(:user, team: team, is_admin: true) } let!(:owner_user) { Fabricate(:user, team: team, is_owner: true) } + before do team.update_attributes!(activated_user_id: activated_user.user_id) end + it 'returns slack admins, owners and user that has installed the bot' do expect(team.admins.count).to eq 3 expect(team.admins.map(&:_id).sort).to eq [ @@ -49,108 +55,139 @@ ].map(&:_id).sort end end - context '#asleep?' do + + describe '#asleep?' do context 'default' do let(:team) { Fabricate(:team, created_at: Time.now.utc) } + it 'false' do expect(team.asleep?).to be false end end + context 'team created two weeks ago' do let(:team) { Fabricate(:team, created_at: 2.weeks.ago) } + it 'is asleep' do expect(team.asleep?).to be true end end + context 'team created two weeks ago and subscribed' do let(:team) { Fabricate(:team, created_at: 2.weeks.ago, subscribed: true) } + before do allow(team).to receive(:inform_subscribed_changed!) team.update_attributes!(subscribed: true) end + it 'is not asleep' do expect(team.asleep?).to be false end + it 'resets subscription_expired_at' do - expect(team.subscription_expired_at).to be nil + expect(team.subscription_expired_at).to be_nil end end + context 'team created over two weeks ago' do let(:team) { Fabricate(:team, created_at: 2.weeks.ago - 1.day) } + it 'is asleep' do expect(team.asleep?).to be true end end + context 'team created over two weeks ago and subscribed' do let(:team) { Fabricate(:team, created_at: 2.weeks.ago - 1.day, subscribed: true) } + it 'is not asleep' do expect(team.asleep?).to be false end end end - context '#subscription_expired!' do + + describe '#subscription_expired!' do let(:team) { Fabricate(:team, created_at: 2.weeks.ago) } + before do expect(team).to receive(:inform!).with(text: team.subscribe_text) expect(team).to receive(:inform_admin!).with(text: team.subscribe_text) team.subscription_expired! end + it 'sets subscription_expired_at' do - expect(team.subscription_expired_at).to_not be nil + expect(team.subscription_expired_at).not_to be_nil end + context '(re)subscribed' do before do expect(team).to receive(:inform!).with(text: team.subscribed_text) expect(team).to receive(:inform_admin!).with(text: team.subscribed_text) team.update_attributes!(subscribed: true) end + it 'resets subscription_expired_at' do - expect(team.subscription_expired_at).to be nil + expect(team.subscription_expired_at).to be_nil end end end - context '#inform!' do + + describe '#inform!' do let(:team) { Fabricate(:team) } + before do team.bot_user_id = 'bot_user_id' end + it 'sends message to all channels', vcr: { cassette_name: 'slack/users_conversations' } do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).exactly(25).times.and_return('ts' => '1503435956.000247') team.inform!(message: 'message') end end + context 'subscribed states' do let(:today) { DateTime.parse('2018/7/15 12:42pm') } let(:subscribed_team) { Fabricate(:team, subscribed: true) } let(:team_created_today) { Fabricate(:team, created_at: today) } let(:team_created_1_week_ago) { Fabricate(:team, created_at: (today - 1.week)) } let(:team_created_3_weeks_ago) { Fabricate(:team, created_at: (today - 3.weeks)) } + before do Timecop.travel(today + 1.day) end + + after do + Timecop.return + end + it 'subscription_expired?' do expect(subscribed_team.subscription_expired?).to be false expect(team_created_1_week_ago.subscription_expired?).to be false expect(team_created_3_weeks_ago.subscription_expired?).to be true end + it 'trial_ends_at' do expect { subscribed_team.trial_ends_at }.to raise_error 'Team is subscribed.' expect(team_created_today.trial_ends_at).to eq team_created_today.created_at + 2.weeks expect(team_created_1_week_ago.trial_ends_at).to eq team_created_1_week_ago.created_at + 2.weeks expect(team_created_3_weeks_ago.trial_ends_at).to eq team_created_3_weeks_ago.created_at + 2.weeks end + it 'remaining_trial_days' do expect { subscribed_team.remaining_trial_days }.to raise_error 'Team is subscribed.' expect(team_created_today.remaining_trial_days).to eq 13 expect(team_created_1_week_ago.remaining_trial_days).to eq 6 expect(team_created_3_weeks_ago.remaining_trial_days).to eq 0 end - context '#inform_trial!' do + + describe '#inform_trial!' do it 'subscribed' do - expect(subscribed_team).to_not receive(:inform!) - expect(subscribed_team).to_not receive(:inform_admin!) + expect(subscribed_team).not_to receive(:inform!) + expect(subscribed_team).not_to receive(:inform_admin!) subscribed_team.inform_trial! end + it '1 week ago' do expect(team_created_1_week_ago).to receive(:inform!).with( text: "Your trial subscription expires in 6 days. #{team_created_1_week_ago.subscribe_text}" @@ -160,25 +197,26 @@ ) team_created_1_week_ago.inform_trial! end + it 'expired' do - expect(team_created_3_weeks_ago).to_not receive(:inform!) - expect(team_created_3_weeks_ago).to_not receive(:inform_admin!) + expect(team_created_3_weeks_ago).not_to receive(:inform!) + expect(team_created_3_weeks_ago).not_to receive(:inform_admin!) team_created_3_weeks_ago.inform_trial! end + it 'informs once' do expect(team_created_1_week_ago).to receive(:inform!).once expect(team_created_1_week_ago).to receive(:inform_admin!).once 2.times { team_created_1_week_ago.inform_trial! } end end - after do - Timecop.return - end end - context '#destroy' do + + describe '#destroy' do let!(:team) { Fabricate(:team) } let!(:user1) { Fabricate(:user, team: team) } let!(:user2) { Fabricate(:user, team: team, access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'revokes access tokens' do allow(team).to receive(:users).and_return([user1, user2]) expect(user1).to receive(:revoke_access_token!) diff --git a/spec/models/team_stats_spec.rb b/spec/models/team_stats_spec.rb index bb118ce..faad835 100644 --- a/spec/models/team_stats_spec.rb +++ b/spec/models/team_stats_spec.rb @@ -2,22 +2,27 @@ describe TeamStats do let(:team) { Fabricate(:team) } + before do allow_any_instance_of(Map).to receive(:update_png!) end + context 'with no activities' do let(:stats) { team.stats } - context '#stats' do + + describe '#stats' do it 'aggregates stats' do expect(stats.count).to eq 0 end end - context '#to_slack' do + + describe '#to_slack' do it 'defaults to no activities' do expect(stats.to_slack).to eq({ text: 'There are no activities in this channel.' }) end end end + context 'with activities' do let!(:user1) { Fabricate(:user, team: team) } let!(:user2) { Fabricate(:user, team: team) } @@ -29,12 +34,15 @@ let!(:activity1) { Fabricate(:user_activity, user: user1) } let!(:activity2) { Fabricate(:user_activity, user: user1) } let!(:activity3) { Fabricate(:user_activity, user: user2) } - context '#stats' do + + describe '#stats' do let(:stats) { team.stats } + it 'returns stats sorted by count' do expect(stats.keys).to eq %w[Run Ride Swim] expect(stats.values.map(&:count)).to eq [4, 2, 1] end + it 'aggregates stats' do expect(stats['Ride'].to_h).to eq( { @@ -67,21 +75,26 @@ } ) end + context 'with activities from another team' do let!(:another_activity) { Fabricate(:user_activity, user: user1) } let!(:another_team_activity) { Fabricate(:user_activity, user: Fabricate(:user, team: Fabricate(:team))) } + it 'does not include that activity' do expect(stats.values.map(&:count)).to eq [5, 2, 1] end end end - context '#to_slack' do + + describe '#to_slack' do let(:stats) { team.stats } + it 'includes all activities' do expect(stats.to_slack[:attachments].count).to eq(3) end end end + context 'with activities across multiple channels' do let!(:user) { Fabricate(:user, team: team) } let!(:user_activity) { Fabricate(:user_activity, user: user) } @@ -89,10 +102,12 @@ let!(:club1_activity) { Fabricate(:club_activity, club: club1) } let!(:club2) { Fabricate(:club, team: team, channel_id: 'channel2') } let!(:club2_activity) { Fabricate(:club_activity, club: club2) } - context '#stats' do + + describe '#stats' do context 'all channels' do let!(:stats) { team.stats } let!(:activities) { [user_activity, club1_activity, club2_activity] } + it 'returns stats for all activities' do expect(stats['Run'].to_h).to eq( { @@ -106,14 +121,17 @@ ) end end + context 'in channel with bragged club activity' do before do allow_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).and_return(ts: 'ts') club1_activity.brag! end + context 'stats' do let(:stats) { team.stats(channel_id: club1.channel_id) } let(:activities) { [club1_activity] } + it 'returns stats for all activities' do expect(stats['Run'].to_h).to eq( { @@ -128,6 +146,7 @@ end end end + context 'in channel with bragged user activity' do before do allow_any_instance_of(Team).to receive(:slack_channels).and_return(['id' => club1_activity.club.channel_id]) @@ -136,9 +155,11 @@ allow_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).and_return(ts: 'ts') user_activity.brag! end + context 'stats' do let(:stats) { team.stats(channel_id: club1_activity.club.channel_id) } let(:activities) { [user_activity] } + it 'returns stats for all activities' do expect(stats['Run'].to_h).to eq( { @@ -153,6 +174,7 @@ end end end + context 'in channel with bragged user and club activities' do before do allow_any_instance_of(Team).to receive(:slack_channels).and_return(['id' => club1_activity.club.channel_id]) @@ -162,9 +184,11 @@ club1_activity.brag! user_activity.brag! end + context 'stats' do let(:stats) { team.stats(channel_id: club1.channel_id) } let(:activities) { [user_activity, club1_activity] } + it 'returns stats for all activities' do expect(stats['Run'].to_h).to eq( { diff --git a/spec/models/user_activity_spec.rb b/spec/models/user_activity_spec.rb index c7211e7..25be5d1 100644 --- a/spec/models/user_activity_spec.rb +++ b/spec/models/user_activity_spec.rb @@ -4,74 +4,96 @@ before do allow(HTTParty).to receive_message_chain(:get, :body).and_return('PNG') end + context 'hidden?' do context 'default' do let(:activity) { Fabricate(:user_activity) } + it 'is not hidden' do expect(activity.hidden?).to be false end end + context 'private' do context 'private and user is private' do let(:user) { Fabricate(:user, private_activities: false) } let(:activity) { Fabricate(:user_activity, user: user, private: true) } + it 'is hidden' do expect(activity.hidden?).to be true end end + context 'private but user is public' do let(:user) { Fabricate(:user, private_activities: true) } let(:activity) { Fabricate(:user_activity, user: user, private: true) } + it 'is not hidden' do expect(activity.hidden?).to be false end end + context 'public but user is private' do let(:user) { Fabricate(:user, private_activities: false) } let(:activity) { Fabricate(:user_activity, user: user, private: false) } + it 'is hidden' do expect(activity.hidden?).to be false end end end + context 'visibility' do context 'user has not set followers_only_activities' do let(:user) { Fabricate(:user, followers_only_activities: false) } + context 'only_me' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'only_me') } + it 'is hidden' do expect(activity.hidden?).to be true end end + context 'followers_only' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'followers_only') } + it 'is hidden' do expect(activity.hidden?).to be true end end + context 'everyone' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'everyone') } + it 'is not hidden' do expect(activity.hidden?).to be false end end end + context 'user has set followers_only_activities' do let(:user) { Fabricate(:user, followers_only_activities: true) } + context 'only_me' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'only_me') } + it 'is hidden' do expect(activity.hidden?).to be true end end + context 'followers_only' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'followers_only') } + it 'is not hidden' do expect(activity.hidden?).to be false end end + context 'everyone' do let(:activity) { Fabricate(:user_activity, user: user, visibility: 'everyone') } + it 'is not hidden' do expect(activity.hidden?).to be false end @@ -79,16 +101,19 @@ end end end + context 'brag!' do let(:team) { Fabricate(:team) } let(:user) { Fabricate(:user, team: team) } let!(:activity) { Fabricate(:user_activity, user: user) } + before do allow_any_instance_of(Team).to receive(:slack_channels).and_return(['id' => 'channel_id']) allow_any_instance_of(User).to receive(:user_deleted?).and_return(false) allow_any_instance_of(User).to receive(:user_in_channel?).and_return(true) allow_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).and_return('ts' => '1503435956.000247') end + it 'sends a message to the subscribed channel' do expect(user.team.slack_client).to receive(:chat_postMessage).with( activity.to_slack.merge( @@ -98,6 +123,7 @@ ).and_return('ts' => 1) expect(activity.brag!).to eq([ts: 1, channel: 'channel_id']) end + it 'warns if the bot leaves the channel' do expect { expect_any_instance_of(Logger).to receive(:warn).with(/not_in_channel/) @@ -105,8 +131,9 @@ raise Slack::Web::Api::Errors::SlackError, 'not_in_channel' } expect(activity.brag!).to eq [] - }.to_not change(User, :count) + }.not_to change(User, :count) end + it 'warns if the account goes inactive' do expect { expect { @@ -115,9 +142,10 @@ raise Slack::Web::Api::Errors::SlackError, 'account_inactive' } expect(activity.brag!).to eq [] - }.to_not change(User, :count) - }.to_not change(UserActivity, :count) + }.not_to change(User, :count) + }.not_to change(UserActivity, :count) end + it 'informs user on restricted_action' do expect { expect(user).to receive(:dm!).with(text: "I wasn't allowed to post into <#channel_id> because of a Slack workspace preference, please contact your Slack admin.") @@ -126,13 +154,15 @@ raise Slack::Web::Api::Errors::SlackError, 'restricted_action' } expect(activity.brag!).to eq [] - }.to_not change(User, :count) + }.not_to change(User, :count) end end + context 'unbrag!' do let(:team) { Fabricate(:team) } let(:user) { Fabricate(:user, team: team) } let!(:activity) { Fabricate(:user_activity, user: user) } + before do activity.update_attributes!( bragged_at: Time.now.utc, @@ -142,6 +172,7 @@ ] ) end + it 'deletes message' do expect(activity.user.team.slack_client).to receive(:chat_delete).with( channel: 'channel1', @@ -157,10 +188,12 @@ expect(activity.reload.channel_messages).to eq [] end end + context 'miles' do let(:team) { Fabricate(:team, units: 'mi') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -187,10 +220,12 @@ ] ) end + context 'with all fields' do before do team.activity_fields = ['All'] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -223,10 +258,12 @@ ) end end + context 'with none fields' do before do team.activity_fields = ['None'] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -244,10 +281,12 @@ ) end end + context 'with all header fields' do before do team.activity_fields = %w[Title Url User Description Date Athlete] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -265,10 +304,12 @@ ) end end + context 'without athlete' do before do team.activity_fields = %w[Title Url User Description Date] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -283,10 +324,12 @@ ) end end + context 'without user' do before do team.activity_fields = %w[Title Url Description Date] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -301,10 +344,12 @@ ) end end + context 'without description' do before do team.activity_fields = %w[Title Url User Date] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -319,10 +364,12 @@ ) end end + context 'without date' do before do team.activity_fields = %w[Title Url Description] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -337,10 +384,12 @@ ) end end + context 'without url' do before do team.activity_fields = %w[Title] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -353,10 +402,12 @@ ) end end + context 'without title' do before do team.activity_fields = %w[Url] end + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -370,10 +421,12 @@ ) end end + context 'without an athlete' do before do user.athlete.destroy end + it 'to_slack' do expect(activity.reload.to_slack).to eq( attachments: [ @@ -398,10 +451,12 @@ ) end end + context 'with a zero speed' do before do activity.update_attributes!(average_speed: 0.0) end + it 'to_slack' do expect(activity.reload.to_slack).to eq( attachments: [ @@ -428,10 +483,12 @@ end end end + context 'km' do let(:team) { Fabricate(:team, units: 'km') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -459,10 +516,12 @@ ) end end + context 'both' do let(:team) { Fabricate(:team, units: 'both') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -490,10 +549,12 @@ ) end end + context 'swim activity in yards' do let(:team) { Fabricate(:team) } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:swim_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -517,10 +578,12 @@ ) end end + context 'swim activity in meters' do let(:team) { Fabricate(:team, units: 'km') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:swim_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -544,10 +607,12 @@ ) end end + context 'swim activity in both' do let(:team) { Fabricate(:team, units: 'both') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:swim_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -571,10 +636,12 @@ ) end end + context 'ride activities in kilometers/hour' do let(:team) { Fabricate(:team, units: 'km') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:ride_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -599,10 +666,12 @@ ) end end + context 'ride activities in both' do let(:team) { Fabricate(:team, units: 'both') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:ride_activity, user: user) } + it 'to_slack' do expect(activity.to_slack).to eq( attachments: [ @@ -627,43 +696,52 @@ ) end end + context 'map' do context 'with a summary polyline' do let(:activity) { Fabricate(:user_activity) } + it 'start_latlng' do expect(activity.start_latlng).to eq([37.82822, -122.26348]) end end + context 'with a blank summary polyline' do let(:map) { Fabricate.build(:map, summary_polyline: '') } let(:activity) { Fabricate(:user_activity, map: map) } + it 'start_latlng' do - expect(activity.start_latlng).to be nil + expect(activity.start_latlng).to be_nil end end end + context 'maps' do context 'without maps' do let(:team) { Fabricate(:team, maps: 'off') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } let(:attachment) { activity.to_slack[:attachments].first } + it 'to_slack' do - expect(attachment.keys).to_not include :image_url - expect(attachment.keys).to_not include :thumb_url + expect(attachment.keys).not_to include :image_url + expect(attachment.keys).not_to include :thumb_url end end + context 'with thumbnail' do let(:team) { Fabricate(:team, maps: 'thumb') } let(:user) { Fabricate(:user, team: team) } let(:activity) { Fabricate(:user_activity, user: user) } let(:attachment) { activity.to_slack[:attachments].first } + it 'to_slack' do - expect(attachment.keys).to_not include :image_url + expect(attachment.keys).not_to include :image_url expect(attachment[:thumb_url]).to eq "https://slava.playplay.io/api/maps/#{activity.map.id}.png" end end end + describe 'create_from_strava!' do let(:user) { Fabricate(:user) } let(:detailed_activity) do @@ -675,13 +753,16 @@ ) ) end + it 'creates an activity' do expect { UserActivity.create_from_strava!(user, detailed_activity) }.to change(UserActivity, :count).by(1) end + context 'with another existing activity' do let!(:activity) { Fabricate(:user_activity, user: user) } + it 'creates another activity' do expect { UserActivity.create_from_strava!(user, detailed_activity) @@ -689,22 +770,27 @@ expect(user.reload.activities.count).to eq 2 end end + context 'with an existing activity' do let!(:activity) { UserActivity.create_from_strava!(user, detailed_activity) } + it 'does not create another activity' do expect { UserActivity.create_from_strava!(user, detailed_activity) - }.to_not change(UserActivity, :count) + }.not_to change(UserActivity, :count) end + it 'does not cause a save without changes' do - expect_any_instance_of(UserActivity).to_not receive(:save!) + expect_any_instance_of(UserActivity).not_to receive(:save!) UserActivity.create_from_strava!(user, detailed_activity) end + it 'updates an existing activity' do activity.update_attributes!(name: 'Original') UserActivity.create_from_strava!(user, detailed_activity) expect(activity.reload.name).to eq 'First Time Breaking 14' end + context 'concurrently' do before do expect(UserActivity).to receive(:where).with( @@ -712,12 +798,13 @@ ).and_return([]) allow(UserActivity).to receive(:where).and_call_original end + it 'does not create a duplicate activity' do expect { expect { UserActivity.create_from_strava!(user, detailed_activity) }.to raise_error(Mongo::Error::OperationFailure) - }.to_not change(UserActivity, :count) + }.not_to change(UserActivity, :count) end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index cf80551..06706ff 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4,44 +4,55 @@ before do allow_any_instance_of(Map).to receive(:update_png!) end - context '#find_by_slack_mention!' do + + describe '#find_by_slack_mention!' do let!(:user) { Fabricate(:user) } + it 'finds by slack id' do expect(User.find_by_slack_mention!(user.team, "<@#{user.user_id}>")).to eq user end + it 'finds by username' do expect(User.find_by_slack_mention!(user.team, user.user_name)).to eq user end + it 'finds by username is case-insensitive' do expect(User.find_by_slack_mention!(user.team, user.user_name.capitalize)).to eq user end + it 'requires a known user' do expect { User.find_by_slack_mention!(user.team, '<@nobody>') }.to raise_error SlackStrava::Error, "I don't know who <@nobody> is!" end end - context '#find_create_or_update_by_slack_id!', vcr: { cassette_name: 'slack/user_info' } do + + describe '#find_create_or_update_by_slack_id!', vcr: { cassette_name: 'slack/user_info' } do let!(:team) { Fabricate(:team) } let(:client) { SlackRubyBot::Client.new } + before do client.owner = team end + context 'with a mismatching user id in slack_mention' do let!(:user) { Fabricate(:user, team: team) } let(:web_client) { double(Slack::Web::Client, users_info: { user: { id: user.user_id, name: user.user_name } }) } + before do allow(client).to receive(:web_client).and_return(web_client) end + it 'finds by different slack id returned from slack info' do expect(User.find_create_or_update_by_slack_id!(client, 'unknown')).to eq user end end + context 'without a user' do it 'creates a user' do expect { user = User.find_create_or_update_by_slack_id!(client, 'whatever') - expect(user).to_not be_nil + expect(user).not_to be_nil expect(user.user_id).to eq 'U007' expect(user.user_name).to eq 'username' expect(user.is_admin).to be true @@ -50,12 +61,14 @@ }.to change(User, :count).by(1) end end + context 'with a user with info matching an existing user id' do let!(:user) { Fabricate(:user, team: team, is_admin: true, user_id: 'U007') } + it 'updates the fields of the existing user' do expect { User.find_create_or_update_by_slack_id!(client, 'whatever') - }.to_not change(User, :count) + }.not_to change(User, :count) user.reload expect(user.user_id).to eq 'U007' expect(user.is_admin).to be true @@ -63,26 +76,31 @@ expect(user.is_owner).to be true end end + context 'with a user' do let!(:user) { Fabricate(:user, team: team) } + it 'creates another user' do expect { User.find_create_or_update_by_slack_id!(client, 'whatever') }.to change(User, :count).by(1) end + it 'updates the username of the existing user' do expect { User.find_create_or_update_by_slack_id!(client, user.user_id) - }.to_not change(User, :count) + }.not_to change(User, :count) expect(user.reload.user_name).to eq 'username' end end + context 'with a user that matches most fields coming from slack' do let!(:user) { Fabricate(:user, team: team, is_admin: true, user_name: 'username') } + it 'updates the fields of the existing user' do expect { User.find_create_or_update_by_slack_id!(client, user.user_id) - }.to_not change(User, :count) + }.not_to change(User, :count) user.reload expect(user.user_name).to eq 'username' expect(user.is_admin).to be true @@ -91,14 +109,17 @@ end end end + context 'sync_new_strava_activities!' do context 'recent created_at', vcr: { cassette_name: 'strava/user_sync_new_strava_activities', allow_playback_repeats: true } do let!(:user) { Fabricate(:user, created_at: DateTime.new(2018, 3, 26), access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'retrieves new activities since created_at' do expect { user.sync_new_strava_activities! }.to change(user.activities, :count).by(3) end + it 'logs and raises errors' do allow(UserActivity).to receive(:create_from_strava!) do |_user, _response| raise Strava::Errors::Fault.new(404, body: { 'message' => 'Not Found', 'errors' => [{ 'resource' => 'Activity', 'field' => 'id', 'code' => 'not_found' }] }) @@ -107,17 +128,21 @@ user.sync_new_strava_activities! }.to raise_error(Strava::Errors::Fault) end + it 'sets activities_at to nil without any bragged activity' do user.sync_new_strava_activities! - expect(user.activities_at).to be nil + expect(user.activities_at).to be_nil end + context 'with unbragged activities' do let!(:activity) { Fabricate(:user_activity, user: user, start_date: DateTime.new(2018, 4, 1)) } + it 'syncs activities since the first one' do expect(user).to receive(:sync_strava_activities!).with(after: activity.start_date.to_i) user.sync_new_strava_activities! end end + context 'with activities in user_sync_new_strava_activities.yml across more than 5 days' do let(:weather) { Fabricate(:one_call_weather) } let(:activities) do @@ -127,6 +152,7 @@ april_04: { dt: Time.parse('2018-04-01T16:58:34Z'), lat: 40.78247, lon: -73.96003 } } end + before do # end of first activity in user_sync_new_strava_activities.yml, with two more a few days ago Timecop.travel(activities[:april_04][:dt] + 4.hours) @@ -140,25 +166,29 @@ ).and_return(weather) # march 26, more than 5 days old, too old end + + after do + Timecop.return + end + it 'fetches weather for all activities' do expect { user.sync_new_strava_activities! }.to change(user.activities, :count).by(3) - expect(user.activities[0].weather).to be nil - expect(user.activities[1].weather).to_not be nil + expect(user.activities[0].weather).to be_nil + expect(user.activities[1].weather).not_to be_nil expect(user.activities[1].weather.temp).to eq 294.31 - expect(user.activities[2].weather).to_not be nil + expect(user.activities[2].weather).not_to be_nil expect(user.activities[2].weather.temp).to eq 294.31 end - after do - Timecop.return - end end + context 'sync_and_brag!' do it 'syncs and brags' do expect_any_instance_of(User).to receive(:inform!) user.sync_and_brag! end + it 'uses a lock' do user_instance_2 = User.find(user._id) bragged_activities = [] @@ -171,83 +201,99 @@ user_instance_2.sync_and_brag! expect(bragged_activities).to eq(['Restarting the Engine', 'First Time Breaking 14']) end + it 'warns on error' do expect_any_instance_of(Logger).to receive(:warn).with(/unexpected error/) allow(user).to receive(:sync_new_strava_activities!).and_raise 'unexpected error' - expect { user.sync_and_brag! }.to_not raise_error + expect { user.sync_and_brag! }.not_to raise_error end + context 'rate limit exceeded' do let(:rate_limit_exceeded_error) { Strava::Errors::Fault.new(429, body: { 'message' => 'Rate Limit Exceeded', 'errors' => [{ 'resource' => 'Application', 'field' => 'rate limit', 'code' => 'exceeded' }] }) } + it 'raises an exception' do allow(user).to receive(:sync_new_strava_activities!).and_raise rate_limit_exceeded_error expect { user.sync_and_brag! }.to raise_error(Strava::Errors::Fault, /Rate Limit Exceeded/) end end + context 'refresh token' do let(:authorization_error) { Strava::Errors::Fault.new(400, body: { 'message' => 'Bad Request', 'errors' => [{ 'resource' => 'RefreshToken', 'field' => 'refresh_token', 'code' => 'invalid' }] }) } + it 'raises an exception and resets token' do allow(user.strava_client).to receive(:paginate).and_raise authorization_error expect(user).to receive(:dm_connect!).with('There was a re-authorization problem with Strava. Make sure that you leave the "View data about your private activities" box checked when reconnecting your Strava account') user.sync_and_brag! - expect(user.access_token).to be nil - expect(user.token_type).to be nil - expect(user.refresh_token).to be nil - expect(user.token_expires_at).to be nil - expect(user.connected_to_strava_at).to be nil + expect(user.access_token).to be_nil + expect(user.token_type).to be_nil + expect(user.refresh_token).to be_nil + expect(user.token_expires_at).to be_nil + expect(user.connected_to_strava_at).to be_nil end end + context 'invalid token' do let(:authorization_error) { Strava::Errors::Fault.new(401, body: { 'message' => 'Authorization Error', 'errors' => [{ 'resource' => 'Athlete', 'field' => 'access_token', 'code' => 'invalid' }] }) } + it 'raises an exception and resets token' do allow(user.strava_client).to receive(:paginate).and_raise authorization_error expect(user).to receive(:dm_connect!).with('There was an authorization problem with Strava. Make sure that you leave the "View data about your private activities" box checked when reconnecting your Strava account') user.sync_and_brag! - expect(user.access_token).to be nil - expect(user.token_type).to be nil - expect(user.refresh_token).to be nil - expect(user.token_expires_at).to be nil - expect(user.connected_to_strava_at).to be nil + expect(user.access_token).to be_nil + expect(user.token_type).to be_nil + expect(user.refresh_token).to be_nil + expect(user.token_expires_at).to be_nil + expect(user.connected_to_strava_at).to be_nil end end + context 'read:permission authorization error' do let(:authorization_error) { Strava::Errors::Fault.new(401, body: { 'message' => 'Authorization Error', 'errors' => [{ 'resource' => 'AccessToken', 'field' => 'activity:read_permission', 'code' => 'missing' }] }) } + it 'raises an exception and resets token' do allow(user.strava_client).to receive(:paginate).and_raise authorization_error expect(user).to receive(:dm_connect!).with('There was an authorization problem with Strava. Make sure that you leave the "View data about your private activities" box checked when reconnecting your Strava account') user.sync_and_brag! - expect(user.access_token).to be nil - expect(user.token_type).to be nil - expect(user.refresh_token).to be nil - expect(user.token_expires_at).to be nil - expect(user.connected_to_strava_at).to be nil + expect(user.access_token).to be_nil + expect(user.token_type).to be_nil + expect(user.refresh_token).to be_nil + expect(user.token_expires_at).to be_nil + expect(user.connected_to_strava_at).to be_nil end end end + context 'with bragged activities' do before do user.sync_new_strava_activities! allow_any_instance_of(User).to receive(:inform!).and_return([{ ts: 'ts', channel: 'C1' }]) user.brag! end + it 'does not reset activities_at back if the most recent bragged activity is in the past' do - expect(user.activities_at).to_not be nil + expect(user.activities_at).not_to be_nil past = Time.parse('2012-01-01T12:34Z') Fabricate(:user_activity, user: user, start_date: past) user.brag! - expect(user.activities_at).to_not eq past + expect(user.activities_at).not_to eq past end + it 'sets activities_at to the most recent bragged activity' do expect(user.activities_at).to eq user.activities.bragged.max(:start_date) end + it 'updates activities since activities_at' do expect(user).to receive(:sync_strava_activities!).with(after: user.activities_at.to_i) user.sync_new_strava_activities! end + context 'latest activity' do let(:last_activity) { user.activities.bragged.desc(:_id).first } + before do allow(user).to receive(:latest_bragged_activity).and_return(last_activity) end + it 'retrieves last activity details and rebrags it with udpated description' do updated_last_activity = last_activity.to_slack updated_last_activity[:attachments].first[:text] = "<@#{user.user_name}> on #{last_activity.start_date_local_s}\n\ndetailed description" @@ -257,55 +303,66 @@ ) user.rebrag! end + it 'does not rebrag if the activity has not changed' do expect_any_instance_of(User).to receive(:update!).once 2.times { user.rebrag! } end end end + context 'without a refresh token (until October 2019)', vcr: { cassette_name: 'strava/refresh_access_token' } do before do user.update_attributes!(refresh_token: nil, token_expires_at: nil) end + it 'refreshes access token using access token' do user.send(:strava_client) expect(user.refresh_token).to eq 'updated-refresh-token' expect(user.access_token).to eq 'updated-access-token' - expect(user.token_expires_at).to_not be_nil + expect(user.token_expires_at).not_to be_nil expect(user.token_type).to eq 'Bearer' end end + context 'with an expired refresh token', vcr: { cassette_name: 'strava/refresh_access_token' } do before do user.update_attributes!(refresh_token: 'refresh_token', token_expires_at: nil) end + it 'refreshes access token' do user.send(:strava_client) expect(user.refresh_token).to eq 'updated-refresh-token' expect(user.access_token).to eq 'updated-access-token' - expect(user.token_expires_at).to_not be_nil + expect(user.token_expires_at).not_to be_nil expect(user.token_type).to eq 'Bearer' end end end + context 'old created_at' do let!(:user) { Fabricate(:user, created_at: DateTime.new(2018, 2, 1), access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'retrieves multiple pages of activities', vcr: { cassette_name: 'strava/user_sync_new_strava_activities_many' } do expect { user.sync_new_strava_activities! }.to change(user.activities, :count).by(14) end end + context 'different connected_to_strava_at includes 8 hours of prior activities' do let!(:user) { Fabricate(:user, connected_to_strava_at: DateTime.new(2018, 2, 1) + 8.hours, access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'retrieves multiple pages of activities', vcr: { cassette_name: 'strava/user_sync_new_strava_activities_many' } do expect { user.sync_new_strava_activities! }.to change(user.activities, :count).by(14) end end + context 'with private activities', vcr: { cassette_name: 'strava/user_sync_new_strava_activities_with_private' } do let!(:user) { Fabricate(:user, created_at: DateTime.new(2018, 3, 26), access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + context 'by default' do it 'includes private activities' do expect { @@ -313,17 +370,20 @@ }.to change(user.activities, :count).by(4) expect(user.activities.select(&:private).count).to eq 2 end + it 'does not brag private activities' do user.sync_new_strava_activities! allow_any_instance_of(UserActivity).to receive(:user).and_return(user) - expect(user).to receive(:inform!).exactly(2).times + expect(user).to receive(:inform!).twice 5.times { user.brag! } end end + context 'with private_activities set to true' do before do user.update_attributes!(private_activities: true) end + it 'brags private activities' do user.sync_new_strava_activities! allow_any_instance_of(UserActivity).to receive(:user).and_return(user) @@ -332,8 +392,10 @@ end end end + context 'with follower only activities', vcr: { cassette_name: 'strava/user_sync_new_strava_activities_privacy' } do let!(:user) { Fabricate(:user, created_at: DateTime.new(2018, 3, 26), access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + context 'by default' do it 'includes followers only activities' do expect { @@ -342,47 +404,56 @@ expect(user.activities.select(&:private).count).to eq 1 expect(user.activities.map(&:visibility)).to eq %w[everyone only_me followers_only] end + it 'brags follower only activities' do user.sync_new_strava_activities! allow_any_instance_of(UserActivity).to receive(:user).and_return(user) - expect(user).to receive(:inform!).exactly(2).times + expect(user).to receive(:inform!).twice 3.times { user.brag! } end end + context 'with followers_only_activities set to false' do before do user.update_attributes!(followers_only_activities: false) end + it 'does not brag follower only activities' do user.sync_new_strava_activities! allow_any_instance_of(UserActivity).to receive(:user).and_return(user) - expect(user).to receive(:inform!).exactly(1).times + expect(user).to receive(:inform!).once 3.times { user.brag! } end end + context 'with private set to false' do before do user.update_attributes!(private_activities: false) end + it 'brags follower only activities' do user.sync_new_strava_activities! allow_any_instance_of(UserActivity).to receive(:user).and_return(user) - expect(user).to receive(:inform!).exactly(2).times + expect(user).to receive(:inform!).twice 3.times { user.brag! } end end end + context 'with sync_activities set to false' do let!(:user) { Fabricate(:user, connected_to_strava_at: DateTime.new(2018, 2, 1), access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer', sync_activities: false) } + it 'does not retrieve any activities' do expect { user.sync_new_strava_activities! - }.to_not change(user.activities, :count) + }.not_to change(user.activities, :count) end end end + context 'brag!' do let!(:user) { Fabricate(:user) } + it 'brags the last unbragged activity' do activity = Fabricate(:user_activity, user: user) expect_any_instance_of(UserActivity).to receive(:brag!).and_return( @@ -401,11 +472,14 @@ expect(results.first[:activity]).to eq activity end end - context '#inform!' do + + describe '#inform!' do let(:user) { Fabricate(:user, user_id: 'U0HLFUZLJ') } + before do user.team.bot_user_id = 'bot_user_id' end + it 'sends message to all channels a user is a member of', vcr: { cassette_name: 'slack/users_conversations_conversations_members' } do expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with( message: 'message', @@ -414,18 +488,22 @@ ).and_return(ts: '1503425956.000247') expect(user.inform!(message: 'message').count).to eq(1) end + it 'does not send messages for a deleted user', vcr: { cassette_name: 'slack/users_info_deleted' } do - expect_any_instance_of(Slack::Web::Client).to_not receive(:chat_postMessage) - expect(user.inform!(message: 'message')).to be nil + expect_any_instance_of(Slack::Web::Client).not_to receive(:chat_postMessage) + expect(user.inform!(message: 'message')).to be_nil end + it 'handles user not found', vcr: { cassette_name: 'slack/users_info_not_found' } do - expect_any_instance_of(Slack::Web::Client).to_not receive(:chat_postMessage) - expect(user.inform!(message: 'message')).to be nil + expect_any_instance_of(Slack::Web::Client).not_to receive(:chat_postMessage) + expect(user.inform!(message: 'message')).to be_nil end end - context '#dm_connect!' do + + describe '#dm_connect!' do let(:user) { Fabricate(:user) } let(:url) { "https://www.strava.com/oauth/authorize?client_id=client-id&redirect_uri=https://slava.playplay.io/connect&response_type=code&scope=activity:read_all&state=#{user.id}" } + it 'uses the default message' do expect(user).to receive(:dm!).with( text: 'Please connect your Strava account.', @@ -440,6 +518,7 @@ ) user.dm_connect! end + it 'uses a custom message' do expect(user).to receive(:dm!).with( text: 'Please reconnect your account.', @@ -455,8 +534,10 @@ user.dm_connect!('Please reconnect your account') end end + context 'sync_strava_activity!', vcr: { cassette_name: 'strava/user_sync_new_strava_activities' } do let!(:user) { Fabricate(:user, access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + context 'with a mismatched athlete ID' do it 'raises an exception' do expect { @@ -464,10 +545,12 @@ }.to raise_error(/Activity athlete ID 26462176 does not match/) end end + context 'with a matching athlete ID' do before do user.athlete.athlete_id = '26462176' end + it 'fetches an activity' do expect { user.sync_strava_activity!('1473024961') @@ -476,19 +559,24 @@ end end end + context 'sync_activity_and_brag!' do let!(:user) { Fabricate(:user) } let(:activity_id) { '1473024961' } + it 'syncs an activity and brags' do expect_any_instance_of(User).to receive(:sync_strava_activity!).with(activity_id) expect_any_instance_of(User).to receive(:brag!) user.sync_activity_and_brag!(activity_id) end + pending 'takes a lock' end - context '#rebrag_activity!', vcr: { cassette_name: 'strava/user_sync_new_strava_activities' } do + + describe '#rebrag_activity!', vcr: { cassette_name: 'strava/user_sync_new_strava_activities' } do let!(:user) { Fabricate(:user, access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } let!(:activity) { Fabricate(:user_activity, user: user, team: user.team, strava_id: '1473024961') } + context 'a previously bragged activity' do before do activity.update_attributes!( @@ -496,30 +584,36 @@ channel_messages: [ChannelMessage.new(channel: 'channel1')] ) end + it 'rebrags' do - expect_any_instance_of(UserActivity).to_not receive(:brag!) + expect_any_instance_of(UserActivity).not_to receive(:brag!) expect_any_instance_of(UserActivity).to receive(:rebrag!) user.rebrag_activity!(activity) end end + context 'a new activity' do it 'does not rebrag' do - expect_any_instance_of(UserActivity).to_not receive(:brag!) - expect_any_instance_of(UserActivity).to_not receive(:rebrag!) + expect_any_instance_of(UserActivity).not_to receive(:brag!) + expect_any_instance_of(UserActivity).not_to receive(:rebrag!) user.rebrag_activity!(activity) end end end - context '#destroy' do + + describe '#destroy' do context 'without an access token' do let!(:user) { Fabricate(:user) } + it 'revokes access token' do - expect_any_instance_of(Strava::Api::Client).to_not receive(:deauthorize) + expect_any_instance_of(Strava::Api::Client).not_to receive(:deauthorize) user.destroy end end + context 'with an access token' do let!(:user) { Fabricate(:user, access_token: 'token', token_expires_at: Time.now + 1.day, token_type: 'Bearer') } + it 'revokes access token' do expect(user.strava_client).to receive(:deauthorize) .with(access_token: user.access_token) diff --git a/spec/slack-strava/app_spec.rb b/spec/slack-strava/app_spec.rb index 19c3fae..91dcaea 100644 --- a/spec/slack-strava/app_spec.rb +++ b/spec/slack-strava/app_spec.rb @@ -4,23 +4,27 @@ subject do SlackStrava::App.instance end - context '#instance' do + + describe '#instance' do it 'is an instance of the strava app' do - expect(subject).to be_a_kind_of(SlackRubyBotServer::App) + expect(subject).to be_a(SlackRubyBotServer::App) expect(subject).to be_an_instance_of(SlackStrava::App) end end - context '#purge_inactive_teams!' do + + describe '#purge_inactive_teams!' do it 'purges teams' do expect(Team).to receive(:purge!) subject.send(:purge_inactive_teams!) end end - context '#deactivate_asleep_teams!' do + + describe '#deactivate_asleep_teams!' do let!(:active_team) { Fabricate(:team, created_at: Time.now.utc) } let!(:active_team_one_week_ago) { Fabricate(:team, created_at: 1.week.ago) } let!(:active_team_two_weeks_ago) { Fabricate(:team, created_at: 2.weeks.ago) } let!(:subscribed_team_a_month_ago) { Fabricate(:team, created_at: 1.month.ago, subscribed: true) } + it 'destroys teams inactive for two weeks' do expect_any_instance_of(Team).to receive(:inform!).with( text: "Your subscription expired more than 2 weeks ago, deactivating. Reactivate at #{SlackRubyBotServer::Service.url}. Your data will be purged in another 2 weeks." @@ -35,17 +39,20 @@ expect(subscribed_team_a_month_ago.reload.active).to be true end end + context 'subscribed' do - include_context :stripe_mock + include_context 'stripe mock' let(:plan) { stripe_helper.create_plan(id: 'slava-yearly', amount: 999) } let(:customer) { Stripe::Customer.create(source: stripe_helper.generate_card_token, plan: plan.id, email: 'foo@bar.com') } let!(:team) { Fabricate(:team, subscribed: true, stripe_customer_id: customer.id) } - context '#check_subscribed_teams!' do + + describe '#check_subscribed_teams!' do it 'ignores active subscriptions' do - expect_any_instance_of(Team).to_not receive(:inform!) - expect_any_instance_of(Team).to_not receive(:inform_admin!) + expect_any_instance_of(Team).not_to receive(:inform!) + expect_any_instance_of(Team).not_to receive(:inform_admin!) subject.send(:check_subscribed_teams!) end + it 'notifies past due subscription' do customer.subscriptions.data.first['status'] = 'past_due' expect(Stripe::Customer).to receive(:retrieve).and_return(customer) @@ -53,6 +60,7 @@ expect_any_instance_of(Team).to receive(:inform_admin!).with(text: "Your subscription to StripeMock Default Plan ID ($9.99) is past due. #{team.update_cc_text}") subject.send(:check_subscribed_teams!) end + it 'notifies canceled subscription' do customer.subscriptions.data.first['status'] = 'canceled' expect(Stripe::Customer).to receive(:retrieve).and_return(customer) @@ -61,6 +69,7 @@ subject.send(:check_subscribed_teams!) expect(team.reload.subscribed?).to be false end + it 'notifies no active subscriptions' do customer.subscriptions.data = [] expect(Stripe::Customer).to receive(:retrieve).and_return(customer) @@ -70,10 +79,12 @@ end end end - context '#check_trials!' do + + describe '#check_trials!' do let!(:active_team) { Fabricate(:team, created_at: Time.now.utc) } let!(:active_team_one_week_ago) { Fabricate(:team, created_at: 1.week.ago) } let!(:active_team_twelve_days_ago) { Fabricate(:team, created_at: 12.days.ago) } + it 'notifies teams' do expect_any_instance_of(Team).to receive(:inform_everyone!).with(text: active_team_twelve_days_ago.trial_message) subject.send(:check_trials!) diff --git a/spec/slack-strava/commands/connect_spec.rb b/spec/slack-strava/commands/connect_spec.rb index 89b2254..adbee46 100644 --- a/spec/slack-strava/commands/connect_spec.rb +++ b/spec/slack-strava/commands/connect_spec.rb @@ -5,14 +5,17 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'connect' do it 'requires a subscription' do expect(message: "#{SlackRubyBot.config.user} connect").to respond_with_slack_message(team.trial_message) end + context 'subscribed team' do let(:team) { Fabricate(:team, subscribed: true) } let(:user) { Fabricate(:user, team: team) } let(:url) { "https://www.strava.com/oauth/authorize?client_id=client-id&redirect_uri=https://slava.playplay.io/connect&response_type=code&scope=activity:read_all&state=#{user.id}" } + it 'connects a user', vcr: { cassette_name: 'slack/user_info' } do expect(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) expect(user).to receive(:dm!).with( @@ -30,10 +33,12 @@ end end end + context 'subscription expiration' do before do team.update_attributes!(created_at: 3.weeks.ago) end + it 'prevents new connections' do expect(message: "#{SlackRubyBot.config.user} connect").to respond_with_slack_message( "Your trial subscription has expired. Subscribe your team for $9.99 a year at https://slava.playplay.io/subscribe?team_id=#{team.team_id} to continue receiving Strava activities in Slack. All proceeds go to NYRR." diff --git a/spec/slack-strava/commands/disconnect_spec.rb b/spec/slack-strava/commands/disconnect_spec.rb index ec0f52f..703d6ae 100644 --- a/spec/slack-strava/commands/disconnect_spec.rb +++ b/spec/slack-strava/commands/disconnect_spec.rb @@ -5,26 +5,32 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'disconnect' do it 'requires a subscription' do expect(message: "#{SlackRubyBot.config.user} disconnect").to respond_with_slack_message(team.trial_message) end + context 'subscribed team' do let(:team) { Fabricate(:team, subscribed: true) } + context 'connected user' do let(:user) { Fabricate(:user, team: team, access_token: 'token', token_type: 'Bearer') } + it 'disconnects a user' do expect(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) expect(user).to receive(:dm!).with(text: 'Your Strava account has been successfully disconnected.') message_hook.call(client, Hashie::Mash.new(channel: 'channel', user: SlackRubyBot.config.user, text: "#{SlackRubyBot.config.user} disconnect")) user.reload - expect(user.access_token).to be nil - expect(user.connected_to_strava_at).to be nil - expect(user.token_type).to be nil + expect(user.access_token).to be_nil + expect(user.connected_to_strava_at).to be_nil + expect(user.token_type).to be_nil end end + context 'disconnected user' do let(:user) { Fabricate(:user, team: team) } + it 'fails to disconnect a user' do expect(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) expect(user).to receive(:dm!).with(text: 'Your Strava account is not connected.') diff --git a/spec/slack-strava/commands/help_spec.rb b/spec/slack-strava/commands/help_spec.rb index afefe2d..48cad0d 100644 --- a/spec/slack-strava/commands/help_spec.rb +++ b/spec/slack-strava/commands/help_spec.rb @@ -4,15 +4,19 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + it 'help' do expect(client).to receive(:say).with(channel: 'channel', text: SlackStrava::Commands::Help::HELP) message_hook.call(client, Hashie::Mash.new(channel: 'channel', text: "#{SlackRubyBot.config.user} help")) end end + context 'non-subscribed team after trial' do let!(:team) { Fabricate(:team, created_at: 2.weeks.ago) } + it 'help' do expect(client).to receive(:say).with(channel: 'channel', text: [ SlackStrava::Commands::Help::HELP, @@ -21,8 +25,10 @@ message_hook.call(client, Hashie::Mash.new(channel: 'channel', text: "#{SlackRubyBot.config.user} help")) end end + context 'non-subscribed team during trial' do let!(:team) { Fabricate(:team, created_at: 1.day.ago) } + it 'help' do expect(client).to receive(:say).with(channel: 'channel', text: [ SlackStrava::Commands::Help::HELP, diff --git a/spec/slack-strava/commands/info_spec.rb b/spec/slack-strava/commands/info_spec.rb index 70a4f5d..dc90326 100644 --- a/spec/slack-strava/commands/info_spec.rb +++ b/spec/slack-strava/commands/info_spec.rb @@ -4,15 +4,19 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + it 'info' do expect(client).to receive(:say).with(channel: 'channel', text: SlackStrava::INFO) message_hook.call(client, Hashie::Mash.new(channel: 'channel', text: "#{SlackRubyBot.config.user} info")) end end + context 'non-subscribed team after trial' do let!(:team) { Fabricate(:team, created_at: 2.weeks.ago) } + it 'help' do expect(client).to receive(:say).with(channel: 'channel', text: [ SlackStrava::INFO, @@ -21,8 +25,10 @@ message_hook.call(client, Hashie::Mash.new(channel: 'channel', text: "#{SlackRubyBot.config.user} info")) end end + context 'non-subscribed team during trial' do let!(:team) { Fabricate(:team, created_at: 1.day.ago) } + it 'help' do expect(client).to receive(:say).with(channel: 'channel', text: [ SlackStrava::INFO, diff --git a/spec/slack-strava/commands/leaderboard_spec.rb b/spec/slack-strava/commands/leaderboard_spec.rb index 4438001..c76fee7 100644 --- a/spec/slack-strava/commands/leaderboard_spec.rb +++ b/spec/slack-strava/commands/leaderboard_spec.rb @@ -4,8 +4,10 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + it 'leaderboard' do expect(client.web_client).to receive(:chat_postMessage).with( text: 'There are no activities with distance in this channel.', @@ -14,6 +16,7 @@ ) message_hook.call(client, Hashie::Mash.new(user: 'user', channel: 'channel', text: "#{SlackRubyBot.config.user} leaderboard")) end + it 'elapsed time' do expect(client.web_client).to receive(:chat_postMessage).with( text: 'There are no activities with elapsed time in this channel.', @@ -22,6 +25,7 @@ ) message_hook.call(client, Hashie::Mash.new(user: 'user', channel: 'channel', text: "#{SlackRubyBot.config.user} leaderboard elapsed time")) end + it 'includes channel' do expect(client.web_client).to receive(:chat_postMessage).with( text: 'There are no activities with distance in this channel.', @@ -31,6 +35,7 @@ expect_any_instance_of(Team).to receive(:leaderboard).with(channel_id: 'channel', metric: 'distance').and_call_original message_hook.call(client, Hashie::Mash.new(user: 'user', channel: 'channel', text: "#{SlackRubyBot.config.user} leaderboard")) end + it 'does not include channel on a DM' do expect(client.web_client).to receive(:chat_postMessage).with( text: 'There are no activities with distance in this channel.', diff --git a/spec/slack-strava/commands/resubscribe_spec.rb b/spec/slack-strava/commands/resubscribe_spec.rb index bb7e6e7..47be11e 100644 --- a/spec/slack-strava/commands/resubscribe_spec.rb +++ b/spec/slack-strava/commands/resubscribe_spec.rb @@ -3,28 +3,34 @@ describe SlackStrava::Commands::Resubscribe, vcr: { cassette_name: 'slack/user_info' } do let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } + shared_examples_for 'resubscribe' do context 'on trial' do before do team.update_attributes!(subscribed: false, subscribed_at: nil) end + it 'displays all set message' do expect(message: "#{SlackRubyBot.config.user} resubscribe").to respond_with_slack_message "You don't have a paid subscription.\n#{team.subscribe_text}" end end + context 'with subscribed_at' do before do team.update_attributes!(subscribed: true, subscribed_at: 1.year.ago) end + it 'displays subscription info' do expect(message: "#{SlackRubyBot.config.user} resubscribe").to respond_with_slack_message "You don't have a paid subscription.\n#{team.subscribe_text}" end end + context 'with a plan' do - include_context :stripe_mock + include_context 'stripe mock' before do stripe_helper.create_plan(id: 'slava-yearly', amount: 999, name: 'Plan') end + context 'a customer' do let!(:customer) do Stripe::Customer.create( @@ -33,8 +39,10 @@ email: 'foo@bar.com' ) end + let(:current_period_end) { Time.at(active_subscription.current_period_end).strftime('%B %d, %Y') } let(:activated_user) { Fabricate(:user) } let(:active_subscription) { team.active_stripe_subscription } + before do team.update_attributes!( subscribed: true, @@ -43,7 +51,7 @@ ) active_subscription.delete(at_period_end: true) end - let(:current_period_end) { Time.at(active_subscription.current_period_end).strftime('%B %d, %Y') } + it 'displays subscription info' do customer_info = [ "Subscribed to Plan ($9.99), will not auto-renew on #{current_period_end}.", @@ -51,20 +59,25 @@ ].join("\n") expect(message: "#{SlackRubyBot.config.user} resubscribe", user: activated_user.user_name).to respond_with_slack_message customer_info end + it 'cannot resubscribe with an invalid subscription id' do expect(message: "#{SlackRubyBot.config.user} resubscribe xyz", user: activated_user.user_name).to respond_with_slack_message 'Sorry, I cannot find a subscription with "xyz".' end + it 'resubscribes' do expect(message: "#{SlackRubyBot.config.user} resubscribe #{active_subscription.id}", user: activated_user.user_name).to respond_with_slack_message 'Successfully enabled auto-renew for Plan ($9.99).' team.reload expect(team.subscribed).to be true - expect(team.stripe_customer_id).to_not be nil + expect(team.stripe_customer_id).not_to be_nil end + context 'not an admin' do let!(:user) { Fabricate(:user, is_admin: false, is_owner: false, team: team) } + before do expect(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) end + it 'cannot resubscribe' do expect(message: "#{SlackRubyBot.config.user} resubscribe xyz").to respond_with_slack_message "Sorry, only <@#{activated_user.user_id}> or a Slack admin can do that." end @@ -72,15 +85,19 @@ end end end + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } let!(:activated_user) { Fabricate(:user, team: team) } + before do team.update_attributes!(activated_user_id: activated_user.user_id) end + it_behaves_like 'resubscribe' context 'with another team' do let!(:team2) { Fabricate(:team) } + it_behaves_like 'resubscribe' end end diff --git a/spec/slack-strava/commands/set_spec.rb b/spec/slack-strava/commands/set_spec.rb index dc22fb1..b3e3290 100644 --- a/spec/slack-strava/commands/set_spec.rb +++ b/spec/slack-strava/commands/set_spec.rb @@ -5,20 +5,25 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:user) { Fabricate(:user, team: team, is_admin: true) } + before do allow(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) end + context 'settings' do it 'requires a subscription' do expect(message: "#{SlackRubyBot.config.user} set units km").to respond_with_slack_message(team.trial_message) end + context 'subscribed team' do let(:team) { Fabricate(:team, subscribed: true) } + it 'errors on invalid setting' do expect(message: "#{SlackRubyBot.config.user} set whatever").to respond_with_slack_message( 'Invalid setting whatever, type `help` for instructions.' ) end + it 'shows current settings' do expect(message: "#{SlackRubyBot.config.user} set").to respond_with_slack_message([ "Activities for team #{team.name} display *miles, feet, yards, and degrees Fahrenheit*.", @@ -29,18 +34,21 @@ 'Your followers only activities will be posted.' ].join("\n")) end + context 'sync' do it 'shows default value of sync' do expect(message: "#{SlackRubyBot.config.user} set sync").to respond_with_slack_message( 'Your activities will sync.' ) end + it 'shows current value of sync set to true' do user.update_attributes!(sync_activities: true) expect(message: "#{SlackRubyBot.config.user} set sync").to respond_with_slack_message( 'Your activities will sync.' ) end + it 'sets sync to false' do user.update_attributes!(sync_activities: true) expect(message: "#{SlackRubyBot.config.user} set sync false").to respond_with_slack_message( @@ -48,16 +56,19 @@ ) expect(user.reload.sync_activities).to be false end + context 'with sync set to false' do before do user.update_attributes!(sync_activities: false) end + it 'sets sync to true' do expect(message: "#{SlackRubyBot.config.user} set sync true").to respond_with_slack_message( 'Your activities will now sync.' ) expect(user.reload.sync_activities).to be true end + context 'with prior activities' do before do allow_any_instance_of(Map).to receive(:update_png!) @@ -65,6 +76,7 @@ 2.times { Fabricate(:user_activity, user: user) } user.brag! end + it 'resets all activities' do expect { expect { @@ -77,18 +89,21 @@ end end end + context 'private' do it 'shows current value of private' do expect(message: "#{SlackRubyBot.config.user} set private").to respond_with_slack_message( 'Your private activities will not be posted.' ) end + it 'shows current value of private set to true' do user.update_attributes!(private_activities: true) expect(message: "#{SlackRubyBot.config.user} set private").to respond_with_slack_message( 'Your private activities will be posted.' ) end + it 'sets private to false' do user.update_attributes!(private_activities: true) expect(message: "#{SlackRubyBot.config.user} set private false").to respond_with_slack_message( @@ -96,6 +111,7 @@ ) expect(user.reload.private_activities).to be false end + it 'sets private to true' do expect(message: "#{SlackRubyBot.config.user} set private true").to respond_with_slack_message( 'Your private activities will now be posted.' @@ -103,18 +119,21 @@ expect(user.reload.private_activities).to be true end end + context 'followers only' do it 'shows current value of followers_only' do expect(message: "#{SlackRubyBot.config.user} set followers").to respond_with_slack_message( 'Your followers only activities will be posted.' ) end + it 'shows current value of followers only set to false' do user.update_attributes!(followers_only_activities: false) expect(message: "#{SlackRubyBot.config.user} set followers").to respond_with_slack_message( 'Your followers only activities will not be posted.' ) end + it 'sets followers only to false' do user.update_attributes!(followers_only_activities: true) expect(message: "#{SlackRubyBot.config.user} set followers false").to respond_with_slack_message( @@ -122,6 +141,7 @@ ) expect(user.reload.followers_only_activities).to be false end + it 'sets followers only to true' do user.update_attributes!(followers_only_activities: false) expect(message: "#{SlackRubyBot.config.user} set followers true").to respond_with_slack_message( @@ -130,6 +150,7 @@ expect(user.reload.followers_only_activities).to be true end end + context 'as team admin' do context 'units' do it 'shows current value of units' do @@ -137,12 +158,14 @@ "Activities for team #{team.name} display *miles, feet, yards, and degrees Fahrenheit*." ) end + it 'shows current value of units set to km' do team.update_attributes!(units: 'km') expect(message: "#{SlackRubyBot.config.user} set units").to respond_with_slack_message( "Activities for team #{team.name} display *kilometers, meters, and degrees Celcius*." ) end + it 'sets units to mi' do team.update_attributes!(units: 'km') expect(message: "#{SlackRubyBot.config.user} set units mi").to respond_with_slack_message( @@ -151,6 +174,7 @@ expect(client.owner.units).to eq 'mi' expect(team.reload.units).to eq 'mi' end + it 'sets units to km' do team.update_attributes!(units: 'mi') expect(message: "#{SlackRubyBot.config.user} set units km").to respond_with_slack_message( @@ -159,6 +183,7 @@ expect(client.owner.units).to eq 'km' expect(team.reload.units).to eq 'km' end + it 'sets units to metric' do team.update_attributes!(units: 'mi') expect(message: "#{SlackRubyBot.config.user} set units metric").to respond_with_slack_message( @@ -167,6 +192,7 @@ expect(client.owner.units).to eq 'km' expect(team.reload.units).to eq 'km' end + it 'sets units to imperial' do team.update_attributes!(units: 'km') expect(message: "#{SlackRubyBot.config.user} set units imperial").to respond_with_slack_message( @@ -175,6 +201,7 @@ expect(client.owner.units).to eq 'mi' expect(team.reload.units).to eq 'mi' end + it 'changes units' do team.update_attributes!(units: 'mi') expect(message: "#{SlackRubyBot.config.user} set units km").to respond_with_slack_message( @@ -183,12 +210,14 @@ expect(client.owner.units).to eq 'km' expect(team.reload.units).to eq 'km' end + it 'shows current value of units set to both' do team.update_attributes!(units: 'both') expect(message: "#{SlackRubyBot.config.user} set units").to respond_with_slack_message( "Activities for team #{team.name} display *both units*." ) end + it 'sets units to both' do team.update_attributes!(units: 'km') expect(message: "#{SlackRubyBot.config.user} set units both").to respond_with_slack_message( @@ -198,18 +227,21 @@ expect(team.reload.units).to eq 'both' end end + context 'maps' do it 'shows current value of maps' do expect(message: "#{SlackRubyBot.config.user} set maps").to respond_with_slack_message( "Maps for team #{team.name} are *displayed in full*." ) end + it 'shows current value of maps set to thumb' do team.update_attributes!(maps: 'thumb') expect(message: "#{SlackRubyBot.config.user} set maps").to respond_with_slack_message( "Maps for team #{team.name} are *displayed as thumbnails*." ) end + it 'sets maps to thumb' do team.update_attributes!(maps: 'off') expect(message: "#{SlackRubyBot.config.user} set maps thumb").to respond_with_slack_message( @@ -217,12 +249,14 @@ ) expect(team.reload.maps).to eq 'thumb' end + it 'sets maps to off' do expect(message: "#{SlackRubyBot.config.user} set maps off").to respond_with_slack_message( "Maps for team #{team.name} are now *not displayed*." ) expect(team.reload.maps).to eq 'off' end + it 'displays an error for an invalid maps value' do expect(message: "#{SlackRubyBot.config.user} set maps foobar").to respond_with_slack_message( 'Invalid value: foobar, possible values are full, off and thumb.' @@ -230,18 +264,21 @@ expect(team.reload.maps).to eq 'full' end end + context 'fields' do it 'shows current value of fields' do expect(message: "#{SlackRubyBot.config.user} set fields").to respond_with_slack_message( "Activity fields for team #{team.name} are *set to default*." ) end + it 'shows current value of fields set to Time and Elapsed Time' do team.update_attributes!(activity_fields: ['Time', 'Elapsed Time']) expect(message: "#{SlackRubyBot.config.user} set fields").to respond_with_slack_message( "Activity fields for team #{team.name} are *Time and Elapsed Time*." ) end + it 'changes fields' do expect(message: "#{SlackRubyBot.config.user} set fields Time, Elapsed Time").to respond_with_slack_message( "Activity fields for team #{team.name} are now *Time and Elapsed Time*." @@ -249,6 +286,7 @@ expect(client.owner.activity_fields).to eq(['Time', 'Elapsed Time']) expect(team.reload.activity_fields).to eq(['Time', 'Elapsed Time']) end + it 'sets fields to none' do expect(message: "#{SlackRubyBot.config.user} set fields none").to respond_with_slack_message( "Activity fields for team #{team.name} are now *not displayed*." @@ -256,6 +294,7 @@ expect(client.owner.activity_fields).to eq(['None']) expect(team.reload.activity_fields).to eq(['None']) end + it 'sets fields to all' do team.update_attributes!(activity_fields: ['None']) expect(message: "#{SlackRubyBot.config.user} set fields all").to respond_with_slack_message( @@ -264,6 +303,7 @@ expect(client.owner.activity_fields).to eq(['All']) expect(team.reload.activity_fields).to eq(['All']) end + it 'sets fields to default' do team.update_attributes!(activity_fields: ['All']) expect(message: "#{SlackRubyBot.config.user} set fields default").to respond_with_slack_message( @@ -272,12 +312,14 @@ expect(client.owner.activity_fields).to eq(['Default']) expect(team.reload.activity_fields).to eq(['Default']) end + it 'sets to invalid fields' do expect(message: "#{SlackRubyBot.config.user} set fields Time, Foo, Bar").to respond_with_slack_message( 'Invalid fields: Foo and Bar, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' ) expect(team.reload.activity_fields).to eq ['Default'] end + context 'some' do it 'sets fields to some' do expect(message: "#{SlackRubyBot.config.user} set fields Title, Url, PR Count, Elapsed Time").to respond_with_slack_message( @@ -286,6 +328,7 @@ expect(team.reload.activity_fields).to eq(['Title', 'Url', 'PR Count', 'Elapsed Time']) end end + context 'each field' do (ActivityFields.values - [ActivityFields::ALL, ActivityFields::DEFAULT, ActivityFields::NONE]).each do |field| context field do @@ -299,14 +342,17 @@ end end end + context 'not as a team admin' do let(:user) { Fabricate(:user, team: team) } + context 'units' do it 'shows current value of units' do expect(message: "#{SlackRubyBot.config.user} set units").to respond_with_slack_message( "Activities for team #{team.name} display *miles, feet, yards, and degrees Fahrenheit*." ) end + it 'cannot set units' do team.update_attributes!(units: 'km') expect(message: "#{SlackRubyBot.config.user} set units mi").to respond_with_slack_message( @@ -315,12 +361,14 @@ expect(team.reload.units).to eq 'km' end end + context 'maps' do it 'shows current value of maps' do expect(message: "#{SlackRubyBot.config.user} set maps").to respond_with_slack_message( "Maps for team #{team.name} are *displayed in full*." ) end + it 'cannot set maps' do team.update_attributes!(maps: 'full') expect(message: "#{SlackRubyBot.config.user} set maps off").to respond_with_slack_message( @@ -329,12 +377,14 @@ expect(team.reload.maps).to eq 'full' end end + context 'fields' do it 'shows current value of fields' do expect(message: "#{SlackRubyBot.config.user} set fields").to respond_with_slack_message( "Activity fields for team #{team.name} are *set to default*." ) end + it 'cannot set fields' do team.update_attributes!(activity_fields: ['None']) expect(message: "#{SlackRubyBot.config.user} set fields all").to respond_with_slack_message( diff --git a/spec/slack-strava/commands/stats_spec.rb b/spec/slack-strava/commands/stats_spec.rb index bba2422..116ad50 100644 --- a/spec/slack-strava/commands/stats_spec.rb +++ b/spec/slack-strava/commands/stats_spec.rb @@ -4,14 +4,17 @@ let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } let(:message_hook) { SlackRubyBot::Hooks::Message.new } + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } + it 'stats' do expect(client.web_client).to receive(:chat_postMessage).with( team.stats(channel_id: 'channel').to_slack.merge(channel: 'channel', as_user: true) ) message_hook.call(client, Hashie::Mash.new(user: 'user', channel: 'channel', text: "#{SlackRubyBot.config.user} stats")) end + it 'includes channel' do expect(client.web_client).to receive(:chat_postMessage).with( team.stats(channel_id: 'channel').to_slack.merge(channel: 'channel', as_user: true) @@ -19,6 +22,7 @@ expect_any_instance_of(Team).to receive(:stats).with(channel_id: 'channel').and_call_original message_hook.call(client, Hashie::Mash.new(user: 'user', channel: 'channel', text: "#{SlackRubyBot.config.user} stats")) end + it 'does not include channel on a DM' do expect(client.web_client).to receive(:chat_postMessage).with( team.stats.to_slack.merge(channel: 'DM', as_user: true) diff --git a/spec/slack-strava/commands/subscription_spec.rb b/spec/slack-strava/commands/subscription_spec.rb index de63cbf..ad8bf4c 100644 --- a/spec/slack-strava/commands/subscription_spec.rb +++ b/spec/slack-strava/commands/subscription_spec.rb @@ -3,26 +3,31 @@ describe SlackStrava::Commands::Subscription, vcr: { cassette_name: 'slack/user_info' } do let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } + shared_examples_for 'subscription' do context 'on trial' do before do team.update_attributes!(subscribed: false, subscribed_at: nil) end + it 'displays subscribe message' do expect(message: "#{SlackRubyBot.config.user} subscription").to respond_with_slack_message team.trial_message end end + context 'with subscribed_at' do it 'displays subscription info' do customer_info = "Subscriber since #{team.subscribed_at.strftime('%B %d, %Y')}." expect(message: "#{SlackRubyBot.config.user} subscription").to respond_with_slack_message customer_info end end + context 'with a plan' do - include_context :stripe_mock + include_context 'stripe mock' before do stripe_helper.create_plan(id: 'slava-yearly', amount: 999, name: 'Plan') end + pending 'a customer with an ach_credit_transfer source' context 'a customer with a bank source' do let!(:customer) do @@ -32,9 +37,11 @@ email: 'foo@bar.com' ) end + before do team.update_attributes!(subscribed: true, stripe_customer_id: customer['id']) end + it 'displays subscription info' do current_period_end = Time.at(customer.subscriptions.first.current_period_end).strftime('%B %d, %Y') bank = customer.sources.first @@ -47,6 +54,7 @@ expect(message: "#{SlackRubyBot.config.user} subscription", user: 'U007').to respond_with_slack_message customer_info end end + context 'a customer with a credit card' do let!(:customer) do Stripe::Customer.create( @@ -55,9 +63,11 @@ email: 'foo@bar.com' ) end + before do team.update_attributes!(subscribed: true, stripe_customer_id: customer['id']) end + it 'displays subscription info' do card = customer.sources.first current_period_end = Time.at(customer.subscriptions.first.current_period_end).strftime('%B %d, %Y') @@ -69,11 +79,13 @@ ].join("\n") expect(message: "#{SlackRubyBot.config.user} subscription", user: 'U007').to respond_with_slack_message customer_info end + context 'past due subscription' do before do customer.subscriptions.data.first['status'] = 'past_due' allow(Stripe::Customer).to receive(:retrieve).and_return(customer) end + it 'displays subscription info' do customer_info = "Customer since #{Time.at(customer.created).strftime('%B %d, %Y')}." customer_info += "\nPast Due subscription created November 03, 2016 to Plan ($9.99)." @@ -86,11 +98,14 @@ end end end + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true, activated_user_id: 'U007') } + it_behaves_like 'subscription' context 'with another team' do let!(:team2) { Fabricate(:team) } + it_behaves_like 'subscription' end end diff --git a/spec/slack-strava/commands/unsubscribe_spec.rb b/spec/slack-strava/commands/unsubscribe_spec.rb index 77ab4ac..77223ef 100644 --- a/spec/slack-strava/commands/unsubscribe_spec.rb +++ b/spec/slack-strava/commands/unsubscribe_spec.rb @@ -3,28 +3,34 @@ describe SlackStrava::Commands::Unsubscribe, vcr: { cassette_name: 'slack/user_info' } do let(:app) { SlackStrava::Server.new(team: team) } let(:client) { app.send(:client) } + shared_examples_for 'unsubscribe' do context 'on trial' do before do team.update_attributes!(subscribed: false, subscribed_at: nil) end + it 'displays all set message' do expect(message: "#{SlackRubyBot.config.user} unsubscribe").to respond_with_slack_message "You don't have a paid subscription, all set." end end + context 'with subscribed_at' do before do team.update_attributes!(subscribed: true, subscribed_at: 1.year.ago) end + it 'displays subscription info' do expect(message: "#{SlackRubyBot.config.user} unsubscribe").to respond_with_slack_message "You don't have a paid subscription, all set." end end + context 'with a plan' do - include_context :stripe_mock + include_context 'stripe mock' before do stripe_helper.create_plan(id: 'slava-yearly', amount: 999, name: 'Plan') end + context 'a customer' do let!(:customer) do Stripe::Customer.create( @@ -33,7 +39,10 @@ email: 'foo@bar.com' ) end + let(:active_subscription) { team.active_stripe_subscription } + let(:current_period_end) { Time.at(active_subscription.current_period_end).strftime('%B %d, %Y') } let(:activated_user) { Fabricate(:user) } + before do team.update_attributes!( subscribed: true, @@ -41,8 +50,7 @@ activated_user_id: activated_user.user_id ) end - let(:active_subscription) { team.active_stripe_subscription } - let(:current_period_end) { Time.at(active_subscription.current_period_end).strftime('%B %d, %Y') } + it 'displays subscription info' do customer_info = [ "Subscribed to Plan ($9.99), will auto-renew on #{current_period_end}.", @@ -50,20 +58,25 @@ ].join("\n") expect(message: "#{SlackRubyBot.config.user} unsubscribe", user: activated_user.user_name).to respond_with_slack_message customer_info end + it 'cannot unsubscribe with an invalid subscription id' do expect(message: "#{SlackRubyBot.config.user} unsubscribe xyz", user: activated_user.user_name).to respond_with_slack_message 'Sorry, I cannot find a subscription with "xyz".' end + it 'unsubscribes' do expect(message: "#{SlackRubyBot.config.user} unsubscribe #{active_subscription.id}", user: activated_user.user_name).to respond_with_slack_message 'Successfully canceled auto-renew for Plan ($9.99).' team.reload expect(team.subscribed).to be true - expect(team.stripe_customer_id).to_not be nil + expect(team.stripe_customer_id).not_to be_nil end + context 'not an admin' do let!(:user) { Fabricate(:user, is_admin: false, is_owner: false, team: team) } + before do expect(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) end + it 'cannot unsubscribe' do expect(message: "#{SlackRubyBot.config.user} unsubscribe xyz").to respond_with_slack_message "Sorry, only <@#{activated_user.user_id}> or a Slack admin can do that." end @@ -71,15 +84,19 @@ end end end + context 'subscribed team' do let!(:team) { Fabricate(:team, subscribed: true) } let!(:activated_user) { Fabricate(:user, team: team) } + before do team.update_attributes!(activated_user_id: activated_user.user_id) end + it_behaves_like 'unsubscribe' context 'with another team' do let!(:team2) { Fabricate(:team) } + it_behaves_like 'unsubscribe' end end diff --git a/spec/slack-strava/server_spec.rb b/spec/slack-strava/server_spec.rb index 5577087..275d3ac 100644 --- a/spec/slack-strava/server_spec.rb +++ b/spec/slack-strava/server_spec.rb @@ -4,7 +4,8 @@ let(:team) { Fabricate(:team) } let(:server) { SlackStrava::Server.new(team: team) } let(:client) { server.send(:client) } - context '#channel_joined' do + + describe '#channel_joined' do it 'sends a welcome message' do allow(client).to receive(:self).and_return(Hashie::Mash.new(id: 'U12345')) message = 'Welcome to Slava! Please DM "*connect*" to <@U12345> to publish your activities in this channel.' @@ -12,9 +13,11 @@ client.send(:callback, Hashie::Mash.new('channel' => { 'id' => 'C12345' }), :channel_joined) end end - context '#member_joined_channel' do + + describe '#member_joined_channel' do let(:user) { Fabricate(:user, team: team) } let(:connect_url) { "https://www.strava.com/oauth/authorize?client_id=client-id&redirect_uri=https://slava.playplay.io/connect&response_type=code&scope=activity:read_all&state=#{user.id}" } + it 'offers to connect account', vcr: { cassette_name: 'slack/user_info' } do allow(client).to receive(:self).and_return(Hashie::Mash.new(id: 'U12345')) allow(User).to receive(:find_create_or_update_by_slack_id!).and_return(user) @@ -36,15 +39,18 @@ client.send(:callback, Hashie::Mash.new('user' => 'U12345', 'channel' => 'C12345'), :member_joined_channel) end end + context 'hooks' do let(:user) { Fabricate(:user, team: team) } + it 'renames user' do client.send(:callback, Hashie::Mash.new(user: { id: user.user_id, name: 'updated' }), :user_change) expect(user.reload.user_name).to eq('updated') end + it 'does not touch a user with the same name' do expect(User).to receive(:where).and_return([user]) - expect(user).to_not receive(:update_attributes!) + expect(user).not_to receive(:update_attributes!) client.send(:callback, Hashie::Mash.new(user: { id: user.user_id, name: user.user_name }), :user_change) end end diff --git a/spec/slack-strava/service_spec.rb b/spec/slack-strava/service_spec.rb index 78128bc..d826594 100644 --- a/spec/slack-strava/service_spec.rb +++ b/spec/slack-strava/service_spec.rb @@ -1,31 +1,38 @@ require 'spec_helper' describe SlackRubyBotServer::Service do - context '#url' do + describe '#url' do before do @rack_env = ENV.fetch('RACK_ENV', nil) end + after do ENV['RACK_ENV'] = @rack_env end + it 'defaults to playplay.io in production' do expect(SlackRubyBotServer::Service.url).to eq 'https://slava.playplay.io' end + context 'in development' do before do ENV['RACK_ENV'] = 'development' end + it 'defaults to localhost' do expect(SlackRubyBotServer::Service.url).to eq 'http://localhost:5000' end end + context 'when set' do before do ENV['URL'] = 'updated' end + after do ENV.delete('URL') end + it 'defaults to ENV' do expect(SlackRubyBotServer::Service.url).to eq 'updated' end diff --git a/spec/slack-strava/version_spec.rb b/spec/slack-strava/version_spec.rb index b4ef78c..6b364b2 100644 --- a/spec/slack-strava/version_spec.rb +++ b/spec/slack-strava/version_spec.rb @@ -2,6 +2,6 @@ describe SlackStrava do it 'has a version' do - expect(SlackStrava::VERSION).to_not be nil + expect(SlackStrava::VERSION).not_to be_nil end end diff --git a/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb b/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb index 35815c1..a757436 100644 --- a/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb +++ b/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb @@ -52,8 +52,9 @@ context 'total count' do it "doesn't return total_count" do response = client.send(model_ps, cursor_params) - expect(response).to_not respond_to(:total_count) + expect(response).not_to respond_to(:total_count) end + it 'returns total_count when total_count query string is specified' do response = client.send(model_ps, cursor_params.merge(total_count: true)) expect(response.total_count).to eq model.all.count diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 348130b..730fe0c 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -10,7 +10,7 @@ Mongoid.purge! end - config.around :each do |example| + config.around do |example| DatabaseCleaner.cleaning do example.run end diff --git a/spec/support/stripe.rb b/spec/support/stripe.rb index f267e4f..1e8aa78 100644 --- a/spec/support/stripe.rb +++ b/spec/support/stripe.rb @@ -1,8 +1,9 @@ -RSpec.shared_context :stripe_mock do +RSpec.shared_context 'stripe mock' do let(:stripe_helper) { StripeMock.create_test_helper } before do StripeMock.start end + after do StripeMock.stop end