propel_authentication 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +290 -0
- data/Rakefile +12 -0
- data/lib/generators/propel_auth/install_generator.rb +486 -0
- data/lib/generators/propel_auth/pack_generator.rb +277 -0
- data/lib/generators/propel_auth/templates/agency.rb +7 -0
- data/lib/generators/propel_auth/templates/agent.rb +7 -0
- data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +99 -0
- data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +90 -0
- data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +126 -0
- data/lib/generators/propel_auth/templates/auth_mailer.rb +180 -0
- data/lib/generators/propel_auth/templates/authenticatable.rb +38 -0
- data/lib/generators/propel_auth/templates/concerns/confirmable.rb +145 -0
- data/lib/generators/propel_auth/templates/concerns/lockable.rb +123 -0
- data/lib/generators/propel_auth/templates/concerns/propel_authentication.rb +44 -0
- data/lib/generators/propel_auth/templates/concerns/rack_session_disable.rb +19 -0
- data/lib/generators/propel_auth/templates/concerns/recoverable.rb +124 -0
- data/lib/generators/propel_auth/templates/config/environments/development_email.rb +43 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_agencies.rb +20 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_agents.rb +11 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_invitations.rb +28 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_organizations.rb +18 -0
- data/lib/generators/propel_auth/templates/db/migrate/create_users.rb +43 -0
- data/lib/generators/propel_auth/templates/db/seeds.rb +29 -0
- data/lib/generators/propel_auth/templates/invitation.rb +133 -0
- data/lib/generators/propel_auth/templates/lib/propel_auth.rb +84 -0
- data/lib/generators/propel_auth/templates/organization.rb +7 -0
- data/lib/generators/propel_auth/templates/propel_auth.rb +132 -0
- data/lib/generators/propel_auth/templates/services/auth_notification_service.rb +89 -0
- data/lib/generators/propel_auth/templates/test/concerns/confirmable_test.rb.tt +247 -0
- data/lib/generators/propel_auth/templates/test/concerns/lockable_test.rb.tt +282 -0
- data/lib/generators/propel_auth/templates/test/concerns/propel_authentication_test.rb.tt +75 -0
- data/lib/generators/propel_auth/templates/test/concerns/recoverable_test.rb.tt +327 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/lockable_integration_test.rb.tt +196 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/password_reset_integration_test.rb.tt +471 -0
- data/lib/generators/propel_auth/templates/test/controllers/auth/tokens_controller_test.rb.tt +265 -0
- data/lib/generators/propel_auth/templates/test/mailers/auth_mailer_test.rb.tt +216 -0
- data/lib/generators/propel_auth/templates/test/mailers/previews/auth_mailer_preview.rb +161 -0
- data/lib/generators/propel_auth/templates/tokens_controller.rb.tt +96 -0
- data/lib/generators/propel_auth/templates/user.rb +21 -0
- data/lib/generators/propel_auth/templates/user_test.rb.tt +81 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.html.erb +213 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.text.erb +56 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.html.erb +213 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.text.erb +32 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.html.erb +166 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.text.erb +32 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.html.erb +194 -0
- data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.text.erb +51 -0
- data/lib/generators/propel_auth/test/dummy/Dockerfile +72 -0
- data/lib/generators/propel_auth/test/dummy/Gemfile +63 -0
- data/lib/generators/propel_auth/test/dummy/Gemfile.lock +394 -0
- data/lib/generators/propel_auth/test/dummy/README.md +24 -0
- data/lib/generators/propel_auth/test/dummy/Rakefile +6 -0
- data/lib/generators/propel_auth/test/dummy/app/assets/stylesheets/application.css +10 -0
- data/lib/generators/propel_auth/test/dummy/app/controllers/application_controller.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/app/helpers/application_helper.rb +2 -0
- data/lib/generators/propel_auth/test/dummy/app/jobs/application_job.rb +7 -0
- data/lib/generators/propel_auth/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/app/models/application_record.rb +3 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/application.html.erb +27 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/lib/generators/propel_auth/test/dummy/app/views/pwa/manifest.json.erb +22 -0
- data/lib/generators/propel_auth/test/dummy/app/views/pwa/service-worker.js +26 -0
- data/lib/generators/propel_auth/test/dummy/bin/brakeman +7 -0
- data/lib/generators/propel_auth/test/dummy/bin/dev +2 -0
- data/lib/generators/propel_auth/test/dummy/bin/docker-entrypoint +14 -0
- data/lib/generators/propel_auth/test/dummy/bin/rails +4 -0
- data/lib/generators/propel_auth/test/dummy/bin/rake +4 -0
- data/lib/generators/propel_auth/test/dummy/bin/rubocop +8 -0
- data/lib/generators/propel_auth/test/dummy/bin/setup +34 -0
- data/lib/generators/propel_auth/test/dummy/bin/thrust +5 -0
- data/lib/generators/propel_auth/test/dummy/config/application.rb +42 -0
- data/lib/generators/propel_auth/test/dummy/config/boot.rb +4 -0
- data/lib/generators/propel_auth/test/dummy/config/cable.yml +10 -0
- data/lib/generators/propel_auth/test/dummy/config/credentials.yml.enc +1 -0
- data/lib/generators/propel_auth/test/dummy/config/database.yml +41 -0
- data/lib/generators/propel_auth/test/dummy/config/environment.rb +5 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/development.rb +72 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/production.rb +89 -0
- data/lib/generators/propel_auth/test/dummy/config/environments/test.rb +53 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/assets.rb +10 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/content_security_policy.rb +25 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/lib/generators/propel_auth/test/dummy/config/initializers/inflections.rb +16 -0
- data/lib/generators/propel_auth/test/dummy/config/locales/en.yml +31 -0
- data/lib/generators/propel_auth/test/dummy/config/master.key +1 -0
- data/lib/generators/propel_auth/test/dummy/config/puma.rb +41 -0
- data/lib/generators/propel_auth/test/dummy/config/routes.rb +2 -0
- data/lib/generators/propel_auth/test/dummy/config/storage.yml +34 -0
- data/lib/generators/propel_auth/test/dummy/config.ru +6 -0
- data/lib/generators/propel_auth/test/dummy/db/schema.rb +14 -0
- data/lib/generators/propel_auth/test/generators/authentication/controllers/tokens_controller_test.rb +230 -0
- data/lib/generators/propel_auth/test/generators/authentication/install_generator_test.rb +490 -0
- data/lib/generators/propel_auth/test/generators/authentication/uninstall_generator_test.rb +408 -0
- data/lib/generators/propel_auth/test/integration/generator_integration_test.rb +158 -0
- data/lib/generators/propel_auth/test/integration/multi_version_generator_test.rb +125 -0
- data/lib/generators/propel_auth/unpack_generator.rb +345 -0
- data/lib/propel_auth.rb +3 -0
- metadata +195 -0
@@ -0,0 +1,408 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "rails/generators/test_case"
|
3
|
+
require_relative '../../../lib/generators/propel/authentication/install_generator'
|
4
|
+
|
5
|
+
class Propel::Authentication::UninstallGeneratorTest < Rails::Generators::TestCase
|
6
|
+
tests Propel::Authentication::InstallGenerator
|
7
|
+
|
8
|
+
def setup
|
9
|
+
self.destination_root = Rails.root.join("tmp/generators", "uninstall_#{Process.pid}")
|
10
|
+
prepare_destination
|
11
|
+
prepare_rails_app_structure
|
12
|
+
|
13
|
+
# First install the authentication system to have something to destroy
|
14
|
+
run_generator
|
15
|
+
|
16
|
+
# Mock database operations to avoid real database changes in tests
|
17
|
+
@original_system_method = Object.method(:system)
|
18
|
+
|
19
|
+
Object.define_singleton_method(:system) do |cmd|
|
20
|
+
if cmd.include?('rails db:rollback')
|
21
|
+
# Simulate successful rollback without actual database access
|
22
|
+
# This prevents production database connection attempts
|
23
|
+
puts "Simulated: #{cmd}"
|
24
|
+
true
|
25
|
+
else
|
26
|
+
@original_system_method.call(cmd)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def teardown
|
32
|
+
# Restore original system method
|
33
|
+
Object.define_singleton_method(:system, @original_system_method) if @original_system_method
|
34
|
+
FileUtils.rm_rf(destination_root)
|
35
|
+
end
|
36
|
+
|
37
|
+
# DESTROY FUNCTIONALITY TESTS
|
38
|
+
|
39
|
+
test "generator destroy shows custom options in help documentation" do
|
40
|
+
help_output = capture(:stdout) do
|
41
|
+
run_generator ["--help"]
|
42
|
+
end
|
43
|
+
|
44
|
+
assert_match(/--force-rollback.*Force rollback migrations in any environment/, help_output)
|
45
|
+
assert_match(/--delete-migrations.*Delete migration files in development/, help_output)
|
46
|
+
assert_match(/--force-cleanup.*Force cleanup migration files in any environment/, help_output)
|
47
|
+
end
|
48
|
+
|
49
|
+
test "generator destroy removes all generated files by default" do
|
50
|
+
# Verify files exist before destroy
|
51
|
+
assert_file "app/models/user.rb"
|
52
|
+
assert_file "app/models/concerns/authenticatable.rb"
|
53
|
+
assert_file "app/controllers/auth/tokens_controller.rb"
|
54
|
+
assert_file "config/initializers/propel_access.rb"
|
55
|
+
|
56
|
+
# Run destroy
|
57
|
+
run_generator [], { behavior: :revoke }
|
58
|
+
|
59
|
+
# Verify files are removed
|
60
|
+
assert_no_file "app/models/user.rb"
|
61
|
+
assert_no_file "app/models/organization.rb"
|
62
|
+
assert_no_file "app/models/agency.rb"
|
63
|
+
assert_no_file "app/models/agent.rb"
|
64
|
+
assert_no_file "app/models/invitation.rb"
|
65
|
+
assert_no_file "app/models/concerns/authenticatable.rb"
|
66
|
+
assert_no_file "app/models/concerns/lockable.rb"
|
67
|
+
assert_no_file "app/controllers/auth/tokens_controller.rb"
|
68
|
+
assert_no_file "app/controllers/concerns/propel_authentication.rb"
|
69
|
+
assert_no_file "config/initializers/propel_access.rb"
|
70
|
+
assert_no_file "config/initializers/propel_access_configuration.rb"
|
71
|
+
assert_no_file "db/seeds.rb"
|
72
|
+
end
|
73
|
+
|
74
|
+
test "generator destroy removes all generated test files" do
|
75
|
+
# Verify test files exist before destroy
|
76
|
+
assert_file "test/models/user_test.rb"
|
77
|
+
assert_file "test/controllers/auth/tokens_controller_test.rb"
|
78
|
+
assert_file "test/concerns/propel_authentication_test.rb"
|
79
|
+
assert_file "test/concerns/lockable_test.rb"
|
80
|
+
|
81
|
+
# Run destroy
|
82
|
+
run_generator [], { behavior: :revoke }
|
83
|
+
|
84
|
+
# Verify test files are removed
|
85
|
+
assert_no_file "test/models/user_test.rb"
|
86
|
+
assert_no_file "test/controllers/auth/tokens_controller_test.rb"
|
87
|
+
assert_no_file "test/concerns/propel_authentication_test.rb"
|
88
|
+
assert_no_file "test/concerns/lockable_test.rb"
|
89
|
+
assert_no_file "test/controllers/auth/lockable_integration_test.rb"
|
90
|
+
end
|
91
|
+
|
92
|
+
test "generator destroy removes all email infrastructure files" do
|
93
|
+
# Verify email infrastructure files exist before destroy
|
94
|
+
assert_file "app/mailers/auth_mailer.rb"
|
95
|
+
assert_file "app/services/auth_notification_service.rb"
|
96
|
+
assert_file "app/views/auth_mailer/password_reset.html.erb"
|
97
|
+
assert_file "app/views/auth_mailer/password_reset.text.erb"
|
98
|
+
assert_file "app/views/auth_mailer/account_unlock.html.erb"
|
99
|
+
assert_file "app/views/auth_mailer/account_unlock.text.erb"
|
100
|
+
assert_file "app/views/auth_mailer/user_invitation.html.erb"
|
101
|
+
assert_file "app/views/auth_mailer/user_invitation.text.erb"
|
102
|
+
assert_file "test/mailers/auth_mailer_test.rb"
|
103
|
+
|
104
|
+
# Run destroy
|
105
|
+
run_generator [], { behavior: :revoke }
|
106
|
+
|
107
|
+
# Verify all email infrastructure files are removed
|
108
|
+
assert_no_file "app/mailers/auth_mailer.rb"
|
109
|
+
assert_no_file "app/services/auth_notification_service.rb"
|
110
|
+
assert_no_file "app/views/auth_mailer/password_reset.html.erb"
|
111
|
+
assert_no_file "app/views/auth_mailer/password_reset.text.erb"
|
112
|
+
assert_no_file "app/views/auth_mailer/account_unlock.html.erb"
|
113
|
+
assert_no_file "app/views/auth_mailer/account_unlock.text.erb"
|
114
|
+
assert_no_file "app/views/auth_mailer/user_invitation.html.erb"
|
115
|
+
assert_no_file "app/views/auth_mailer/user_invitation.text.erb"
|
116
|
+
assert_no_file "test/mailers/auth_mailer_test.rb"
|
117
|
+
|
118
|
+
# Verify auth_mailer directory is removed
|
119
|
+
assert_no_directory "app/views/auth_mailer"
|
120
|
+
end
|
121
|
+
|
122
|
+
test "generator destroy cleans up empty directories after email infrastructure removal" do
|
123
|
+
# Create a scenario where services directory exists but becomes empty
|
124
|
+
# First, add a non-PropelAccess service to verify we don't delete non-empty directories
|
125
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/services"))
|
126
|
+
File.write(File.join(destination_root, "app/services/other_service.rb"), "class OtherService; end")
|
127
|
+
|
128
|
+
# Verify both services exist before destroy
|
129
|
+
assert_file "app/services/auth_notification_service.rb"
|
130
|
+
assert_file "app/services/other_service.rb"
|
131
|
+
|
132
|
+
# Run destroy
|
133
|
+
run_generator [], { behavior: :revoke }
|
134
|
+
|
135
|
+
# Verify only PropelAccess service is removed, directory preserved
|
136
|
+
assert_no_file "app/services/auth_notification_service.rb"
|
137
|
+
assert_file "app/services/other_service.rb"
|
138
|
+
assert_directory "app/services"
|
139
|
+
|
140
|
+
# Clean up other service to test empty directory removal
|
141
|
+
File.delete(File.join(destination_root, "app/services/other_service.rb"))
|
142
|
+
end
|
143
|
+
|
144
|
+
test "generator destroy handles missing email infrastructure files gracefully" do
|
145
|
+
# Remove some email files manually to simulate partial installation
|
146
|
+
FileUtils.rm_f(File.join(destination_root, "app/mailers/auth_mailer.rb"))
|
147
|
+
FileUtils.rm_rf(File.join(destination_root, "app/views/auth_mailer"))
|
148
|
+
|
149
|
+
# Verify files are missing
|
150
|
+
assert_no_file "app/mailers/auth_mailer.rb"
|
151
|
+
assert_no_directory "app/views/auth_mailer"
|
152
|
+
|
153
|
+
# Run destroy - should not error on missing files
|
154
|
+
assert_nothing_raised do
|
155
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
156
|
+
assert_match(/Cleaning up email and notification infrastructure/, output)
|
157
|
+
assert_match(/Cleaned up email and notification infrastructure/, output)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Verify remaining files are still cleaned up
|
161
|
+
assert_no_file "app/services/auth_notification_service.rb"
|
162
|
+
assert_no_file "test/mailers/auth_mailer_test.rb"
|
163
|
+
end
|
164
|
+
|
165
|
+
test "generator destroy shows email infrastructure cleanup messages" do
|
166
|
+
# Run destroy and capture output
|
167
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
168
|
+
|
169
|
+
# Verify cleanup messages are shown
|
170
|
+
assert_match(/Cleaning up email and notification infrastructure/, output)
|
171
|
+
assert_match(/Cleaned up email and notification infrastructure/, output)
|
172
|
+
|
173
|
+
# Verify the cleanup is mentioned in the summary
|
174
|
+
assert_match(/Created multi-channel email infrastructure/, output)
|
175
|
+
end
|
176
|
+
|
177
|
+
test "generator destroy preserves migration files by default in development" do
|
178
|
+
# Simulate being in development environment
|
179
|
+
stub_rails_env('development') do
|
180
|
+
# Create mock migration files
|
181
|
+
create_mock_migration_files
|
182
|
+
|
183
|
+
# Verify migration files exist before destroy
|
184
|
+
migration_files = Dir.glob(File.join(destination_root, "db/migrate/*_create_{organizations,users,agencies,agents,invitations}.rb"))
|
185
|
+
assert migration_files.length > 0, "Should have migration files before destroy"
|
186
|
+
|
187
|
+
# Run destroy
|
188
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
189
|
+
|
190
|
+
# Verify migration files still exist (preserved for safety)
|
191
|
+
migration_files_after = Dir.glob(File.join(destination_root, "db/migrate/*_create_{organizations,users,agencies,agents,invitations}.rb"))
|
192
|
+
assert_equal migration_files.length, migration_files_after.length, "Migration files should be preserved by default"
|
193
|
+
|
194
|
+
# Verify safety message is shown
|
195
|
+
assert_match(/Migration files preserved for production safety/, output)
|
196
|
+
assert_match(/You may manually delete/, output)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
test "generator destroy deletes migration files with --delete-migrations flag in development" do
|
201
|
+
stub_rails_env('development') do
|
202
|
+
# Create mock migration files
|
203
|
+
create_mock_migration_files
|
204
|
+
|
205
|
+
# Verify migration files exist before destroy
|
206
|
+
migration_files = Dir.glob(File.join(destination_root, "db/migrate/*_create_{organizations,users,agencies,agents,invitations}.rb"))
|
207
|
+
assert migration_files.length > 0, "Should have migration files before destroy"
|
208
|
+
|
209
|
+
# Run destroy with delete flag
|
210
|
+
output = capture(:stdout) { run_generator ["--delete-migrations"], { behavior: :revoke } }
|
211
|
+
|
212
|
+
# Verify migration files are deleted
|
213
|
+
migration_files_after = Dir.glob(File.join(destination_root, "db/migrate/*_create_{organizations,users,agencies,agents,invitations}.rb"))
|
214
|
+
assert_equal 0, migration_files_after.length, "Migration files should be deleted with --delete-migrations flag"
|
215
|
+
|
216
|
+
# Verify deletion message is shown
|
217
|
+
assert_match(/Removed.*authentication migration files/, output)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
test "generator destroy handles missing Rails application gracefully" do
|
222
|
+
# Remove Rails application files to simulate non-Rails directory
|
223
|
+
FileUtils.rm_f(File.join(destination_root, "config/application.rb"))
|
224
|
+
|
225
|
+
stub_rails_env('development') do
|
226
|
+
# Create mock migration files
|
227
|
+
create_mock_migration_files
|
228
|
+
|
229
|
+
# Run destroy
|
230
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
231
|
+
|
232
|
+
# Verify graceful error handling
|
233
|
+
assert_match(/Warning: Could not rollback migrations automatically/, output)
|
234
|
+
assert_match(/Not in a Rails application directory/, output)
|
235
|
+
assert_match(/Please manually run: rails db:rollback/, output)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# INTEGRATION TESTS
|
240
|
+
|
241
|
+
test "generator destroy can be run multiple times safely" do
|
242
|
+
# Run destroy first time
|
243
|
+
run_generator [], { behavior: :revoke }
|
244
|
+
|
245
|
+
# Verify files are removed
|
246
|
+
assert_no_file "app/models/user.rb"
|
247
|
+
assert_no_file "app/controllers/auth/tokens_controller.rb"
|
248
|
+
|
249
|
+
# Run destroy second time (should not error)
|
250
|
+
assert_nothing_raised do
|
251
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
252
|
+
assert_match(/No authentication migrations found to rollback/, output)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
test "generator destroy reports accurate migration count" do
|
257
|
+
stub_rails_env('development') do
|
258
|
+
# Create exactly 5 mock migration files
|
259
|
+
create_mock_migration_files
|
260
|
+
|
261
|
+
# Run destroy
|
262
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
263
|
+
|
264
|
+
# Verify accurate count reporting
|
265
|
+
assert_match(/Found 5 migrations to rollback/, output)
|
266
|
+
assert_match(/Successfully rolled back 5 authentication migrations/, output)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
test "generator destroy provides detailed debugging information" do
|
271
|
+
stub_rails_env('development') do
|
272
|
+
# Create mock migration files
|
273
|
+
create_mock_migration_files
|
274
|
+
|
275
|
+
# Run destroy
|
276
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
277
|
+
|
278
|
+
# Verify debugging information is provided
|
279
|
+
assert_match(/Working directory:/, output)
|
280
|
+
assert_match(/Found.*migrations to rollback/, output)
|
281
|
+
assert_match(/Executing:.*rails db:rollback:primary/, output)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
test "generator destroy handles bundler context correctly" do
|
286
|
+
stub_rails_env('development') do
|
287
|
+
# Create mock migration files
|
288
|
+
create_mock_migration_files
|
289
|
+
|
290
|
+
# Run destroy
|
291
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
292
|
+
|
293
|
+
# Verify bundle exec is used for proper gem context
|
294
|
+
assert_match(/bundle exec rails db:rollback/, output)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# VALIDATION TESTS
|
299
|
+
|
300
|
+
test "generator destroy validates Rails environment properly" do
|
301
|
+
# Test each environment behavior
|
302
|
+
%w[development test].each do |env|
|
303
|
+
stub_rails_env(env) do
|
304
|
+
# The setup method already runs run_generator which creates the migration files
|
305
|
+
# Let's verify they exist and show exactly what files are present
|
306
|
+
existing_migration_files = Dir.glob(File.join(destination_root, "db/migrate/*.rb"))
|
307
|
+
puts "\n=== DEBUG: Migration files present before destroy in #{env} ==="
|
308
|
+
existing_migration_files.each { |f| puts " #{File.basename(f)}" }
|
309
|
+
|
310
|
+
# Debug: if no migration files exist from setup, this indicates a problem with the generator itself
|
311
|
+
if existing_migration_files.empty?
|
312
|
+
flunk "Generator setup should have created migration files, but found none. This indicates the generator's copy_authentication_migrations method is not working."
|
313
|
+
end
|
314
|
+
|
315
|
+
# Now test the destroy behavior
|
316
|
+
output = capture(:stdout) { run_generator [], { behavior: :revoke } }
|
317
|
+
|
318
|
+
puts "\n=== DEBUG: Captured output in #{env} ==="
|
319
|
+
puts "Output: '#{output}'"
|
320
|
+
puts "Output contains 'Rolling back'?: #{output.include?('Rolling back')}"
|
321
|
+
|
322
|
+
# Only test development and test environments - they should both allow rollback
|
323
|
+
assert_match(/Rolling back authentication migrations/, output)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
test "generator destroy validates option combinations correctly" do
|
329
|
+
stub_rails_env('development') do
|
330
|
+
create_mock_migration_files
|
331
|
+
|
332
|
+
# Test valid option combinations
|
333
|
+
valid_combinations = [
|
334
|
+
["--delete-migrations"],
|
335
|
+
["--force-rollback"],
|
336
|
+
["--force-cleanup"],
|
337
|
+
["--force-rollback", "--delete-migrations"],
|
338
|
+
["--force-rollback", "--force-cleanup"]
|
339
|
+
]
|
340
|
+
|
341
|
+
valid_combinations.each do |options|
|
342
|
+
# Should not raise errors with valid combinations
|
343
|
+
assert_nothing_raised do
|
344
|
+
capture(:stdout) { run_generator options, { behavior: :revoke } }
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
private
|
351
|
+
|
352
|
+
def prepare_rails_app_structure
|
353
|
+
# Create minimal Rails app structure needed for testing
|
354
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/models/concerns"))
|
355
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/controllers/auth"))
|
356
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/controllers/concerns"))
|
357
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/mailers"))
|
358
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/services"))
|
359
|
+
FileUtils.mkdir_p(File.join(destination_root, "app/views/auth_mailer"))
|
360
|
+
FileUtils.mkdir_p(File.join(destination_root, "config/initializers"))
|
361
|
+
FileUtils.mkdir_p(File.join(destination_root, "test/models"))
|
362
|
+
FileUtils.mkdir_p(File.join(destination_root, "test/controllers/auth"))
|
363
|
+
FileUtils.mkdir_p(File.join(destination_root, "test/concerns"))
|
364
|
+
FileUtils.mkdir_p(File.join(destination_root, "test/mailers"))
|
365
|
+
FileUtils.mkdir_p(File.join(destination_root, "db/migrate"))
|
366
|
+
|
367
|
+
# Create essential Rails files
|
368
|
+
File.write(File.join(destination_root, "config/application.rb"), "class Application < Rails::Application; end")
|
369
|
+
File.write(File.join(destination_root, "config/routes.rb"), "Rails.application.routes.draw do\nend")
|
370
|
+
File.write(File.join(destination_root, "Gemfile"), "source 'https://rubygems.org'\ngem 'rails'")
|
371
|
+
end
|
372
|
+
|
373
|
+
def create_mock_migration_files
|
374
|
+
# Create mock migration files that match the authentication system pattern
|
375
|
+
# FIXED: Use Rails naming convention that the generator actually looks for
|
376
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S").to_i
|
377
|
+
|
378
|
+
migrations = %w[
|
379
|
+
create_organizations
|
380
|
+
create_users
|
381
|
+
create_agencies
|
382
|
+
create_agents
|
383
|
+
create_invitations
|
384
|
+
]
|
385
|
+
|
386
|
+
migrations.each_with_index do |migration_name, index|
|
387
|
+
file_timestamp = (timestamp + index).to_s
|
388
|
+
filename = "#{file_timestamp}_#{migration_name}.rb" # FIXED: Remove duplicate 'create_' prefix
|
389
|
+
filepath = File.join(destination_root, "db/migrate", filename)
|
390
|
+
|
391
|
+
File.write(filepath, <<~MIGRATION)
|
392
|
+
class #{migration_name.camelize} < ActiveRecord::Migration[7.0]
|
393
|
+
def change
|
394
|
+
# Mock migration content
|
395
|
+
end
|
396
|
+
end
|
397
|
+
MIGRATION
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def stub_rails_env(env)
|
402
|
+
original_env = ENV['RAILS_ENV']
|
403
|
+
ENV['RAILS_ENV'] = env
|
404
|
+
yield
|
405
|
+
ensure
|
406
|
+
ENV['RAILS_ENV'] = original_env
|
407
|
+
end
|
408
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class GeneratorIntegrationTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
# When running from the dummy app, Rails.root IS the dummy app
|
7
|
+
# When running from gem root, Rails.root is the gem and we need to navigate to dummy
|
8
|
+
if Rails.root.to_s.end_with?('/lib/propel_access/test/dummy')
|
9
|
+
@dummy_path = Rails.root
|
10
|
+
else
|
11
|
+
@dummy_path = Rails.root.join("lib/propel_access/test/dummy")
|
12
|
+
end
|
13
|
+
@original_rails_env = ENV['RAILS_ENV']
|
14
|
+
backup_dummy_app_state
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
restore_dummy_app_state
|
19
|
+
ENV['RAILS_ENV'] = @original_rails_env
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_generator_produces_working_authentication_system
|
23
|
+
# TDD Step (Red): This test expects the generator to produce a working system
|
24
|
+
|
25
|
+
# 1. Run generator in dummy app
|
26
|
+
run_generator_in_dummy_app
|
27
|
+
|
28
|
+
# 2. Test that files are generated correctly
|
29
|
+
test_authentication_api_endpoints
|
30
|
+
|
31
|
+
# Success: If we get here, the generator produced working code
|
32
|
+
# Note: Database testing is complex in this context, but file generation works
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def backup_dummy_app_state
|
38
|
+
# Simple backup strategy: remember which files existed before
|
39
|
+
@original_files = Dir.glob("#{@dummy_path}/**/*").select { |f| File.file?(f) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def restore_dummy_app_state
|
43
|
+
# Use the enhanced Rails destroy command that now handles database rollback
|
44
|
+
Dir.chdir(@dummy_path) do
|
45
|
+
# Set test environment for destroy command
|
46
|
+
ENV['RAILS_ENV'] = 'test'
|
47
|
+
|
48
|
+
# Use our enhanced destroy command that handles migrations AND files
|
49
|
+
system("rails destroy propel:authentication:install > /dev/null 2>&1")
|
50
|
+
|
51
|
+
# Reset routes.rb to original state (destroy command may not handle route removal)
|
52
|
+
File.write('config/routes.rb', "Rails.application.routes.draw do\nend\n")
|
53
|
+
|
54
|
+
# Clean up database files to ensure fresh start
|
55
|
+
FileUtils.rm_f('storage/test.sqlite3')
|
56
|
+
FileUtils.rm_f('db/test.sqlite3')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_generator_in_dummy_app
|
61
|
+
Dir.chdir(@dummy_path) do
|
62
|
+
# Set test environment
|
63
|
+
ENV['RAILS_ENV'] = 'test'
|
64
|
+
|
65
|
+
# Run the generator (without suppressing output for debugging)
|
66
|
+
result = system("rails generate propel:authentication:install")
|
67
|
+
assert result, "Generator should run successfully"
|
68
|
+
|
69
|
+
# Create database and run migrations (database should be clean from proper cleanup)
|
70
|
+
migration_result = system("rails db:create db:migrate RAILS_ENV=test")
|
71
|
+
assert migration_result, "Database migration should run successfully"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_user_model_creation
|
76
|
+
# Load the generated models into the current Rails environment
|
77
|
+
Dir.chdir(@dummy_path) do
|
78
|
+
# Load the generated models directly
|
79
|
+
load 'app/models/organization.rb'
|
80
|
+
load 'app/models/concerns/authenticatable.rb'
|
81
|
+
load 'app/models/user.rb'
|
82
|
+
|
83
|
+
# Test organization creation (required for user)
|
84
|
+
organization = Organization.create!(
|
85
|
+
name: "Test Organization",
|
86
|
+
website: "https://test.com"
|
87
|
+
)
|
88
|
+
assert organization.persisted?, "Organization should be created successfully"
|
89
|
+
|
90
|
+
# Test user creation with generated model
|
91
|
+
user = User.create!(
|
92
|
+
email: "integration-test@example.com",
|
93
|
+
username: "integrationtest",
|
94
|
+
password: "securepassword123",
|
95
|
+
password_confirmation: "securepassword123",
|
96
|
+
organization: organization,
|
97
|
+
first_name: "Integration",
|
98
|
+
last_name: "Test"
|
99
|
+
)
|
100
|
+
|
101
|
+
assert user.persisted?, "User should be created successfully"
|
102
|
+
assert_equal "integration-test@example.com", user.email
|
103
|
+
assert user.authenticate("securepassword123"), "User should authenticate with correct password"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_jwt_token_generation
|
108
|
+
Dir.chdir(@dummy_path) do
|
109
|
+
# Models should already be loaded from previous test
|
110
|
+
# Create test data
|
111
|
+
organization = Organization.create!(name: "Token Test Org")
|
112
|
+
user = User.create!(
|
113
|
+
email: "token-test@example.com",
|
114
|
+
username: "tokentest",
|
115
|
+
password: "password123",
|
116
|
+
organization: organization
|
117
|
+
)
|
118
|
+
|
119
|
+
# Test JWT token generation
|
120
|
+
token = user.generate_jwt_token
|
121
|
+
assert token.present?, "Should generate JWT token"
|
122
|
+
assert_equal 3, token.split('.').length, "JWT should have 3 parts"
|
123
|
+
|
124
|
+
# Test JWT token validation
|
125
|
+
found_user = User.find_by_jwt_token(token)
|
126
|
+
assert_equal user.id, found_user.id, "Should find user by JWT token"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_authentication_api_endpoints
|
131
|
+
Dir.chdir(@dummy_path) do
|
132
|
+
# Test that the controller files exist and have proper structure
|
133
|
+
assert(File.exist?('app/controllers/auth/tokens_controller.rb'),
|
134
|
+
"Tokens controller should be generated")
|
135
|
+
|
136
|
+
controller_content = File.read('app/controllers/auth/tokens_controller.rb')
|
137
|
+
assert_match(/def create/, controller_content,
|
138
|
+
"Controller should have create action (login)")
|
139
|
+
assert_match(/def destroy/, controller_content,
|
140
|
+
"Controller should have destroy action (logout)")
|
141
|
+
assert_match(/def me/, controller_content,
|
142
|
+
"Controller should have me action")
|
143
|
+
assert_match(/include PropelAuthentication/, controller_content,
|
144
|
+
"Controller should include authentication concern")
|
145
|
+
|
146
|
+
# Test routes were generated
|
147
|
+
routes_content = File.read('config/routes.rb')
|
148
|
+
assert_match(/namespace :auth/, routes_content,
|
149
|
+
"Routes should include auth namespace")
|
150
|
+
assert_match(/post 'login'/, routes_content,
|
151
|
+
"Routes should include login endpoint")
|
152
|
+
assert_match(/delete 'logout'/, routes_content,
|
153
|
+
"Routes should include logout endpoint")
|
154
|
+
assert_match(/get 'me'/, routes_content,
|
155
|
+
"Routes should include me endpoint")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'generators/propel/authentication/install_generator'
|
3
|
+
|
4
|
+
class MultiVersionGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests Propel::Authentication::InstallGenerator
|
6
|
+
destination Rails.root.join('tmp/generators')
|
7
|
+
setup :prepare_destination
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@original_config = PropelAccess.configuration.dup if defined?(PropelAccess)
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
PropelAccess.configuration = @original_config if @original_config && defined?(PropelAccess)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
test "generator creates dynamic API versioning structure" do
|
20
|
+
run_generator ["--configurable-versioning"]
|
21
|
+
|
22
|
+
# Verify base controllers are created
|
23
|
+
assert_file "app/controllers/api/auth/base_tokens_controller.rb" do |content|
|
24
|
+
assert_match(/class Api::Auth::BaseTokensController < ApplicationController/, content)
|
25
|
+
assert_match(/POST \/api\/\{version\}\/auth\/login/, content)
|
26
|
+
assert_match(/def create/, content)
|
27
|
+
assert_match(/def show/, content)
|
28
|
+
assert_match(/def destroy/, content)
|
29
|
+
assert_match(/def unlock/, content)
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_file "app/controllers/api/auth/base_passwords_controller.rb" do |content|
|
33
|
+
assert_match(/class Api::Auth::BasePasswordsController < ApplicationController/, content)
|
34
|
+
assert_match(/POST \/api\/\{version\}\/auth\/reset/, content)
|
35
|
+
assert_match(/def create/, content)
|
36
|
+
assert_match(/def show/, content)
|
37
|
+
assert_match(/def update/, content)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verify runtime configurable routes are created
|
41
|
+
assert_file "config/routes.rb" do |content|
|
42
|
+
assert_match(/# Runtime configurable API versioning routes/, content)
|
43
|
+
assert_match(/api_version = PropelAccess\.configuration\.api_version/, content)
|
44
|
+
assert_match(/namespace :api do/, content)
|
45
|
+
assert_match(/namespace api_version do/, content)
|
46
|
+
assert_match(/namespace :auth do/, content)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
test "generated controllers inherit from base controllers with configurable versioning" do
|
51
|
+
# Test with default configurable versioning
|
52
|
+
run_generator ["--configurable-versioning"]
|
53
|
+
|
54
|
+
# The versioned controllers should be created but inherit from base
|
55
|
+
# Since we can't predict the exact namespace without knowing the runtime version,
|
56
|
+
# we need to test this through the dummy app integration
|
57
|
+
|
58
|
+
# Verify the templates are set up for inheritance
|
59
|
+
assert_file "app/controllers/api/auth/base_tokens_controller.rb"
|
60
|
+
assert_file "app/controllers/api/auth/base_passwords_controller.rb"
|
61
|
+
end
|
62
|
+
|
63
|
+
test "multi-version functionality with dummy app" do
|
64
|
+
# This test will be run against the dummy app to verify
|
65
|
+
# that the same authentication code works with different API versions
|
66
|
+
|
67
|
+
# Skip if not in dummy app context
|
68
|
+
skip "Multi-version test requires dummy app context" unless Rails.root.to_s.include?('test/dummy')
|
69
|
+
|
70
|
+
# Test with v1 configuration
|
71
|
+
with_api_version('v1') do
|
72
|
+
test_authentication_endpoints('v1')
|
73
|
+
end
|
74
|
+
|
75
|
+
# Test with v2 configuration
|
76
|
+
with_api_version('v2') do
|
77
|
+
test_authentication_endpoints('v2')
|
78
|
+
end
|
79
|
+
|
80
|
+
# Test with v3 configuration
|
81
|
+
with_api_version('v3') do
|
82
|
+
test_authentication_endpoints('v3')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def with_api_version(version)
|
89
|
+
original_version = PropelAccess.configuration.api_version
|
90
|
+
PropelAccess.configuration.api_version = version
|
91
|
+
Rails.application.reload_routes!
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
PropelAccess.configuration.api_version = original_version
|
95
|
+
Rails.application.reload_routes!
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_authentication_endpoints(version)
|
99
|
+
# Verify route helpers exist for the version
|
100
|
+
login_path_method = "api_#{version}_auth_login_path"
|
101
|
+
me_path_method = "api_#{version}_auth_me_path"
|
102
|
+
logout_path_method = "api_#{version}_auth_logout_path"
|
103
|
+
|
104
|
+
# These should be callable without error
|
105
|
+
assert_respond_to Rails.application.routes.url_helpers, login_path_method.to_sym
|
106
|
+
assert_respond_to Rails.application.routes.url_helpers, me_path_method.to_sym
|
107
|
+
assert_respond_to Rails.application.routes.url_helpers, logout_path_method.to_sym
|
108
|
+
|
109
|
+
# Verify the paths are correctly versioned
|
110
|
+
login_path = Rails.application.routes.url_helpers.send(login_path_method)
|
111
|
+
assert_equal "/api/#{version}/auth/login", login_path
|
112
|
+
|
113
|
+
me_path = Rails.application.routes.url_helpers.send(me_path_method)
|
114
|
+
assert_equal "/api/#{version}/auth/me", me_path
|
115
|
+
|
116
|
+
logout_path = Rails.application.routes.url_helpers.send(logout_path_method)
|
117
|
+
assert_equal "/api/#{version}/auth/logout", logout_path
|
118
|
+
end
|
119
|
+
|
120
|
+
def run_generator(args = [])
|
121
|
+
# Ensure we have the configurable versioning flag by default
|
122
|
+
args = ["--configurable-versioning"] if args.empty?
|
123
|
+
super(args)
|
124
|
+
end
|
125
|
+
end
|