ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

281 lines
8.5 KiB

  1. require 'rails_helper'
  2. require 'set'
  3. RSpec.describe 'Posts API', type: :request do
  4. # create / update で thumbnail.attach は走るが、
  5. # resized_thumbnail! が MiniMagick 依存でコケやすいので request spec ではスタブしとくのが無難。
  6. before do
  7. allow_any_instance_of(Post).to receive(:resized_thumbnail!).and_return(true)
  8. end
  9. def dummy_upload
  10. # 中身は何でもいい(加工処理はスタブしてる)
  11. Rack::Test::UploadedFile.new(StringIO.new('dummy'), 'image/jpeg', original_filename: 'dummy.jpg')
  12. end
  13. let!(:tag_name) { TagName.create!(name: 'spec_tag') }
  14. let!(:tag) { Tag.create!(tag_name: tag_name, category: 'general') }
  15. let!(:post_record) do
  16. Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
  17. PostTag.create!(post: p, tag: tag)
  18. end
  19. end
  20. describe "GET /posts" do
  21. let!(:user) { create_member_user! }
  22. let!(:tag_name) { TagName.create!(name: "spec_tag") }
  23. let!(:tag) { Tag.create!(tag_name:, category: "general") }
  24. let!(:tag_name2) { TagName.create!(name: 'unko') }
  25. let!(:tag2) { Tag.create!(tag_name: tag_name2, category: 'deerjikist') }
  26. let!(:hit_post) do
  27. Post.create!(uploaded_user: user, title: "hello spec world",
  28. url: 'https://example.com/spec').tap do |p|
  29. PostTag.create!(post: p, tag:)
  30. end
  31. end
  32. let!(:miss_post) do
  33. Post.create!(uploaded_user: user, title: "unrelated title",
  34. url: 'https://example.com/spec2').tap do |p|
  35. PostTag.create!(post: p, tag: tag2)
  36. end
  37. end
  38. it "returns posts with tag name in JSON" do
  39. get "/posts"
  40. expect(response).to have_http_status(:ok)
  41. posts = json.fetch("posts")
  42. # 全postの全tagが name を含むこと
  43. expect(posts).not_to be_empty
  44. posts.each do |p|
  45. expect(p["tags"]).to be_an(Array)
  46. p["tags"].each do |t|
  47. expect(t).to include("name", "category")
  48. end
  49. end
  50. expect(json['count']).to be_an(Integer)
  51. # spec_tag を含む投稿が存在すること
  52. all_tag_names = posts.flat_map { |p| p["tags"].map { |t| t["name"] } }
  53. expect(all_tag_names).to include("spec_tag")
  54. end
  55. context "when q is provided" do
  56. it "filters posts by q (hit case)" do
  57. get "/posts", params: { tags: "spec_tag" }
  58. expect(response).to have_http_status(:ok)
  59. ids = json.fetch("posts").map { |p| p["id"] }
  60. expect(ids).to include(hit_post.id)
  61. expect(ids).not_to include(miss_post.id)
  62. expect(json['count']).to be_an(Integer)
  63. end
  64. it "returns empty posts when nothing matches" do
  65. get "/posts", params: { tags: "no_such_keyword_12345" }
  66. expect(response).to have_http_status(:ok)
  67. expect(json.fetch("posts")).to eq([])
  68. expect(json.fetch('count')).to eq(0)
  69. end
  70. end
  71. end
  72. describe 'GET /posts/:id' do
  73. subject(:request) { get "/posts/#{post_id}" }
  74. context 'when post exists' do
  75. let(:post_id) { post_record.id }
  76. it 'returns post with tag tree + related + viewed' do
  77. request
  78. expect(response).to have_http_status(:ok)
  79. expect(json).to include('id' => post_record.id)
  80. expect(json).to have_key('tags')
  81. expect(json['tags']).to be_an(Array)
  82. # show は build_tag_tree_for を使うので、tags はツリー形式(children 付き)
  83. node = json['tags'][0]
  84. expect(node).to include('id', 'name', 'category', 'post_count', 'children')
  85. expect(node['name']).to eq('spec_tag')
  86. expect(json).to have_key('related')
  87. expect(json['related']).to be_an(Array)
  88. expect(json).to have_key('viewed')
  89. expect([true, false]).to include(json['viewed'])
  90. end
  91. end
  92. context 'when post does not exist' do
  93. let(:post_id) { 999_999_999 }
  94. it 'returns 404' do
  95. request
  96. expect(response).to have_http_status(:not_found)
  97. end
  98. end
  99. end
  100. describe 'POST /posts' do
  101. let(:member) { create(:user, :member) }
  102. it '401 when not logged in' do
  103. sign_out
  104. post '/posts', params: { title: 't', url: 'https://example.com/x', tags: 'a', thumbnail: dummy_upload }
  105. expect(response).to have_http_status(:unauthorized)
  106. end
  107. it '403 when not member' do
  108. sign_in_as(create(:user, role: 'guest'))
  109. post '/posts', params: { title: 't', url: 'https://example.com/x', tags: 'a', thumbnail: dummy_upload }
  110. expect(response).to have_http_status(:forbidden)
  111. end
  112. it '201 and creates post + tags when member' do
  113. sign_in_as(member)
  114. post '/posts', params: {
  115. title: 'new post',
  116. url: 'https://example.com/new',
  117. tags: 'spec_tag', # 既存タグ名を投げる
  118. thumbnail: dummy_upload
  119. }
  120. expect(response).to have_http_status(:created)
  121. expect(json).to include('id', 'title', 'url')
  122. # tags が name を含むこと(API 側の serialization が正しいこと)
  123. expect(json).to have_key('tags')
  124. expect(json['tags']).to be_an(Array)
  125. expect(json['tags'][0]).to have_key('name')
  126. end
  127. end
  128. describe 'PUT /posts/:id' do
  129. let(:member) { create(:user, :member) }
  130. it '401 when not logged in' do
  131. sign_out
  132. put "/posts/#{post_record.id}", params: { title: 'updated', tags: 'spec_tag' }
  133. expect(response).to have_http_status(:unauthorized)
  134. end
  135. it '403 when not member' do
  136. sign_in_as(create(:user, role: 'guest'))
  137. put "/posts/#{post_record.id}", params: { title: 'updated', tags: 'spec_tag' }
  138. expect(response).to have_http_status(:forbidden)
  139. end
  140. it '200 and updates title + resync tags when member' do
  141. sign_in_as(member)
  142. # 追加で別タグも作って、更新時に入れ替わることを見る
  143. tn2 = TagName.create!(name: 'spec_tag_2')
  144. Tag.create!(tag_name: tn2, category: 'general')
  145. put "/posts/#{post_record.id}", params: {
  146. title: 'updated title',
  147. tags: 'spec_tag_2'
  148. }
  149. expect(response).to have_http_status(:ok)
  150. expect(json).to have_key('tags')
  151. expect(json['tags']).to be_an(Array)
  152. # show と同様、update 後レスポンスもツリー形式
  153. names = json['tags'].map { |n| n['name'] }
  154. expect(names).to include('spec_tag_2')
  155. end
  156. end
  157. describe 'GET /posts/random' do
  158. it '404 when no posts' do
  159. PostTag.delete_all
  160. Post.delete_all
  161. get '/posts/random'
  162. expect(response).to have_http_status(:not_found)
  163. end
  164. it '200 and returns viewed boolean' do
  165. get '/posts/random'
  166. expect(response).to have_http_status(:ok)
  167. expect(json).to have_key('viewed')
  168. expect([true, false]).to include(json['viewed'])
  169. end
  170. end
  171. describe 'GET /posts/changes' do
  172. let(:member) { create(:user, :member) }
  173. it 'returns add/remove events (history) for a post' do
  174. # add
  175. tn2 = TagName.create!(name: 'spec_tag2')
  176. tag2 = Tag.create!(tag_name: tn2, category: 'general')
  177. pt = PostTag.create!(post: post_record, tag: tag2, created_user: member)
  178. # remove (discard)
  179. pt.discard_by!(member)
  180. get '/posts/changes', params: { id: post_record.id }
  181. expect(response).to have_http_status(:ok)
  182. expect(json).to include('changes', 'count')
  183. expect(json['changes']).to be_an(Array)
  184. expect(json['count']).to be >= 2
  185. types = json['changes'].map { |e| e['change_type'] }.uniq
  186. expect(types).to include('add')
  187. expect(types).to include('remove')
  188. end
  189. end
  190. describe 'POST /posts/:id/viewed' do
  191. let(:user) { create(:user) }
  192. it '401 when not logged in' do
  193. sign_out
  194. post "/posts/#{ post_record.id }/viewed"
  195. expect(response).to have_http_status(:unauthorized)
  196. end
  197. it '204 and marks viewed when logged in' do
  198. sign_in_as(user)
  199. post "/posts/#{ post_record.id }/viewed"
  200. expect(response).to have_http_status(:no_content)
  201. expect(user.reload.viewed?(post_record)).to be(true)
  202. end
  203. end
  204. describe 'DELETE /posts/:id/viewed' do
  205. let(:user) { create(:user) }
  206. it '401 when not logged in' do
  207. sign_out
  208. delete "/posts/#{ post_record.id }/viewed"
  209. expect(response).to have_http_status(:unauthorized)
  210. end
  211. it '204 and unmarks viewed when logged in' do
  212. sign_in_as(user)
  213. # 先に viewed 付けてから外す
  214. user.viewed_posts << post_record
  215. delete "/posts/#{ post_record.id }/viewed"
  216. expect(response).to have_http_status(:no_content)
  217. expect(user.reload.viewed?(post_record)).to be(false)
  218. end
  219. end
  220. end