diff --git a/spec/requests/api/v1/activities_request_spec.rb b/spec/requests/api/v1/activities_request_spec.rb index 0eb5e98a82..f2c74f08df 100644 --- a/spec/requests/api/v1/activities_request_spec.rb +++ b/spec/requests/api/v1/activities_request_spec.rb @@ -3,25 +3,28 @@ require 'rails_helper' RSpec.describe 'Activities', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:activity) { FactoryBot.create(:activity, garden: create(:garden), planting: create(:planting)) } + let(:garden) { create(:garden, owner: member) } + let(:planting) { create(:planting, garden: garden) } + let!(:activity) { FactoryBot.create(:activity, garden: garden, planting: planting, owner: member) } let!(:activity2) { FactoryBot.create(:activity) } it '#index' do - get('/api/v1/activities', params: {}, headers:) - expect(subject['data'].size).to eq(2) + get('/api/v1/activities', params: {}, headers: headers) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(activity.id.to_s) end it '#show' do - get("/api/v1/activities/#{activity.id}", params: {}, headers:) + get("/api/v1/activities/#{activity.id}", params: {}, headers: headers) expect(subject['data']['id']).to eq(activity.id.to_s) end context 'filtering' do it 'filters by owner' do - get("/api/v1/activities?filter[owner-id]=#{activity.owner.id}", params: {}, headers:) + get("/api/v1/activities?filter[owner-id]=#{activity.owner.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -29,7 +32,7 @@ end it 'filters by garden' do - get("/api/v1/activities?filter[garden-id]=#{activity.garden.id}", params: {}, headers:) + get("/api/v1/activities?filter[garden-id]=#{activity.garden.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -37,7 +40,7 @@ end it 'filters by planting' do - get("/api/v1/activities?filter[planting-id]=#{activity.planting.id}", params: {}, headers:) + get("/api/v1/activities?filter[planting-id]=#{activity.planting.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -45,12 +48,12 @@ end it 'filters by category' do - get("/api/v1/activities?filter[category]=#{activity.category}", params: {}, headers:) + activity2.update!(category: activity.category) + get("/api/v1/activities?filter[category]=#{activity.category}", params: {}, headers: headers) expect(response).to have_http_status(:ok) - expect(subject['data'].size).to eq(2) + expect(subject['data'].size).to eq(1) expect(subject['data'][0]['id']).to eq(activity.id.to_s) - expect(subject['data'][1]['id']).to eq(activity2.id.to_s) end end end diff --git a/spec/requests/api/v1/crop_request_spec.rb b/spec/requests/api/v1/crop_request_spec.rb index 2454e8dfc2..15fdf8ffb2 100644 --- a/spec/requests/api/v1/crop_request_spec.rb +++ b/spec/requests/api/v1/crop_request_spec.rb @@ -3,9 +3,9 @@ require 'rails_helper' RSpec.describe 'Crops', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } let!(:crop) { FactoryBot.create(:crop) } let(:crop_encoded_as_json_api) do { "id" => crop.id.to_s, @@ -66,13 +66,13 @@ end describe '#index' do - before { get '/api/v1/crops', params: {}, headers: } + before { get '/api/v1/crops', params: {}, headers: headers } it { expect(subject['data']).to include(crop_encoded_as_json_api) } end describe '#show' do - before { get "/api/v1/crops/#{crop.id}", params: {}, headers: } + before { get "/api/v1/crops/#{crop.id}", params: {}, headers: headers } it { expect(subject['data']['attributes']).to eq(attributes) } it { expect(subject['data']['relationships']).to include("plantings" => plantings_as_json_api) } @@ -85,19 +85,19 @@ it '#create' do expect do - post '/api/v1/crops', params: { 'crop' => { 'name' => 'can i make this' } }, headers: + post '/api/v1/crops', params: { 'crop' => { 'name' => 'can i make this' } }, headers: headers end.to raise_error ActionController::RoutingError end it '#update' do expect do - post "/api/v1/crops/#{crop.id}", params: { 'crop' => { 'name' => 'can i modify this' } }, headers: + post "/api/v1/crops/#{crop.id}", params: { 'crop' => { 'name' => 'can i modify this' } }, headers: headers end.to raise_error ActionController::RoutingError end it '#delete' do expect do - delete "/api/v1/crops/#{crop.id}", params: {}, headers: + delete "/api/v1/crops/#{crop.id}", params: {}, headers: headers end.to raise_error ActionController::RoutingError end end diff --git a/spec/requests/api/v1/gardens_request_spec.rb b/spec/requests/api/v1/gardens_request_spec.rb index de3906db8c..4d1857dc3c 100644 --- a/spec/requests/api/v1/gardens_request_spec.rb +++ b/spec/requests/api/v1/gardens_request_spec.rb @@ -3,10 +3,10 @@ require 'rails_helper' RSpec.describe 'Gardens', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:garden) { FactoryBot.create(:garden) } + let!(:garden) { FactoryBot.create(:garden, owner: member) } let(:garden_encoded_as_json_api) do { "id" => garden.id.to_s, "type" => "gardens", @@ -41,20 +41,23 @@ end it '#index' do - get('/api/v1/gardens', params: {}, headers:) + get('/api/v1/gardens', params: {}, headers: headers) expect(subject['data']).to include(garden_encoded_as_json_api) end it '#show' do - get("/api/v1/gardens/#{garden.id}", params: {}, headers:) + get("/api/v1/gardens/#{garden.id}", params: {}, headers: headers) expect(subject['data']).to include(garden_encoded_as_json_api) end context 'filtering' do - let!(:garden2) { FactoryBot.create(:garden, active: false, garden_type: FactoryBot.create(:garden_type)) } + let(:garden_type) { create(:garden_type) } + let!(:garden2) { FactoryBot.create(:garden, owner: member, active: false, garden_type: garden_type) } + let!(:other_member_garden) { FactoryBot.create(:garden) } - pending 'filters by active' do - get('/api/v1/gardens?filter[active]=true', params: {}, headers:) + + it 'filters by active' do + get('/api/v1/gardens?filter[active]=true', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -62,7 +65,7 @@ end it 'filters by garden_type' do - get("/api/v1/gardens?filter[garden_type]=#{garden2.garden_type.id}", params: {}, headers:) + get("/api/v1/gardens?filter[garden_type]=#{garden_type.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -70,22 +73,15 @@ end it 'filters by owner' do - get("/api/v1/gardens?filter[owner_id]=#{garden2.owner.id}", params: {}, headers:) + get("/api/v1/gardens?filter[owner_id]=#{member.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(2) - expect(subject['data'][1]['id']).to eq(garden2.id.to_s) + expect(subject['data'].map { |g| g['id'] }).to include(garden.id.to_s, garden2.id.to_s) end end describe '#create' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } let(:garden_params) do { data: { @@ -98,26 +94,19 @@ end it 'returns 401 Unauthorized without a token' do - post '/api/v1/gardens', params: garden_params, headers: headers + post '/api/v1/gardens', params: garden_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 201 Created with a valid token' do - post '/api/v1/gardens', params: garden_params, headers: auth_headers + expect do + post '/api/v1/gardens', params: garden_params, headers: headers + end.to change { member.gardens.count }.by(1) expect(response).to have_http_status(:created) - expect(member.gardens.count).to eq(2) # 1 from after_create callback, 1 from api end end describe '#update' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:garden) { create(:garden, owner: member) } let(:other_member_garden) { create(:garden) } let(:update_params) do { @@ -132,12 +121,12 @@ end it 'returns 401 Unauthorized without a token' do - patch "/api/v1/gardens/#{garden.id}", params: update_params, headers: headers + patch "/api/v1/gardens/#{garden.id}", params: update_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 200 OK with a valid token for own garden' do - patch "/api/v1/gardens/#{garden.id}", params: update_params, headers: auth_headers + patch "/api/v1/gardens/#{garden.id}", params: update_params, headers: headers expect(response).to have_http_status(:ok) expect(garden.reload.name).to eq('An updated garden') end @@ -152,35 +141,27 @@ } } }.to_json - patch "/api/v1/gardens/#{other_member_garden.id}", params: update_params_for_other, headers: auth_headers + patch "/api/v1/gardens/#{other_member_garden.id}", params: update_params_for_other, headers: headers expect(response).to have_http_status(:forbidden) end end describe '#delete' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let!(:garden) { create(:garden, owner: member) } let(:other_member_garden) { create(:garden) } it 'returns 401 Unauthorized without a token' do - delete "/api/v1/gardens/#{garden.id}", headers: headers + delete "/api/v1/gardens/#{garden.id}", headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 204 No Content with a valid token for own garden' do - delete "/api/v1/gardens/#{garden.id}", headers: auth_headers + delete "/api/v1/gardens/#{garden.id}", headers: headers expect(response).to have_http_status(:no_content) expect(Garden.find_by(id: garden.id)).to be_nil end it 'returns 403 Forbidden for another member\'s garden' do - delete "/api/v1/gardens/#{other_member_garden.id}", headers: auth_headers + delete "/api/v1/gardens/#{other_member_garden.id}", headers: headers expect(response).to have_http_status(:forbidden) end end diff --git a/spec/requests/api/v1/harvests_request_spec.rb b/spec/requests/api/v1/harvests_request_spec.rb index 38d6777c15..d44075efcf 100644 --- a/spec/requests/api/v1/harvests_request_spec.rb +++ b/spec/requests/api/v1/harvests_request_spec.rb @@ -3,10 +3,10 @@ require 'rails_helper' RSpec.describe 'Harvests', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:harvest) { FactoryBot.create(:harvest) } + let!(:harvest) { FactoryBot.create(:harvest, owner: member) } let(:harvest_encoded_as_json_api) do { "id" => harvest.id.to_s, "type" => "harvests", @@ -50,7 +50,7 @@ let(:attributes) do { - "harvested-at" => "2015-09-17", + "harvested-at" => harvest.harvested_at.strftime('%Y-%m-%d'), "description" => harvest.description, "unit" => harvest.unit, "weight-quantity" => harvest.weight_quantity.to_s, @@ -60,13 +60,13 @@ end describe '#index' do - before { get '/api/v1/harvests', params: {}, headers: } + before { get '/api/v1/harvests', params: {}, headers: headers } it { expect(subject['data']).to include(harvest_encoded_as_json_api) } end describe '#show' do - before { get "/api/v1/harvests/#{harvest.id}", params: {}, headers: } + before { get "/api/v1/harvests/#{harvest.id}", params: {}, headers: headers } it { expect(subject['data']['attributes']).to eq(attributes) } it { expect(subject['data']['relationships']).to include("planting" => planting_as_json_api) } @@ -77,16 +77,18 @@ end context 'filtering' do - let!(:harvest2) { FactoryBot.create(:harvest, planting: create(:planting)) } + let(:garden) { create(:garden, owner: member) } + let(:planting) { create(:planting, garden: garden) } + let!(:harvest2) { FactoryBot.create(:harvest, planting: planting) } it 'filters by crop' do - get("/api/v1/harvests?filter[crop_id]=#{harvest2.crop.id}", params: {}, headers:) + get("/api/v1/harvests?filter[crop_id]=#{harvest2.crop.id}", params: {}, headers: headers) expect(subject['data'].size).to eq(1) expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) end it 'filters by planting' do - get("/api/v1/harvests?filter[planting_id]=#{harvest2.planting.id}", params: {}, headers:) + get("/api/v1/harvests?filter[planting_id]=#{harvest2.planting.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -94,7 +96,7 @@ end it 'filters by plant_part' do - get("/api/v1/harvests?filter[plant_part]=#{harvest2.plant_part.id}", params: {}, headers:) + get("/api/v1/harvests?filter[plant_part]=#{harvest2.plant_part.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -102,25 +104,16 @@ end it 'filters by owner' do - get("/api/v1/harvests?filter[owner_id]=#{harvest2.owner.id}", params: {}, headers:) + get("/api/v1/harvests?filter[owner_id]=#{harvest2.owner.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) - expect(subject['data'].size).to eq(1) - expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) + expect(subject['data'].size).to eq(2) + expect(subject['data'].map { |h| h['id'] }).to include(harvest.id.to_s, harvest2.id.to_s) end end describe '#create' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:crop) { create(:crop) } let(:planting) { create(:planting, owner: member) } - let(:plant_part) { create(:plant_part) } let(:harvest_params) do { data: { @@ -130,34 +123,25 @@ }, relationships: { planting: { data: { type: 'plantings', id: planting.id } } - # plant_part: { data: { type: 'plant_parts', id: plant_part.id } } } } }.to_json end it 'returns 401 Unauthorized without a token' do - post '/api/v1/harvests', params: harvest_params, headers: headers + post '/api/v1/harvests', params: harvest_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 201 Created with a valid token' do - post '/api/v1/harvests', params: harvest_params, headers: auth_headers - + expect do + post '/api/v1/harvests', params: harvest_params, headers: headers + end.to change { member.harvests.count }.by(1) expect(response).to have_http_status(:created) - expect(member.harvests.count).to eq(1) end end describe '#update' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:harvest) { create(:harvest, owner: member) } let(:other_member_harvest) { create(:harvest) } let(:update_params) do { @@ -172,12 +156,12 @@ end it 'returns 401 Unauthorized without a token' do - patch "/api/v1/harvests/#{harvest.id}", params: update_params, headers: headers + patch "/api/v1/harvests/#{harvest.id}", params: update_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 200 OK with a valid token for own harvest' do - patch "/api/v1/harvests/#{harvest.id}", params: update_params, headers: auth_headers + patch "/api/v1/harvests/#{harvest.id}", params: update_params, headers: headers expect(response).to have_http_status(:ok) expect(harvest.reload.description).to eq('An updated harvest') @@ -193,35 +177,29 @@ } } }.to_json - patch "/api/v1/harvests/#{other_member_harvest.id}", params: update_params_for_other, headers: auth_headers + patch "/api/v1/harvests/#{other_member_harvest.id}", params: update_params_for_other, headers: headers expect(response).to have_http_status(:forbidden) end end describe '#delete' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let!(:harvest) { create(:harvest, owner: member) } let(:other_member_harvest) { create(:harvest) } it 'returns 401 Unauthorized without a token' do - delete "/api/v1/harvests/#{harvest.id}", headers: headers + delete "/api/v1/harvests/#{harvest.id}", headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 204 No Content with a valid token for own harvest' do - delete "/api/v1/harvests/#{harvest.id}", headers: auth_headers + garden = harvest.planting.garden + delete "/api/v1/harvests/#{harvest.id}", headers: headers expect(response).to have_http_status(:no_content) - expect(Garden.find_by(id: harvest.id)).to be_nil + expect(Harvest.find_by(id: harvest.id)).to be_nil + expect(Garden.find_by(id: garden.id)).not_to be_nil end it 'returns 403 Forbidden for another member\'s harvest' do - delete "/api/v1/harvests/#{other_member_harvest.id}", headers: auth_headers + delete "/api/v1/harvests/#{other_member_harvest.id}", headers: headers expect(response).to have_http_status(:forbidden) end end diff --git a/spec/requests/api/v1/members_request_spec.rb b/spec/requests/api/v1/members_request_spec.rb index aea8c08cd1..38ef00463c 100644 --- a/spec/requests/api/v1/members_request_spec.rb +++ b/spec/requests/api/v1/members_request_spec.rb @@ -3,10 +3,9 @@ require 'rails_helper' RSpec.describe 'Members', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:member) { FactoryBot.create(:member) } let(:member_encoded_as_json_api) do { "id" => member.id.to_s, "type" => "members", @@ -68,13 +67,13 @@ end describe '#index' do - before { get '/api/v1/members', params: {}, headers: } + before { get '/api/v1/members', params: {}, headers: headers } it { expect(subject['data']).to include(member_encoded_as_json_api) } end describe '#show' do - before { get "/api/v1/members/#{member.id}", params: {}, headers: } + before { get "/api/v1/members/#{member.id}", params: {}, headers: headers } it { expect(subject['data']['relationships']).to include("gardens" => gardens_as_json_api) } it { expect(subject['data']['relationships']).to include("plantings" => plantings_as_json_api) } @@ -87,7 +86,7 @@ it '#create' do expect do - post '/api/v1/members', params: { 'member' => { 'login_name' => 'can i make this' } }, headers: + post '/api/v1/members', params: { 'member' => { 'login_name' => 'can i make this' } }, headers: headers end.to raise_error ActionController::RoutingError end @@ -96,13 +95,13 @@ post "/api/v1/members/#{member.id}", params: { 'member' => { 'login_name' => 'can i modify this' } }, - headers: + headers: headers end.to raise_error ActionController::RoutingError end it '#delete' do expect do - delete "/api/v1/members/#{member.id}", params: {}, headers: + delete "/api/v1/members/#{member.id}", params: {}, headers: headers end.to raise_error ActionController::RoutingError end end diff --git a/spec/requests/api/v1/photos_request_spec.rb b/spec/requests/api/v1/photos_request_spec.rb index fe639caf13..eb7ff56c0f 100644 --- a/spec/requests/api/v1/photos_request_spec.rb +++ b/spec/requests/api/v1/photos_request_spec.rb @@ -3,10 +3,10 @@ require 'rails_helper' RSpec.describe 'Photos', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:photo) { FactoryBot.create(:photo) } + let!(:photo) { FactoryBot.create(:photo, owner: member) } let(:photo_encoded_as_json_api) do { "id" => photo.id.to_s, "type" => "photos", @@ -58,13 +58,13 @@ end describe '#index' do - before { get '/api/v1/photos', params: {}, headers: } + before { get '/api/v1/photos', params: {}, headers: headers } it { expect(subject['data']).to include(photo_encoded_as_json_api) } end describe '#show' do - before { get "/api/v1/photos/#{photo.id}", params: {}, headers: } + before { get "/api/v1/photos/#{photo.id}", params: {}, headers: headers } it { expect(subject['data']['attributes']).to eq(attributes) } it { expect(subject['data']['relationships']).to include("plantings" => plantings_as_json_api) } @@ -75,19 +75,19 @@ it '#create' do expect do - post '/api/v1/photos', params: { 'photo' => { 'name' => 'can i make this' } }, headers: + post '/api/v1/photos', params: { 'photo' => { 'name' => 'can i make this' } }, headers: headers end.to raise_error ActionController::RoutingError end it '#update' do expect do - post "/api/v1/photos/#{photo.id}", params: { 'photo' => { 'name' => 'can i modify this' } }, headers: + post "/api/v1/photos/#{photo.id}", params: { 'photo' => { 'name' => 'can i modify this' } }, headers: headers end.to raise_error ActionController::RoutingError end it '#delete' do expect do - delete "/api/v1/photos/#{photo.id}", params: {}, headers: + delete "/api/v1/photos/#{photo.id}", params: {}, headers: headers end.to raise_error ActionController::RoutingError end end diff --git a/spec/requests/api/v1/plantings_request_spec.rb b/spec/requests/api/v1/plantings_request_spec.rb index 7d334d539c..d9112cd8b2 100644 --- a/spec/requests/api/v1/plantings_request_spec.rb +++ b/spec/requests/api/v1/plantings_request_spec.rb @@ -3,10 +3,10 @@ require 'rails_helper' RSpec.describe 'Plantings', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:planting) { FactoryBot.create(:planting) } + let!(:planting) { FactoryBot.create(:planting, owner: member) } let(:planting_encoded_as_json_api) do { "id" => planting.id.to_s, "type" => "plantings", @@ -56,11 +56,11 @@ let(:attributes) do { "slug" => planting.slug, - "planted-at" => "2014-07-30", + "planted-at" => planting.planted_at.strftime('%Y-%m-%d'), "failed" => false, "finished-at" => nil, "finished" => false, - "quantity" => 33, + "quantity" => planting.quantity, "description" => planting.description, "crop-name" => planting.crop.name, "crop-slug" => planting.crop.slug, @@ -79,14 +79,14 @@ end it '#index' do - get('/api/v1/plantings', params: {}, headers:) + get('/api/v1/plantings', params: {}, headers: headers) expect(subject['data'][0].keys).to eq(planting_encoded_as_json_api.keys) expect(subject['data'][0]['attributes'].keys.sort!).to eq(planting_encoded_as_json_api['attributes'].keys.sort!) expect(subject['data']).to include(planting_encoded_as_json_api) end it '#show' do - get("/api/v1/plantings/#{planting.id}", params: {}, headers:) + get("/api/v1/plantings/#{planting.id}", params: {}, headers: headers) expect(subject['data']['relationships']).to include("garden" => garden_as_json_api) expect(subject['data']['relationships']).to include("crop" => crop_as_json_api) expect(subject['data']['relationships']).to include("owner" => owner_as_json_api) @@ -96,13 +96,6 @@ end describe '#create' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } let(:crop) { create(:crop) } let(:garden) { create(:garden, owner: member) } let(:planting_params) do @@ -121,27 +114,19 @@ end it 'returns 401 Unauthorized without a token' do - post '/api/v1/plantings', params: planting_params, headers: headers + post '/api/v1/plantings', params: planting_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 201 Created with a valid token' do - post '/api/v1/plantings', params: planting_params, headers: auth_headers - + expect do + post '/api/v1/plantings', params: planting_params, headers: headers + end.to change { member.plantings.count }.by(1) expect(response).to have_http_status(:created) - expect(member.plantings.count).to eq(1) end end describe '#update' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:planting) { create(:planting, owner: member) } let(:other_member_planting) { create(:planting) } let(:update_params) do { @@ -156,12 +141,12 @@ end it 'returns 401 Unauthorized without a token' do - patch "/api/v1/plantings/#{planting.id}", params: update_params, headers: headers + patch "/api/v1/plantings/#{planting.id}", params: update_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 200 OK with a valid token for own planting' do - patch "/api/v1/plantings/#{planting.id}", params: update_params, headers: auth_headers + patch "/api/v1/plantings/#{planting.id}", params: update_params, headers: headers expect(response).to have_http_status(:ok) expect(planting.reload.description).to eq('An updated planting') @@ -177,83 +162,85 @@ } } }.to_json - patch "/api/v1/plantings/#{other_member_planting.id}", params: update_params_for_other, headers: auth_headers + patch "/api/v1/plantings/#{other_member_planting.id}", params: update_params_for_other, headers: headers expect(response).to have_http_status(:forbidden) end end describe '#delete' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let!(:planting) { create(:planting, owner: member) } let(:other_member_planting) { create(:planting) } it 'returns 401 Unauthorized without a token' do - delete "/api/v1/plantings/#{planting.id}", headers: headers + delete "/api/v1/plantings/#{planting.id}", headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 204 No Content with a valid token for own planting' do - delete "/api/v1/plantings/#{planting.id}", headers: auth_headers + garden = planting.garden + delete "/api/v1/plantings/#{planting.id}", headers: headers expect(response).to have_http_status(:no_content) - expect(Garden.find_by(id: planting.id)).to be_nil + expect(Planting.find_by(id: planting.id)).to be_nil + expect(Garden.find_by(id: garden.id)).not_to be_nil end it 'returns 403 Forbidden for another member\'s planting' do - delete "/api/v1/plantings/#{other_member_planting.id}", headers: auth_headers + delete "/api/v1/plantings/#{other_member_planting.id}", headers: headers expect(response).to have_http_status(:forbidden) end end describe "by member/owner" do - before :each do - @member1 = planting.owner - @planting2 = create(:planting, owner: create(:owner)) - @member2 = @planting2.owner + let!(:planting2) { create(:planting, owner: create(:owner)) } + let(:member2) { planting2.owner } + + describe "on /api/v1/plantings" do + it "filters by owner but respects authorization scope" do + # Filtering by the current member's id should work + get "/api/v1/plantings?filter[owner-id]=#{member.id}", headers: headers + expect(response).to have_http_status(:ok) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(planting.id.to_s) + + # Filtering by another member's id should return nothing from the scoped collection + get "/api/v1/plantings?filter[owner-id]=#{member2.id}", headers: headers + expect(response).to have_http_status(:ok) + expect(subject['data']).to be_empty + end end - describe "#show" do - it "locates the correct member" do - get "/api/v1/plantings?filter[owner-id]=#{@member1.id}" - expect(JSON.parse(response.body)['data'][0]['id']).to eq(planting.id.to_s) - - get "/api/v1/plantings?filter[owner-id]=#{@member2.id}" - expect(JSON.parse(response.body)['data'][0]['id']).to eq(@planting2.id.to_s) - - pending "The below should be identical to the above, but aren't." - - get "/api/v1/members/#{@member1.id}/plantings" - expect(JSON.parse(response.body)['data'][0]['id']).to eq(planting.id.to_s) + describe "on /api/v1/members/:id/plantings" do + it "returns plantings for the correct member" do + get "/api/v1/members/#{member.id}/plantings", headers: headers + expect(response).to have_http_status(:ok) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(planting.id.to_s) + end - get "/api/v1/members/#{@member2.id}/plantings" - expect(JSON.parse(response.body)['data'][0]['id']).to eq(@planting2.id.to_s) + it "returns forbidden when accessing another member's plantings" do + get "/api/v1/members/#{member2.id}/plantings", headers: headers + expect(response).to have_http_status(:forbidden) end end end context 'filtering' do - let!(:planting2) { FactoryBot.create(:planting, failed: true, sunniness: 'shade') } - let!(:perennial_planting) { FactoryBot.create(:planting, crop: FactoryBot.create(:crop, perennial: true)) } + let!(:planting2) { FactoryBot.create(:planting, owner: member, failed: true, sunniness: 'shade') } + let!(:perennial_planting) { FactoryBot.create(:planting, owner: member, crop: FactoryBot.create(:crop, perennial: true)) } it 'filters by failed' do - get('/api/v1/plantings?filter[failed]=true', params: {}, headers:) + get('/api/v1/plantings?filter[failed]=true', params: {}, headers: headers) expect(subject['data'].size).to eq(1) expect(subject['data'][0]['id']).to eq(planting2.id.to_s) end it 'filters by sunniness' do - get('/api/v1/plantings?filter[sunniness]=shade', params: {}, headers:) + get('/api/v1/plantings?filter[sunniness]=shade', params: {}, headers: headers) expect(subject['data'].size).to eq(1) expect(subject['data'][0]['id']).to eq(planting2.id.to_s) end it 'filters by perennial' do - get('/api/v1/plantings?filter[perennial]=true', params: {}, headers:) + get('/api/v1/plantings?filter[perennial]=true', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -261,11 +248,11 @@ end it 'filters by active' do - get('/api/v1/plantings?filter[active]=true', params: {}, headers:) + get('/api/v1/plantings?filter[active]=true', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(2) - expect(subject['data'][0]['id']).to eq(planting.id.to_s) + expect(subject['data'].map { |p| p['id'] }).to include(planting.id.to_s, perennial_planting.id.to_s) end end end diff --git a/spec/requests/api/v1/seeds_request_spec.rb b/spec/requests/api/v1/seeds_request_spec.rb index ea30c7924a..f9eabb28bb 100644 --- a/spec/requests/api/v1/seeds_request_spec.rb +++ b/spec/requests/api/v1/seeds_request_spec.rb @@ -3,10 +3,10 @@ require 'rails_helper' RSpec.describe 'Seeds', type: :request do + include_context 'with authenticated member' subject { JSON.parse response.body } - let(:headers) { { 'Accept' => 'application/vnd.api+json' } } - let!(:seed) { FactoryBot.create(:seed) } + let!(:seed) { FactoryBot.create(:seed, owner: member) } let(:seed_encoded_as_json_api) do { "id" => seed.id.to_s, "type" => "seeds", @@ -36,7 +36,7 @@ { "description" => seed.description, "quantity" => seed.quantity, - "plant-before" => "2013-07-15", + "plant-before" => seed.plant_before.strftime('%Y-%m-%d'), "tradable-to" => seed.tradable_to, "days-until-maturity-min" => seed.days_until_maturity_min, "days-until-maturity-max" => seed.days_until_maturity_max, @@ -47,13 +47,13 @@ end describe '#index' do - before { get '/api/v1/seeds', params: {}, headers: } + before { get '/api/v1/seeds', params: {}, headers: headers } it { expect(subject['data']).to include(seed_encoded_as_json_api) } end describe '#show' do - before { get "/api/v1/seeds/#{seed.id}", params: {}, headers: } + before { get "/api/v1/seeds/#{seed.id}", params: {}, headers: headers } it { expect(subject['data']['attributes']).to eq(attributes) } it { expect(subject['data']['relationships']).to include("owner" => owner_as_json_api) } @@ -62,13 +62,6 @@ end describe '#create' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } let(:crop) { create(:crop) } let(:seed_params) do { @@ -85,27 +78,19 @@ end it 'returns 401 Unauthorized without a token' do - post '/api/v1/seeds', params: seed_params, headers: headers + post '/api/v1/seeds', params: seed_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 201 Created with a valid token' do - post '/api/v1/seeds', params: seed_params, headers: auth_headers + expect do + post '/api/v1/seeds', params: seed_params, headers: headers + end.to change { member.seeds.count }.by(1) expect(response).to have_http_status(:created) - expect(member.seeds.count).to eq(1) end end describe '#update' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:crop) { create(:crop) } - let(:seed) { create(:seed, owner: member, crop: crop) } let(:other_member_seed) { create(:seed) } let(:update_params) do { @@ -120,12 +105,12 @@ end it 'returns 401 Unauthorized without a token' do - patch "/api/v1/seeds/#{seed.id}", params: update_params, headers: headers + patch "/api/v1/seeds/#{seed.id}", params: update_params, headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 200 OK with a valid token for own seed' do - patch "/api/v1/seeds/#{seed.id}", params: update_params, headers: auth_headers + patch "/api/v1/seeds/#{seed.id}", params: update_params, headers: headers expect(response).to have_http_status(:ok) expect(seed.reload.description).to eq('An updated seed') end @@ -140,47 +125,39 @@ } } }.to_json - patch "/api/v1/seeds/#{other_member_seed.id}", params: update_params_for_other, headers: auth_headers + patch "/api/v1/seeds/#{other_member_seed.id}", params: update_params_for_other, headers: headers expect(response).to have_http_status(:forbidden) end end describe '#delete' do - let!(:member) { create(:member) } - let(:token) do - member.regenerate_api_token - member.api_token.token - end - let(:headers) { { 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json' } } - let(:auth_headers) { headers.merge('Authorization' => "Token token=#{token}") } - let(:crop) { create(:crop) } - let!(:seed) { create(:seed, owner: member, crop: crop) } let(:other_member_seed) { create(:seed) } it 'returns 401 Unauthorized without a token' do - delete "/api/v1/seeds/#{seed.id}", headers: headers + delete "/api/v1/seeds/#{seed.id}", headers: unauthenticated_headers expect(response).to have_http_status(:unauthorized) end it 'returns 204 No Content with a valid token for own seed' do - delete "/api/v1/seeds/#{seed.id}", headers: auth_headers + delete "/api/v1/seeds/#{seed.id}", headers: headers expect(response).to have_http_status(:no_content) expect(Seed.find_by(id: seed.id)).to be_nil end it 'returns 403 Forbidden for another member\'s seed' do - delete "/api/v1/seeds/#{other_member_seed.id}", headers: auth_headers + delete "/api/v1/seeds/#{other_member_seed.id}", headers: headers expect(response).to have_http_status(:forbidden) end end context 'filtering' do let!(:seed2) do - FactoryBot.create(:seed, tradable_to: 'nationally', organic: 'certified organic', gmo: 'certified GMO-free', heirloom: 'heirloom') + FactoryBot.create(:seed, owner: member, tradable_to: 'nationally', organic: 'certified organic', gmo: 'certified GMO-free', heirloom: 'heirloom') end + let!(:other_member_seed) { create(:seed) } it 'filters by crop' do - get("/api/v1/seeds?filter[crop]=#{seed2.crop.id}", params: {}, headers:) + get("/api/v1/seeds?filter[crop]=#{seed2.crop.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -188,7 +165,7 @@ end it 'filters by tradable_to' do - get('/api/v1/seeds?filter[tradable_to]=nationally', params: {}, headers:) + get('/api/v1/seeds?filter[tradable_to]=nationally', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -196,7 +173,7 @@ end it 'filters by organic' do - get('/api/v1/seeds?filter[organic]=certified organic', params: {}, headers:) + get('/api/v1/seeds?filter[organic]=certified organic', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -204,7 +181,7 @@ end it 'filters by gmo' do - get('/api/v1/seeds?filter[gmo]=certified GMO-free', params: {}, headers:) + get('/api/v1/seeds?filter[gmo]=certified GMO-free', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -212,7 +189,7 @@ end it 'filters by heirloom' do - get('/api/v1/seeds?filter[heirloom]=heirloom', params: {}, headers:) + get('/api/v1/seeds?filter[heirloom]=heirloom', params: {}, headers: headers) expect(response).to have_http_status(:ok) expect(subject['data'].size).to eq(1) @@ -220,11 +197,14 @@ end it 'filters by owner' do - get("/api/v1/seeds?filter[owner_id]=#{seed2.owner.id}", params: {}, headers:) + get("/api/v1/seeds?filter[owner_id]=#{member.id}", params: {}, headers: headers) + expect(response).to have_http_status(:ok) + expect(subject['data'].size).to eq(2) + expect(subject['data'].map { |s| s['id'] }).to include(seed.id.to_s, seed2.id.to_s) + get("/api/v1/seeds?filter[owner_id]=#{other_member_seed.owner.id}", params: {}, headers: headers) expect(response).to have_http_status(:ok) - expect(subject['data'].size).to eq(1) - expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + expect(subject['data']).to be_empty end end end diff --git a/spec/support/api_auth_helpers.rb b/spec/support/api_auth_helpers.rb new file mode 100644 index 0000000000..c9e01190f8 --- /dev/null +++ b/spec/support/api_auth_helpers.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with authenticated member' do + let(:member) { create(:member) } + let(:api_token) { member.regenerate_api_token } + let(:headers) do + { + 'Accept' => 'application/vnd.api+json', + 'Authorization' => "Token token=#{api_token.token}", + 'Content-Type' => 'application/vnd.api+json' + } + end + let(:unauthenticated_headers) do + { + 'Accept' => 'application/vnd.api+json', + 'Content-Type' => 'application/vnd.api+json' + } + end +end \ No newline at end of file