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,490 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "rails/generators/test_case"
|
3
|
+
require_relative '../../../lib/generators/propel/authentication/install_generator'
|
4
|
+
|
5
|
+
class Propel::Authentication::InstallGeneratorTest < Rails::Generators::TestCase
|
6
|
+
tests Propel::Authentication::InstallGenerator
|
7
|
+
|
8
|
+
setup do
|
9
|
+
self.destination_root = Rails.root.join("tmp/generators", Process.pid.to_s)
|
10
|
+
prepare_destination
|
11
|
+
prepare_rails_app_structure
|
12
|
+
run_generator
|
13
|
+
end
|
14
|
+
|
15
|
+
teardown do
|
16
|
+
FileUtils.rm_rf(destination_root)
|
17
|
+
end
|
18
|
+
|
19
|
+
test "generator creates propel access initializer and configuration file" do
|
20
|
+
|
21
|
+
assert_file "config/initializers/propel_access_configuration.rb" do |content|
|
22
|
+
assert_match(/module PropelAccess/, content)
|
23
|
+
assert_match(/class Configuration/, content)
|
24
|
+
assert_match(/attr_accessor :jwt_secret/, content)
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_file "config/initializers/propel_access.rb" do |content|
|
28
|
+
assert_match(/require_relative 'propel_access_configuration'/, content)
|
29
|
+
assert_match(/PropelAccess\.configure/, content)
|
30
|
+
assert_match(/jwt_secret/, content)
|
31
|
+
assert_match(/jwt_expiration/, content)
|
32
|
+
assert_match(/password_length/, content)
|
33
|
+
assert_match(/24\.hours/, content)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
test "generator creates all core models with proper JWT-ready associations" do
|
38
|
+
assert_file "app/models/user.rb" do |content|
|
39
|
+
assert_match(/class User < ApplicationRecord/, content)
|
40
|
+
assert_match(/include Authenticatable/, content)
|
41
|
+
assert_match(/has_many :invitations/, content)
|
42
|
+
assert_match(/belongs_to :organization/, content)
|
43
|
+
# Verify user model is set up for multi-tenant JWT system
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_file "app/models/organization.rb" do |content|
|
47
|
+
assert_match(/class Organization < ApplicationRecord/, content)
|
48
|
+
assert_match(/has_many :users/, content)
|
49
|
+
assert_match(/has_many :agencies/, content)
|
50
|
+
# Verify organization structure for multi-tenant context
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_file "app/models/agency.rb" do |content|
|
54
|
+
assert_match(/class Agency < ApplicationRecord/, content)
|
55
|
+
assert_match(/belongs_to :organization/, content)
|
56
|
+
assert_match(/has_many :agents/, content)
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_file "app/models/agent.rb" do |content|
|
60
|
+
assert_match(/class Agent < ApplicationRecord/, content)
|
61
|
+
assert_match(/belongs_to :agency/, content)
|
62
|
+
assert_match(/belongs_to :user/, content)
|
63
|
+
# Verify agent model connects users to agencies
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_file "app/models/invitation.rb" do |content|
|
67
|
+
assert_match(/class Invitation < ApplicationRecord/, content)
|
68
|
+
assert_match(/belongs_to :organization/, content)
|
69
|
+
assert_match(/belongs_to :inviter, class_name: 'User'/, content)
|
70
|
+
assert_match(/validates :email_address/, content)
|
71
|
+
# Verify invitation system supports JWT token-based invitations
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
test "generator creates database seeds file" do
|
76
|
+
assert_file "db/seeds.rb" do |content|
|
77
|
+
assert_match(/# Create test organization and user for authentication testing/, content)
|
78
|
+
assert_match(/organization = Organization\.find_or_create_by!/, content)
|
79
|
+
assert_match(/user = User\.find_or_create_by!/, content)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
test "generator creates authenticatable concern with JWT token generation and validation" do
|
84
|
+
assert_file "app/models/concerns/authenticatable.rb" do |content|
|
85
|
+
assert_match(/module Authenticatable/, content)
|
86
|
+
assert_match(/extend ActiveSupport::Concern/, content)
|
87
|
+
assert_match(/has_secure_password/, content)
|
88
|
+
assert_match(/validates :email_address/, content)
|
89
|
+
assert_match(/validates :password/, content)
|
90
|
+
|
91
|
+
# EPIC 1 REQUIREMENT: JWT token generation functionality
|
92
|
+
assert_match(/def generate_jwt_token/, content)
|
93
|
+
assert_match(/JWT\.encode/, content)
|
94
|
+
|
95
|
+
# EPIC 1 REQUIREMENT: JWT token validation functionality
|
96
|
+
assert_match(/class_methods do/, content)
|
97
|
+
assert_match(/def find_by_jwt_token/, content)
|
98
|
+
assert_match(/JWT\.decode/, content)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
test "generator creates JWT authentication controller for complete API functionality" do
|
103
|
+
assert_file "app/controllers/auth/tokens_controller.rb" do |content|
|
104
|
+
assert_match(/class Auth::TokensController < ApplicationController/, content)
|
105
|
+
|
106
|
+
# EPIC 1 REQUIREMENT: Login endpoint
|
107
|
+
assert_match(/def create/, content)
|
108
|
+
assert_match(/POST \/auth\/login/, content)
|
109
|
+
|
110
|
+
# EPIC 1 REQUIREMENT: Logout endpoint
|
111
|
+
assert_match(/def destroy/, content)
|
112
|
+
assert_match(/DELETE \/auth\/logout/, content)
|
113
|
+
|
114
|
+
# EPIC 1 REQUIREMENT: Current user endpoint
|
115
|
+
assert_match(/def me/, content)
|
116
|
+
assert_match(/GET \/auth\/me/, content)
|
117
|
+
|
118
|
+
# Verify proper JWT authentication middleware
|
119
|
+
assert_match(/include PropelAuthentication/, content)
|
120
|
+
assert_match(/return unless authenticate_user/, content)
|
121
|
+
|
122
|
+
# Verify proper API response format
|
123
|
+
assert_match(/render json:/, content)
|
124
|
+
assert_match(/status: :unauthorized/, content)
|
125
|
+
assert_match(/status: :unprocessable_entity/, content)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
test "generator creates comprehensive migrations with proper database structure for JWT system" do
|
130
|
+
assert_migration "db/migrate/create_users.rb" do |migration|
|
131
|
+
assert_match(/create_table :users/, migration)
|
132
|
+
# Core authentication fields
|
133
|
+
assert_match(/t\.string :email_address, null: false/, migration)
|
134
|
+
assert_match(/t\.string :username, null: false/, migration)
|
135
|
+
assert_match(/t\.string :password_digest, null: false/, migration)
|
136
|
+
assert_match(/t\.string :phone_number/, migration)
|
137
|
+
|
138
|
+
# User profile fields
|
139
|
+
assert_match(/t\.string :first_name/, migration)
|
140
|
+
assert_match(/t\.string :last_name/, migration)
|
141
|
+
assert_match(/t\.string :time_zone/, migration)
|
142
|
+
|
143
|
+
# Authentication status fields for JWT system
|
144
|
+
assert_match(/t\.datetime :confirmed_at/, migration)
|
145
|
+
assert_match(/t\.integer :status, default: 0/, migration)
|
146
|
+
assert_match(/t\.datetime :last_login_at/, migration)
|
147
|
+
assert_match(/t\.integer :failed_login_attempts, default: 0/, migration)
|
148
|
+
assert_match(/t\.datetime :locked_at/, migration)
|
149
|
+
|
150
|
+
# Multi-tenant association
|
151
|
+
assert_match(/t\.references :organization/, migration)
|
152
|
+
|
153
|
+
# JSON fields for flexible data storage
|
154
|
+
assert_match(/t\.jsonb :meta, default: {}/, migration)
|
155
|
+
assert_match(/t\.jsonb :settings, default: {}/, migration)
|
156
|
+
|
157
|
+
# Proper indexes for JWT authentication performance
|
158
|
+
assert_match(/add_index :users, :email_address, unique: true/, migration)
|
159
|
+
assert_match(/add_index :users, :username, unique: true/, migration)
|
160
|
+
assert_match(/add_index :users, :phone_number, unique: true/, migration)
|
161
|
+
end
|
162
|
+
|
163
|
+
assert_migration "db/migrate/create_organizations.rb" do |migration|
|
164
|
+
assert_match(/create_table :organizations/, migration)
|
165
|
+
assert_match(/t\.string :name/, migration)
|
166
|
+
assert_match(/t\.jsonb :settings/, migration)
|
167
|
+
# Verify organization supports multi-tenant JWT context
|
168
|
+
end
|
169
|
+
|
170
|
+
assert_migration "db/migrate/create_agencies.rb" do |migration|
|
171
|
+
assert_match(/create_table :agencies/, migration)
|
172
|
+
assert_match(/t\.references :organization/, migration)
|
173
|
+
assert_match(/foreign_key: true/, migration)
|
174
|
+
# Verify proper foreign key relationships for multi-tenant system
|
175
|
+
end
|
176
|
+
|
177
|
+
assert_migration "db/migrate/create_agents.rb" do |migration|
|
178
|
+
assert_match(/create_table :agents/, migration)
|
179
|
+
assert_match(/t\.references :agency/, migration)
|
180
|
+
assert_match(/t\.references :user/, migration)
|
181
|
+
# Verify agent-user relationship for role-based JWT tokens
|
182
|
+
end
|
183
|
+
|
184
|
+
assert_migration "db/migrate/create_invitations.rb" do |migration|
|
185
|
+
assert_match(/create_table :invitations/, migration)
|
186
|
+
assert_match(/t\.references :organization/, migration)
|
187
|
+
assert_match(/t\.references :inviter/, migration)
|
188
|
+
assert_match(/t\.string :email_address/, migration)
|
189
|
+
assert_match(/t\.integer :status/, migration)
|
190
|
+
# Verify invitation system integrates with JWT authentication
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
test "generator injects complete JWT authentication routes into routes file" do
|
195
|
+
assert_file "config/routes.rb" do |content|
|
196
|
+
assert_match(/namespace :auth/, content)
|
197
|
+
|
198
|
+
# EPIC 1 REQUIREMENT: JWT token management routes with explicit declarations
|
199
|
+
assert_match(/post 'login', to: 'tokens#create'/, content)
|
200
|
+
assert_match(/delete 'logout', to: 'tokens#destroy'/, content)
|
201
|
+
assert_match(/get 'me', to: 'tokens#me'/, content)
|
202
|
+
|
203
|
+
# EPIC 1 REQUIREMENT: Password reset routes
|
204
|
+
assert_match(/post 'reset', to: 'passwords#create'/, content)
|
205
|
+
assert_match(/get 'reset', to: 'passwords#show'/, content)
|
206
|
+
assert_match(/patch 'reset', to: 'passwords#update'/, content)
|
207
|
+
|
208
|
+
# Verify no session-based routes (JWT-first approach)
|
209
|
+
assert_no_match(/sessions/, content)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
test "generator adds required JWT and authentication gems to Gemfile" do
|
214
|
+
assert_file "Gemfile" do |content|
|
215
|
+
# EPIC 1 REQUIREMENT: Core authentication gems
|
216
|
+
assert_match(/gem ['"]bcrypt['"]/, content)
|
217
|
+
assert_match(/gem ['"]jwt['"]/, content)
|
218
|
+
|
219
|
+
# Verify gem versions are specified for security
|
220
|
+
assert_match(/bcrypt['"], ['"]~>/, content)
|
221
|
+
assert_match(/jwt['"], ['"]~>/, content)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
test "generator creates functional model tests that verify complete JWT authentication workflow" do
|
226
|
+
assert_file "test/models/user_test.rb" do |content|
|
227
|
+
assert_match(/class UserTest < ActiveSupport::TestCase/, content)
|
228
|
+
|
229
|
+
# EPIC 1 REQUIREMENT: Test JWT token generation
|
230
|
+
assert_match(/test ['"]should generate valid JWT token['"]/, content)
|
231
|
+
assert_match(/user\.generate_jwt_token/, content)
|
232
|
+
assert_match(/JWT\.decode/, content)
|
233
|
+
|
234
|
+
# EPIC 1 REQUIREMENT: Test JWT token validation
|
235
|
+
assert_match(/test ['"]should validate JWT token and find user['"]/, content)
|
236
|
+
assert_match(/User\.find_by_jwt_token/, content)
|
237
|
+
|
238
|
+
# EPIC 1 REQUIREMENT: Test authentication workflow
|
239
|
+
assert_match(/test ['"]should authenticate with correct password['"]/, content)
|
240
|
+
assert_match(/user\.authenticate/, content)
|
241
|
+
|
242
|
+
# EPIC 1 REQUIREMENT: Test validation rules
|
243
|
+
assert_match(/test ['"]should validate email_address uniqueness['"]/, content)
|
244
|
+
assert_match(/test ['"]should validate password strength['"]/, content)
|
245
|
+
|
246
|
+
# Verify tests use actual JWT functionality, not mocks
|
247
|
+
assert_no_match(/mock/, content)
|
248
|
+
assert_no_match(/stub/, content)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
test "generator creates JWT authentication controller that supports comprehensive testing" do
|
253
|
+
assert_file "app/controllers/auth/tokens_controller.rb" do |content|
|
254
|
+
# EPIC 1 REQUIREMENT: Verify controller supports JWT authentication testing
|
255
|
+
assert_match(/class Auth::TokensController < ApplicationController/, content)
|
256
|
+
|
257
|
+
# Verify login endpoint structure for testing
|
258
|
+
assert_match(/def create/, content)
|
259
|
+
assert_match(/render json:/, content)
|
260
|
+
|
261
|
+
# Verify logout endpoint structure for testing
|
262
|
+
assert_match(/def destroy/, content)
|
263
|
+
|
264
|
+
# Verify current user endpoint structure for testing
|
265
|
+
assert_match(/def me/, content)
|
266
|
+
|
267
|
+
# Verify JWT authentication support for testing
|
268
|
+
assert_match(/include PropelAuthentication/, content)
|
269
|
+
assert_match(/return unless authenticate_user/, content)
|
270
|
+
|
271
|
+
# Verify proper error handling for testing
|
272
|
+
assert_match(/status: :unauthorized/, content)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
test "generator runs idempotently without creating duplicate content or breaking existing JWT functionality" do
|
277
|
+
# Run generator again
|
278
|
+
run_generator
|
279
|
+
|
280
|
+
# Should not raise errors and files should still exist with correct content
|
281
|
+
assert_file "app/models/user.rb"
|
282
|
+
assert_file "app/models/concerns/authenticatable.rb"
|
283
|
+
assert_file "app/controllers/auth/tokens_controller.rb"
|
284
|
+
assert_file "config/initializers/propel_access.rb"
|
285
|
+
|
286
|
+
# Check that no duplicate content was added to routes
|
287
|
+
assert_file "config/routes.rb" do |content|
|
288
|
+
# Should only have one namespace :auth block
|
289
|
+
auth_blocks = content.scan(/namespace :auth/).length
|
290
|
+
assert_equal 1, auth_blocks, "Should have exactly one auth namespace block"
|
291
|
+
|
292
|
+
# Should only have one set of JWT routes
|
293
|
+
login_routes = content.scan(/post 'login'/).length
|
294
|
+
assert_equal 1, login_routes, "Should have exactly one login route"
|
295
|
+
end
|
296
|
+
|
297
|
+
# Check that no duplicate gems were added
|
298
|
+
assert_file "Gemfile" do |content|
|
299
|
+
bcrypt_occurrences = content.scan(/gem ['"]bcrypt['"]/).length
|
300
|
+
jwt_occurrences = content.scan(/gem ['"]jwt['"]/).length
|
301
|
+
assert_equal 1, bcrypt_occurrences, "Should have exactly one bcrypt gem entry"
|
302
|
+
assert_equal 1, jwt_occurrences, "Should have exactly one jwt gem entry"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
test "generated JWT authentication system has valid syntax and proper structure for real Rails API app" do
|
307
|
+
# Verify all generated Ruby files have valid syntax
|
308
|
+
ruby_files = [
|
309
|
+
"app/models/user.rb",
|
310
|
+
"app/models/organization.rb",
|
311
|
+
"app/models/agency.rb",
|
312
|
+
"app/models/agent.rb",
|
313
|
+
"app/models/invitation.rb",
|
314
|
+
"app/models/concerns/authenticatable.rb",
|
315
|
+
"app/controllers/auth/tokens_controller.rb",
|
316
|
+
"test/models/user_test.rb",
|
317
|
+
"config/initializers/propel_access.rb"
|
318
|
+
]
|
319
|
+
|
320
|
+
ruby_files.each do |file_path|
|
321
|
+
assert_file file_path do |content|
|
322
|
+
# Basic syntax validation - no syntax errors should be present
|
323
|
+
assert_no_match(/syntax error/, content)
|
324
|
+
assert_no_match(/unexpected/, content)
|
325
|
+
|
326
|
+
# Ensure proper class/module structure
|
327
|
+
if file_path.include?("models/concerns/")
|
328
|
+
assert_match(/^module \w+/, content)
|
329
|
+
assert_match(/extend ActiveSupport::Concern/, content)
|
330
|
+
elsif file_path.include?("controllers/")
|
331
|
+
assert_match(/^class .*Controller < ApplicationController/, content) unless file_path.include?("_test.rb")
|
332
|
+
elsif file_path.include?("models/")
|
333
|
+
assert_match(/^class \w+ < ApplicationRecord/, content) unless file_path.include?("_test.rb")
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
test "generated user model integrates properly with JWT-enabled authenticatable concern" do
|
340
|
+
assert_file "app/models/user.rb" do |user_content|
|
341
|
+
assert_match(/include Authenticatable/, user_content)
|
342
|
+
assert_match(/belongs_to :organization/, user_content)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Verify the concern provides the expected JWT functionality
|
346
|
+
assert_file "app/models/concerns/authenticatable.rb" do |concern_content|
|
347
|
+
assert_match(/has_secure_password/, concern_content)
|
348
|
+
assert_match(/validates :email_address/, concern_content)
|
349
|
+
assert_match(/validates :password/, concern_content)
|
350
|
+
|
351
|
+
# EPIC 1 REQUIREMENT: JWT functionality must be present
|
352
|
+
assert_match(/def generate_jwt_token/, concern_content)
|
353
|
+
assert_match(/JWT\.encode/, concern_content)
|
354
|
+
assert_match(/class_methods do/, concern_content)
|
355
|
+
assert_match(/def find_by_jwt_token/, concern_content)
|
356
|
+
assert_match(/JWT\.decode/, concern_content)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
test "generated JWT authentication controller provides complete API functionality" do
|
361
|
+
assert_file "app/controllers/auth/tokens_controller.rb" do |content|
|
362
|
+
# Verify all required API endpoints are present
|
363
|
+
assert_match(/def create/, content) # Login
|
364
|
+
assert_match(/def destroy/, content) # Logout
|
365
|
+
assert_match(/def me/, content) # Current user
|
366
|
+
|
367
|
+
# Verify proper JSON API responses
|
368
|
+
assert_match(/render json:/, content)
|
369
|
+
assert_match(/status: :ok/, content)
|
370
|
+
assert_match(/status: :unauthorized/, content)
|
371
|
+
|
372
|
+
# Verify JWT token handling
|
373
|
+
assert_match(/include PropelAuthentication/, content)
|
374
|
+
assert_match(/return unless authenticate_user/, content)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
test "generated migrations create proper foreign key relationships for multi-tenant JWT system" do
|
379
|
+
# Verify organizations are the root of the multi-tenant hierarchy
|
380
|
+
assert_migration "db/migrate/create_organizations.rb" do |migration|
|
381
|
+
assert_match(/create_table :organizations/, migration)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Verify users belong to organizations (for JWT token context)
|
385
|
+
assert_migration "db/migrate/create_users.rb" do |migration|
|
386
|
+
assert_match(/t\.references :organization/, migration)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Verify agencies reference organizations
|
390
|
+
assert_migration "db/migrate/create_agencies.rb" do |migration|
|
391
|
+
assert_match(/t\.references :organization/, migration)
|
392
|
+
assert_match(/foreign_key: true/, migration)
|
393
|
+
end
|
394
|
+
|
395
|
+
# Verify agents reference both agencies and users (for role-based JWT claims)
|
396
|
+
assert_migration "db/migrate/create_agents.rb" do |migration|
|
397
|
+
assert_match(/t\.references :agency/, migration)
|
398
|
+
assert_match(/t\.references :user/, migration)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Verify invitations have polymorphic relationship (for flexible JWT-based invitations)
|
402
|
+
assert_migration "db/migrate/create_invitations.rb" do |migration|
|
403
|
+
assert_match(/t\.references :organization/, migration)
|
404
|
+
assert_match(/t\.references :inviter/, migration)
|
405
|
+
assert_match(/t\.string :email_address/, migration)
|
406
|
+
assert_match(/t\.integer :status/, migration)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
test "generated JWT configuration provides secure defaults for production use" do
|
411
|
+
assert_file "config/initializers/propel_access.rb" do |content|
|
412
|
+
# Verify JWT configuration structure exists
|
413
|
+
assert_match(/PropelAccess\.configure/, content)
|
414
|
+
|
415
|
+
# EPIC 1 REQUIREMENT: Secure JWT configuration
|
416
|
+
assert_match(/jwt_secret/, content)
|
417
|
+
assert_match(/jwt_expiration/, content)
|
418
|
+
|
419
|
+
# Verify secure defaults
|
420
|
+
assert_match(/24\.hours/, content) # Reasonable token expiration
|
421
|
+
assert_match(/password_length.*8/, content) # Strong password requirement
|
422
|
+
|
423
|
+
# Verify environment-aware configuration
|
424
|
+
assert_match(/Rails\.env/, content)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
test "generator creates complete JWT authentication system that works immediately for JavaScript frontends" do
|
429
|
+
# This is the EPIC 1 GOAL: Complete working system from day one
|
430
|
+
|
431
|
+
# Verify all components exist
|
432
|
+
assert_file "app/models/user.rb"
|
433
|
+
assert_file "app/models/concerns/authenticatable.rb"
|
434
|
+
assert_file "app/controllers/auth/tokens_controller.rb"
|
435
|
+
assert_file "config/initializers/propel_access.rb"
|
436
|
+
|
437
|
+
# Verify JWT routes are accessible
|
438
|
+
assert_file "config/routes.rb" do |content|
|
439
|
+
assert_match(/post 'login'/, content)
|
440
|
+
assert_match(/delete 'logout'/, content)
|
441
|
+
assert_match(/get 'me'/, content)
|
442
|
+
end
|
443
|
+
|
444
|
+
# Verify required gems are added
|
445
|
+
assert_file "Gemfile" do |content|
|
446
|
+
assert_match(/gem ['"]jwt['"]/, content)
|
447
|
+
assert_match(/gem ['"]bcrypt['"]/, content)
|
448
|
+
end
|
449
|
+
|
450
|
+
# Verify the system would work for a JavaScript frontend
|
451
|
+
controller_code = File.read(File.join(destination_root, "app/controllers/auth/tokens_controller.rb"))
|
452
|
+
assert_match(/render json:/, controller_code)
|
453
|
+
assert_match(/include PropelAuthentication/, controller_code)
|
454
|
+
|
455
|
+
# Verify JWT token functionality exists
|
456
|
+
authenticatable_code = File.read(File.join(destination_root, "app/models/concerns/authenticatable.rb"))
|
457
|
+
assert_match(/JWT\.encode/, authenticatable_code)
|
458
|
+
assert_match(/JWT\.decode/, authenticatable_code)
|
459
|
+
end
|
460
|
+
|
461
|
+
# test "generator does not create RackSessionDisable concern or include it in controller for API-only apps" do
|
462
|
+
# original_api_only = Rails.application.config.api_only
|
463
|
+
# Rails.application.config.api_only = true
|
464
|
+
# begin
|
465
|
+
# run_generator
|
466
|
+
|
467
|
+
# assert_no_file "app/controllers/concerns/rack_session_disable.rb"
|
468
|
+
# assert_file "app/controllers/auth/tokens_controller.rb" do |content|
|
469
|
+
# assert_no_match(/include RackSessionDisable/, content)
|
470
|
+
# end
|
471
|
+
# ensure
|
472
|
+
# Rails.application.config.api_only = original_api_only
|
473
|
+
# end
|
474
|
+
# end
|
475
|
+
|
476
|
+
# test "generator creates RackSessionDisable concern and includes it in controller for full stack apps" do
|
477
|
+
# original_api_only = Rails.application.config.api_only
|
478
|
+
# Rails.application.config.api_only = false
|
479
|
+
# begin
|
480
|
+
# run_generator
|
481
|
+
|
482
|
+
# assert_file "app/controllers/concerns/rack_session_disable.rb"
|
483
|
+
# assert_file "app/controllers/auth/tokens_controller.rb" do |content|
|
484
|
+
# assert_match(/include RackSessionDisable/, content)
|
485
|
+
# end
|
486
|
+
# ensure
|
487
|
+
# Rails.application.config.api_only = original_api_only
|
488
|
+
# end
|
489
|
+
# end
|
490
|
+
end
|