Tags API Endpoint complete with full tests

This commit is contained in:
Hank Grabowski 2018-11-08 09:33:30 -05:00 committed by Frank Rousseau
parent db7fe69ce4
commit 7ae36de2cf
8 changed files with 348 additions and 16 deletions

View 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

View file

@ -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?
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
tag = tag_followings_service.create(params["name"])
render json: tag.to_json, status: :created
rescue StandardError
head :forbidden
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

View 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

View file

@ -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."

View file

@ -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"

View file

@ -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

View 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

View 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