API Paging library and used in appropriate controllers with full tests

This commit is contained in:
Hank Grabowski 2018-12-05 14:07:44 -05:00
parent 43c111bd98
commit 7109773b83
37 changed files with 709 additions and 108 deletions

View file

@ -12,8 +12,10 @@ module Api
end
def index
aspects = current_user.aspects.map {|a| AspectPresenter.new(a).as_api_json(false) }
render json: aspects
aspects_query = current_user.aspects
aspects_page = index_pager(aspects_query).response
aspects_page[:data] = aspects_page[:data].map {|a| AspectPresenter.new(a).as_api_json(false) }
render json: aspects_page
end
def show

View file

@ -44,6 +44,14 @@ module Api
def current_user
current_token ? current_token.authorization.user : nil
end
def index_pager(query)
Api::Paging::RestPaginatorBuilder.new(query, request).index_pager(params)
end
def time_pager(query)
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params)
end
end
end
end

View file

@ -29,8 +29,11 @@ module Api
end
def index
comments = comment_service.find_for_post(params[:post_id])
render json: comments.map {|x| comment_as_json(x) }
comments_query = comment_service.find_for_post(params[:post_id])
params[:after] = Time.utc(1900).iso8601 if params.permit(:before, :after).empty?
comments_page = time_pager(comments_query).response
comments_page[:data] = comments_page[:data].map {|x| comment_as_json(x) }
render json: comments_page
end
def destroy

View file

@ -1,5 +1,7 @@
# frozen_string_literal: true
require "api/paging/index_paginator"
module Api
module V1
class ContactsController < Api::V1::BaseController
@ -16,8 +18,12 @@ module Api
end
def index
contacts = aspects_membership_service.contacts_in_aspect(params[:aspect_id])
render json: contacts.map {|c| ContactPresenter.new(c, current_user).as_api_json_without_contact }
contacts_query = aspects_membership_service.contacts_in_aspect(params[:aspect_id])
contacts_page = index_pager(contacts_query).response
contacts_page[:data] = contacts_page[:data].map do |c|
ContactPresenter.new(c, current_user).as_api_json_without_contact
end
render json: contacts_page
end
def create

View file

@ -21,10 +21,11 @@ module Api
params.permit(:only_after, :only_unread)
mapped_params = {}
mapped_params[:only_after] = params[:only_after] if params.has_key?(:only_after)
mapped_params[:unread] = params[:only_unread] if params.has_key?(:only_unread)
conversations = conversation_service.all_for_user(mapped_params)
render json: conversations.map {|x| conversation_as_json(x) }
conversations_query = conversation_service.all_for_user(mapped_params)
conversations_page = pager(conversations_query, "conversations.created_at").response
conversations_page[:data] = conversations_page[:data].map {|x| conversation_as_json(x) }
render json: conversations_page
end
def show
@ -34,7 +35,7 @@ module Api
def create
params.require(%i[subject body recipients])
recipient_ids = JSON.parse(params[:recipients]).map {|p| Person.find_from_guid_or_username(id: p).id }
recipient_ids = params[:recipients].map {|p| Person.find_from_guid_or_username(id: p).id }
conversation = conversation_service.build(
params[:subject],
params[:body],
@ -58,12 +59,18 @@ module Api
head :no_content
end
private
def conversation_service
ConversationService.new(current_user)
end
def conversation_as_json(conversation)
ConversationPresenter.new(conversation).as_api_json
ConversationPresenter.new(conversation, current_user).as_api_json
end
def pager(query, sort_field)
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, sort_field)
end
end
end

View file

@ -20,8 +20,10 @@ module Api
end
def show
likes = like_service.find_for_post(params[:post_id])
render json: likes.map {|x| like_json(x) }
likes_query = like_service.find_for_post(params[:post_id])
likes_page = index_pager(likes_query).response
likes_page[:data] = likes_page[:data].map {|x| like_json(x) }
render json: likes_page
end
def create

View file

@ -29,12 +29,13 @@ module Api
def index
conversation = conversation_service.find!(params[:conversation_id])
conversation.set_read(current_user)
render(
json: conversation.messages.map {|x| message_json(x) },
status: :created
)
messages_page = index_pager(conversation.messages).response
messages_page[:data] = messages_page[:data].map {|x| message_json(x) }
render json: messages_page
end
private
def conversation_service
ConversationService.new(current_user)
end

View file

@ -27,8 +27,12 @@ module Api
def index
after_date = Date.iso8601(params[:only_after]) if params.has_key?(:only_after)
notifications = service.index(params[:only_unread], after_date)
render json: notifications.map {|note| NotificationPresenter.new(note, default_serializer_options).as_api_json }
notifications_query = service.index(params[:only_unread], after_date)
notifications_page = time_pager(notifications_query).response
notifications_page[:data] = notifications_page[:data].map do |note|
NotificationPresenter.new(note, default_serializer_options).as_api_json
end
render json: notifications_page
rescue ArgumentError
render json: I18n.t("api.endpoint_errors.notifications.cant_process"), status: :unprocessable_entity
end

View file

@ -16,8 +16,9 @@ module Api
end
def index
photos = current_user.photos
render json: photos.map {|p| PhotoPresenter.new(p).as_api_json(true) }
photos_page = time_pager(current_user.photos).response
photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
render json: photos_page
end
def show

View file

@ -20,10 +20,15 @@ module Api
end
def show
reshares = reshare_service.find_for_post(params[:post_id]).map do |r|
{guid: r.guid, author: PersonPresenter.new(r.author).as_api_json}
reshares_query = reshare_service.find_for_post(params[:post_id])
reshares_page = index_pager(reshares_query).response
reshares_page[:data] = reshares_page[:data].map do |r|
{
guid: r.guid,
author: PersonPresenter.new(r.author).as_api_json
}
end
render json: reshares
render json: reshares_page
end
def create
@ -34,6 +39,8 @@ module Api
render json: PostPresenter.new(reshare, current_user).as_api_response
end
private
def reshare_service
@reshare_service ||= ReshareService.new(current_user)
end

View file

@ -14,17 +14,27 @@ module Api
def user_index
parameters = params.permit(:tag, :name_or_handle)
raise RuntimeError if parameters.keys.length != 1
people = if params.has_key?(:tag)
people_query = if params.has_key?(:tag)
Person.profile_tagged_with(params[:tag])
else
Person.search(params[:name_or_handle], current_user)
end
render json: people.map {|p| PersonPresenter.new(p).as_api_json }
user_page = index_pager(people_query).response
user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json }
render json: user_page
end
def post_index
posts = Stream::Tag.new(current_user, params.require(:tag)).posts
render json: posts.map {|p| PostPresenter.new(p).as_api_response }
posts_query = Stream::Tag.new(current_user, params.require(:tag)).posts
posts_page = time_pager(posts_query, "posts.created_at", "created_at").response
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response }
render json: posts_page
end
private
def time_pager(query, query_time_field, data_time_field)
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, query_time_field, data_time_field)
end
end
end

View file

@ -9,12 +9,12 @@ module Api
def aspects
aspect_ids = params.has_key?(:aspect_ids) ? JSON.parse(params[:aspect_ids]) : []
@stream = Stream::Aspect.new(current_user, aspect_ids, max_time: max_time)
@stream = Stream::Aspect.new(current_user, aspect_ids, max_time: stream_max_time)
stream_responder
end
def activity
stream_responder(Stream::Activity)
stream_responder(Stream::Activity, "posts.interacted_at", "interacted_at")
end
def multi
@ -39,10 +39,25 @@ module Api
private
def stream_responder(stream_klass=nil)
@stream = stream_klass.present? ? stream_klass.new(current_user, max_time: max_time) : @stream
def stream_responder(stream_klass=nil, query_time_field="posts.created_at", data_time_field="created_at")
@stream = stream_klass.present? ? stream_klass.new(current_user, max_time: stream_max_time) : @stream
posts_page = pager(@stream.stream_posts, query_time_field, data_time_field).response
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
posts_page[:links].delete(:previous)
render json: posts_page
end
render json: @stream.stream_posts.map {|p| PostPresenter.new(p, current_user).as_api_response }
def stream_max_time
if params.has_key?("before")
Time.iso8601(params["before"])
else
max_time
end
end
def pager(query, query_time_field, data_time_field)
Api::Paging::RestPaginatorBuilder.new(query, request, true, 15)
.time_pager(params, query_time_field, data_time_field)
end
end
end

View file

@ -44,20 +44,26 @@ module Api
return
end
contacts_with_profile = AspectsMembershipService.new(current_user).all_contacts
render json: contacts_with_profile.map {|c| PersonPresenter.new(c.person).as_api_json }
contacts_query = AspectsMembershipService.new(current_user).all_contacts
contacts_page = index_pager(contacts_query).response
contacts_page[:data] = contacts_page[:data].map {|c| PersonPresenter.new(c.person).as_api_json }
render json: contacts_page
end
def photos
person = Person.find_by!(guid: params[:user_id])
photos = Photo.visible(current_user, person, :all, Time.current)
render json: photos.map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
photos_query = Photo.visible(current_user, person, :all, Time.current)
photos_page = time_pager(photos_query).response
photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
render json: photos_page
end
def posts
person = Person.find_by!(guid: params[:user_id])
posts = current_user.posts_from(person)
render json: posts.map {|post| PostPresenter.new(post, current_user).as_api_response }
posts_query = current_user.posts_from(person, false)
posts_page = time_pager(posts_query).response
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
render json: posts_page
end
private

View file

@ -67,8 +67,10 @@ module User::Querying
contact_for(person).aspects
end
def posts_from(person)
Post.from_person_visible_by_user(self, person).order("posts.created_at desc")
def posts_from(person, with_order=true)
base_query = Post.from_person_visible_by_user(self, person)
return base_query.order("posts.created_at desc") if with_order
base_query
end
def photos_from(person, opts={})

View file

@ -2,8 +2,7 @@
class ConversationPresenter < BasePresenter
def as_api_json
read = !@presentable.conversation_visibilities.empty? &&
@presentable.conversation_visibilities[0].unread == 0
read = @presentable.conversation_visibilities.find_by(person_id: current_user.person_id)&.unread == 0
{
guid: @presentable.guid,
subject: @presentable.subject,

View file

@ -3,7 +3,7 @@
class NotificationPresenter < BasePresenter
def as_api_json(include_target=true)
data = base_hash
data = data.merge(target: target_json) if include_target
data = data.merge(target: target_json) if include_target && target
data
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Api
module Paging
class IndexPaginator
def initialize(query_base, current_page, limit)
@query_base = query_base
@current_page = current_page.to_i
@limit = limit.to_i
end
def page_data
@page_data ||= @query_base.paginate(page: @current_page, per_page: @limit)
@max_page = (@query_base.count * 1.0 / @limit * 1.0).ceil
@max_page = 1 if @max_page < 1
@page_data
end
def next_page(for_url=true)
page_data
return nil if for_url && @current_page == @max_page
return "page=#{@current_page + 1}" if for_url
IndexPaginator.new(@query_base, @current_page + 1, @limit)
end
def previous_page(for_url=true)
page_data
return nil if for_url && @current_page == 1
return "page=#{@current_page - 1}" if for_url
IndexPaginator.new(@query_base, @current_page - 1, @limit)
end
def filter_parameters(parameters)
parameters.delete(:page)
end
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Api
module Paging
class RestPagedResponseBuilder
def initialize(pager, request, allowed_params=nil)
@pager = pager
@base_url = request.original_url.split("?").first if request
@query_parameters = if allowed_params
allowed_params
elsif request&.query_parameters
request&.query_parameters
else
{}
end
end
def response
{
links: navigation_builder,
data: @pager.page_data
}
end
private
def navigation_builder
previous_page = @pager.previous_page
links = {}
links[:previous] = link_builder(previous_page) if previous_page
next_page = @pager.next_page
links[:next] = link_builder(next_page) if next_page
links
end
def link_builder(page_parameter)
"#{@base_url}?#{filtered_original_parameters}#{page_parameter}"
end
def filtered_original_parameters
@pager.filter_parameters(@query_parameters)
return "" if @query_parameters.empty?
@query_parameters.map {|k, v| "#{k}=#{v}" }.join("&") + "&"
end
end
end
end

View file

@ -0,0 +1,79 @@
# frozen_string_literal: true
module Api
module Paging
class RestPaginatorBuilder
MAX_LIMIT = 100
DEFAULT_LIMIT = 15
def initialize(base_query, request, allow_default_page=true, default_limit=DEFAULT_LIMIT)
@base_query = base_query
@request = request
@allow_default_page = allow_default_page
@default_limit = if default_limit < MAX_LIMIT && default_limit > 0
default_limit
else
DEFAULT_LIMIT
end
end
def index_pager(params)
current_page = current_page_settings(params)
paged_response_builder(IndexPaginator.new(@base_query, current_page, limit_settings(params)))
end
def time_pager(params, query_time_field="created_at", data_time_field=query_time_field)
is_descending, current_time = time_settings(params)
paged_response_builder(
TimePaginator.new(
query_base: @base_query,
query_time_field: query_time_field,
data_time_field: data_time_field,
current_time: current_time,
is_descending: is_descending,
limit: limit_settings(params)
)
)
end
private
def current_page_settings(params)
if params["page"]
requested_page = params["page"]
requested_page = 1 if requested_page < 1
requested_page
elsif @allow_default_page
1
else
raise ActionController::ParameterMissing
end
end
def paged_response_builder(paginator)
Api::Paging::RestPagedResponseBuilder.new(paginator, @request)
end
def time_settings(params)
time_params = params.permit("before", "after")
time_params["before"] = (Time.current + 1.year).iso8601 if time_params.empty? && @allow_default_page
raise "Missing time parameters for query building" if time_params.empty?
if time_params["before"]
is_descending = true
current_time = Time.iso8601(time_params["before"])
else
is_descending = false
current_time = Time.iso8601(time_params["after"])
end
[is_descending, current_time]
end
def limit_settings(params)
requested_limit = params["per_page"]
return @default_limit unless requested_limit
requested_limit = [1, requested_limit].max
[requested_limit, MAX_LIMIT].min
end
end
end
end

View file

@ -0,0 +1,103 @@
# frozen_string_literal: true
module Api
module Paging
class TimePaginator
def initialize(opts={})
@query_base = opts[:query_base]
@query_time_field = opts[:query_time_field]
@data_time_field = opts[:data_time_field]
@current_time = opts[:current_time]
@limit = opts[:limit]
@is_descending = opts[:is_descending]
direction = if @is_descending
"<"
else
">"
end
@time_query_string = "#{@query_time_field} #{direction} ?"
@sort_string = if @is_descending
"#{@query_time_field} DESC"
else
"#{@query_time_field} ASC"
end
end
def page_data
return @data if @data
@data = @query_base.where([@time_query_string, @current_time.iso8601(3)]).limit(@limit).order(@sort_string)
time_data = @data.map {|d| d[@data_time_field] }.sort
@min_time = time_data.first
@max_time = time_data.last + 0.001.seconds if time_data.last
@data
end
def next_page(for_url=true)
page_data
return nil unless next_time
return next_page_as_query_parameter if for_url
TimePaginator.new(
query_base: @query_base,
query_time_field: @query_time_field,
query_data_field: @data_time_field,
current_time: next_time,
is_descending: @is_descending,
limit: @limit
)
end
def previous_page(for_url=true)
page_data
return nil unless previous_time
return previous_page_as_query_parameter if for_url
TimePaginator.new(
query_base: @query_base,
query_time_field: @query_time_field,
query_data_field: @data_time_field,
current_time: previous_time,
is_descending: !@is_descending,
limit: @limit
)
end
def filter_parameters(parameters)
parameters.delete(:before)
parameters.delete(:after)
end
private
def next_time
if @is_descending
@min_time
else
@max_time
end
end
def previous_time
if @is_descending
@max_time
else
@min_time
end
end
def next_page_as_query_parameter
if @is_descending
"before=#{next_time.iso8601(3)}"
else
"after=#{next_time.iso8601(3)}"
end
end
def previous_page_as_query_parameter
if @is_descending
"after=#{previous_time.iso8601(3)}"
else
"before=#{previous_time.iso8601(3)}"
end
end
end
end
end

View file

@ -20,7 +20,7 @@ describe Api::V1::AspectsController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
aspects = JSON.parse(response.body)
aspects = response_body_data(response)
expect(aspects.length).to eq(auth.user.aspects.length)
aspects.each do |aspect|
found_aspect = auth.user.aspects.find_by(id: aspect["id"])
@ -299,4 +299,8 @@ describe Api::V1::AspectsController do
end
end
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
end

View file

@ -70,8 +70,8 @@ describe Api::V1::CommentsController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
expect(response_body(response).length).to eq(2)
comments = response_body(response)
comments = response_body_data(response)
expect(comments.length).to eq(2)
confirm_comment_format(comments[0], auth.user, @comment_text1)
confirm_comment_format(comments[1], auth.user, @comment_text2)
end
@ -289,6 +289,10 @@ describe Api::V1::CommentsController do
JSON.parse(response.body)
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
private
# rubocop:disable Metrics/AbcSize

View file

@ -26,7 +26,7 @@ describe Api::V1::ContactsController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
contacts = JSON.parse(response.body)
contacts = response_body_data(response)
expect(contacts.length).to eq(1)
confirm_person_format(contacts[0], alice)
@ -35,7 +35,7 @@ describe Api::V1::ContactsController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
contacts = JSON.parse(response.body)
contacts = response_body_data(response)
expect(contacts.length).to eq(@aspect1.contacts.length)
end
end
@ -218,6 +218,10 @@ describe Api::V1::ContactsController do
end
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
def aspects_membership_service(user=auth.user)
AspectsMembershipService.new(user)
end

View file

@ -13,10 +13,10 @@ describe Api::V1::ConversationsController do
alice.share_with auth.user.person, alice.aspects[0]
auth.user.disconnected_by(eve)
@conversation = {
@conversation_request = {
subject: "new conversation",
body: "first message",
recipients: JSON.generate([alice.guid]),
recipients: [alice.guid],
access_token: access_token
}
end
@ -24,10 +24,10 @@ describe Api::V1::ConversationsController do
describe "#create" do
context "with valid data" do
it "creates the conversation" do
post api_v1_conversations_path, params: @conversation
post api_v1_conversations_path, params: @conversation_request
expect(response.status).to eq 201
conversation = JSON.parse(response.body)
confirm_conversation_format(conversation, @conversation, [auth.user, alice])
confirm_conversation_format(conversation, @conversation_request, [auth.user, alice])
end
end
@ -75,7 +75,7 @@ describe Api::V1::ConversationsController do
incomplete_conversation = {
subject: "new conversation",
body: "first message",
recipients: JSON.generate(["999_999_999"]),
recipients: ["999_999_999"],
access_token: access_token
}
post api_v1_conversations_path, params: incomplete_conversation
@ -87,7 +87,7 @@ describe Api::V1::ConversationsController do
incomplete_conversation = {
subject: "new conversation",
body: "first message",
recipients: JSON.generate([eve.guid]),
recipients: [eve.guid],
access_token: access_token
}
post api_v1_conversations_path, params: incomplete_conversation
@ -99,25 +99,28 @@ describe Api::V1::ConversationsController do
describe "#index" do
before do
post api_v1_conversations_path, params: @conversation
post api_v1_conversations_path, params: @conversation
post api_v1_conversations_path, params: @conversation_request
@read_conversation_guid = JSON.parse(response.body)["guid"]
@read_conversation = conversation_service.find!(@read_conversation_guid)
post api_v1_conversations_path, params: @conversation_request
sleep(1)
post api_v1_conversations_path, params: @conversation
conversation_guid = JSON.parse(response.body)["guid"]
conversation = conversation_service.find!(conversation_guid)
conversation.conversation_visibilities[0].unread = 1
conversation.conversation_visibilities[0].save!
conversation.conversation_visibilities[1].unread = 1
conversation.conversation_visibilities[1].save!
@date = conversation.created_at
post api_v1_conversations_path, params: @conversation_request
@conversation_guid = JSON.parse(response.body)["guid"]
@conversation = conversation_service.find!(@conversation_guid)
@conversation.conversation_visibilities[0].unread = 1
@conversation.conversation_visibilities[0].save!
@conversation.conversation_visibilities[1].unread = 1
@conversation.conversation_visibilities[1].save!
@date = @conversation.created_at
end
it "returns all the user conversations" do
get api_v1_conversations_path, params: {access_token: access_token}
expect(response.status).to eq 200
returned_conversations = JSON.parse(response.body)
returned_conversations = response_body_data(response)
expect(returned_conversations.length).to eq 3
confirm_conversation_format(returned_conversations[0], @conversation, [auth.user, alice])
actual_conversation = returned_conversations.select {|c| c["guid"] == @read_conversation_guid }[0]
confirm_conversation_format(actual_conversation, @read_conversation, [auth.user, alice])
end
it "returns all the user unread conversations" do
@ -126,7 +129,7 @@ describe Api::V1::ConversationsController do
params: {only_unread: true, access_token: access_token}
)
expect(response.status).to eq 200
expect(JSON.parse(response.body).length).to eq 2
expect(response_body_data(response).length).to eq 2
end
it "returns all the user conversations after a given date" do
@ -135,14 +138,16 @@ describe Api::V1::ConversationsController do
params: {only_after: @date, access_token: access_token}
)
expect(response.status).to eq 200
expect(JSON.parse(response.body).length).to eq 1
expect(response_body_data(response).length).to eq 1
end
end
describe "#show" do
context "valid conversation ID" do
before do
post api_v1_conversations_path, params: @conversation
post api_v1_conversations_path, params: @conversation_request
@conversation_guid = JSON.parse(response.body)["guid"]
@conversation = conversation_service.find!(@conversation_guid)
end
it "returns the corresponding conversation" do
@ -177,13 +182,13 @@ describe Api::V1::ConversationsController do
auth.user.person, auth_participant.user.aspects[0]
)
@conversation = {
@conversation_request = {
subject: "new conversation",
body: "first message",
recipients: JSON.generate([auth_participant.user.guid]),
recipients: [auth_participant.user.guid],
access_token: access_token
}
post api_v1_conversations_path, params: @conversation
post api_v1_conversations_path, params: @conversation_request
@conversation_guid = JSON.parse(response.body)["guid"]
end
@ -251,6 +256,10 @@ describe Api::V1::ConversationsController do
private
def response_body_data(response)
JSON.parse(response.body)["data"]
end
# rubocop:disable Metrics/AbcSize
def confirm_conversation_format(conversation, ref_conversation, ref_participants)
expect(conversation["guid"]).to_not be_nil

View file

@ -23,7 +23,7 @@ describe Api::V1::LikesController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
likes = response_body(response)
likes = response_body_data(response)
expect(likes.length).to eq(0)
end
@ -36,7 +36,7 @@ describe Api::V1::LikesController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
likes = response_body(response)
likes = response_body_data(response)
expect(likes.length).to eq(3)
confirm_like_format(likes, alice)
confirm_like_format(likes, bob)
@ -170,4 +170,8 @@ describe Api::V1::LikesController do
def response_body(response)
JSON.parse(response.body)
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
end

View file

@ -15,7 +15,7 @@ describe Api::V1::MessagesController do
@conversation = {
subject: "new conversation",
body: "first message",
recipients: JSON.generate([alice.guid]),
recipients: [alice.guid],
access_token: access_token
}
@ -43,7 +43,7 @@ describe Api::V1::MessagesController do
api_v1_conversation_messages_path(@conversation_guid),
params: {access_token: access_token}
)
messages = JSON.parse(response.body)
messages = response_body_data(response)
expect(messages.length).to eq 2
confirm_message_format(messages[1], @message_text, auth.user)
end
@ -93,7 +93,8 @@ describe Api::V1::MessagesController do
api_v1_conversation_messages_path(@conversation_guid),
params: {access_token: access_token}
)
messages = JSON.parse(response.body)
expect(response.status).to eq 200
messages = response_body_data(response)
expect(messages.length).to eq 1
confirm_message_format(messages[0], "first message", auth.user)
@ -105,10 +106,14 @@ describe Api::V1::MessagesController do
private
def response_body_data(response)
JSON.parse(response.body)["data"]
end
def get_conversation(conversation_id)
conversation_service = ConversationService.new(auth.user)
raw_conversation = conversation_service.find!(conversation_id)
ConversationPresenter.new(raw_conversation).as_api_json
ConversationPresenter.new(raw_conversation, auth.user).as_api_json
end
def confirm_message_format(message, ref_message, author)

View file

@ -26,7 +26,7 @@ describe Api::V1::NotificationsController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
notification = JSON.parse(response.body)
notification = response_body_data(response)
expect(notification.length).to eq(1)
confirm_notification_format(notification[0], @notification, "also_commented", nil)
end
@ -37,7 +37,7 @@ describe Api::V1::NotificationsController do
params: {only_unread: true, access_token: access_token}
)
expect(response.status).to eq(200)
notification = JSON.parse(response.body)
notification = response_body_data(response)
expect(notification.length).to eq(1)
@notification.set_read_state(true)
get(
@ -45,7 +45,7 @@ describe Api::V1::NotificationsController do
params: {only_unread: true, access_token: access_token}
)
expect(response.status).to eq(200)
notification = JSON.parse(response.body)
notification = response_body_data(response)
expect(notification.length).to eq(0)
end
@ -55,7 +55,7 @@ describe Api::V1::NotificationsController do
params: {only_after: (Date.current - 1.day).iso8601, access_token: access_token}
)
expect(response.status).to eq(200)
notification = JSON.parse(response.body)
notification = response_body_data(response)
expect(notification.length).to eq(1)
@notification.set_read_state(true)
get(
@ -63,7 +63,7 @@ describe Api::V1::NotificationsController do
params: {only_after: (Date.current + 1.day).iso8601, access_token: access_token}
)
expect(response.status).to eq(200)
notification = JSON.parse(response.body)
notification = response_body_data(response)
expect(notification.length).to eq(0)
end
end
@ -195,6 +195,10 @@ describe Api::V1::NotificationsController do
private
def response_body_data(response)
JSON.parse(response.body)["data"]
end
# rubocop:disable Metrics/AbcSize
def confirm_notification_format(notification, ref_notification, expected_type, target)
expect(notification["guid"]).to eq(ref_notification.guid)

View file

@ -93,7 +93,7 @@ describe Api::V1::PhotosController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
photos = JSON.parse(response.body)
photos = response_body_data(response)
expect(photos.length).to eq(2)
end
end
@ -267,6 +267,10 @@ describe Api::V1::PhotosController do
end
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
# rubocop:disable Metrics/AbcSize
def confirm_photo_format(photo, ref_photo, ref_user)
expect(photo["guid"]).to eq(ref_photo.guid)

View file

@ -35,7 +35,7 @@ describe Api::V1::ResharesController do
)
expect(response.status).to eq(200)
reshares = JSON.parse(response.body)
reshares = response_body_data(response)
expect(reshares.length).to eq(1)
reshare = reshares[0]
expect(reshare["guid"]).not_to be_nil
@ -57,7 +57,7 @@ describe Api::V1::ResharesController do
}
)
expect(response.status).to eq(200)
reshares = JSON.parse(response.body)
reshares = response_body_data(response)
expect(reshares.length).to eq(0)
end
end
@ -169,7 +169,6 @@ describe Api::V1::ResharesController do
access_token: access_token
}
)
puts(response.body)
expect(response.status).to eq(404)
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found"))
end
@ -204,6 +203,10 @@ describe Api::V1::ResharesController do
@reshare_service ||= ReshareService.new(user)
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
# rubocop:disable Metrics/AbcSize
def confirm_person_format(post_person, user)
expect(post_person["guid"]).to eq(user.guid)

View file

@ -39,7 +39,7 @@ describe Api::V1::SearchController do
params: {tag: "one", access_token: access_token}
)
expect(response.status).to eq(200)
users = JSON.parse(response.body)
users = response_body_data(response)
expect(users.length).to eq(14)
end
@ -49,7 +49,7 @@ describe Api::V1::SearchController do
params: {name_or_handle: "Terry", access_token: access_token}
)
expect(response.status).to eq(200)
users = JSON.parse(response.body)
users = response_body_data(response)
expect(users.length).to eq(1)
end
@ -59,7 +59,7 @@ describe Api::V1::SearchController do
params: {name_or_handle: "findable", access_token: access_token}
)
expect(response.status).to eq(200)
users = JSON.parse(response.body)
users = response_body_data(response)
expect(users.length).to eq(1)
end
@ -69,7 +69,7 @@ describe Api::V1::SearchController do
params: {name_or_handle: "Closed", access_token: access_token}
)
expect(response.status).to eq(200)
users = JSON.parse(response.body)
users = response_body_data(response)
expect(users.length).to eq(0)
end
@ -79,7 +79,7 @@ describe Api::V1::SearchController do
params: {name_or_handle: "unsearchable@example.org", access_token: access_token}
)
expect(response.status).to eq(200)
users = JSON.parse(response.body)
users = response_body_data(response)
expect(users.length).to eq(0)
end
@ -131,7 +131,7 @@ describe Api::V1::SearchController do
params: {tag: "tag2", access_token: access_token}
)
expect(response.status).to eq(200)
posts = JSON.parse(response.body)
posts = response_body_data(response)
expect(posts.length).to eq(2)
end
@ -152,4 +152,8 @@ describe Api::V1::SearchController do
expect(response.status).to eq(401)
end
end
def response_body_data(response)
JSON.parse(response.body)["data"]
end
end

View file

@ -20,7 +20,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 1
confirm_post_format(post[0], auth.user, @status)
end
@ -31,7 +31,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 1
confirm_post_format(post[0], auth.user, @status)
end
@ -52,7 +52,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 0
end
end
@ -64,7 +64,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 1
confirm_post_format(post[0], auth.user, @status)
end
@ -77,7 +77,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 1
confirm_post_format(post[0], auth.user, @status)
end
@ -90,7 +90,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 0
end
end
@ -102,7 +102,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 0
end
end
@ -114,7 +114,7 @@ describe Api::V1::StreamsController do
params: {access_token: access_token}
)
expect(response.status).to eq 200
post = JSON.parse(response.body)
post = response_body_data(response)
expect(post.length).to eq 1
confirm_post_format(post[0], auth.user, @status)
end
@ -205,4 +205,8 @@ describe Api::V1::StreamsController do
confirm_person_format(root["author"], root_poster)
end
# rubocop:enable Metrics/AbcSize
def response_body_data(response)
JSON.parse(response.body)["data"]
end
end

View file

@ -237,7 +237,7 @@ describe Api::V1::UsersController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
contacts = JSON.parse(response.body)
contacts = response_body_data(response)
expect(contacts.length).to eq(0)
auth.user.share_with(alice.person, auth.user.aspects.first)
@ -246,7 +246,7 @@ describe Api::V1::UsersController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
contacts = JSON.parse(response.body)
contacts = response_body_data(response)
expect(contacts.length).to eq(1)
confirm_person_format(contacts[0], alice)
end
@ -301,7 +301,7 @@ describe Api::V1::UsersController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
photos = JSON.parse(response.body)
photos = response_body_data(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)
@ -361,7 +361,7 @@ describe Api::V1::UsersController do
params: {access_token: access_token}
)
expect(response.status).to eq(200)
posts = JSON.parse(response.body)
posts = response_body_data(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)
@ -370,13 +370,13 @@ describe Api::V1::UsersController do
confirm_post_format(post[0], alice, @public_post1)
end
it "returns logged in user's photos" do
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 = JSON.parse(response.body)
posts = response_body_data(response)
expect(posts.length).to eq(2)
end
end
@ -483,4 +483,8 @@ describe Api::V1::UsersController do
end
end
# rubocop:enable Metrics/AbcSize
def response_body_data(response)
JSON.parse(response.body)["data"]
end
end

View file

@ -0,0 +1,61 @@
# frozen_string_literal: true
describe Api::Paging::IndexPaginator do
before do
(1..7).each do |i|
public = !(i == 1 || i == 6)
alice.post(
:status_message,
text: "Post #{i}",
public: public
)
end
@alice_search = alice.posts.where(public: true).order("id ASC")
@limit = 2
end
it "goes through using direct paging" do
pager = Api::Paging::IndexPaginator.new(@alice_search, 1, @limit)
page = pager.page_data
validate_page(page, :created_at, false)
page_count = 0
until page&.empty?
page_count += 1
pager = pager.next_page(false)
page = pager.page_data
validate_page(page, :created_at, false)
end
expect(page_count).to eq(3)
end
it "goes through using Query Parameter data" do
page_num = 1
pager = Api::Paging::IndexPaginator.new(@alice_search, page_num, @limit)
page = pager.page_data
validate_page(page, :created_at, false)
page_count = 0
until page&.empty?
page_count += 1
break unless pager.next_page
np = pager.next_page.split("=").last.to_i
pager = Api::Paging::IndexPaginator.new(@alice_search, np, @limit)
page = pager.page_data
validate_page(page, :created_at, false)
end
expect(page_count).to eq(3)
end
def validate_page(page, field, is_descending)
expect(page.length).to be <= @limit
last_value = nil
page.each do |element|
last_value ||= element[field]
if is_descending
expect(last_value).to be >= element[field]
else
expect(last_value).to be <= element[field]
end
last_value = element[field]
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
describe Api::Paging::RestPagedResponseBuilder do
before do
@pager = Api::Paging::IndexPaginator.new(alice.posts, 1, 5)
end
it "returns page of data" do
builder = Api::Paging::RestPagedResponseBuilder.new(@pager, nil)
response = builder.response
expect(response[:links]).not_to be_nil
expect(response[:data]).not_to be_nil
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
describe Api::Paging::RestPaginatorBuilder do
it "generates page response builder called with index-based pager" do
params = ActionController::Parameters.new(page: 1)
pager = Api::Paging::RestPaginatorBuilder.new(alice.posts, nil).index_pager(params)
expect(pager.is_a?(Api::Paging::RestPagedResponseBuilder)).to be_truthy
end
it "generates page response builder with time-based pager" do
params = ActionController::Parameters.new(before: Time.current.iso8601)
pager = Api::Paging::RestPaginatorBuilder.new(alice.posts, nil).time_pager(params)
expect(pager.is_a?(Api::Paging::RestPagedResponseBuilder)).to be_truthy
end
end

View file

@ -0,0 +1,119 @@
# frozen_string_literal: true
describe Api::Paging::TimePaginator do
before do
(1..7).each do |i|
public = !(i == 1 || i == 6)
alice.post(
:status_message,
text: "Post #{i}",
public: public
)
sleep(0.01.seconds)
end
@alice_search = alice.posts.where(public: true)
@limit = 2
end
it "goes through decending using direct paging" do
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: Time.current,
is_descending: true,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, true, nil)
while page&.empty?
pager = pager.next_page(false)
page = pager.page_data
last_time = validate_page(page, :created_at, true, last_time)
end
end
it "goes through descending using Query Parameter data" do
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: Time.current,
is_descending: true,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, true, nil)
while page&.empty?
next_time = Time.iso8601(pager.next_page.split("=").last)
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: next_time,
is_descending: true,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, true, last_time)
end
end
it "goes through ascending using direct paging" do
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: (Time.current - 1.year),
is_descending: false,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, false, nil)
while page&.empty?
pager = pager.next_page(false)
page = pager.page_data
last_time = validate_page(page, :created_at, false, last_time)
end
end
it "goes through ascending using Query Parameter data" do
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: (Time.current - 1.year),
is_descending: false,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, false, nil)
while page&.empty?
next_time = Time.iso8601(pager.next_page.split("=").last)
pager = Api::Paging::TimePaginator.new(
query_base: @alice_search,
query_time_field: :created_at,
data_time_field: :created_at,
current_time: next_time,
is_descending: false,
limit: @limit
)
page = pager.page_data
last_time = validate_page(page, :created_at, false, last_time)
end
end
def validate_page(page, field, is_descending, last_time)
expect(page.length).to be <= @limit
page.each do |element|
last_time ||= element[field]
if is_descending
expect(last_time).to be >= element[field]
else
expect(last_time).to be <= element[field]
end
last_time = element[field]
end
last_time
end
end