Merge pull request #10 from frankrousseau/api-tag-updates
Api Tag Endpoint Feature Complete With Tests
This commit is contained in:
commit
b17e519cb4
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.xml
|
||||
def create
|
||||
name_normalized = ActsAsTaggableOn::Tag.normalize(params['name'])
|
||||
|
||||
if name_normalized.nil? || name_normalized.empty?
|
||||
tag = tag_followings_service.create(params["name"])
|
||||
render json: tag.to_json, status: :created
|
||||
rescue StandardError
|
||||
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
|
||||
|
||||
# DELETE /tag_followings/1
|
||||
# DELETE /tag_followings/1.xml
|
||||
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|
|
||||
format.any(:js, :json) { head :no_content }
|
||||
end
|
||||
|
|
@ -56,4 +46,10 @@ class TagFollowingsController < ApplicationController
|
|||
redirect_to followed_tags_stream_path unless request.format == :mobile
|
||||
gon.preloads[:tagFollowings] = tags
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_followings_service
|
||||
TagFollowingService.new(current_user)
|
||||
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"
|
||||
failed_create: "Failed to create the post"
|
||||
failed_delete: "Not allowed to delete the post"
|
||||
tags:
|
||||
cant_process: "Failed to process the tag followings request"
|
||||
|
||||
error:
|
||||
not_found: "No record found for given id."
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ Rails.application.routes.draw do
|
|||
resources :messages, only: %i[index create]
|
||||
end
|
||||
|
||||
resources :tag_followings, only: %i[index create destroy]
|
||||
get "streams/activity" => "streams#activity", :as => "activity_stream"
|
||||
get "streams/main" => "streams#multi", :as => "stream"
|
||||
get "streams/tags" => "streams#followed_tags", :as => "followed_tags_stream"
|
||||
|
|
|
|||
|
|
@ -29,4 +29,67 @@ describe TagFollowingsController, type: :controller do
|
|||
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
|
||||
|
|
|
|||
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