Tags API Endpoint complete with full tests
This commit is contained in:
parent
db7fe69ce4
commit
7ae36de2cf
8 changed files with 348 additions and 16 deletions
39
app/controllers/api/v1/tag_followings_controller.rb
Executable file
39
app/controllers/api/v1/tag_followings_controller.rb
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class TagFollowingsController < Api::V1::BaseController
|
||||||
|
before_action only: %i[index] do
|
||||||
|
require_access_token %w[read]
|
||||||
|
end
|
||||||
|
|
||||||
|
before_action only: %i[create destroy] do
|
||||||
|
require_access_token %w[read write]
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from StandardError do
|
||||||
|
render json: I18n.t("api.endpoint_errors.tags.cant_process"), status: :bad_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: tag_followings_service.index.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
tag_followings_service.create(params.require(:name))
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
tag_followings_service.destroy_by_name(params.require(:id))
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tag_followings_service
|
||||||
|
TagFollowingService.new(current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -14,28 +14,18 @@ class TagFollowingsController < ApplicationController
|
||||||
# POST /tag_followings
|
# POST /tag_followings
|
||||||
# POST /tag_followings.xml
|
# POST /tag_followings.xml
|
||||||
def create
|
def create
|
||||||
name_normalized = ActsAsTaggableOn::Tag.normalize(params['name'])
|
tag = tag_followings_service.create(params["name"])
|
||||||
|
render json: tag.to_json, status: :created
|
||||||
if name_normalized.nil? || name_normalized.empty?
|
rescue StandardError
|
||||||
head :forbidden
|
head :forbidden
|
||||||
else
|
|
||||||
@tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
|
|
||||||
@tag_following = current_user.tag_followings.new(:tag_id => @tag.id)
|
|
||||||
|
|
||||||
if @tag_following.save
|
|
||||||
render :json => @tag.to_json, :status => 201
|
|
||||||
else
|
|
||||||
head :forbidden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# DELETE /tag_followings/1
|
# DELETE /tag_followings/1
|
||||||
# DELETE /tag_followings/1.xml
|
# DELETE /tag_followings/1.xml
|
||||||
def destroy
|
def destroy
|
||||||
tag_following = current_user.tag_followings.find_by_tag_id( params['id'] )
|
success = tag_followings_service.destroy(params["id"])
|
||||||
|
|
||||||
if tag_following && tag_following.destroy
|
if success
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.any(:js, :json) { head :no_content }
|
format.any(:js, :json) { head :no_content }
|
||||||
end
|
end
|
||||||
|
|
@ -56,4 +46,10 @@ class TagFollowingsController < ApplicationController
|
||||||
redirect_to followed_tags_stream_path unless request.format == :mobile
|
redirect_to followed_tags_stream_path unless request.format == :mobile
|
||||||
gon.preloads[:tagFollowings] = tags
|
gon.preloads[:tagFollowings] = tags
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tag_followings_service
|
||||||
|
TagFollowingService.new(current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
33
app/services/tag_following_service.rb
Normal file
33
app/services/tag_following_service.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TagFollowingService
|
||||||
|
def initialize(user=nil)
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(name)
|
||||||
|
name_normalized = ActsAsTaggableOn::Tag.normalize(name)
|
||||||
|
raise ArgumentError, "Name field null or empty" if name_normalized.blank?
|
||||||
|
|
||||||
|
tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
|
||||||
|
tag_following = @user.tag_followings.new(tag_id: tag.id)
|
||||||
|
|
||||||
|
raise "Can't process tag entity" unless tag_following.save
|
||||||
|
tag
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy(id)
|
||||||
|
tag_following = @user.tag_followings.find_by(tag_id: id)
|
||||||
|
tag_following&.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_by_name(name)
|
||||||
|
name_normalized = ActsAsTaggableOn::Tag.normalize(name)
|
||||||
|
followed_tag = @user.followed_tags.find_by(name: name_normalized)
|
||||||
|
destroy(followed_tag.id) if followed_tag
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@user.followed_tags
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -972,6 +972,8 @@ en:
|
||||||
post_not_found: "Post with provided guid could not be found"
|
post_not_found: "Post with provided guid could not be found"
|
||||||
failed_create: "Failed to create the post"
|
failed_create: "Failed to create the post"
|
||||||
failed_delete: "Not allowed to delete the post"
|
failed_delete: "Not allowed to delete the post"
|
||||||
|
tags:
|
||||||
|
cant_process: "Failed to process the tag followings request"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
not_found: "No record found for given id."
|
not_found: "No record found for given id."
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ Rails.application.routes.draw do
|
||||||
resources :messages, only: %i[index create]
|
resources :messages, only: %i[index create]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :tag_followings, only: %i[index create destroy]
|
||||||
get "streams/activity" => "streams#activity", :as => "activity_stream"
|
get "streams/activity" => "streams#activity", :as => "activity_stream"
|
||||||
get "streams/main" => "streams#multi", :as => "stream"
|
get "streams/main" => "streams#multi", :as => "stream"
|
||||||
get "streams/tags" => "streams#followed_tags", :as => "followed_tags_stream"
|
get "streams/tags" => "streams#followed_tags", :as => "followed_tags_stream"
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,67 @@ describe TagFollowingsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
before do
|
||||||
|
sign_in alice, scope: :user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Creates new tag with valid name" do
|
||||||
|
name = SecureRandom.uuid
|
||||||
|
post :create, params: {name: name}
|
||||||
|
expect(response.status).to be(201)
|
||||||
|
tag_data = JSON.parse(response.body)
|
||||||
|
expect(tag_data["name"]).to eq(name)
|
||||||
|
expect(tag_data.has_key?("id")).to be_truthy
|
||||||
|
expect(tag_data["taggings_count"]).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Fails with missing name field" do
|
||||||
|
post :create, params: {}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#destroy" do
|
||||||
|
before do
|
||||||
|
sign_in alice, scope: :user
|
||||||
|
@tag_name = SecureRandom.uuid
|
||||||
|
post :create, params: {name: @tag_name}
|
||||||
|
@tag_id_to_delete = JSON.parse(response.body)["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Deletes tag with valid id" do
|
||||||
|
delete :destroy, params: {id: @tag_id_to_delete}, format: :json
|
||||||
|
expect(response.status).to eq(204)
|
||||||
|
expect(alice.followed_tags.find_by(name: @tag_name)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Fails with missing name field" do
|
||||||
|
delete :create, params: {}, format: :json
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Fails with bad Tag ID" do
|
||||||
|
delete :create, params: {id: -1}, format: :json
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
before do
|
||||||
|
sign_in alice, scope: :user
|
||||||
|
post :create, params: {name: "tag1"}
|
||||||
|
post :create, params: {name: "tag2"}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Gets Tags" do
|
||||||
|
get :index, format: :json
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
tag_followings = JSON.parse(response.body)
|
||||||
|
expect(tag_followings.length).to eq(2)
|
||||||
|
expect(tag_followings.find(name: "tag1")).to_not be_nil
|
||||||
|
expect(tag_followings.find(name: "tag2")).to_not be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
120
spec/integration/api/tag_followings_controller_spec.rb
Executable file
120
spec/integration/api/tag_followings_controller_spec.rb
Executable file
|
|
@ -0,0 +1,120 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
describe Api::V1::TagFollowingsController do
|
||||||
|
let(:auth) { FactoryGirl.create(:auth_with_read_and_write) }
|
||||||
|
let!(:access_token) { auth.create_access_token.to_s }
|
||||||
|
|
||||||
|
before do
|
||||||
|
@expected_tags = %w[tag1 tag2 tag3]
|
||||||
|
@expected_tags.each {|tag| add_tag(tag, auth.user) }
|
||||||
|
@initial_count = @expected_tags.length
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
context "valid tag ID" do
|
||||||
|
it "succeeds in adding a tag" do
|
||||||
|
post(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {name: "tag4", access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(204)
|
||||||
|
|
||||||
|
get(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
items = JSON.parse(response.body)
|
||||||
|
expect(items.length).to eq(@initial_count + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "missing name parameter" do
|
||||||
|
it "fails to add" do
|
||||||
|
post(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.tags.cant_process"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "duplicate tag" do
|
||||||
|
it "fails to add" do
|
||||||
|
post(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {name: "tag3", access_token: access_token}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.tags.cant_process"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
context "list all followed tags" do
|
||||||
|
it "succeeds" do
|
||||||
|
get(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
items = JSON.parse(response.body)
|
||||||
|
expect(items.length).to eq(@expected_tags.length)
|
||||||
|
@expected_tags.each {|tag| expect(items.find(tag)).to be_truthy }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#delete" do
|
||||||
|
context "valid tag" do
|
||||||
|
it "succeeds in deleting tag" do
|
||||||
|
delete(
|
||||||
|
api_v1_tag_following_path("tag1"),
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(204)
|
||||||
|
|
||||||
|
get(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
items = JSON.parse(response.body)
|
||||||
|
expect(items.length).to eq(@initial_count - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "tag that's not followed" do
|
||||||
|
it "does nothing" do
|
||||||
|
delete(
|
||||||
|
api_v1_tag_following_path(SecureRandom.uuid.to_s),
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(204)
|
||||||
|
|
||||||
|
get(
|
||||||
|
api_v1_tag_followings_path,
|
||||||
|
params: {access_token: access_token}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
items = JSON.parse(response.body)
|
||||||
|
expect(items.length).to eq(@initial_count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def add_tag(name, user)
|
||||||
|
tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name)
|
||||||
|
tag_following = user.tag_followings.new(tag_id: tag.id)
|
||||||
|
tag_following.save
|
||||||
|
tag_following
|
||||||
|
end
|
||||||
|
end
|
||||||
78
spec/services/tag_following_service_spec.rb
Normal file
78
spec/services/tag_following_service_spec.rb
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe TagFollowingService do
|
||||||
|
before do
|
||||||
|
add_tag("tag1", alice)
|
||||||
|
add_tag("tag2", alice)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
it "Creates new tag with valid name" do
|
||||||
|
name = SecureRandom.uuid
|
||||||
|
expect(alice.followed_tags.find_by(name: name)).to be_nil
|
||||||
|
tag_data = tag_following_service(alice).create(name)
|
||||||
|
expect(alice.followed_tags.find_by(name: name).name).to eq(name)
|
||||||
|
expect(tag_data["name"]).to eq(name)
|
||||||
|
expect(tag_data["id"]).to be_truthy
|
||||||
|
expect(tag_data["taggings_count"]).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Throws error with empty tag" do
|
||||||
|
expect { tag_following_service(alice).create(nil) }.to raise_error(ArgumentError)
|
||||||
|
expect { tag_following_service(alice).create("") }.to raise_error(ArgumentError)
|
||||||
|
expect { tag_following_service(alice).create("#") }.to raise_error(ArgumentError)
|
||||||
|
expect { tag_following_service(alice).create(" ") }.to raise_error(ArgumentError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#destroy" do
|
||||||
|
it "Deletes tag with valid name" do
|
||||||
|
name = SecureRandom.uuid
|
||||||
|
add_tag(name, alice)
|
||||||
|
expect(alice.followed_tags.find_by(name: name).name).to eq(name)
|
||||||
|
expect(tag_following_service(alice).destroy_by_name(name)).to be_truthy
|
||||||
|
expect(alice.followed_tags.find_by(name: name)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Deletes tag with id" do
|
||||||
|
name = SecureRandom.uuid
|
||||||
|
new_tag = add_tag(name, alice)
|
||||||
|
expect(alice.followed_tags.find_by(name: name).name).to eq(name)
|
||||||
|
expect(tag_following_service(alice).destroy(new_tag.tag_id)).to be_truthy
|
||||||
|
expect(alice.followed_tags.find_by(name: name)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Does nothing with tag that isn't already followed" do
|
||||||
|
original_length = alice.followed_tags.length
|
||||||
|
tag_following_service(alice).destroy_by_name(SecureRandom.uuid)
|
||||||
|
tag_following_service(alice).destroy(-1)
|
||||||
|
expect(alice.followed_tags.length).to eq(original_length)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Does nothing with empty tag name" do
|
||||||
|
original_length = alice.followed_tags.length
|
||||||
|
tag_following_service(alice).destroy_by_name("")
|
||||||
|
expect(alice.followed_tags.length).to eq(original_length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
it "Returns user's list of tags" do
|
||||||
|
tags = tag_following_service(alice).index
|
||||||
|
expect(tags.length).to eq(alice.followed_tags.length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tag_following_service(user=alice)
|
||||||
|
TagFollowingService.new(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_tag(name, user)
|
||||||
|
tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name)
|
||||||
|
tag_following = user.tag_followings.new(tag_id: tag.id)
|
||||||
|
tag_following.save
|
||||||
|
tag_following
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue