This commit is contained in:
@@ -49,13 +49,19 @@ class TagsController < ApplicationController
|
|||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.member?
|
||||||
|
|
||||||
|
name = params[:name].presence
|
||||||
|
category = params[:category].presence
|
||||||
|
|
||||||
tag = Tag.find(params[:id])
|
tag = Tag.find(params[:id])
|
||||||
|
|
||||||
attrs = { name: params[:name].presence,
|
if name.present?
|
||||||
category: params[:category].presence }.compact
|
tag.tag_name.update!(name:)
|
||||||
|
end
|
||||||
|
|
||||||
tag.update!(attrs) if attrs.present?
|
if category.present?
|
||||||
|
tag.update!(category:)
|
||||||
|
end
|
||||||
|
|
||||||
render json: tag
|
render json: tag.as_json(methods: [:name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,5 +7,9 @@ FactoryBot.define do
|
|||||||
trait :member do
|
trait :member do
|
||||||
role { "member" }
|
role { "member" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :admin do
|
||||||
|
role { 'admin' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# spec/requests/tag_children_spec.rb
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe "TagChildren", type: :request do
|
||||||
|
let!(:parent) { create(:tag) }
|
||||||
|
let!(:child) { create(:tag) }
|
||||||
|
|
||||||
|
# ここは君のUser factoryに合わせて調整
|
||||||
|
let(:user) { create_member_user! }
|
||||||
|
let(:admin) { create_admin_user! }
|
||||||
|
|
||||||
|
# current_user を ApplicationController でスタブ
|
||||||
|
def stub_current_user(user_or_nil)
|
||||||
|
allow_any_instance_of(ApplicationController)
|
||||||
|
.to receive(:current_user)
|
||||||
|
.and_return(user_or_nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST /tag_children" do
|
||||||
|
subject(:do_request) do
|
||||||
|
post "/tags/#{ parent_id }/children/#{ child_id }"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when not logged in" do
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 401" do
|
||||||
|
stub_current_user(nil)
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in but not admin" do
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 403" do
|
||||||
|
stub_current_user(user)
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when admin and params are present" do
|
||||||
|
before { stub_current_user(admin) }
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 204 and adds child to parent.children" do
|
||||||
|
expect(parent.children).not_to include(child)
|
||||||
|
|
||||||
|
expect { do_request }
|
||||||
|
.to change { parent.reload.children.ids.include?(child.id) }
|
||||||
|
.from(false).to(true)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when Tag.find raises (invalid ids) it still returns 204" do
|
||||||
|
before { stub_current_user(admin) }
|
||||||
|
|
||||||
|
let(:parent_id) { -1 }
|
||||||
|
let(:child_id) { -1 }
|
||||||
|
|
||||||
|
it "returns 204 (rescue nil)" do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE /tag_children" do
|
||||||
|
subject(:do_request) do
|
||||||
|
delete "/tags/#{ parent_id }/children/#{ child_id }"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when not logged in" do
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 401" do
|
||||||
|
stub_current_user(nil)
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in but not admin" do
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 403" do
|
||||||
|
stub_current_user(user)
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when admin and params are present" do
|
||||||
|
before do
|
||||||
|
stub_current_user(admin)
|
||||||
|
parent.children << child
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:parent_id) { parent.id }
|
||||||
|
let(:child_id) { child.id }
|
||||||
|
|
||||||
|
it "returns 204 and removes child from parent.children" do
|
||||||
|
expect(parent.reload.children).to include(child)
|
||||||
|
|
||||||
|
expect { do_request }
|
||||||
|
.to change { parent.reload.children.ids.include?(child.id) }
|
||||||
|
.from(true).to(false)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when Tag.find raises (invalid ids) it still returns 204" do
|
||||||
|
before { stub_current_user(admin) }
|
||||||
|
|
||||||
|
let(:parent_id) { -1 }
|
||||||
|
let(:child_id) { -1 }
|
||||||
|
|
||||||
|
it "returns 204 (rescue nil)" do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -76,4 +76,103 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:not_found)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# member? を持つ user を想定(Factory 側で trait 作ってもOK)
|
||||||
|
let(:member_user) { create(:user) }
|
||||||
|
let(:non_member_user) { create(:user) }
|
||||||
|
|
||||||
|
def stub_current_user(user)
|
||||||
|
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(member_user).to receive(:member?).and_return(true)
|
||||||
|
allow(non_member_user).to receive(:member?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PATCH /tags/:id" do
|
||||||
|
context "未ログイン" do
|
||||||
|
before { stub_current_user(nil) }
|
||||||
|
|
||||||
|
it "401 を返す" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: "new" }
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "ログインしてゐるが member でない" do
|
||||||
|
before { stub_current_user(non_member_user) }
|
||||||
|
|
||||||
|
it "403 を返す" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: "new" }
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "member" do
|
||||||
|
before { stub_current_user(member_user) }
|
||||||
|
|
||||||
|
it "name だけ更新できる" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: "new" }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
tag.reload
|
||||||
|
expect(tag.name).to eq("new")
|
||||||
|
expect(tag.category).to eq("general")
|
||||||
|
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["id"]).to eq(tag.id)
|
||||||
|
expect(json["name"]).to eq("new")
|
||||||
|
expect(json["category"]).to eq("general")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "category だけ更新できる" do
|
||||||
|
patch "/tags/#{tag.id}", params: { category: "meme" }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
tag.reload
|
||||||
|
expect(tag.name).to eq("spec_tag")
|
||||||
|
expect(tag.category).to eq("meme")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "空文字は presence により無視され、更新は走らない(値が変わらない)" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: "", category: " " }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
tag.reload
|
||||||
|
expect(tag.name).to eq("spec_tag")
|
||||||
|
expect(tag.category).to eq("general")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "両方更新できる" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: "n", category: "meta" }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
tag.reload
|
||||||
|
expect(tag.name).to eq("n")
|
||||||
|
expect(tag.category).to eq("meta")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "存在しない id だと RecordNotFound になる(通常は 404)" do
|
||||||
|
# Rails 設定次第で例外がそのまま上がる/404になる
|
||||||
|
# APIなら rescue_from で 404 にしてることが多いので、その場合は 404 を期待。
|
||||||
|
patch "/tags/999999999", params: { name: "x" }
|
||||||
|
|
||||||
|
expect(response.status).to be_in([404, 500])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "バリデーションで update! が失敗したら(通常は 422 か 500)" do
|
||||||
|
patch "/tags/#{tag.id}", params: { name: 'new', category: 'nico' }
|
||||||
|
|
||||||
|
# rescue_from の実装次第で変はる:
|
||||||
|
# - RecordInvalid を 422 にしてるなら 422
|
||||||
|
# - 未処理なら 500
|
||||||
|
expect(response.status).to be_in([422, 500])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,4 +5,11 @@ module TestRecords
|
|||||||
role: 'member',
|
role: 'member',
|
||||||
banned: false)
|
banned: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_admin_user!
|
||||||
|
User.create!(name: 'spec admin',
|
||||||
|
inheritance_code: SecureRandom.hex(16),
|
||||||
|
role: 'admin',
|
||||||
|
banned: false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
|
||||||
|
RSpec.describe 'post_similarity:calc' do
|
||||||
|
include RakeTaskHelper
|
||||||
|
|
||||||
|
it 'calls Similarity::Calc with Post and :tags' do
|
||||||
|
# 必要最低限のデータ
|
||||||
|
t1 = Tag.create!(name: "t1")
|
||||||
|
t2 = Tag.create!(name: "t2")
|
||||||
|
t3 = Tag.create!(name: "t3")
|
||||||
|
|
||||||
|
p1 = Post.create!(url: "https://example.com/1")
|
||||||
|
p2 = Post.create!(url: "https://example.com/2")
|
||||||
|
p3 = Post.create!(url: "https://example.com/3")
|
||||||
|
|
||||||
|
# kept スコープが絡むなら、PostTag がデフォで kept になる前提
|
||||||
|
PostTag.create!(post: p1, tag: t1)
|
||||||
|
PostTag.create!(post: p1, tag: t2)
|
||||||
|
|
||||||
|
PostTag.create!(post: p2, tag: t1)
|
||||||
|
PostTag.create!(post: p2, tag: t3)
|
||||||
|
|
||||||
|
PostTag.create!(post: p3, tag: t3)
|
||||||
|
|
||||||
|
expect { run_rake_task("post_similarity:calc") }
|
||||||
|
.to change { PostSimilarity.count }.from(0)
|
||||||
|
|
||||||
|
ps = PostSimilarity.find_by!(post_id: p1.id, target_post_id: p2.id)
|
||||||
|
ps_rev = PostSimilarity.find_by!(post_id: p2.id, target_post_id: p1.id)
|
||||||
|
expect(ps_rev.cos).to eq(ps.cos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
|
||||||
|
RSpec.describe 'tag_similarity:calc' do
|
||||||
|
include RakeTaskHelper
|
||||||
|
|
||||||
|
it 'calls Similarity::Calc with Tag and :posts' do
|
||||||
|
# 必要最低限のデータ
|
||||||
|
t1 = Tag.create!(name: "t1")
|
||||||
|
t2 = Tag.create!(name: "t2")
|
||||||
|
t3 = Tag.create!(name: "t3")
|
||||||
|
|
||||||
|
p1 = Post.create!(url: "https://example.com/1")
|
||||||
|
p2 = Post.create!(url: "https://example.com/2")
|
||||||
|
p3 = Post.create!(url: "https://example.com/3")
|
||||||
|
|
||||||
|
# kept スコープが絡むなら、PostTag がデフォで kept になる前提
|
||||||
|
PostTag.create!(post: p1, tag: t1)
|
||||||
|
PostTag.create!(post: p1, tag: t2)
|
||||||
|
|
||||||
|
PostTag.create!(post: p2, tag: t1)
|
||||||
|
PostTag.create!(post: p2, tag: t3)
|
||||||
|
|
||||||
|
PostTag.create!(post: p3, tag: t3)
|
||||||
|
|
||||||
|
expect { run_rake_task("tag_similarity:calc") }
|
||||||
|
.to change { TagSimilarity.count }.from(0)
|
||||||
|
|
||||||
|
ps = TagSimilarity.find_by!(tag_id: t1.id, target_tag_id: t2.id)
|
||||||
|
ps_rev = TagSimilarity.find_by!(tag_id: t2.id, target_tag_id: t1.id)
|
||||||
|
expect(ps_rev.cos).to eq(ps.cos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Reference in New Issue
Block a user