panda-core 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +6 -0
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -0
- data/lib/panda/core/testing/rails_helper.rb +174 -0
- data/lib/panda/core/testing/support/authentication_helpers.rb +58 -0
- data/lib/panda/core/testing/support/authentication_test_helpers.rb +260 -0
- data/lib/panda/core/testing/support/generator_spec_helper.rb +97 -0
- data/lib/panda/core/testing/support/html_helpers.rb +11 -0
- data/lib/panda/core/testing/support/omniauth_setup.rb +17 -0
- data/lib/panda/core/testing/support/service_stubs.rb +40 -0
- data/lib/panda/core/testing/support/setup.rb +11 -0
- data/lib/panda/core/version.rb +1 -1
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abf323ca5beed58dcb17cd7f39ed76028413524b20e40cd2de8d9c5d21b97fff
|
|
4
|
+
data.tar.gz: 992d91647d9cd2567b7d818beb2f7a2d173fb16edf8f78f222a576a1e6b9534f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42f8a466d633d31ee8e77650c0afcd9f768ef7990df74b185d3722e614a623f176944a610bee1ca4eee60f33edba812eda96b0b25f5e9466773c1f1920bd1213
|
|
7
|
+
data.tar.gz: a0f9ec858050df85d6a9cac0a04dc179673ad5438d8bd6c9deeb1ee0d52c5a1d0f5c2e0af9784cc717fcd59a987efd79f9ecc55d3d2456ba842d5056acff363b
|
|
@@ -19,10 +19,14 @@ export default class extends Controller {
|
|
|
19
19
|
static targets = ["button", "menu", "icon"]
|
|
20
20
|
|
|
21
21
|
connect() {
|
|
22
|
+
// Ensure menu starts in correct state
|
|
22
23
|
// Check if this menu should be expanded by default (if a child is active)
|
|
23
24
|
const hasActiveChild = this.menuTarget.querySelector(".bg-mid")
|
|
24
25
|
if (hasActiveChild) {
|
|
25
26
|
this.expand()
|
|
27
|
+
} else {
|
|
28
|
+
// Explicitly ensure the menu is collapsed if no active child
|
|
29
|
+
this.collapse()
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -42,6 +46,7 @@ export default class extends Controller {
|
|
|
42
46
|
|
|
43
47
|
expand() {
|
|
44
48
|
this.menuTarget.classList.remove("hidden")
|
|
49
|
+
this.menuTarget.style.display = ""
|
|
45
50
|
this.buttonTarget.setAttribute("aria-expanded", "true")
|
|
46
51
|
|
|
47
52
|
if (this.hasIconTarget) {
|
|
@@ -51,6 +56,7 @@ export default class extends Controller {
|
|
|
51
56
|
|
|
52
57
|
collapse() {
|
|
53
58
|
this.menuTarget.classList.add("hidden")
|
|
59
|
+
this.menuTarget.style.display = "none"
|
|
54
60
|
this.buttonTarget.setAttribute("aria-expanded", "false")
|
|
55
61
|
|
|
56
62
|
if (this.hasIconTarget) {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared RSpec configuration for all Panda gems
|
|
4
|
+
# This file provides common test infrastructure that can be extended by individual gems
|
|
5
|
+
#
|
|
6
|
+
# IMPORTANT: This file should be required AFTER the dummy app is loaded
|
|
7
|
+
# Individual gem rails_helper files should:
|
|
8
|
+
# 1. Set up SimpleCov
|
|
9
|
+
# 2. Require panda/core and the dummy app
|
|
10
|
+
# 3. Require this file
|
|
11
|
+
# 4. Load gem-specific support files
|
|
12
|
+
|
|
13
|
+
# Require common test gems (assumes Rails and RSpec/Rails are already loaded by gem's rails_helper)
|
|
14
|
+
require "database_cleaner/active_record"
|
|
15
|
+
require "shoulda/matchers"
|
|
16
|
+
require "capybara"
|
|
17
|
+
require "capybara/rspec"
|
|
18
|
+
require "puma"
|
|
19
|
+
|
|
20
|
+
# Configure fixtures
|
|
21
|
+
ActiveRecord::FixtureSet.context_class.send :include, ActiveSupport::Testing::TimeHelpers
|
|
22
|
+
|
|
23
|
+
# Shoulda Matchers configuration
|
|
24
|
+
Shoulda::Matchers.configure do |config|
|
|
25
|
+
config.integrate do |with|
|
|
26
|
+
with.test_framework :rspec
|
|
27
|
+
with.library :rails
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Load all support files from panda-core
|
|
32
|
+
# Files are now in lib/panda/core/testing/support/ to be included in the published gem
|
|
33
|
+
support_path = File.expand_path("../support", __FILE__)
|
|
34
|
+
Dir[File.join(support_path, "**/*.rb")].sort.each { |f| require f }
|
|
35
|
+
|
|
36
|
+
RSpec.configure do |config|
|
|
37
|
+
# Include panda-core route helpers by default
|
|
38
|
+
config.include Panda::Core::Engine.routes.url_helpers if defined?(Panda::Core::Engine)
|
|
39
|
+
config.include Rails.application.routes.url_helpers
|
|
40
|
+
|
|
41
|
+
# Standard RSpec configuration
|
|
42
|
+
config.use_transactional_fixtures = true
|
|
43
|
+
config.infer_spec_type_from_file_location!
|
|
44
|
+
config.filter_rails_from_backtrace!
|
|
45
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
|
46
|
+
config.disable_monkey_patching!
|
|
47
|
+
|
|
48
|
+
# Print total example count before running
|
|
49
|
+
config.before(:suite) do
|
|
50
|
+
puts "Total examples to run: #{RSpec.world.example_count}\n"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Use specific formatter for GitHub Actions
|
|
54
|
+
if ENV["GITHUB_ACTIONS"] == "true"
|
|
55
|
+
require "rspec/github"
|
|
56
|
+
config.add_formatter RSpec::Github::Formatter
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Controller testing support (if rails-controller-testing is available)
|
|
60
|
+
if defined?(Rails::Controller::Testing)
|
|
61
|
+
config.include Rails::Controller::Testing::TestProcess, type: :controller
|
|
62
|
+
config.include Rails::Controller::Testing::TemplateAssertions, type: :controller
|
|
63
|
+
config.include Rails::Controller::Testing::Integration, type: :controller
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Reset column information before suite
|
|
67
|
+
config.before(:suite) do
|
|
68
|
+
if defined?(Panda::Core::User)
|
|
69
|
+
Panda::Core::User.connection.schema_cache.clear!
|
|
70
|
+
Panda::Core::User.reset_column_information
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Disable prepared statements for PostgreSQL tests to avoid caching issues
|
|
75
|
+
config.before(:suite) do
|
|
76
|
+
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
|
77
|
+
ActiveRecord::Base.connection.unprepared_statement do
|
|
78
|
+
# This block intentionally left empty
|
|
79
|
+
end
|
|
80
|
+
# Disable prepared statements globally for test environment
|
|
81
|
+
ActiveRecord::Base.establish_connection(
|
|
82
|
+
ActiveRecord::Base.connection_db_config.configuration_hash.merge(prepared_statements: false)
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Disable logging during tests
|
|
88
|
+
config.before(:suite) do
|
|
89
|
+
Rails.logger.level = Logger::ERROR
|
|
90
|
+
ActiveRecord::Base.logger = nil if defined?(ActiveRecord)
|
|
91
|
+
ActionController::Base.logger = nil if defined?(ActionController)
|
|
92
|
+
ActionMailer::Base.logger = nil if defined?(ActionMailer)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Suppress Rails command output during generator tests
|
|
96
|
+
config.before(:each, type: :generator) do
|
|
97
|
+
allow(Rails::Command).to receive(:invoke).and_return(true)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Force all examples to run
|
|
101
|
+
config.filter_run_including({})
|
|
102
|
+
config.run_all_when_everything_filtered = true
|
|
103
|
+
|
|
104
|
+
# Allow using focus keywords "f... before a specific test"
|
|
105
|
+
config.filter_run_when_matching :focus
|
|
106
|
+
|
|
107
|
+
# Retry flaky tests automatically
|
|
108
|
+
# This is especially useful for system tests that may have timing issues
|
|
109
|
+
config.around(:each, :flaky) do |example|
|
|
110
|
+
retry_count = example.metadata[:retry] || 3
|
|
111
|
+
retry_count.times do |i|
|
|
112
|
+
example.run
|
|
113
|
+
break unless example.exception
|
|
114
|
+
|
|
115
|
+
if i < retry_count - 1
|
|
116
|
+
puts "\n[RETRY] Test failed, retrying... (attempt #{i + 2}/#{retry_count})"
|
|
117
|
+
puts "[RETRY] Exception: #{example.exception.class.name}: #{example.exception.message[0..100]}"
|
|
118
|
+
example.instance_variable_set(:@exception, nil)
|
|
119
|
+
sleep 1 # Brief pause between retries
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Exclude EditorJS tests by default unless specifically requested
|
|
125
|
+
config.filter_run_excluding :editorjs unless ENV["INCLUDE_EDITORJS"] == "true"
|
|
126
|
+
|
|
127
|
+
# Use verbose output if only running one spec file
|
|
128
|
+
config.default_formatter = "doc" if config.files_to_run.one?
|
|
129
|
+
|
|
130
|
+
# Print the 10 slowest examples and example groups at the
|
|
131
|
+
# end of the spec run, to help surface which specs are running
|
|
132
|
+
# particularly slow.
|
|
133
|
+
config.profile_examples = 10
|
|
134
|
+
|
|
135
|
+
# Run specs in random order to surface order dependencies. If you find an
|
|
136
|
+
# order dependency and want to debug it, you can fix the order by providing
|
|
137
|
+
# the seed, which is printed after each run: --seed 1234
|
|
138
|
+
Kernel.srand config.seed
|
|
139
|
+
config.order = :random
|
|
140
|
+
|
|
141
|
+
# Note: Fixture configuration is gem-specific and should be done in each gem's rails_helper.rb
|
|
142
|
+
# This includes config.fixture_paths and config.global_fixtures
|
|
143
|
+
|
|
144
|
+
# Bullet configuration (if available)
|
|
145
|
+
if defined?(Bullet) && Bullet.enable?
|
|
146
|
+
config.before(:each) do
|
|
147
|
+
Bullet.start_request
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
config.after(:each) do
|
|
151
|
+
Bullet.perform_out_of_channel_notifications if Bullet.notification?
|
|
152
|
+
Bullet.end_request
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# OmniAuth test mode
|
|
157
|
+
OmniAuth.config.test_mode = true if defined?(OmniAuth)
|
|
158
|
+
|
|
159
|
+
# DatabaseCleaner configuration
|
|
160
|
+
config.before(:suite) do
|
|
161
|
+
# Allow DATABASE_URL in CI environment
|
|
162
|
+
if ENV["DATABASE_URL"]
|
|
163
|
+
DatabaseCleaner.allow_remote_database_url = true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
DatabaseCleaner.clean_with :truncation
|
|
167
|
+
|
|
168
|
+
# Hook for gems to add custom suite setup
|
|
169
|
+
# Gems can define Panda::Testing.before_suite_hook and it will be called here
|
|
170
|
+
if defined?(Panda::Testing) && Panda::Testing.respond_to?(:before_suite_hook)
|
|
171
|
+
Panda::Testing.before_suite_hook
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module AuthenticationHelpers
|
|
6
|
+
# Create test users with fixed IDs for consistent fixture references
|
|
7
|
+
def create_admin_user
|
|
8
|
+
Panda::Core::User.find_or_create_by!(id: "8f481fcb-d9c8-55d7-ba17-5ea5d9ed8b7a") do |user|
|
|
9
|
+
user.email = "admin@test.example.com"
|
|
10
|
+
user.firstname = "Admin"
|
|
11
|
+
user.lastname = "User"
|
|
12
|
+
user.admin = true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_regular_user
|
|
17
|
+
Panda::Core::User.find_or_create_by!(id: "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d") do |user|
|
|
18
|
+
user.email = "user@test.example.com"
|
|
19
|
+
user.firstname = "Regular"
|
|
20
|
+
user.lastname = "User"
|
|
21
|
+
user.admin = false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# For request specs - set session directly
|
|
26
|
+
def sign_in_as(user)
|
|
27
|
+
session[:user_id] = user.id
|
|
28
|
+
Panda::Core::Current.user = user
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# For system specs - use test session endpoint if available
|
|
32
|
+
def login_as_admin
|
|
33
|
+
admin_user = create_admin_user
|
|
34
|
+
if defined?(Panda::CMS)
|
|
35
|
+
# CMS provides test session endpoint
|
|
36
|
+
post "/admin/test_sessions", params: {user_id: admin_user.id}
|
|
37
|
+
else
|
|
38
|
+
# Fall back to direct session setting
|
|
39
|
+
sign_in_as(admin_user)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def login_as_regular_user
|
|
44
|
+
regular_user = create_regular_user
|
|
45
|
+
if defined?(Panda::CMS)
|
|
46
|
+
post "/admin/test_sessions", params: {user_id: regular_user.id}
|
|
47
|
+
else
|
|
48
|
+
sign_in_as(regular_user)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
RSpec.configure do |config|
|
|
56
|
+
# Include authentication helpers for all spec types (model, request, system, component, etc.)
|
|
57
|
+
config.include Panda::Core::AuthenticationHelpers
|
|
58
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Comprehensive authentication test helpers for Panda Core and consuming gems
|
|
4
|
+
# This module provides helpers for:
|
|
5
|
+
# - Creating test users with fixed IDs
|
|
6
|
+
# - OAuth mocking and configuration
|
|
7
|
+
# - Test session management
|
|
8
|
+
# - Request and system test authentication
|
|
9
|
+
|
|
10
|
+
module Panda
|
|
11
|
+
module Core
|
|
12
|
+
module AuthenticationTestHelpers
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# USER CREATION HELPERS
|
|
15
|
+
# ============================================================================
|
|
16
|
+
|
|
17
|
+
# Create an admin user with fixed ID for consistent fixture references
|
|
18
|
+
def create_admin_user(attributes = {})
|
|
19
|
+
ensure_columns_loaded
|
|
20
|
+
admin_id = "8f481fcb-d9c8-55d7-ba17-5ea5d9ed8b7a"
|
|
21
|
+
Panda::Core::User.find_or_create_by!(id: admin_id) do |user|
|
|
22
|
+
user.email = attributes[:email] || "admin@example.com"
|
|
23
|
+
user.firstname = attributes[:firstname] || "Admin" if user.respond_to?(:firstname=)
|
|
24
|
+
user.lastname = attributes[:lastname] || "User" if user.respond_to?(:lastname=)
|
|
25
|
+
user.name = attributes[:name] || "Admin User" if user.respond_to?(:name=) && !user.respond_to?(:firstname=)
|
|
26
|
+
user.image_url = attributes[:image_url] || default_image_url if user.respond_to?(:image_url=)
|
|
27
|
+
# Use is_admin for the actual column, but support both for compatibility
|
|
28
|
+
if user.respond_to?(:is_admin=)
|
|
29
|
+
user.is_admin = attributes.fetch(:admin, true)
|
|
30
|
+
elsif user.respond_to?(:admin=)
|
|
31
|
+
user.admin = attributes.fetch(:admin, true)
|
|
32
|
+
end
|
|
33
|
+
# Only set OAuth fields if they exist on the model
|
|
34
|
+
user.uid = attributes[:uid] || "admin_oauth_uid_123" if user.respond_to?(:uid=)
|
|
35
|
+
user.provider = attributes[:provider] || "google_oauth2" if user.respond_to?(:provider=)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Create a regular user with fixed ID for consistent fixture references
|
|
40
|
+
def create_regular_user(attributes = {})
|
|
41
|
+
ensure_columns_loaded
|
|
42
|
+
regular_id = "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
|
|
43
|
+
Panda::Core::User.find_or_create_by!(id: regular_id) do |user|
|
|
44
|
+
user.email = attributes[:email] || "user@example.com"
|
|
45
|
+
user.firstname = attributes[:firstname] || "Regular" if user.respond_to?(:firstname=)
|
|
46
|
+
user.lastname = attributes[:lastname] || "User" if user.respond_to?(:lastname=)
|
|
47
|
+
user.name = attributes[:name] || "Regular User" if user.respond_to?(:name=) && !user.respond_to?(:firstname=)
|
|
48
|
+
user.image_url = attributes[:image_url] || default_image_url(dark: true) if user.respond_to?(:image_url=)
|
|
49
|
+
# Use is_admin for the actual column, but support both for compatibility
|
|
50
|
+
if user.respond_to?(:is_admin=)
|
|
51
|
+
user.is_admin = attributes.fetch(:admin, false)
|
|
52
|
+
elsif user.respond_to?(:admin=)
|
|
53
|
+
user.admin = attributes.fetch(:admin, false)
|
|
54
|
+
end
|
|
55
|
+
# Only set OAuth fields if they exist on the model
|
|
56
|
+
user.uid = attributes[:uid] || "user_oauth_uid_456" if user.respond_to?(:uid=)
|
|
57
|
+
user.provider = attributes[:provider] || "google_oauth2" if user.respond_to?(:provider=)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Backwards compatibility with fixture access patterns
|
|
62
|
+
def admin_user
|
|
63
|
+
ensure_columns_loaded
|
|
64
|
+
@admin_user ||= Panda::Core::User.find_by(email: "admin@example.com") || create_admin_user
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def regular_user
|
|
68
|
+
ensure_columns_loaded
|
|
69
|
+
@regular_user ||= Panda::Core::User.find_by(email: "user@example.com") || create_regular_user
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ============================================================================
|
|
73
|
+
# OMNIAUTH HELPERS
|
|
74
|
+
# ============================================================================
|
|
75
|
+
|
|
76
|
+
def clear_omniauth_config
|
|
77
|
+
OmniAuth.config.mock_auth.clear
|
|
78
|
+
Rails.application.env_config.delete("omniauth.auth") if defined?(Rails.application)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def mock_oauth_for_user(user, provider: :google_oauth2)
|
|
82
|
+
clear_omniauth_config
|
|
83
|
+
OmniAuth.config.test_mode = true
|
|
84
|
+
OmniAuth.config.mock_auth[provider] = OmniAuth::AuthHash.new({
|
|
85
|
+
provider: provider.to_s,
|
|
86
|
+
uid: (user.respond_to?(:uid) ? user.uid : nil) || user.id,
|
|
87
|
+
info: {
|
|
88
|
+
email: user.email,
|
|
89
|
+
name: user.name,
|
|
90
|
+
image: user.respond_to?(:image_url) ? user.image_url : nil
|
|
91
|
+
},
|
|
92
|
+
credentials: {
|
|
93
|
+
token: "mock_token_#{user.id}",
|
|
94
|
+
expires_at: Time.now + 1.week
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# ============================================================================
|
|
100
|
+
# REQUEST SPEC HELPERS (Direct Session Manipulation)
|
|
101
|
+
# ============================================================================
|
|
102
|
+
|
|
103
|
+
# For request specs - set session directly (fast, no HTTP requests)
|
|
104
|
+
def sign_in_as(user)
|
|
105
|
+
# This works in request specs where we have direct access to the session
|
|
106
|
+
begin
|
|
107
|
+
if respond_to?(:session)
|
|
108
|
+
session[:user_id] = user.id
|
|
109
|
+
end
|
|
110
|
+
rescue NoMethodError
|
|
111
|
+
# Session method doesn't exist in this context (e.g., system specs)
|
|
112
|
+
end
|
|
113
|
+
Panda::Core::Current.user = user
|
|
114
|
+
user
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ============================================================================
|
|
118
|
+
# SYSTEM SPEC HELPERS (HTTP-based Authentication)
|
|
119
|
+
# ============================================================================
|
|
120
|
+
|
|
121
|
+
# For system specs - use test session endpoint (works across processes)
|
|
122
|
+
# This uses the TestSessionsController which is only available in test environment
|
|
123
|
+
#
|
|
124
|
+
# NOTE: Due to Cuprite's redirect handling, we visit the target path directly
|
|
125
|
+
# after setting up the session via the test endpoint. Flash messages won't be
|
|
126
|
+
# available in system tests due to cross-process timing. Use request specs
|
|
127
|
+
# to test flash messages (see authentication_request_spec.rb).
|
|
128
|
+
def login_with_test_endpoint(user, return_to: nil, expect_success: true)
|
|
129
|
+
return_path = return_to || determine_default_redirect_path
|
|
130
|
+
|
|
131
|
+
# Visit the test login endpoint (sets session via Redis)
|
|
132
|
+
# Note: Capybara/Cuprite may not follow the redirect properly, so we
|
|
133
|
+
# manually navigate to the expected destination
|
|
134
|
+
visit "/admin/test_login/#{user.id}?return_to=#{return_path}"
|
|
135
|
+
|
|
136
|
+
# Wait briefly for session to be set
|
|
137
|
+
sleep 0.2
|
|
138
|
+
|
|
139
|
+
# Manually visit the destination since Cuprite doesn't reliably follow redirects
|
|
140
|
+
if expect_success
|
|
141
|
+
visit return_path
|
|
142
|
+
# Wait for page to load
|
|
143
|
+
sleep 0.2
|
|
144
|
+
|
|
145
|
+
# Verify we're on the expected path
|
|
146
|
+
expect(page).to have_current_path(return_path, wait: 2)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Convenience method: Login with Google OAuth provider (using test endpoint)
|
|
151
|
+
def login_with_google(user, expect_success: true)
|
|
152
|
+
login_with_test_endpoint(user, return_to: determine_default_redirect_path, expect_success: expect_success)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Convenience method: Login with GitHub OAuth provider (using test endpoint)
|
|
156
|
+
def login_with_github(user, expect_success: true)
|
|
157
|
+
login_with_test_endpoint(user, return_to: determine_default_redirect_path, expect_success: expect_success)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Convenience method: Login with Microsoft OAuth provider (using test endpoint)
|
|
161
|
+
def login_with_microsoft(user, expect_success: true)
|
|
162
|
+
login_with_test_endpoint(user, return_to: determine_default_redirect_path, expect_success: expect_success)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# High-level helper: Login as admin (creates user if needed)
|
|
166
|
+
def login_as_admin(attributes = {})
|
|
167
|
+
user = create_admin_user(attributes)
|
|
168
|
+
if respond_to?(:visit)
|
|
169
|
+
# System spec - use test endpoint
|
|
170
|
+
login_with_test_endpoint(user, expect_success: true)
|
|
171
|
+
else
|
|
172
|
+
# Request spec - use direct session
|
|
173
|
+
sign_in_as(user)
|
|
174
|
+
end
|
|
175
|
+
user
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# High-level helper: Login as regular user (creates user if needed)
|
|
179
|
+
def login_as_user(attributes = {})
|
|
180
|
+
user = create_regular_user(attributes)
|
|
181
|
+
if respond_to?(:visit)
|
|
182
|
+
# System spec - regular users get redirected to login
|
|
183
|
+
login_with_test_endpoint(user, expect_success: false)
|
|
184
|
+
else
|
|
185
|
+
# Request spec - use direct session
|
|
186
|
+
sign_in_as(user)
|
|
187
|
+
end
|
|
188
|
+
user
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Manual OAuth login (slower, but tests actual OAuth flow)
|
|
192
|
+
# Use this when you need to test the OAuth callback handler itself
|
|
193
|
+
def manual_login_with_oauth(user, provider: :google_oauth2)
|
|
194
|
+
mock_oauth_for_user(user, provider: provider)
|
|
195
|
+
|
|
196
|
+
visit admin_login_path
|
|
197
|
+
expect(page).to have_css("#button-sign-in-#{provider}")
|
|
198
|
+
find("#button-sign-in-#{provider}").click
|
|
199
|
+
|
|
200
|
+
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[provider]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ============================================================================
|
|
204
|
+
# PRIVATE HELPER METHODS
|
|
205
|
+
# ============================================================================
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def ensure_columns_loaded
|
|
210
|
+
return if @columns_loaded
|
|
211
|
+
Panda::Core::User.connection.schema_cache.clear!
|
|
212
|
+
Panda::Core::User.reset_column_information
|
|
213
|
+
@columns_loaded = true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def default_image_url(dark: false)
|
|
217
|
+
color = dark ? "%23999" : "%23ccc"
|
|
218
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Crect width='100' height='100' fill='#{color}'/%3E%3C/svg%3E"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def determine_default_redirect_path
|
|
222
|
+
# Check if we're in a CMS context
|
|
223
|
+
if defined?(Panda::CMS)
|
|
224
|
+
"/admin/cms"
|
|
225
|
+
else
|
|
226
|
+
"/admin"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def admin_login_path
|
|
231
|
+
if defined?(panda_core)
|
|
232
|
+
panda_core.admin_login_path
|
|
233
|
+
else
|
|
234
|
+
"/admin/login"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def admin_root_path
|
|
239
|
+
if defined?(panda_core)
|
|
240
|
+
panda_core.admin_root_path
|
|
241
|
+
else
|
|
242
|
+
"/admin"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Configure RSpec to include these helpers in appropriate test types
|
|
250
|
+
RSpec.configure do |config|
|
|
251
|
+
config.include Panda::Core::AuthenticationTestHelpers, type: :request
|
|
252
|
+
config.include Panda::Core::AuthenticationTestHelpers, type: :system
|
|
253
|
+
config.include Panda::Core::AuthenticationTestHelpers, type: :controller
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Configure OmniAuth for testing
|
|
257
|
+
OmniAuth.config.test_mode = true
|
|
258
|
+
OmniAuth.config.on_failure = proc { |env|
|
|
259
|
+
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
|
|
260
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module GeneratorSpecHelper
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before(:each) do
|
|
8
|
+
prepare_destination
|
|
9
|
+
@original_stdout = $stdout
|
|
10
|
+
$stdout = File.new(File::NULL, "w")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after(:each) do
|
|
14
|
+
FileUtils.rm_rf(destination_root)
|
|
15
|
+
$stdout = @original_stdout
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def destination_root
|
|
20
|
+
@destination_root ||= File.expand_path("../../tmp/generators", __dir__)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def prepare_destination
|
|
24
|
+
FileUtils.rm_rf(destination_root)
|
|
25
|
+
FileUtils.mkdir_p(destination_root)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run_generator(args = [])
|
|
29
|
+
args = Array(args)
|
|
30
|
+
# Use the generator namespace instead of class name
|
|
31
|
+
generator_name = described_class.namespace
|
|
32
|
+
Rails::Generators.invoke(generator_name, args, destination_root: destination_root)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generator
|
|
36
|
+
@generator ||= described_class.new([], destination_root: destination_root)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def file_exists?(path)
|
|
40
|
+
File.exist?(File.join(destination_root, path))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read_file(path)
|
|
44
|
+
File.read(File.join(destination_root, path))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def capture(stream)
|
|
50
|
+
stream = stream.to_s
|
|
51
|
+
captured_stream = StringIO.new
|
|
52
|
+
|
|
53
|
+
# Map stream names to their global variables to avoid eval
|
|
54
|
+
streams = {
|
|
55
|
+
"stdout" => $stdout,
|
|
56
|
+
"stderr" => $stderr,
|
|
57
|
+
"stdin" => $stdin
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
original_stream = streams[stream]
|
|
61
|
+
case stream
|
|
62
|
+
when "stdout"
|
|
63
|
+
$stdout = captured_stream
|
|
64
|
+
when "stderr"
|
|
65
|
+
$stderr = captured_stream
|
|
66
|
+
when "stdin"
|
|
67
|
+
$stdin = captured_stream
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, "Unsupported stream: #{stream}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
yield
|
|
73
|
+
captured_stream.string
|
|
74
|
+
ensure
|
|
75
|
+
case stream
|
|
76
|
+
when "stdout"
|
|
77
|
+
$stdout = original_stream
|
|
78
|
+
when "stderr"
|
|
79
|
+
$stderr = original_stream
|
|
80
|
+
when "stdin"
|
|
81
|
+
$stdin = original_stream
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
RSpec.configure do |config|
|
|
87
|
+
config.include GeneratorSpecHelper, type: :generator
|
|
88
|
+
|
|
89
|
+
# Ensure generator tests have a clean environment
|
|
90
|
+
config.before(:each, type: :generator) do
|
|
91
|
+
prepare_destination
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
config.after(:each, type: :generator) do
|
|
95
|
+
FileUtils.rm_rf(destination_root) if defined?(destination_root)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Configure authentication providers for tests
|
|
4
|
+
RSpec.configure do |config|
|
|
5
|
+
config.before(:each, type: :controller) do
|
|
6
|
+
# Set up test authentication providers
|
|
7
|
+
Panda::Core.configure do |core_config|
|
|
8
|
+
core_config.authentication_providers = {
|
|
9
|
+
google_oauth2: {
|
|
10
|
+
client_id: "test_client_id",
|
|
11
|
+
client_secret: "test_client_secret",
|
|
12
|
+
options: {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
# Stub services that make external HTTP requests to prevent them in tests
|
|
7
|
+
RSpec.configure do |config|
|
|
8
|
+
config.before(:each) do
|
|
9
|
+
# Stub URI.open to prevent external HTTP requests when downloading avatars
|
|
10
|
+
# This affects AttachAvatarService which downloads avatars from OAuth providers
|
|
11
|
+
# Tests that specifically need real HTTP requests can override this stub
|
|
12
|
+
allow(URI).to receive(:open).and_wrap_original do |original_method, *args, **kwargs, &block|
|
|
13
|
+
url = args[0]
|
|
14
|
+
|
|
15
|
+
# Only stub avatar downloads from OAuth providers and test URLs
|
|
16
|
+
if /googleusercontent\.com|githubusercontent\.com|graph\.microsoft\.com|example\.com/.match?(url.to_s)
|
|
17
|
+
# Use the actual test fixture file instead of creating a fake file
|
|
18
|
+
# This ensures compatibility with Active Storage
|
|
19
|
+
fixture_path = Panda::Core::Engine.root.join("spec", "fixtures", "files", "test_image.jpg")
|
|
20
|
+
downloaded_file = File.open(fixture_path, "rb")
|
|
21
|
+
|
|
22
|
+
# Add methods that AttachAvatarService expects
|
|
23
|
+
downloaded_file.define_singleton_method(:content_type) { "image/jpeg" }
|
|
24
|
+
downloaded_file.define_singleton_method(:size) { File.size(fixture_path) }
|
|
25
|
+
|
|
26
|
+
# Call the block with our file if a block is given
|
|
27
|
+
if block_given?
|
|
28
|
+
result = block.call(downloaded_file)
|
|
29
|
+
downloaded_file.close unless downloaded_file.closed?
|
|
30
|
+
result
|
|
31
|
+
else
|
|
32
|
+
downloaded_file
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
# For other URLs, use the original method
|
|
36
|
+
original_method.call(*args, **kwargs, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/panda/core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: panda-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Otaina Limited
|
|
@@ -470,6 +470,14 @@ files:
|
|
|
470
470
|
- lib/panda/core/services/base_service.rb
|
|
471
471
|
- lib/panda/core/sluggable.rb
|
|
472
472
|
- lib/panda/core/subscribers/authentication_subscriber.rb
|
|
473
|
+
- lib/panda/core/testing/rails_helper.rb
|
|
474
|
+
- lib/panda/core/testing/support/authentication_helpers.rb
|
|
475
|
+
- lib/panda/core/testing/support/authentication_test_helpers.rb
|
|
476
|
+
- lib/panda/core/testing/support/generator_spec_helper.rb
|
|
477
|
+
- lib/panda/core/testing/support/html_helpers.rb
|
|
478
|
+
- lib/panda/core/testing/support/omniauth_setup.rb
|
|
479
|
+
- lib/panda/core/testing/support/service_stubs.rb
|
|
480
|
+
- lib/panda/core/testing/support/setup.rb
|
|
473
481
|
- lib/panda/core/version.rb
|
|
474
482
|
- lib/tasks/assets.rake
|
|
475
483
|
- lib/tasks/panda/core/migrations.rake
|