require 'rails_helper' RSpec.describe 'Users', type: :request do let(:remote_ip) { '203.0.113.10' } before do allow_any_instance_of(ActionDispatch::Request) .to receive(:remote_ip) .and_return(remote_ip) end def auth_headers(user) { 'X-Transfer-Code' => user.inheritance_code } end describe 'POST /users' do it 'creates guest user, IpAddress and UserIp, and returns code' do expect { post '/users' }.to change(User, :count).by(1) .and change(IpAddress, :count).by(1) .and change(UserIp, :count).by(1) expect(response).to have_http_status(:created) expect(json['code']).to be_present expect(json['user']['role']).to eq('guest') user = User.last ip_address = IpAddress.find_by(ip_address: IPAddr.new(remote_ip).hton) expect(user.role).to eq('guest') expect(ip_address).to be_present expect(UserIp.exists?(user:, ip_address:)).to eq(true) end it 'returns 403 and does not create user when current IP address is banned' do IpAddress.create!( ip_address: IPAddr.new(remote_ip).hton, banned_at: Time.current ) expect { post '/users' }.not_to change(User, :count) expect(response).to have_http_status(:forbidden) expect(UserIp.count).to eq(0) end end describe 'POST /users/code/renew' do it 'returns 401 when not logged in' do post '/users/code/renew' expect(response).to have_http_status(:unauthorized) end it 'returns 403 when current user is banned' do user = create(:user, :banned) post '/users/code/renew', headers: auth_headers(user) expect(response).to have_http_status(:forbidden) end it 'returns 403 when current IP address is banned' do user = create(:user) IpAddress.create!( ip_address: IPAddr.new(remote_ip).hton, banned_at: Time.current ) post '/users/code/renew', headers: auth_headers(user) expect(response).to have_http_status(:forbidden) end end describe 'PUT /users/:id' do let(:user) { create(:user, name: 'old-name', role: 'guest') } it 'returns 401 when current_user id mismatch' do other_user = create(:user) put "/users/#{user.id}", params: { name: 'new-name' }, headers: auth_headers(other_user) expect(response).to have_http_status(:unauthorized) end it 'returns 400 when name is blank' do put "/users/#{user.id}", params: { name: ' ' }, headers: auth_headers(user) expect(response).to have_http_status(:bad_request) end it 'updates name and returns user slice' do put "/users/#{user.id}", params: { name: 'new-name' }, headers: auth_headers(user) expect(response).to have_http_status(:ok) expect(json['id']).to eq(user.id) expect(json['name']).to eq('new-name') user.reload expect(user.name).to eq('new-name') end it 'returns 403 when current user is banned' do user.update!(banned_at: Time.current) put "/users/#{user.id}", params: { name: 'new-name' }, headers: auth_headers(user) expect(response).to have_http_status(:forbidden) user.reload expect(user.name).to eq('old-name') end it 'returns 403 when current IP address is banned' do IpAddress.create!( ip_address: IPAddr.new(remote_ip).hton, banned_at: Time.current ) put "/users/#{user.id}", params: { name: 'new-name' }, headers: auth_headers(user) expect(response).to have_http_status(:forbidden) user.reload expect(user.name).to eq('old-name') end end describe 'POST /users/verify' do it 'returns valid:false when code not found' do post '/users/verify', params: { code: 'nope' } expect(response).to have_http_status(:ok) expect(json['valid']).to eq(false) end it 'returns 403 when current IP address is banned' do user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest') IpAddress.create!( ip_address: IPAddr.new(remote_ip).hton, banned_at: Time.current ) expect { post '/users/verify', params: { code: user.inheritance_code } }.not_to change(UserIp, :count) expect(response).to have_http_status(:forbidden) end it 'returns 403 when verified user is banned' do user = create( :user, :banned, inheritance_code: SecureRandom.uuid, role: 'guest' ) expect { post '/users/verify', params: { code: user.inheritance_code } }.not_to change(UserIp, :count) expect(response).to have_http_status(:forbidden) end it 'creates IpAddress and UserIp, and returns valid:true with user slice' do user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest') expect { post '/users/verify', params: { code: user.inheritance_code } }.to change(UserIp, :count).by(1) .and change(IpAddress, :count).by(1) expect(response).to have_http_status(:ok) expect(json['valid']).to eq(true) expect(json['user']['id']).to eq(user.id) expect(json['user']['inheritance_code']).to eq(user.inheritance_code) expect(json['user']['role']).to eq('guest') ip_address = IpAddress.find_by(ip_address: IPAddr.new(remote_ip).hton) expect(ip_address).to be_present expect(UserIp.exists?(user:, ip_address:)).to eq(true) end it 'is idempotent for same user and same IP address' do user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest') post '/users/verify', params: { code: user.inheritance_code } expect(response).to have_http_status(:ok) expect { post '/users/verify', params: { code: user.inheritance_code } }.not_to change(UserIp, :count) expect(response).to have_http_status(:ok) expect(json['valid']).to eq(true) end it 'creates another UserIp for same user and different IP address' do user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest') post '/users/verify', params: { code: user.inheritance_code } expect(response).to have_http_status(:ok) allow_any_instance_of(ActionDispatch::Request) .to receive(:remote_ip) .and_return('203.0.113.11') expect { post '/users/verify', params: { code: user.inheritance_code } }.to change(UserIp, :count).by(1) expect(response).to have_http_status(:ok) expect(json['valid']).to eq(true) end end describe 'GET /users/me' do it 'returns 404 when code not found' do get '/users/me', params: { code: 'nope' } expect(response).to have_http_status(:not_found) end it 'returns user slice when found' do user = create(:user, inheritance_code: SecureRandom.uuid, name: 'me', role: 'guest') get '/users/me', params: { code: user.inheritance_code } expect(response).to have_http_status(:ok) expect(json['id']).to eq(user.id) expect(json['name']).to eq('me') expect(json['inheritance_code']).to eq(user.inheritance_code) expect(json['role']).to eq('guest') end it 'returns 403 when current IP address is banned' do user = create(:user, inheritance_code: SecureRandom.uuid) IpAddress.create!( ip_address: IPAddr.new(remote_ip).hton, banned_at: Time.current ) get '/users/me', params: { code: user.inheritance_code } expect(response).to have_http_status(:forbidden) end end end