feat: タグ名を別管理に変更(#215) (#219)

Merge branch 'main' into feature/215

#215 ニコニコ同期テスト

#215 テスト・ケース追加

#215 テスト・ケース追加

#215 テスト・ケース追加

#215 テスト・ケース追加

Merge remote-tracking branch 'origin/main' into feature/215

Merge branch 'main' into feature/215

#215

#215

Merge remote-tracking branch 'origin/main' into feature/215

#215

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #219
This commit was merged in pull request #219.
This commit is contained in:
2026-01-15 12:40:41 +09:00
parent 74141f2a84
commit fa2030f9a5
34 changed files with 1268 additions and 119 deletions
+217 -12
View File
@@ -1,30 +1,235 @@
require 'rails_helper'
require 'set'
RSpec.describe 'Posts API', type: :request do
# create / update で thumbnail.attach は走るが、
# resized_thumbnail! が MiniMagick 依存でコケやすいので request spec ではスタブしとくのが無難。
before do
allow_any_instance_of(Post).to receive(:resized_thumbnail!).and_return(true)
end
def dummy_upload
# 中身は何でもいい(加工処理はスタブしてる)
Rack::Test::UploadedFile.new(StringIO.new('dummy'), 'image/jpeg', original_filename: 'dummy.jpg')
end
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
let!(:tag) { Tag.create!(tag_name: tag_name, category: 'general') }
let!(:post_record) do
Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
PostTag.create!(post: p, tag: tag)
end
end
describe 'GET /posts' do
it 'returns tags with name in JSON' do
tn = TagName.create!(name: 'gen:spec_tag')
tag = Tag.create!(tag_name: tn, category: 'general')
post = Post.create!(title: 'spec post', url: 'https://example.com/spec')
PostTag.create!(post: post, tag: tag)
it 'returns posts with tag name in JSON' do
get '/posts'
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json).to have_key('posts')
expect(json['posts']).to be_a(Array)
expect(json['posts']).to be_an(Array)
expect(json['posts']).not_to be_empty
tags = json['posts'][0]['tags']
expect(tags).to be_a(Array)
expect(tags).to be_an(Array)
expect(tags).not_to be_empty
# Tag は name カラムを持たないので、API 側が methods: [:name] 等で出す必要がある
expect(tags[0]).to have_key('name')
expect(tags[0]['name']).to eq('gen:spec_tag')
expect(tags.map { |t| t['name'] }).to include('spec_tag')
expect(tags[0]).to include('category')
end
end
describe 'GET /posts/:id' do
subject(:request) { get "/posts/#{post_id}" }
context 'when post exists' do
let(:post_id) { post_record.id }
it 'returns post with tag tree + related + viewed' do
request
expect(response).to have_http_status(:ok)
expect(json).to include('id' => post_record.id)
expect(json).to have_key('tags')
expect(json['tags']).to be_an(Array)
# show は build_tag_tree_for を使うので、tags はツリー形式(children 付き)
node = json['tags'][0]
expect(node).to include('id', 'name', 'category', 'post_count', 'children')
expect(node['name']).to eq('spec_tag')
expect(json).to have_key('related')
expect(json['related']).to be_an(Array)
expect(json).to have_key('viewed')
expect([true, false]).to include(json['viewed'])
end
end
context 'when post does not exist' do
let(:post_id) { 999_999_999 }
it 'returns 404' do
request
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST /posts' do
let(:member) { create(:user, :member) }
it '401 when not logged in' do
sign_out
post '/posts', params: { title: 't', url: 'https://example.com/x', tags: 'a', thumbnail: dummy_upload }
expect(response).to have_http_status(:unauthorized)
end
it '403 when not member' do
sign_in_as(create(:user, role: 'guest'))
post '/posts', params: { title: 't', url: 'https://example.com/x', tags: 'a', thumbnail: dummy_upload }
expect(response).to have_http_status(:forbidden)
end
it '201 and creates post + tags when member' do
sign_in_as(member)
post '/posts', params: {
title: 'new post',
url: 'https://example.com/new',
tags: 'spec_tag', # 既存タグ名を投げる
thumbnail: dummy_upload
}
expect(response).to have_http_status(:created)
expect(json).to include('id', 'title', 'url')
# tags が name を含むこと(API 側の serialization が正しいこと)
expect(json).to have_key('tags')
expect(json['tags']).to be_an(Array)
expect(json['tags'][0]).to have_key('name')
end
end
describe 'PUT /posts/:id' do
let(:member) { create(:user, :member) }
it '401 when not logged in' do
sign_out
put "/posts/#{post_record.id}", params: { title: 'updated', tags: 'spec_tag' }
expect(response).to have_http_status(:unauthorized)
end
it '403 when not member' do
sign_in_as(create(:user, role: 'guest'))
put "/posts/#{post_record.id}", params: { title: 'updated', tags: 'spec_tag' }
expect(response).to have_http_status(:forbidden)
end
it '200 and updates title + resync tags when member' do
sign_in_as(member)
# 追加で別タグも作って、更新時に入れ替わることを見る
tn2 = TagName.create!(name: 'spec_tag_2')
Tag.create!(tag_name: tn2, category: 'general')
put "/posts/#{post_record.id}", params: {
title: 'updated title',
tags: 'spec_tag_2'
}
expect(response).to have_http_status(:ok)
expect(json).to have_key('tags')
expect(json['tags']).to be_an(Array)
# show と同様、update 後レスポンスもツリー形式
names = json['tags'].map { |n| n['name'] }
expect(names).to include('spec_tag_2')
end
end
describe 'GET /posts/random' do
it '404 when no posts' do
PostTag.delete_all
Post.delete_all
get '/posts/random'
expect(response).to have_http_status(:not_found)
end
it '200 and returns viewed boolean' do
get '/posts/random'
expect(response).to have_http_status(:ok)
expect(json).to have_key('viewed')
expect([true, false]).to include(json['viewed'])
end
end
describe 'GET /posts/changes' do
let(:member) { create(:user, :member) }
it 'returns add/remove events (history) for a post' do
# add
tn2 = TagName.create!(name: 'spec_tag2')
tag2 = Tag.create!(tag_name: tn2, category: 'general')
pt = PostTag.create!(post: post_record, tag: tag2, created_user: member)
# remove (discard)
pt.discard_by!(member)
get '/posts/changes', params: { id: post_record.id }
expect(response).to have_http_status(:ok)
expect(json).to include('changes', 'count')
expect(json['changes']).to be_an(Array)
expect(json['count']).to be >= 2
types = json['changes'].map { |e| e['change_type'] }.uniq
expect(types).to include('add')
expect(types).to include('remove')
end
end
describe 'POST /posts/:id/viewed' do
let(:user) { create(:user) }
it '401 when not logged in' do
sign_out
post "/posts/#{ post_record.id }/viewed"
expect(response).to have_http_status(:unauthorized)
end
it '204 and marks viewed when logged in' do
sign_in_as(user)
post "/posts/#{ post_record.id }/viewed"
expect(response).to have_http_status(:no_content)
expect(user.reload.viewed?(post_record)).to be(true)
end
end
describe 'DELETE /posts/:id/unviewed' do
let(:user) { create(:user) }
it '401 when not logged in' do
sign_out
delete "/posts/#{ post_record.id }/viewed"
expect(response).to have_http_status(:unauthorized)
end
it '204 and unmarks viewed when logged in' do
sign_in_as(user)
# 先に viewed 付けてから外す
user.viewed_posts << post_record
delete "/posts/#{ post_record.id }/viewed"
expect(response).to have_http_status(:no_content)
expect(user.reload.viewed?(post_record)).to be(false)
end
end
end