Skip to content

Commit 65991cb

Browse files
committed
Add RSpec testing framework and Rswag for API documentation. Configure Swagger endpoints and initialize Rswag settings. Create specs for authentication and posts management, including user registration, login, and CRUD operations for posts.
1 parent d097d0d commit 65991cb

13 files changed

Lines changed: 1197 additions & 0 deletions

File tree

.rspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--require spec_helper

Gemfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,12 @@ group :development, :test do
5050

5151
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
5252
gem "rubocop-rails-omakase", require: false
53+
54+
# RSpec testing framework
55+
gem "rspec-rails"
56+
57+
# Swagger/OpenAPI documentation
58+
gem "rswag-specs"
59+
gem "rswag-ui"
60+
gem "rswag-api"
5361
end

Gemfile.lock

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ GEM
7575
securerandom (>= 0.3)
7676
tzinfo (~> 2.0, >= 2.0.5)
7777
uri (>= 0.13.1)
78+
addressable (2.8.8)
79+
public_suffix (>= 2.0.2, < 8.0)
7880
ast (2.4.3)
7981
base64 (0.3.0)
8082
bcrypt (3.1.20)
@@ -96,6 +98,7 @@ GEM
9698
debug (1.11.1)
9799
irb (~> 1.10)
98100
reline (>= 0.3.8)
101+
diff-lcs (1.6.2)
99102
dotenv (3.2.0)
100103
drb (2.2.3)
101104
ed25519 (1.4.0)
@@ -122,6 +125,9 @@ GEM
122125
rdoc (>= 4.0.0)
123126
reline (>= 0.4.2)
124127
json (2.18.0)
128+
json-schema (6.1.0)
129+
addressable (~> 2.8)
130+
bigdecimal (>= 3.1, < 5)
125131
jwt (3.1.2)
126132
base64
127133
kamal (2.10.1)
@@ -190,6 +196,7 @@ GEM
190196
psych (5.3.1)
191197
date
192198
stringio
199+
public_suffix (7.0.2)
193200
puma (7.1.0)
194201
nio4r (~> 2.0)
195202
raabro (1.4.0)
@@ -244,6 +251,34 @@ GEM
244251
regexp_parser (2.11.3)
245252
reline (0.6.3)
246253
io-console (~> 0.5)
254+
rspec-core (3.13.6)
255+
rspec-support (~> 3.13.0)
256+
rspec-expectations (3.13.5)
257+
diff-lcs (>= 1.2.0, < 2.0)
258+
rspec-support (~> 3.13.0)
259+
rspec-mocks (3.13.7)
260+
diff-lcs (>= 1.2.0, < 2.0)
261+
rspec-support (~> 3.13.0)
262+
rspec-rails (8.0.2)
263+
actionpack (>= 7.2)
264+
activesupport (>= 7.2)
265+
railties (>= 7.2)
266+
rspec-core (~> 3.13)
267+
rspec-expectations (~> 3.13)
268+
rspec-mocks (~> 3.13)
269+
rspec-support (~> 3.13)
270+
rspec-support (3.13.6)
271+
rswag-api (2.17.0)
272+
activesupport (>= 5.2, < 8.2)
273+
railties (>= 5.2, < 8.2)
274+
rswag-specs (2.17.0)
275+
activesupport (>= 5.2, < 8.2)
276+
json-schema (>= 2.2, < 7.0)
277+
railties (>= 5.2, < 8.2)
278+
rspec-core (>= 2.14)
279+
rswag-ui (2.17.0)
280+
actionpack (>= 5.2, < 8.2)
281+
railties (>= 5.2, < 8.2)
247282
rubocop (1.82.1)
248283
json (~> 2.3)
249284
language_server-protocol (~> 3.17.0.2)
@@ -338,6 +373,10 @@ DEPENDENCIES
338373
puma (>= 5.0)
339374
rack-cors
340375
rails (~> 8.1.1)
376+
rspec-rails
377+
rswag-api
378+
rswag-specs
379+
rswag-ui
341380
rubocop-rails-omakase
342381
solid_cable
343382
solid_cache

config/initializers/rswag_api.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Rswag::Api.configure do |c|
2+
3+
# Specify a root folder where Swagger JSON files are located
4+
# This is used by the Swagger middleware to serve requests for API descriptions
5+
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
6+
# that it's configured to generate files in the same folder
7+
c.openapi_root = Rails.root.to_s + '/swagger'
8+
9+
# Inject a lambda function to alter the returned Swagger prior to serialization
10+
# The function will have access to the rack env for the current request
11+
# For example, you could leverage this to dynamically assign the "host" property
12+
#
13+
#c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
14+
end

config/initializers/rswag_ui.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Rswag::Ui.configure do |c|
2+
3+
# List the Swagger endpoints that you want to be documented through the
4+
# swagger-ui. The first parameter is the path (absolute or relative to the UI
5+
# host) to the corresponding endpoint and the second is a title that will be
6+
# displayed in the document selector.
7+
# NOTE: If you're using rspec-api to expose Swagger files
8+
# (under openapi_root) as JSON or YAML endpoints, then the list below should
9+
# correspond to the relative paths for those endpoints.
10+
11+
c.openapi_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs'
12+
13+
# Add Basic Auth in case your API is private
14+
# c.basic_auth_enabled = true
15+
# c.basic_auth_credentials 'username', 'password'
16+
end

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
Rails.application.routes.draw do
2+
mount Rswag::Ui::Engine => '/api-docs'
3+
mount Rswag::Api::Engine => '/api-docs'
24
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
35

46
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.

spec/rails_helper.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This file is copied to spec/ when you run 'rails generate rspec:install'
2+
require 'spec_helper'
3+
ENV['RAILS_ENV'] ||= 'test'
4+
require_relative '../config/environment'
5+
# Prevent database truncation if the environment is production
6+
abort("The Rails environment is running in production mode!") if Rails.env.production?
7+
# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file
8+
# that will avoid rails generators crashing because migrations haven't been run yet
9+
# return unless Rails.env.test?
10+
require 'rspec/rails'
11+
# Add additional requires below this line. Rails is not loaded until this point!
12+
13+
# Requires supporting ruby files with custom matchers and macros, etc, in
14+
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
15+
# run as spec files by default. This means that files in spec/support that end
16+
# in _spec.rb will both be required and run as specs, causing the specs to be
17+
# run twice. It is recommended that you do not name files matching this glob to
18+
# end with _spec.rb. You can configure this pattern with the --pattern
19+
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
20+
#
21+
# The following line is provided for convenience purposes. It has the downside
22+
# of increasing the boot-up time by auto-requiring all files in the support
23+
# directory. Alternatively, in the individual `*_spec.rb` files, manually
24+
# require only the support files necessary.
25+
Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
26+
27+
# Ensures that the test database schema matches the current schema file.
28+
# If there are pending migrations it will invoke `db:test:prepare` to
29+
# recreate the test database by loading the schema.
30+
# If you are not using ActiveRecord, you can remove these lines.
31+
begin
32+
ActiveRecord::Migration.maintain_test_schema!
33+
rescue ActiveRecord::PendingMigrationError => e
34+
abort e.to_s.strip
35+
end
36+
RSpec.configure do |config|
37+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
38+
config.fixture_paths = [
39+
Rails.root.join('spec/fixtures')
40+
]
41+
42+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
43+
# examples within a transaction, remove the following line or assign false
44+
# instead of true.
45+
config.use_transactional_fixtures = true
46+
47+
# You can uncomment this line to turn off ActiveRecord support entirely.
48+
# config.use_active_record = false
49+
50+
# RSpec Rails uses metadata to mix in different behaviours to your tests,
51+
# for example enabling you to call `get` and `post` in request specs. e.g.:
52+
#
53+
# RSpec.describe UsersController, type: :request do
54+
# # ...
55+
# end
56+
#
57+
# The different available types are documented in the features, such as in
58+
# https://rspec.info/features/8-0/rspec-rails
59+
#
60+
# You can also this infer these behaviours automatically by location, e.g.
61+
# /spec/models would pull in the same behaviour as `type: :model` but this
62+
# behaviour is considered legacy and will be removed in a future version.
63+
#
64+
# To enable this behaviour uncomment the line below.
65+
# config.infer_spec_type_from_file_location!
66+
67+
# Filter lines from Rails gems in backtraces.
68+
config.filter_rails_from_backtrace!
69+
# arbitrary gems may also be filtered via:
70+
# config.filter_gems_from_backtrace("gem name")
71+
end

spec/requests/api/v1/auth_spec.rb

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# frozen_string_literal: true
2+
3+
require 'swagger_helper'
4+
5+
RSpec.describe 'Auth API', type: :request do
6+
path '/api/v1/signup' do
7+
post 'Register a new user' do
8+
tags 'Authentication'
9+
consumes 'application/json'
10+
produces 'application/json'
11+
12+
parameter name: :user, in: :body, schema: {
13+
type: :object,
14+
properties: {
15+
name: { type: :string, example: 'John Doe' },
16+
email: { type: :string, format: :email, example: 'john@example.com' },
17+
password: { type: :string, minLength: 6, example: 'password123' },
18+
password_confirmation: { type: :string, example: 'password123' }
19+
},
20+
required: %w[name email password]
21+
}
22+
23+
response '201', 'User created successfully' do
24+
schema type: :object,
25+
properties: {
26+
user: { '$ref' => '#/components/schemas/User' },
27+
message: { type: :string, example: 'Account created successfully' }
28+
},
29+
required: %w[user message]
30+
31+
let(:user) { { name: 'John Doe', email: 'john@example.com', password: 'password123' } }
32+
run_test!
33+
end
34+
35+
response '422', 'Validation errors' do
36+
schema '$ref' => '#/components/schemas/ValidationErrors'
37+
38+
let(:user) { { name: '', email: 'invalid', password: '123' } }
39+
run_test!
40+
end
41+
end
42+
end
43+
44+
path '/api/v1/login' do
45+
post 'Authenticate user and receive token cookie' do
46+
tags 'Authentication'
47+
consumes 'application/json'
48+
produces 'application/json'
49+
50+
parameter name: :credentials, in: :body, schema: {
51+
type: :object,
52+
properties: {
53+
email: { type: :string, format: :email, example: 'john@example.com' },
54+
password: { type: :string, example: 'password123' }
55+
},
56+
required: %w[email password]
57+
}
58+
59+
response '200', 'Logged in successfully' do
60+
schema type: :object,
61+
properties: {
62+
user: { '$ref' => '#/components/schemas/User' },
63+
message: { type: :string, example: 'Logged in successfully' }
64+
},
65+
required: %w[user message]
66+
67+
let(:existing_user) { User.create!(name: 'John', email: 'john@example.com', password: 'password123') }
68+
let(:credentials) { { email: existing_user.email, password: 'password123' } }
69+
run_test!
70+
end
71+
72+
response '401', 'Invalid credentials' do
73+
schema '$ref' => '#/components/schemas/Error'
74+
75+
let(:credentials) { { email: 'wrong@example.com', password: 'wrong' } }
76+
run_test!
77+
end
78+
end
79+
end
80+
81+
path '/api/v1/logout' do
82+
delete 'Log out current user' do
83+
tags 'Authentication'
84+
produces 'application/json'
85+
security [ cookieAuth: [] ]
86+
87+
response '200', 'Logged out successfully' do
88+
schema type: :object,
89+
properties: {
90+
message: { type: :string, example: 'Logged out successfully' }
91+
},
92+
required: %w[message]
93+
94+
let(:user) { User.create!(name: 'John', email: 'john@example.com', password: 'password123') }
95+
before { sign_in(user) }
96+
run_test!
97+
end
98+
end
99+
end
100+
101+
path '/api/v1/me' do
102+
get 'Get current authenticated user' do
103+
tags 'Authentication'
104+
produces 'application/json'
105+
security [ cookieAuth: [] ]
106+
107+
response '200', 'Current user retrieved' do
108+
schema '$ref' => '#/components/schemas/User'
109+
110+
let(:user) { User.create!(name: 'John', email: 'john@example.com', password: 'password123') }
111+
before { sign_in(user) }
112+
run_test!
113+
end
114+
115+
response '401', 'Not authenticated' do
116+
schema '$ref' => '#/components/schemas/Error'
117+
run_test!
118+
end
119+
end
120+
end
121+
end

0 commit comments

Comments
 (0)