add tests for controllers and ActionCable channels; improve main backend docs

This commit is contained in:
2026-10-27 10:32:40 +03:00
parent b122cbb652
commit 50e8aacfa9
17 changed files with 1127 additions and 63 deletions
@@ -0,0 +1,41 @@
require "rails_helper"
RSpec.describe Api::MatchmakingController, type: :controller do
routes do
ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
delete "destroy", to: "api/matchmaking#destroy"
end
end
end
let(:user) { User.create!(nickname: "CaptainHook", password: "password123") }
let(:token) { JwtService.encode(user_id: user.id) }
describe "DELETE #destroy" do
context "when authenticated" do
it "cancels matchmaking for current user and returns success message" do
request.headers["Authorization"] = "Bearer #{token}"
expect(MatchmakingService).to receive(:cancel).with(user.id)
delete :destroy
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["message"]).to eq("Matchmaking cancelled")
end
end
context "when unauthenticated" do
it "returns 401 unauthorized and does not call cancel" do
expect(MatchmakingService).not_to receive(:cancel)
delete :destroy
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Unauthorized")
end
end
end
end
@@ -0,0 +1,114 @@
require "rails_helper"
RSpec.describe Api::RegistrationsController, type: :controller do
describe "POST #create" do
context "with valid parameters" do
let(:valid_params) do
{
user: {
nickname: "CaptainNemo",
password: "secretpassword"
}
}
end
it "creates a new user and returns 201 with user data and JWT token" do
expect {
post :create, params: valid_params
}.to change(User, :count).by(1)
expect(response).to have_http_status(:created)
json = JSON.parse(response.body)
expect(json["token"]).to be_present
expect(json["user"]).to include(
"nickname" => "CaptainNemo",
"avatar_url" => nil
)
expect(json["user"]["id"]).to be_present
expect(json["user"]["created_at"]).to be_present
# Verify token is valid and decodes to created user
decoded = JwtService.decode(json["token"])
expect(decoded[:user_id]).to eq(json["user"]["id"])
end
it "attaches avatar and returns avatar_url when avatar is provided" do
avatar = fixture_file_upload(
Rails.root.join("spec", "fixtures", "files", "avatar.png"),
"image/png"
) rescue Rack::Test::UploadedFile.new(
StringIO.new("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15c4"),
"image/png",
original_filename: "avatar.png"
)
params_with_avatar = valid_params.deep_merge(user: { avatar: avatar })
post :create, params: params_with_avatar
expect(response).to have_http_status(:created)
json = JSON.parse(response.body)
expect(json["user"]["avatar_url"]).to be_present
user = User.find(json["user"]["id"])
expect(user.avatar).to be_attached
end
end
context "with invalid parameters" do
it "returns 422 unprocessable entity when nickname is missing" do
post :create, params: { user: { nickname: "", password: "password123" } }
expect(response).to have_http_status(:unprocessable_content)
json = JSON.parse(response.body)
expect(json["errors"]).to be_an(Array)
expect(json["errors"]).to include("Nickname can't be blank")
end
it "returns 422 unprocessable entity when password is too short" do
post :create, params: { user: { nickname: "CaptainNemo", password: "123" } }
expect(response).to have_http_status(:unprocessable_content)
json = JSON.parse(response.body)
expect(json["errors"]).to include("Password is too short (minimum is 6 characters)")
end
it "returns 422 unprocessable entity when nickname is already taken" do
User.create!(nickname: "CaptainNemo", password: "password123")
post :create, params: { user: { nickname: "captainnemo", password: "password123" } }
expect(response).to have_http_status(:unprocessable_content)
json = JSON.parse(response.body)
expect(json["errors"]).to include("Nickname has already been taken")
end
it "returns 422 when avatar has an unsupported content type" do
invalid_avatar = Rack::Test::UploadedFile.new(
StringIO.new("plain text content"),
"text/plain",
original_filename: "test.txt"
)
post :create, params: {
user: {
nickname: "CaptainNemo",
password: "password123",
avatar: invalid_avatar
}
}
expect(response).to have_http_status(:unprocessable_content)
json = JSON.parse(response.body)
expect(json["errors"]).to include("Avatar must be a PNG, JPEG, GIF or WebP image")
end
it "raises ParameterMissing when user param is omitted" do
expect {
post :create, params: {}
}.to raise_error(ActionController::ParameterMissing)
end
end
end
end
@@ -0,0 +1,154 @@
require "rails_helper"
RSpec.describe Api::SessionsController, type: :controller do
let!(:user) { User.create!(nickname: "AdmiralNelson", password: "securepassword") }
let(:token) { JwtService.encode(user_id: user.id) }
describe "POST #create" do
context "with valid credentials" do
it "returns 200 OK with user info and JWT token" do
post :create, params: { nickname: "AdmiralNelson", password: "securepassword" }
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["token"]).to be_present
expect(json["user"]).to include(
"id" => user.id,
"nickname" => "AdmiralNelson",
"avatar_url" => nil
)
decoded = JwtService.decode(json["token"])
expect(decoded[:user_id]).to eq(user.id)
end
end
context "with invalid credentials" do
it "returns 401 when password does not match" do
post :create, params: { nickname: "AdmiralNelson", password: "wrongpassword" }
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Invalid nickname or password")
expect(json["token"]).to be_nil
end
it "returns 401 when user does not exist" do
post :create, params: { nickname: "GhostCaptain", password: "securepassword" }
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Invalid nickname or password")
end
it "returns 401 when nickname is omitted" do
post :create, params: { password: "securepassword" }
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Invalid nickname or password")
end
it "returns 401 when password is omitted" do
post :create, params: { nickname: "AdmiralNelson" }
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Invalid nickname or password")
end
it "returns 401 when credentials are blank" do
post :create, params: { nickname: "", password: "" }
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Invalid nickname or password")
end
end
end
describe "DELETE #destroy" do
context "when authenticated" do
it "returns 200 with success message" do
request.headers["Authorization"] = "Bearer #{token}"
delete :destroy
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["message"]).to eq("Signed out successfully")
end
end
context "when unauthenticated" do
it "returns 401 unauthorized" do
delete :destroy
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Unauthorized")
end
end
end
describe "GET #me" do
context "when authenticated with Authorization header" do
it "returns 200 with user data" do
request.headers["Authorization"] = "Bearer #{token}"
get :me
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["user"]).to include(
"id" => user.id,
"nickname" => "AdmiralNelson",
"avatar_url" => nil
)
end
it "includes avatar_url when user has an attached avatar" do
user.avatar.attach(
io: StringIO.new("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15c4"),
filename: "avatar.png",
content_type: "image/png"
)
request.headers["Authorization"] = "Bearer #{token}"
get :me
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["user"]["avatar_url"]).to be_present
end
end
context "when authenticated with token in query params" do
it "returns 200 with user data" do
get :me, params: { token: token }
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json["user"]["id"]).to eq(user.id)
end
end
context "when unauthenticated" do
it "returns 401 unauthorized" do
get :me
expect(response).to have_http_status(:unauthorized)
json = JSON.parse(response.body)
expect(json["error"]).to eq("Unauthorized")
end
end
context "when token is expired" do
it "returns 401 unauthorized" do
expired_token = JwtService.encode(user_id: user.id, exp: 1.hour.ago.to_i)
request.headers["Authorization"] = "Bearer #{expired_token}"
get :me
expect(response).to have_http_status(:unauthorized)
end
end
end
end