docbrown/spec/requests/api/v1/audience_segments_spec.rb
Anna Buianova 9c9663a5b5
Renamed display_ads to billboards: variables and descriptions in tests (#19920)
* Renamed display ad variables and descriptions in tests

* Renamed api docs display_ads => billboards

* Updated billboard cypress seeds

* Fixed api billboards specs

* Update db/schema.rb

Co-authored-by: Duke Greene <dukegreene@gmail.com>

---------

Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Duke Greene <dukegreene@gmail.com>
2023-08-11 15:00:07 -04:00

353 lines
12 KiB
Ruby

require "rails_helper"
RSpec.describe "Api::V1::AudienceSegments" do
let(:v1_headers) { { "content-type" => "application/json", "Accept" => "application/vnd.forem.api-v1+json" } }
let(:api_secret) { create(:api_secret) }
let(:admin) { api_secret.user }
let(:headers) { v1_headers.merge({ "api-key" => api_secret.secret }) }
before do
admin.add_role(:admin)
end
shared_examples "an admin-only protected resource" do
context "when no API secret is provided" do
let(:headers) { v1_headers }
it "returns unauthorized" do
make_request
expect(response).to have_http_status(:unauthorized)
end
end
context "when the authenticated user is not an admin" do
let(:regular_api_secret) { create(:api_secret) }
let(:headers) { v1_headers.merge({ "api-key" => regular_api_secret.secret }) }
it "returns unauthorized" do
make_request
expect(response).to have_http_status(:unauthorized)
end
end
end
shared_examples "an endpoint for only manual audience segments" do
it "returns not found if the segment is automatic" do
segment.update!(type_of: "trusted")
make_request
expect(response).to have_http_status(:not_found)
end
it "returns not found if the segment has been deleted" do
segment.destroy
make_request
expect(response).to have_http_status(:not_found)
end
end
describe "GET /api/segments" do
subject(:make_request) { get api_segments_path, headers: headers }
let(:first_manual_segment) { AudienceSegment.create!(type_of: "manual") }
let(:second_manual_segment) { AudienceSegment.create!(type_of: "manual") }
let(:automatic_segment) { AudienceSegment.create!(type_of: "trusted") }
let(:users) { create_list(:user, 3) }
it_behaves_like "an admin-only protected resource"
it "returns only manual segments including their user counts" do
first_manual_segment.users << users
expected_data = [
hash_including(
"id" => first_manual_segment.id,
"type_of" => "manual",
"user_count" => 3,
),
hash_including(
"id" => second_manual_segment.id,
"type_of" => "manual",
"user_count" => 0,
),
]
excluded_data = hash_including("id" => automatic_segment.id)
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body).to include(*expected_data)
expect(response.parsed_body).not_to include(excluded_data)
end
it "supports pagination" do
5.times.each { AudienceSegment.create!(type_of: :manual) }
get api_segments_path, params: { page: 1, per_page: 3 }, headers: headers
expect(response.parsed_body.length).to eq(3)
get api_segments_path, params: { page: 2, per_page: 4 }, headers: headers
expect(response.parsed_body.length).to eq(1)
end
it "respects API_PER_PAGE_MAX env variable limit" do
5.times.each { AudienceSegment.create!(type_of: :manual) }
allow(ApplicationConfig).to receive(:[]).and_return(nil)
allow(ApplicationConfig).to receive(:[]).with("APP_PROTOCOL").and_return("http://")
allow(ApplicationConfig).to receive(:[]).with("API_PER_PAGE_MAX").and_return(3)
get api_segments_path, params: { per_page: 10 }, headers: headers
expect(response.parsed_body.length).to eq(3)
end
end
describe "GET /api/segments/:id" do
subject(:make_request) { get api_segment_path(segment.id), headers: headers }
let(:segment) { AudienceSegment.create!(type_of: "manual") }
let(:users) { create_list(:user, 3) }
it_behaves_like "an admin-only protected resource"
it_behaves_like "an endpoint for only manual audience segments"
it "returns the segment including its user count" do
segment.users << users
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body).to include(
"id" => segment.id,
"type_of" => "manual",
"user_count" => 3,
)
end
end
describe "POST /api/segments" do
subject(:make_request) { post api_segments_path, headers: headers }
it_behaves_like "an admin-only protected resource"
it "creates a new manual audience segment" do
make_request
expect(response).to have_http_status(:created)
expect(response.media_type).to eq("application/json")
segment = AudienceSegment.last
expect(segment.manual?).to be(true)
expect(response.parsed_body).to include(
"id" => segment.id,
"type_of" => "manual",
)
end
end
describe "GET /api/segments/:id/users" do
subject(:make_request) { get users_api_segment_path(segment.id), headers: headers }
let(:segment) { AudienceSegment.create!(type_of: "manual") }
let(:users) { create_list(:user, 5) }
it_behaves_like "an admin-only protected resource"
it_behaves_like "an endpoint for only manual audience segments"
it "returns a list of users in the segment" do
segment.users << users
expected_data = users.map do |user|
hash_including(
"id" => user.id,
"username" => user.username,
"name" => user.name,
"twitter_username" => user.twitter_username,
"github_username" => user.github_username,
"summary" => user.profile.summary,
"location" => user.profile.location,
"website_url" => user.profile.website_url,
)
end
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body).to match_array(expected_data)
end
it "supports pagination" do
segment.users << users
get users_api_segment_path(segment.id), params: { page: 1, per_page: 3 }, headers: headers
expect(response.parsed_body.length).to eq(3)
get users_api_segment_path(segment.id), params: { page: 2, per_page: 4 }, headers: headers
expect(response.parsed_body.length).to eq(1)
end
it "respects API_PER_PAGE_MAX env variable limit" do
segment.users << users
allow(ApplicationConfig).to receive(:[]).and_return(nil)
allow(ApplicationConfig).to receive(:[]).with("APP_PROTOCOL").and_return("http://")
allow(ApplicationConfig).to receive(:[]).with("API_PER_PAGE_MAX").and_return(3)
get users_api_segment_path(segment.id), params: { per_page: 5 }, headers: headers
expect(response.parsed_body.length).to eq(3)
end
end
describe "PUT /api/segments/:id/add_users" do
subject(:make_request) do
put add_users_api_segment_path(segment.id), params: { user_ids: user_ids }, headers: headers, as: :json
end
let(:segment) { AudienceSegment.create!(type_of: "manual") }
let(:users) { create_list(:user, 3) }
let(:user_ids) { users.map(&:id) }
it_behaves_like "an admin-only protected resource"
it_behaves_like "an endpoint for only manual audience segments"
it "adds the provided users" do
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(segment.users).to match_array(users)
data = response.parsed_body
expect(data["succeeded"]).to match_array(user_ids)
expect(data["failed"]).to be_empty
end
it "returns user ids that failed to be added" do
fake_user_ids = [999_999, 777_777]
params = { user_ids: user_ids + fake_user_ids }
put add_users_api_segment_path(segment.id), params: params, headers: headers, as: :json
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(segment.users).to match_array(users)
data = response.parsed_body
expect(data["succeeded"]).to match_array(user_ids)
expect(data["failed"]).to match_array(fake_user_ids)
end
it "handles empty, missing or too large user_ids" do
put add_users_api_segment_path(segment.id), headers: headers
expect(response).to have_http_status(:unprocessable_entity)
put add_users_api_segment_path(segment.id), params: { user_ids: [] }, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
params = { user_ids: (1..10_100).to_a }
put add_users_api_segment_path(segment.id), params: params, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
describe "PUT /api/segments/:id/remove_users" do
subject(:make_request) do
put remove_users_api_segment_path(segment.id), params: { user_ids: user_ids }, headers: headers, as: :json
end
let(:segment) { AudienceSegment.create!(type_of: "manual") }
let(:users) { create_list(:user, 3) }
let(:user_ids) { users.map(&:id) }
let(:retained_users) { create_list(:user, 3) }
let(:users_not_in_segment) { create_list(:user, 3) }
before do
segment.users << users
segment.users << retained_users
end
it_behaves_like "an admin-only protected resource"
it_behaves_like "an endpoint for only manual audience segments"
it "removes only the provided users if they are part of the segment" do
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(segment.users).to match_array(retained_users)
data = response.parsed_body
expect(data["succeeded"]).to match_array(user_ids)
expect(data["failed"]).to be_empty
end
it "returns user ids that failed to be removed" do
ids_to_fail = [*users_not_in_segment.map(&:id), 123_456]
params = { user_ids: user_ids + ids_to_fail }
put remove_users_api_segment_path(segment.id), params: params, headers: headers, as: :json
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(segment.users).to match_array(retained_users)
data = response.parsed_body
expect(data["succeeded"]).to match_array(user_ids)
expect(data["failed"]).to match_array(ids_to_fail)
end
it "handles empty, missing or too large user_ids" do
put remove_users_api_segment_path(segment.id), headers: headers
expect(response).to have_http_status(:unprocessable_entity)
put remove_users_api_segment_path(segment.id), params: { user_ids: [] }, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
params = { user_ids: (1..10_100).to_a }
put remove_users_api_segment_path(segment.id), params: params, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
describe "DELETE /api/segments/:id" do
subject(:make_request) { delete api_segment_path(segment.id), headers: headers }
let(:segment) { AudienceSegment.create!(type_of: "manual") }
let(:users) { create_list(:user, 3) }
let(:billboard) { create(:billboard, published: true, approved: true, type_of: "community") }
it_behaves_like "an admin-only protected resource"
it_behaves_like "an endpoint for only manual audience segments"
it "destroys the segment and cleans up its list of users" do
segment.users << users
make_request
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(AudienceSegment.exists?(segment.id)).to be(false)
expect(SegmentedUser.where(audience_segment_id: segment.id)).to be_empty
end
it "does not destroy the segment or its user list if it is associated with a billboard" do
segment.users << users
billboard.update!(audience_segment: segment)
make_request
expect(response).to have_http_status(:conflict)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body.keys).to include("error")
expect(segment.reload.users).to match_array(users)
end
end
end