# frozen_string_literal: true require 'rails_helper' RSpec.describe 'Api::V1::Points', type: :request do let!(:user) { create(:user) } let!(:points) do (1..15).map do |i| create(:point, user:, timestamp: 1.day.ago + i.minutes) end end let(:point_params) do { locations: [ { geometry: { type: 'Point', coordinates: [1.0, 1.0] }, properties: { timestamp: '2025-01-17T21:03:01Z' } } ] } end describe 'GET /index' do context 'when regular version of points is requested' do it 'renders a successful response' do get api_v1_points_url(api_key: user.api_key) expect(response).to be_successful end it 'returns a list of points' do get api_v1_points_url(api_key: user.api_key) expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.size).to eq(15) end it 'returns a list of points with pagination' do get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10) expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.size).to eq(5) end it 'returns a list of points with pagination headers' do get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10) expect(response).to have_http_status(:ok) expect(response.headers['X-Current-Page']).to eq('2') expect(response.headers['X-Total-Pages']).to eq('2') end end context 'when slim version of points is requested' do it 'renders a successful response' do get api_v1_points_url(api_key: user.api_key, slim: 'true') expect(response).to be_successful end it 'returns a list of points' do get api_v1_points_url(api_key: user.api_key, slim: 'true') expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.size).to eq(15) end it 'returns a list of points with pagination' do get api_v1_points_url(api_key: user.api_key, slim: 'true', page: 2, per_page: 10) expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.size).to eq(5) end it 'returns a list of points with pagination headers' do get api_v1_points_url(api_key: user.api_key, slim: 'true', page: 2, per_page: 10) expect(response).to have_http_status(:ok) expect(response.headers['X-Current-Page']).to eq('2') expect(response.headers['X-Total-Pages']).to eq('2') end it 'returns a list of points with slim attributes' do get api_v1_points_url(api_key: user.api_key, slim: 'true') expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) json_response.each do |point| expect(point.keys).to eq(%w[id latitude longitude timestamp]) end end end context 'when order param is provided' do it 'returns points in ascending order' do get api_v1_points_url(api_key: user.api_key, order: 'asc') expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.first['timestamp']).to be < json_response.last['timestamp'] end it 'returns points in descending order' do get api_v1_points_url(api_key: user.api_key, order: 'desc') expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response.first['timestamp']).to be > json_response.last['timestamp'] end end end describe 'POST /create' do it 'returns a successful response' do post "/api/v1/points?api_key=#{user.api_key}", params: point_params expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body)['data'] expect(json_response.size).to be_positive expect(json_response.first['latitude']).to eq(1.0) expect(json_response.first['longitude']).to eq(1.0) expect(json_response.first['timestamp']).to be_an_instance_of(Integer) end context 'when user is inactive' do before do user.update(status: :inactive, active_until: 1.day.ago) end it 'returns an unauthorized response' do post "/api/v1/points?api_key=#{user.api_key}", params: point_params expect(response).to have_http_status(:unauthorized) end end end describe 'PUT /update' do it 'returns a successful response' do put "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}", params: { point: { latitude: 1.0, longitude: 1.1 } } expect(response).to have_http_status(:success) end context 'when user is inactive' do before do user.update(status: :inactive, active_until: 1.day.ago) end it 'returns an unauthorized response' do put "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}", params: { point: { latitude: 1.0, longitude: 1.1 } } expect(response).to have_http_status(:unauthorized) end end end describe 'DELETE /destroy' do it 'returns a successful response' do delete "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}" expect(response).to have_http_status(:success) end context 'when user is inactive' do before do user.update(status: :inactive, active_until: 1.day.ago) end it 'returns an unauthorized response' do delete "/api/v1/points/#{points.first.id}?api_key=#{user.api_key}" expect(response).to have_http_status(:unauthorized) end end end describe 'DELETE /bulk_destroy' do let(:point_ids) { points.first(5).map(&:id) } it 'returns a successful response' do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: } expect(response).to have_http_status(:ok) end it 'deletes multiple points' do expect do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: } end.to change { user.points.count }.by(-5) end it 'returns the count of deleted points' do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: } json_response = JSON.parse(response.body) expect(json_response['message']).to eq('Points were successfully destroyed') expect(json_response['count']).to eq(5) end it 'only deletes points belonging to the current user' do other_user = create(:user) other_points = create_list(:point, 3, user: other_user) all_point_ids = point_ids + other_points.map(&:id) expect do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: all_point_ids } end.to change { user.points.count }.by(-5) .and change { other_user.points.count }.by(0) end context 'when no point_ids are provided' do it 'returns success with zero count' do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: [] } expect(response).to have_http_status(:ok) json_response = JSON.parse(response.body) expect(json_response['count']).to eq(0) end end context 'when point_ids parameter is missing' do it 'returns an error' do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}" expect(response).to have_http_status(:unprocessable_entity) json_response = JSON.parse(response.body) expect(json_response['error']).to eq('No points selected') end end context 'when user is inactive' do before do user.update(status: :inactive, active_until: 1.day.ago) end it 'returns an unauthorized response' do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: } expect(response).to have_http_status(:unauthorized) end it 'does not delete any points' do expect do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: } end.not_to(change { user.points.count }) end end context 'when deleting all user points' do it 'successfully deletes all points' do all_point_ids = points.map(&:id) expect do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: all_point_ids } end.to change { user.points.count }.from(15).to(0) end end context 'when some point_ids do not exist' do it 'deletes only existing points' do non_existent_ids = [999_999, 888_888] mixed_ids = point_ids + non_existent_ids expect do delete "/api/v1/points/bulk_destroy?api_key=#{user.api_key}", params: { point_ids: mixed_ids } end.to change { user.points.count }.by(-5) json_response = JSON.parse(response.body) expect(json_response['count']).to eq(5) end end end end