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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +290 -0
  4. data/Rakefile +12 -0
  5. data/lib/generators/propel_auth/install_generator.rb +486 -0
  6. data/lib/generators/propel_auth/pack_generator.rb +277 -0
  7. data/lib/generators/propel_auth/templates/agency.rb +7 -0
  8. data/lib/generators/propel_auth/templates/agent.rb +7 -0
  9. data/lib/generators/propel_auth/templates/auth/base_passwords_controller.rb.tt +99 -0
  10. data/lib/generators/propel_auth/templates/auth/base_tokens_controller.rb.tt +90 -0
  11. data/lib/generators/propel_auth/templates/auth/passwords_controller.rb.tt +126 -0
  12. data/lib/generators/propel_auth/templates/auth_mailer.rb +180 -0
  13. data/lib/generators/propel_auth/templates/authenticatable.rb +38 -0
  14. data/lib/generators/propel_auth/templates/concerns/confirmable.rb +145 -0
  15. data/lib/generators/propel_auth/templates/concerns/lockable.rb +123 -0
  16. data/lib/generators/propel_auth/templates/concerns/propel_authentication.rb +44 -0
  17. data/lib/generators/propel_auth/templates/concerns/rack_session_disable.rb +19 -0
  18. data/lib/generators/propel_auth/templates/concerns/recoverable.rb +124 -0
  19. data/lib/generators/propel_auth/templates/config/environments/development_email.rb +43 -0
  20. data/lib/generators/propel_auth/templates/db/migrate/create_agencies.rb +20 -0
  21. data/lib/generators/propel_auth/templates/db/migrate/create_agents.rb +11 -0
  22. data/lib/generators/propel_auth/templates/db/migrate/create_invitations.rb +28 -0
  23. data/lib/generators/propel_auth/templates/db/migrate/create_organizations.rb +18 -0
  24. data/lib/generators/propel_auth/templates/db/migrate/create_users.rb +43 -0
  25. data/lib/generators/propel_auth/templates/db/seeds.rb +29 -0
  26. data/lib/generators/propel_auth/templates/invitation.rb +133 -0
  27. data/lib/generators/propel_auth/templates/lib/propel_auth.rb +84 -0
  28. data/lib/generators/propel_auth/templates/organization.rb +7 -0
  29. data/lib/generators/propel_auth/templates/propel_auth.rb +132 -0
  30. data/lib/generators/propel_auth/templates/services/auth_notification_service.rb +89 -0
  31. data/lib/generators/propel_auth/templates/test/concerns/confirmable_test.rb.tt +247 -0
  32. data/lib/generators/propel_auth/templates/test/concerns/lockable_test.rb.tt +282 -0
  33. data/lib/generators/propel_auth/templates/test/concerns/propel_authentication_test.rb.tt +75 -0
  34. data/lib/generators/propel_auth/templates/test/concerns/recoverable_test.rb.tt +327 -0
  35. data/lib/generators/propel_auth/templates/test/controllers/auth/lockable_integration_test.rb.tt +196 -0
  36. data/lib/generators/propel_auth/templates/test/controllers/auth/password_reset_integration_test.rb.tt +471 -0
  37. data/lib/generators/propel_auth/templates/test/controllers/auth/tokens_controller_test.rb.tt +265 -0
  38. data/lib/generators/propel_auth/templates/test/mailers/auth_mailer_test.rb.tt +216 -0
  39. data/lib/generators/propel_auth/templates/test/mailers/previews/auth_mailer_preview.rb +161 -0
  40. data/lib/generators/propel_auth/templates/tokens_controller.rb.tt +96 -0
  41. data/lib/generators/propel_auth/templates/user.rb +21 -0
  42. data/lib/generators/propel_auth/templates/user_test.rb.tt +81 -0
  43. data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.html.erb +213 -0
  44. data/lib/generators/propel_auth/templates/views/auth_mailer/account_unlock.text.erb +56 -0
  45. data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.html.erb +213 -0
  46. data/lib/generators/propel_auth/templates/views/auth_mailer/email_confirmation.text.erb +32 -0
  47. data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.html.erb +166 -0
  48. data/lib/generators/propel_auth/templates/views/auth_mailer/password_reset.text.erb +32 -0
  49. data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.html.erb +194 -0
  50. data/lib/generators/propel_auth/templates/views/auth_mailer/user_invitation.text.erb +51 -0
  51. data/lib/generators/propel_auth/test/dummy/Dockerfile +72 -0
  52. data/lib/generators/propel_auth/test/dummy/Gemfile +63 -0
  53. data/lib/generators/propel_auth/test/dummy/Gemfile.lock +394 -0
  54. data/lib/generators/propel_auth/test/dummy/README.md +24 -0
  55. data/lib/generators/propel_auth/test/dummy/Rakefile +6 -0
  56. data/lib/generators/propel_auth/test/dummy/app/assets/stylesheets/application.css +10 -0
  57. data/lib/generators/propel_auth/test/dummy/app/controllers/application_controller.rb +4 -0
  58. data/lib/generators/propel_auth/test/dummy/app/helpers/application_helper.rb +2 -0
  59. data/lib/generators/propel_auth/test/dummy/app/jobs/application_job.rb +7 -0
  60. data/lib/generators/propel_auth/test/dummy/app/mailers/application_mailer.rb +4 -0
  61. data/lib/generators/propel_auth/test/dummy/app/models/application_record.rb +3 -0
  62. data/lib/generators/propel_auth/test/dummy/app/views/layouts/application.html.erb +27 -0
  63. data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  64. data/lib/generators/propel_auth/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  65. data/lib/generators/propel_auth/test/dummy/app/views/pwa/manifest.json.erb +22 -0
  66. data/lib/generators/propel_auth/test/dummy/app/views/pwa/service-worker.js +26 -0
  67. data/lib/generators/propel_auth/test/dummy/bin/brakeman +7 -0
  68. data/lib/generators/propel_auth/test/dummy/bin/dev +2 -0
  69. data/lib/generators/propel_auth/test/dummy/bin/docker-entrypoint +14 -0
  70. data/lib/generators/propel_auth/test/dummy/bin/rails +4 -0
  71. data/lib/generators/propel_auth/test/dummy/bin/rake +4 -0
  72. data/lib/generators/propel_auth/test/dummy/bin/rubocop +8 -0
  73. data/lib/generators/propel_auth/test/dummy/bin/setup +34 -0
  74. data/lib/generators/propel_auth/test/dummy/bin/thrust +5 -0
  75. data/lib/generators/propel_auth/test/dummy/config/application.rb +42 -0
  76. data/lib/generators/propel_auth/test/dummy/config/boot.rb +4 -0
  77. data/lib/generators/propel_auth/test/dummy/config/cable.yml +10 -0
  78. data/lib/generators/propel_auth/test/dummy/config/credentials.yml.enc +1 -0
  79. data/lib/generators/propel_auth/test/dummy/config/database.yml +41 -0
  80. data/lib/generators/propel_auth/test/dummy/config/environment.rb +5 -0
  81. data/lib/generators/propel_auth/test/dummy/config/environments/development.rb +72 -0
  82. data/lib/generators/propel_auth/test/dummy/config/environments/production.rb +89 -0
  83. data/lib/generators/propel_auth/test/dummy/config/environments/test.rb +53 -0
  84. data/lib/generators/propel_auth/test/dummy/config/initializers/assets.rb +10 -0
  85. data/lib/generators/propel_auth/test/dummy/config/initializers/content_security_policy.rb +25 -0
  86. data/lib/generators/propel_auth/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  87. data/lib/generators/propel_auth/test/dummy/config/initializers/inflections.rb +16 -0
  88. data/lib/generators/propel_auth/test/dummy/config/locales/en.yml +31 -0
  89. data/lib/generators/propel_auth/test/dummy/config/master.key +1 -0
  90. data/lib/generators/propel_auth/test/dummy/config/puma.rb +41 -0
  91. data/lib/generators/propel_auth/test/dummy/config/routes.rb +2 -0
  92. data/lib/generators/propel_auth/test/dummy/config/storage.yml +34 -0
  93. data/lib/generators/propel_auth/test/dummy/config.ru +6 -0
  94. data/lib/generators/propel_auth/test/dummy/db/schema.rb +14 -0
  95. data/lib/generators/propel_auth/test/generators/authentication/controllers/tokens_controller_test.rb +230 -0
  96. data/lib/generators/propel_auth/test/generators/authentication/install_generator_test.rb +490 -0
  97. data/lib/generators/propel_auth/test/generators/authentication/uninstall_generator_test.rb +408 -0
  98. data/lib/generators/propel_auth/test/integration/generator_integration_test.rb +158 -0
  99. data/lib/generators/propel_auth/test/integration/multi_version_generator_test.rb +125 -0
  100. data/lib/generators/propel_auth/unpack_generator.rb +345 -0
  101. data/lib/propel_auth.rb +3 -0
  102. 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