Initial commit

This commit is contained in:
2026-05-18 13:17:28 -04:00
committed by GitHub
commit 16fadfd529
91 changed files with 3321 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
require "swagger_helper"
RSpec.describe "api/v1/admin", type: :request do
path "/api/v1/admin/dashboard" do
get "admin dashboard (admin only)" do
tags "Admin"
produces "application/json"
security [ bearer_auth: [] ]
response "200", "admin dashboard accessed" do
let!(:admin_user) { User.create!(email: "admin@example.com", password: "123456", password_confirmation: "123456", role: :admin) }
let(:Authorization) { "Bearer #{JsonWebToken.encode(user_id: admin_user.id)}" }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["message"]).to include("Welcome to admin dashboard")
expect(data["stats"]).to be_present
expect(data["stats"]["total_users"]).to be_a(Integer)
end
end
response "403", "forbidden for non-admin users" do
let!(:regular_user) { User.create!(email: "user@example.com", password: "123456", password_confirmation: "123456", role: :user) }
let(:Authorization) { "Bearer #{JsonWebToken.encode(user_id: regular_user.id)}" }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to include("Forbidden")
end
end
response "401", "unauthorized without token" do
let(:Authorization) { "" }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to include("unauthorized")
end
end
end
end
end
+37
View File
@@ -0,0 +1,37 @@
require "swagger_helper"
RSpec.describe "api/v1/auth", type: :request do
path "/api/v1/login" do
post "logs in a user" do
tags "Auth"
consumes "application/json"
produces "application/json"
security []
parameter name: :credentials, in: :body, schema: {
type: :object,
required: %w[email password],
properties: {
email: {
type: :string,
example: "bob@random.com"
},
password: {
type: :string,
example: "123456"
}
}
}
response "200", "logged in" do
let!(:user) { User.create(email: "bob@random.com", password: "123456", password_confirmation: "123456") }
let(:credentials) { { email: "bob@random.com", password: "123456" } }
run_test!
end
response "401", "invalid credentials" do
let(:credentials) { { email: "notbob@example.com", password: "wrong" } }
run_test!
end
end
end
end
+55
View File
@@ -0,0 +1,55 @@
require "swagger_helper"
RSpec.describe "api/v1/logout", type: :request do
path "/api/v1/logout" do
post "logs out user and blacklists token" do
tags "Auth"
consumes "application/json"
produces "application/json"
security [ bearer_auth: [] ]
let!(:user) { User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456") }
response "200", "successfully logged out" do
let(:Authorization) do
token = JsonWebToken.encode(user_id: user.id)
"Bearer #{token}"
end
run_test! do |response|
data = JSON.parse(response.body)
expect(data["message"]).to include("Successfully logged out")
# verify token was blacklisted by checking the response
# (we can't decode the token variable here as it's scoped to the let block)
end
end
response "401", "unauthorized without token" do
let(:Authorization) { "" }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to include("unauthorized")
end
end
end
end
describe "blacklisted token rejection" do
it "rejects requests with blacklisted tokens" do
user = User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456")
token = JsonWebToken.encode(user_id: user.id)
# first logout to blacklist the token
post "/api/v1/logout", headers: { "Authorization" => "Bearer #{token}" }
expect(response).to have_http_status(:ok)
# try to access protected endpoint with blacklisted token
get "/api/v1/profile", headers: { "Authorization" => "Bearer #{token}" }
expect(response).to have_http_status(:unauthorized)
data = JSON.parse(response.body)
expect(data["error"]).to include("Token has been revoked")
end
end
end
+23
View File
@@ -0,0 +1,23 @@
require "swagger_helper"
RSpec.describe "api/v1/profile", type: :request do
path "/api/v1/profile" do
get "get current user info using jwt token" do
tags "Profile"
security [ bearer_auth: [] ]
produces "application/json"
response "200", "profile fetched" do
let!(:user) { User.create(email: "bob@random.com", password: "123456", password_confirmation: "123456") }
let(:Authorization) { "Bearer #{JsonWebToken.encode(user_id: user.id)}" }
run_test!
end
response "401", "unauthorized access" do
let(:Authorization) { "" }
run_test!
end
end
end
end
+29
View File
@@ -0,0 +1,29 @@
require "rails_helper"
RSpec.describe "Rack::Attack throttling", type: :request do
describe "POST /api/v1/login" do
let!(:user) do
User.create(email: "bob@random.com", password: "123456", password_confirmation: "123456")
end
it "throttles after 3 login attempts" do
3.times do
post "/api/v1/login", params: {
email: "bob@random.com",
password: "wrongpassword"
}.to_json, headers: { "CONTENT_TYPE" => "application/json" }
expect(response.status).to_not eq(429)
end
# this one is going to be blocked. too much attempt
post "/api/v1/login", params: {
email: "bob@random.com",
password: "wrongpassword"
}.to_json, headers: { "CONTENT_TYPE" => "application/json" }
expect(response.status).to eq(429)
expect(response.body).to include("chill out")
end
end
end
+55
View File
@@ -0,0 +1,55 @@
require "swagger_helper"
RSpec.describe "api/v1/refresh", type: :request do
path "/api/v1/refresh" do
post "refreshes access token using refresh token" do
tags "Auth"
consumes "application/json"
produces "application/json"
security []
parameter name: :refresh_request, in: :body, schema: {
type: :object,
required: %w[refresh_token],
properties: {
refresh_token: { type: :string, example: "your_refresh_token_here" }
}
}
let(:user) { User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456") }
let(:refresh_token_record) { user.refresh_tokens.create! }
response "200", "new access token issued" do
let(:refresh_request) { { refresh_token: refresh_token_record.token } }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["access_token"]).to be_present
expect(data["user"]["email"]).to eq("test@example.com")
end
end
response "401", "invalid or expired refresh token" do
let(:refresh_request) { { refresh_token: "invalid_token" } }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to include("Invalid or expired refresh token")
end
end
response "401", "revoked refresh token" do
before do
refresh_token_record.revoke!
end
let(:refresh_request) { { refresh_token: refresh_token_record.token } }
run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to include("Invalid or expired refresh token")
end
end
end
end
end
+51
View File
@@ -0,0 +1,51 @@
require "swagger_helper"
RSpec.describe "api/v1/signup", type: :request do
path "/api/v1/signup" do
post "registers a new user and returns a jwt" do
tags "Auth"
consumes "application/json"
produces "application/json"
security []
parameter name: :user, in: :body, schema: {
type: :object,
required: %w[email password password_confirmation],
properties: {
email: { type: :string, example: "newbob@example.com" },
password: { type: :string, example: "123456" },
password_confirmation: { type: :string, example: "123456" }
}
}
response "201", "user created and token returned" do
let(:user) do
{
email: "newbob@example.com",
password: "123456",
password_confirmation: "123456"
}
end
run_test!
end
response "422", "validation failed invalid email + password" do
let(:user) do
{
email: "",
password: "123456",
password_confirmation: "000000"
}
end
run_test! do |response|
data = JSON.parse(response.body)
expect(data["errors"]).to include(
"Email can't be blank",
"Password confirmation doesn't match Password"
)
end
end
end
end
end