604 lines
20 KiB
Ruby
604 lines
20 KiB
Ruby
# frozen_sTring_literal: true
|
|
|
|
require "spec_helper"
|
|
|
|
describe Api::V1::UsersController do
|
|
include PeopleHelper
|
|
|
|
let(:full_scopes) {
|
|
%w[openid public:read public:modify private:read private:modify contacts:read profile profile:modify]
|
|
}
|
|
|
|
let(:auth) {
|
|
FactoryGirl.create(
|
|
:auth_with_default_scopes,
|
|
scopes: full_scopes,
|
|
user: FactoryGirl.create(:user, profile: FactoryGirl.create(:profile_with_image_url))
|
|
)
|
|
}
|
|
|
|
let(:auth_public_only) {
|
|
FactoryGirl.create(
|
|
:auth_with_default_scopes,
|
|
scopes: %w[openid public:read public:modify]
|
|
)
|
|
}
|
|
|
|
let(:auth_read_only) {
|
|
FactoryGirl.create(
|
|
:auth_with_default_scopes,
|
|
scopes: %w[openid public:read private:read contacts:read profile]
|
|
)
|
|
}
|
|
|
|
let(:auth_public_only_read_only) {
|
|
FactoryGirl.create(
|
|
:auth_with_default_scopes,
|
|
scopes: %w[openid public:read]
|
|
)
|
|
}
|
|
|
|
let(:auth_minimum_scopes) {
|
|
FactoryGirl.create(:auth_with_default_scopes)
|
|
}
|
|
let!(:access_token) { auth.create_access_token.to_s }
|
|
let!(:access_token_public_only) { auth_public_only.create_access_token.to_s }
|
|
let!(:access_token_read_only) { auth_read_only.create_access_token.to_s }
|
|
let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s }
|
|
let!(:access_token_minimum_scopes) { auth_minimum_scopes.create_access_token.to_s }
|
|
let(:invalid_token) { SecureRandom.hex(9) }
|
|
|
|
before do
|
|
alice.person.profile = FactoryGirl.create(:profile_with_image_url)
|
|
end
|
|
|
|
describe "#show" do
|
|
context "Current User" do
|
|
it "succeeds when logged in" do
|
|
get(
|
|
api_v1_user_path,
|
|
params: {access_token: access_token}
|
|
)
|
|
user = response_body(response)
|
|
expect(response.status).to eq(200)
|
|
expect(user["guid"]).to eq(auth.user.guid)
|
|
confirm_self_data_format(user)
|
|
|
|
expect(user.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
get(
|
|
api_v1_user_path,
|
|
params: {access_token: invalid_token}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
|
|
context "Single User" do
|
|
it "succeeds with public user not in Aspect" do
|
|
alice.profile[:public_details] = true
|
|
alice.profile.save
|
|
get(
|
|
"/api/v1/users/#{alice.guid}",
|
|
params: {access_token: access_token}
|
|
)
|
|
user = response_body(response)
|
|
expect(response.status).to eq(200)
|
|
expect(user["guid"]).to eq(alice.person.guid)
|
|
confirm_public_profile_hash(user)
|
|
|
|
expect(user.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "succeeds with in Aspect valid user" do
|
|
alice.profile[:public_details] = true
|
|
alice.profile.save
|
|
aspect = auth.user.aspects.create(name: "first")
|
|
auth.user.share_with(alice.person, aspect)
|
|
get(
|
|
"/api/v1/users/#{alice.guid}",
|
|
params: {access_token: access_token}
|
|
)
|
|
user = response_body(response)
|
|
expect(response.status).to eq(200)
|
|
expect(user["guid"]).to eq(alice.person.guid)
|
|
confirm_public_profile_hash(user)
|
|
|
|
expect(user.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "succeeds with limited data on non-public/not shared" do
|
|
eve.person.profile = FactoryGirl.create(:profile_with_image_url)
|
|
eve.profile[:public_details] = false
|
|
eve.profile.save
|
|
get(
|
|
"/api/v1/users/#{eve.guid}",
|
|
params: {access_token: access_token}
|
|
)
|
|
user = response_body(response)
|
|
expect(response.status).to eq(200)
|
|
expect(user["guid"]).to eq(eve.person.guid)
|
|
confirm_private_profile_hash(user)
|
|
|
|
eve.aspects.create(name: "new aspect")
|
|
eve.share_with(auth.user.person, eve.aspects.first)
|
|
get(
|
|
"/api/v1/users/#{eve.guid}",
|
|
params: {access_token: access_token}
|
|
)
|
|
user = response_body(response)
|
|
expect(response.status).to eq(200)
|
|
expect(user["guid"]).to eq(eve.person.guid)
|
|
confirm_public_profile_hash(user)
|
|
|
|
expect(user.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
get(
|
|
"/api/v1/users/#{alice.guid}",
|
|
params: {access_token: invalid_token}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
|
|
it "fails for private profile if don't have contacts:read" do
|
|
unsearchable_user = FactoryGirl.create(
|
|
:person,
|
|
diaspora_handle: "unsearchable@example.org",
|
|
profile: FactoryGirl.build(
|
|
:profile,
|
|
first_name: "Unsearchable",
|
|
last_name: "Person",
|
|
searchable: false
|
|
)
|
|
)
|
|
|
|
get(
|
|
"/api/v1/users/#{unsearchable_user.guid}",
|
|
params: {access_token: access_token_minimum_scopes}
|
|
)
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "fails with invalid user GUID" do
|
|
get(
|
|
"/api/v1/users/999_999_999",
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(404)
|
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found"))
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#update" do
|
|
context "Partial with all valid fields" do
|
|
it "succeeds when logged in" do
|
|
new_location = "New Location"
|
|
new_bio = "New Bio"
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {location: new_location, bio: new_bio, access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
user = response_body(response)
|
|
confirm_self_data_format(user)
|
|
expect(user["bio"]).to eq(new_bio)
|
|
expect(user["location"]).to eq(new_location)
|
|
auth.user.profile.reload
|
|
expect(auth.user.profile[:location]).to eq(new_location)
|
|
expect(auth.user.profile[:bio]).to eq(new_bio)
|
|
end
|
|
end
|
|
|
|
context "Full with all valid fields" do
|
|
it "succeeds" do
|
|
new_bio = "new bio"
|
|
new_birthday = Date.current + 100
|
|
new_gender = "ask1"
|
|
new_location = "new location"
|
|
new_name = "New Name"
|
|
new_searchable = !auth.user.profile[:searchable]
|
|
new_show_profile_info = !auth.user.profile[:public_details]
|
|
new_nsfw = !auth.user.profile[:nsfw]
|
|
new_tags = %w[tag1 tag2]
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {
|
|
bio: new_bio,
|
|
location: new_location,
|
|
gender: new_gender,
|
|
birthday: new_birthday,
|
|
name: new_name,
|
|
searchable: new_searchable,
|
|
show_profile_info: new_show_profile_info,
|
|
nsfw: new_nsfw,
|
|
tags: new_tags,
|
|
access_token: access_token
|
|
}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
user = response_body(response)
|
|
confirm_self_data_format(user)
|
|
expect(user["bio"]).to eq(new_bio)
|
|
expect(user["birthday"]).to eq(new_birthday.iso8601)
|
|
expect(user["location"]).to eq(new_location)
|
|
expect(user["gender"]).to eq(new_gender)
|
|
expect(user["name"]).to eq(new_name)
|
|
expect(user["searchable"]).to eq(new_searchable)
|
|
expect(user["show_profile_info"]).to eq(new_show_profile_info)
|
|
expect(user["nsfw"]).to eq(new_nsfw)
|
|
expect(user["tags"]).to eq(new_tags)
|
|
auth.user.profile.reload
|
|
expect(auth.user.profile[:location]).to eq(new_location)
|
|
expect(auth.user.profile[:bio]).to eq(new_bio)
|
|
expect(birthday_format(auth.user.profile[:birthday])).to eq(birthday_format(new_birthday))
|
|
expect(auth.user.profile[:gender]).to eq(new_gender)
|
|
expect(auth.user.profile[:first_name]).to eq(new_name)
|
|
expect(auth.user.profile[:last_name]).to eq(nil)
|
|
expect(auth.user.profile[:searchable]).to eq(new_searchable)
|
|
expect(auth.user.profile[:public_details]).to eq(new_show_profile_info)
|
|
expect(auth.user.profile[:nsfw]).to eq(new_nsfw)
|
|
expect(user["tags"]).to eq(new_tags)
|
|
end
|
|
end
|
|
|
|
context "fails" do
|
|
it "skips invalid fields" do
|
|
new_bio = "new bio"
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {
|
|
bio: new_bio,
|
|
no_idea_what_field_this_is: "some value",
|
|
access_token: access_token
|
|
}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
user = response_body(response)
|
|
expect(user["bio"]).to eq(new_bio)
|
|
expect(user.has_key?("no_idea_what_field_this_is")).to be_falsey
|
|
end
|
|
|
|
it "fails to update guid" do
|
|
new_bio = "new bio"
|
|
original_guid = auth.user.guid.to_s
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {
|
|
bio: new_bio,
|
|
guid: "999_999_999",
|
|
access_token: access_token
|
|
}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
user = response_body(response)
|
|
expect(user["bio"]).to eq(new_bio)
|
|
expect(user["guid"]).to eq(original_guid)
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {
|
|
location: "New Location",
|
|
bio: "New Bio",
|
|
access_token: invalid_token
|
|
}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
|
|
it "fails if read only token" do
|
|
patch(
|
|
api_v1_user_path,
|
|
params: {
|
|
location: "New Location",
|
|
bio: "New Bio",
|
|
access_token: access_token_read_only
|
|
}
|
|
)
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#contacts" do
|
|
it "succeeds when logged in and ask for own" do
|
|
get(
|
|
api_v1_user_contacts_path(auth.user.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
contacts = response_body(response)
|
|
expect(contacts.length).to eq(0)
|
|
|
|
aspect = auth.user.aspects.create(name: "first")
|
|
auth.user.share_with(alice.person, aspect)
|
|
get(
|
|
api_v1_user_contacts_path(auth.user.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
contacts = response_body(response)
|
|
expect(contacts.length).to eq(1)
|
|
confirm_person_format(contacts[0], alice)
|
|
|
|
expect(contacts.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "fails with invalid GUID" do
|
|
get(
|
|
api_v1_user_contacts_path("999_999_999"),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(404)
|
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found"))
|
|
end
|
|
|
|
it "fails with other user's GUID" do
|
|
get(
|
|
api_v1_user_contacts_path(alice.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(404)
|
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found"))
|
|
end
|
|
|
|
it "fails if insufficient scope token" do
|
|
get(
|
|
api_v1_user_contacts_path(alice.guid),
|
|
params: {access_token: access_token_public_only_read_only}
|
|
)
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
get(
|
|
api_v1_user_contacts_path(alice.guid),
|
|
params: {access_token: invalid_token}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
|
|
describe "#photos" do
|
|
before do
|
|
alice_private_spec = alice.aspects.create(name: "private aspect")
|
|
alice.share_with(eve.person, alice_private_spec)
|
|
alice.share_with(auth.user.person, alice.aspects.first)
|
|
|
|
auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true)
|
|
auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true)
|
|
@public_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true)
|
|
@public_photo2 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true)
|
|
@shared_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name),
|
|
to: alice.aspects.first.id)
|
|
@private_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name),
|
|
to: alice_private_spec.id)
|
|
end
|
|
|
|
context "logged in" do
|
|
it "returns only visible photos of other user" do
|
|
get(
|
|
api_v1_user_photos_path(alice.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
photos = response_body(response)
|
|
expect(photos.length).to eq(3)
|
|
guids = photos.map {|photo| photo["guid"] }
|
|
expect(guids).to include(@public_photo1.guid, @public_photo2.guid, @shared_photo1.guid)
|
|
expect(guids).not_to include(@private_photo1.guid)
|
|
confirm_photos(photos)
|
|
|
|
expect(photos.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "returns only public photos of other user without private:read scope in token" do
|
|
get(
|
|
api_v1_user_photos_path(alice.guid),
|
|
params: {access_token: access_token_public_only_read_only}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
photos = response_body(response)
|
|
expect(photos.length).to eq(2)
|
|
guids = photos.map {|photo| photo["guid"] }
|
|
expect(guids).to include(@public_photo1.guid, @public_photo2.guid)
|
|
expect(guids).not_to include(@private_photo1.guid, @shared_photo1.guid)
|
|
end
|
|
|
|
it "returns logged in user's photos" do
|
|
get(
|
|
api_v1_user_photos_path(auth.user.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
photos = response_body(response)
|
|
expect(photos.length).to eq(2)
|
|
end
|
|
end
|
|
|
|
it "fails with invalid GUID" do
|
|
get(
|
|
api_v1_user_photos_path("999_999_999"),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(404)
|
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found"))
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
get(
|
|
api_v1_user_photos_path(alice.guid),
|
|
params: {access_token: invalid_token}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
|
|
describe "#posts" do
|
|
before do
|
|
alice_private_spec = alice.aspects.create(name: "private aspect")
|
|
alice.share_with(eve.person, alice_private_spec)
|
|
alice.share_with(auth.user.person, alice.aspects.first)
|
|
|
|
auth.user.post(:status_message, text: "auth user message1", public: true)
|
|
auth.user.post(:status_message, text: "auth user message2", public: true)
|
|
auth.user.post(:status_message, text: "auth user message3", public: false, to: "all")
|
|
@public_post1 = alice.post(:status_message, text: "alice public message1", public: true)
|
|
@public_post2 = alice.post(:status_message, text: "alice public message2", public: true)
|
|
@shared_post1 = alice.post(:status_message, text: "alice limited to auth user message",
|
|
public: false, to: alice.aspects.first.id)
|
|
@private_post1 = alice.post(:status_message, text: "alice limited hidden from auth user message",
|
|
public: false, to: alice_private_spec.id)
|
|
end
|
|
|
|
context "logged in" do
|
|
it "returns only visible posts of other user" do
|
|
get(
|
|
api_v1_user_posts_path(alice.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
posts = response_body(response)
|
|
expect(posts.length).to eq(3)
|
|
guids = posts.map {|post| post["guid"] }
|
|
expect(guids).to include(@public_post1.guid, @public_post2.guid, @shared_post1.guid)
|
|
expect(guids).not_to include(@private_post1.guid)
|
|
post = posts.select {|p| p["guid"] == @public_post1.guid }
|
|
confirm_post_format(post[0], alice, @public_post1)
|
|
|
|
expect(posts.to_json).to match_json_schema(:api_v1_schema)
|
|
end
|
|
|
|
it "returns logged in user's posts" do
|
|
get(
|
|
api_v1_user_posts_path(auth.user.guid),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
posts = response_body(response)
|
|
expect(posts.length).to eq(3)
|
|
end
|
|
|
|
it "returns public posts only without private:read scope in token" do
|
|
get(
|
|
api_v1_user_posts_path(auth.user.guid),
|
|
params: {access_token: access_token_public_only_read_only}
|
|
)
|
|
expect(response.status).to eq(200)
|
|
posts = response_body(response)
|
|
expect(posts.length).to eq(2)
|
|
end
|
|
end
|
|
|
|
it "fails with invalid GUID" do
|
|
get(
|
|
api_v1_user_posts_path("999_999_999"),
|
|
params: {access_token: access_token}
|
|
)
|
|
expect(response.status).to eq(404)
|
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found"))
|
|
end
|
|
|
|
it "fails if invalid token" do
|
|
get(
|
|
api_v1_user_posts_path(alice.person.guid),
|
|
params: {access_token: invalid_token}
|
|
)
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
|
|
def confirm_self_data_format(json)
|
|
confirm_common_profile_elements(json)
|
|
confirm_profile_details(json)
|
|
expect(json).to have_key("searchable")
|
|
expect(json).to have_key("show_profile_info")
|
|
end
|
|
|
|
def confirm_public_profile_hash(json)
|
|
confirm_common_profile_elements(json)
|
|
confirm_profile_details(json)
|
|
expect(json).to have_key("blocked")
|
|
expect(json).to have_key("relationship")
|
|
expect(json).to have_key("aspects")
|
|
end
|
|
|
|
# rubocop:disable Metrics/AbcSize
|
|
def confirm_private_profile_hash(json)
|
|
confirm_common_profile_elements(json)
|
|
expect(json).to have_key("blocked")
|
|
expect(json).to have_key("relationship")
|
|
expect(json).to have_key("aspects")
|
|
expect(json).not_to have_key("birthday")
|
|
expect(json).not_to have_key("gender")
|
|
expect(json).not_to have_key("location")
|
|
expect(json).not_to have_key("bio")
|
|
end
|
|
|
|
def confirm_common_profile_elements(json)
|
|
expect(json).to have_key("guid")
|
|
expect(json).to have_key("diaspora_id")
|
|
expect(json).to have_key("name")
|
|
expect(json).to have_key("avatar")
|
|
expect(json).to have_key("tags")
|
|
end
|
|
|
|
def confirm_profile_details(json)
|
|
expect(json).to have_key("birthday")
|
|
expect(json).to have_key("gender")
|
|
expect(json).to have_key("location")
|
|
expect(json).to have_key("bio")
|
|
end
|
|
|
|
def confirm_post_format(post, user, reference_post)
|
|
confirm_post_top_level(post, reference_post)
|
|
confirm_person_format(post["author"], user)
|
|
confirm_interactions(post["interaction_counters"], reference_post)
|
|
end
|
|
|
|
def confirm_post_top_level(post, reference_post)
|
|
expect(post["guid"]).to eq(reference_post.guid)
|
|
expect(post.has_key?("created_at")).to be_truthy
|
|
expect(post["created_at"]).not_to be_nil
|
|
expect(post["title"]).to eq(reference_post.message.title)
|
|
expect(post["body"]).to eq(reference_post.message.plain_text_for_json)
|
|
expect(post["post_type"]).to eq(reference_post.post_type)
|
|
expect(post["provider_display_name"]).to eq(reference_post.provider_display_name)
|
|
expect(post["public"]).to eq(reference_post.public)
|
|
expect(post["nsfw"]).to eq(reference_post.nsfw)
|
|
end
|
|
|
|
def confirm_interactions(interactions, reference_post)
|
|
expect(interactions["comments"]).to eq(reference_post.comments_count)
|
|
expect(interactions["likes"]).to eq(reference_post.likes_count)
|
|
expect(interactions["reshares"]).to eq(reference_post.reshares_count)
|
|
end
|
|
|
|
def confirm_person_format(post_person, user)
|
|
expect(post_person["guid"]).to eq(user.guid)
|
|
expect(post_person["diaspora_id"]).to eq(user.diaspora_handle)
|
|
expect(post_person["name"]).to eq(user.name)
|
|
expect(post_person["avatar"]).to eq(user.profile.image_url(size: :thumb_medium))
|
|
end
|
|
|
|
def confirm_photos(photos)
|
|
photos.each do |photo|
|
|
expect(photo.has_key?("guid")).to be_truthy
|
|
expect(photo["dimensions"].has_key?("height")).to be_truthy
|
|
expect(photo["sizes"]["small"]).to be_truthy
|
|
expect(photo["sizes"]["medium"]).to be_truthy
|
|
expect(photo["sizes"]["large"]).to be_truthy
|
|
end
|
|
end
|
|
# rubocop:enable Metrics/AbcSize
|
|
|
|
def response_body(response)
|
|
JSON.parse(response.body)
|
|
end
|
|
end
|