Skip to content

Commit 6c800ce

Browse files
committed
Add comprehensive tests for OAuth integration and user creation
- Introduced new spec for Simple OAuth Flow in `oauth_simple_spec.rb` to validate user creation and integration handling. - Enhanced `person_platform_integration_spec.rb` with tests for attributes extraction, token management, and integration updates. - Created `devise_user_spec.rb` to test user creation from OAuth and attribute setting from auth hash. - Added support helpers in `oauth_test_helpers.rb` for generating mock OAuth auth hashes for various providers. - Implemented shared examples in `oauth_examples.rb` for consistent testing of OAuth authentication flows and token management.
1 parent 23f9855 commit 6c800ce

File tree

24 files changed

+2120
-36
lines changed

24 files changed

+2120
-36
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# frozen_string_literal: true
2+
3+
# app/builders/better_together/external_platform_builder.rb
4+
5+
module BetterTogether
6+
# Builder to create external OAuth provider platforms
7+
class ExternalPlatformBuilder < Builder
8+
OAUTH_PROVIDERS = [
9+
{
10+
name: 'GitHub',
11+
url: 'https://github.com',
12+
identifier: 'github',
13+
description: 'GitHub OAuth Provider',
14+
time_zone: 'UTC'
15+
},
16+
{
17+
name: 'Google',
18+
url: 'https://accounts.google.com',
19+
identifier: 'google',
20+
description: 'Google OAuth Provider',
21+
time_zone: 'UTC'
22+
},
23+
{
24+
name: 'Facebook',
25+
url: 'https://www.facebook.com',
26+
identifier: 'facebook',
27+
description: 'Facebook OAuth Provider',
28+
time_zone: 'UTC'
29+
},
30+
{
31+
name: 'LinkedIn',
32+
url: 'https://linkedin.com',
33+
identifier: 'linkedin',
34+
description: 'LinkedIn OAuth Provider',
35+
time_zone: 'UTC'
36+
}
37+
].freeze
38+
39+
class << self
40+
def seed_data
41+
puts "Creating #{OAUTH_PROVIDERS.length} external OAuth provider platforms..."
42+
43+
OAUTH_PROVIDERS.each do |provider_attrs|
44+
create_external_platform(provider_attrs)
45+
end
46+
47+
puts '✓ Successfully processed all OAuth providers'
48+
end
49+
50+
# Clear existing external platforms - Use with caution!
51+
def clear_existing
52+
count = Platform.external.count
53+
Platform.external.delete_all
54+
puts "✓ Cleared #{count} existing external platforms"
55+
end
56+
57+
private
58+
59+
def create_external_platform(provider_attrs)
60+
platform = Platform.find_or_initialize_by(
61+
identifier: provider_attrs[:identifier],
62+
external: true
63+
)
64+
65+
if platform.new_record?
66+
platform.assign_attributes(
67+
name: provider_attrs[:name],
68+
url: provider_attrs[:url],
69+
description: provider_attrs[:description],
70+
time_zone: provider_attrs[:time_zone],
71+
external: true,
72+
host: false,
73+
privacy: 'public'
74+
)
75+
76+
platform.save!
77+
puts " ✓ Created external platform: #{platform.name}"
78+
else
79+
puts " - External platform already exists: #{platform.name}"
80+
end
81+
82+
platform
83+
end
84+
end
85+
end
86+
end

app/controllers/better_together/users/omniauth_callbacks_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module BetterTogether
44
module Users
5-
class OmniauthCallbacksController < ::Devise::OmniauthCallbacksController # rubocop:todo Style/Documentation
5+
class OmniauthCallbacksController < BetterTogether::OmniauthCallbacksController # rubocop:todo Style/Documentation
66
include DeviseLocales
77

88
skip_before_action :check_platform_privacy

app/models/better_together/person_platform_integration.rb

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,49 @@ def self.attributes_from_omniauth(auth)
7777
attributes[:name] = auth.info.name if auth.info.name.present?
7878
attributes[:image_url] = URI.parse(auth.info.image) if auth.info.image.present?
7979

80-
attributes[:profile_url] = auth.info.urls.first.last unless person_platform_integration.persisted?
80+
# Set profile_url from the auth hash if available
81+
if auth.extra&.raw_info&.html_url.present?
82+
attributes[:profile_url] = auth.extra.raw_info.html_url
83+
elsif auth.info&.urls.present? && auth.info.urls.is_a?(Hash)
84+
attributes[:profile_url] = auth.info.urls.values.first
85+
end
8186

8287
attributes
8388
end
8489

85-
def self.update_or_initialize(person_platform_integration, auth)
90+
def self.update_or_initialize(person_platform_integration, auth, platform: nil)
91+
attributes = attributes_from_omniauth(auth)
92+
93+
# Find the external OAuth platform based on the provider
94+
external_platform = find_external_platform_for_provider(auth.provider)
95+
attributes[:platform] = external_platform if external_platform.present?
96+
97+
# Allow manual platform override (for backward compatibility)
98+
attributes[:platform] = platform if platform.present?
99+
86100
if person_platform_integration.present?
87-
person_platform_integration.update(attributes_from_omniauth(auth))
101+
person_platform_integration.update(attributes)
88102
else
89-
person_platform_integration = new(
90-
attributes_from_omniauth(auth)
91-
)
103+
person_platform_integration = new(attributes)
92104
end
93105

94106
person_platform_integration
95107
end
108+
109+
private_class_method def self.find_external_platform_for_provider(provider)
110+
# Map OAuth provider names to platform identifiers
111+
provider_mapping = {
112+
'github' => 'github',
113+
'google_oauth2' => 'google',
114+
'facebook' => 'facebook',
115+
'linkedin' => 'linkedin',
116+
'twitter' => 'twitter'
117+
}
118+
119+
platform_identifier = provider_mapping[provider.to_s]
120+
return nil unless platform_identifier
121+
122+
Platform.external.find_by(identifier: platform_identifier)
123+
end
96124
end
97125
end

app/models/better_together/platform.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class Platform < ApplicationRecord
3131

3232
validates :url, presence: true, uniqueness: true
3333
validates :time_zone, presence: true
34+
validates :external, inclusion: { in: [true, false] }
35+
36+
scope :external, -> { where(external: true) }
37+
scope :internal, -> { where(external: false) }
38+
scope :oauth_providers, -> { external }
3439

3540
has_one_attached :profile_image
3641
has_one_attached :cover_image

app/models/concerns/better_together/devise_user.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module DeviseUser
1515
validates :email, presence: true, uniqueness: { case_sensitive: false }
1616

1717
def self.from_omniauth(person_platform_integration:, auth:, current_user:)
18+
# PersonPlatformIntegration will automatically find the correct external OAuth platform
1819
person_platform_integration = PersonPlatformIntegration.update_or_initialize(person_platform_integration, auth)
1920

2021
return person_platform_integration.user if person_platform_integration.user.present?
@@ -30,7 +31,7 @@ def self.from_omniauth(person_platform_integration:, auth:, current_user:)
3031

3132
person_attributes = {
3233
name: person_platform_integration.name || user.email.split('@').first || 'Unidentified Person',
33-
handle: person_platform_integration.handle || user.email.split('@').first
34+
identifier: person_platform_integration.handle || user.email.split('@').first
3435
}
3536
user.build_person(person_attributes)
3637

config/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,9 @@ en:
15451545
send_paranoid_instructions: If your account exists, you will receive an email
15461546
with instructions for how to unlock it in a few minutes.
15471547
unlocked: Your account has been unlocked successfully. Please sign in to continue.
1548+
devise_omniauth_callbacks:
1549+
failure: There was a problem signing you in through %{kind}. %{reason}
1550+
success: Successfully authenticated from %{kind} account.
15481551
errors:
15491552
format: "%{attribute} %{message}"
15501553
internal_server_error:

config/locales/es.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,9 @@ es:
15361536
send_paranoid_instructions: Si tu cuenta existe, vas a recibir instrucciones
15371537
para desbloquear tu cuenta en unos pocos minutos.
15381538
unlocked: Tu cuenta ha sido desbloqueada. Ya puedes iniciar sesión.
1539+
devise_omniauth_callbacks:
1540+
failure: Hubo un problema al iniciar sesión a través de %{kind}. %{reason}
1541+
success: Autenticado exitosamente desde la cuenta de %{kind}.
15391542
errors:
15401543
format: "%{attribute} %{message}"
15411544
internal_server_error:

config/locales/fr.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,9 @@ fr:
15631563
vous recevrez sous quelques minutes un message avec des instructions pour
15641564
déverrouiller votre compte.
15651565
unlocked: Votre compte a bien été déverrouillé. Veuillez vous connecter.
1566+
devise_omniauth_callbacks:
1567+
failure: Il y a eu un problème lors de votre connexion via %{kind}. %{reason}
1568+
success: Authentification réussie depuis le compte %{kind}.
15661569
errors:
15671570
format: "%{attribute} %{message}"
15681571
internal_server_error:

config/routes.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
require 'sidekiq/web'
44

55
BetterTogether::Engine.routes.draw do # rubocop:todo Metrics/BlockLength
6+
# Enable Omniauth for Devise
7+
devise_for :users, class_name: BetterTogether.user_class.to_s,
8+
only: :omniauth_callbacks,
9+
controllers: { omniauth_callbacks: 'better_together/users/omniauth_callbacks' }
10+
611
scope ':locale', # rubocop:todo Metrics/BlockLength
712
locale: /#{I18n.available_locales.join('|')}/ do
813
# bt base path
914
scope path: BetterTogether.route_scope_path do # rubocop:todo Metrics/BlockLength
1015
# Aug 2nd 2024: Inherit from blank devise controllers to fix issue generating locale paths for devise
1116
# https://github.com/heartcombo/devise/issues/4282#issuecomment-259706108
12-
# Uncomment omniauth_callbacks and unlocks if/when used
17+
# Uncomment unlocks if/when used
1318
devise_for :users,
1419
class_name: BetterTogether.user_class.to_s,
1520
controllers: {
1621
confirmations: 'better_together/users/confirmations',
17-
omniauth_callbacks: 'better_together/users/omniauth_callbacks',
1822
passwords: 'better_together/users/passwords',
1923
registrations: 'better_together/users/registrations',
2024
sessions: 'better_together/users/sessions'
2125
# unlocks: 'better_together/users/unlocks'
2226
},
2327
module: 'devise',
24-
skip: %i[unlocks],
28+
skip: %i[unlocks omniauth_callbacks],
2529
path: 'users',
2630
path_names: {
2731
sign_in: 'sign-in',
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class AddExternalToBetterTogetherPlatforms < ActiveRecord::Migration[7.1]
2+
def change
3+
add_column :better_together_platforms, :external, :boolean, default: false, null: false
4+
add_index :better_together_platforms, :external
5+
end
6+
end

0 commit comments

Comments
 (0)