Browse Source

#215 テスト・ケース追加

pull/219/head
みてるぞ 1 month ago
parent
commit
43c79d64b0
3 changed files with 467 additions and 10 deletions
  1. +212
    -9
      backend/spec/requests/posts_spec.rb
  2. +90
    -1
      backend/spec/requests/users_spec.rb
  3. +165
    -0
      backend/spec/requests/wiki_spec.rb

+ 212
- 9
backend/spec/requests/posts_spec.rb View File

@@ -1,19 +1,33 @@
require 'rails_helper'
require 'set'


RSpec.describe 'Posts API', type: :request do
describe 'GET /posts' do
it 'returns tags with name in JSON' do
tn = TagName.create!(name: 'spec_tag')
tag = Tag.create!(tag_name: tn, category: 'general')
# 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') }

post = Post.create!(title: 'spec post', url: 'https://example.com/spec')
PostTag.create!(post: post, tag: tag)
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 posts with tag name in JSON' do
get '/posts'

expect(response).to have_http_status(:ok)

expect(json).to have_key('posts')
expect(json['posts']).to be_an(Array)
expect(json['posts']).not_to be_empty
@@ -22,11 +36,200 @@ RSpec.describe 'Posts API', type: :request do
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('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

+ 90
- 1
backend/spec/requests/users_spec.rb View File

@@ -12,10 +12,99 @@ RSpec.describe "Users", type: :request do
end

describe "POST /users/code/renew" do
it "should be 401 when not logged in (recommended behavior)" do
it "returns 401 when not logged in" do
sign_out
post "/users/code/renew"
expect(response).to have_http_status(:unauthorized)
end
end

describe "PUT /users/:id" do
let(:user) { create(:user, name: "old-name", role: "guest") }

it "returns 401 when current_user id mismatch" do
sign_in_as(create(:user))
put "/users/#{user.id}", params: { name: "new-name" }
expect(response).to have_http_status(:unauthorized)
end

it "returns 400 when name is blank" do
sign_in_as(user)
put "/users/#{user.id}", params: { name: " " }
expect(response).to have_http_status(:bad_request)
end

it "updates name and returns 201 with user slice" do
sign_in_as(user)
put "/users/#{user.id}", params: { name: "new-name" }

expect(response).to have_http_status(:created)
expect(json["id"]).to eq(user.id)
expect(json["name"]).to eq("new-name")

user.reload
expect(user.name).to eq("new-name")
end
end

describe "POST /users/verify" do
it "returns valid:false when code not found" do
post "/users/verify", params: { code: "nope" }
expect(response).to have_http_status(:ok)
expect(json["valid"]).to eq(false)
end

it "creates IpAddress and UserIp, and returns valid:true with user slice" do
user = create(:user, inheritance_code: SecureRandom.uuid, role: "guest")

# request.remote_ip を固定
allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return("203.0.113.10")

expect {
post "/users/verify", params: { code: user.inheritance_code }
}.to change(UserIp, :count).by(1)

expect(response).to have_http_status(:ok)
expect(json["valid"]).to eq(true)
expect(json["user"]["id"]).to eq(user.id)
expect(json["user"]["inheritance_code"]).to eq(user.inheritance_code)
expect(json["user"]["role"]).to eq("guest")

# ついでに IpAddress もできてることを確認(ipの保存形式がバイナリでも count で見れる)
expect(IpAddress.count).to be >= 1
end

it "is idempotent for same user+ip (does not create duplicate UserIp)" do
user = create(:user, inheritance_code: SecureRandom.uuid, role: "guest")
allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return("203.0.113.10")

post "/users/verify", params: { code: user.inheritance_code }
expect(response).to have_http_status(:ok)

expect {
post "/users/verify", params: { code: user.inheritance_code }
}.not_to change(UserIp, :count)

expect(response).to have_http_status(:ok)
expect(json["valid"]).to eq(true)
end
end

describe "GET /users/me" do
it "returns 404 when code not found" do
get "/users/me", params: { code: "nope" }
expect(response).to have_http_status(:not_found)
end

it "returns user slice when found" do
user = create(:user, inheritance_code: SecureRandom.uuid, name: "me", role: "guest")
get "/users/me", params: { code: user.inheritance_code }

expect(response).to have_http_status(:ok)
expect(json["id"]).to eq(user.id)
expect(json["name"]).to eq("me")
expect(json["inheritance_code"]).to eq(user.inheritance_code)
expect(json["role"]).to eq("guest")
end
end
end

+ 165
- 0
backend/spec/requests/wiki_spec.rb View File

@@ -256,4 +256,169 @@ RSpec.describe 'Wiki API', type: :request do
expect(response).to have_http_status(:not_found)
end
end

describe 'GET /wiki/search' do
before do
# 追加で検索ヒット用
TagName.create!(name: 'spec_wiki_title_2')
WikiPage.create!(tag_name: TagName.find_by!(name: 'spec_wiki_title_2'),
created_user: user, updated_user: user)

TagName.create!(name: 'unrelated_title')
WikiPage.create!(tag_name: TagName.find_by!(name: 'unrelated_title'),
created_user: user, updated_user: user)
end

it 'returns up to 20 pages filtered by title like' do
get "/wiki/search?title=#{CGI.escape('spec_wiki')}"

expect(response).to have_http_status(:ok)
expect(json).to be_an(Array)

titles = json.map { |p| p['title'] }
expect(titles).to include('spec_wiki_title', 'spec_wiki_title_2')
expect(titles).not_to include('unrelated_title')
end

it 'returns all when title param is blank' do
get "/wiki/search?title=#{CGI.escape('')}"

expect(response).to have_http_status(:ok)
expect(json).to be_an(Array)
expect(json.map { |p| p['title'] }).to include('spec_wiki_title')
end
end

describe 'GET /wiki/changes' do
let!(:rev1) do
Wiki::Commit.content!(page: page, body: "a\nb", created_user: user, message: 'r1')
page.current_revision
end

let!(:rev2) do
Wiki::Commit.content!(page: page, body: "a\nc", created_user: user, message: 'r2')
page.current_revision
end

it 'returns latest revisions (optionally filtered by page id)' do
get "/wiki/changes?id=#{page.id}"

expect(response).to have_http_status(:ok)
expect(json).to be_an(Array)
expect(json).not_to be_empty

top = json.first
expect(top).to include(
'revision_id' => rev2.id,
'pred' => rev2.base_revision_id,
'succ' => nil,
'kind' => 'content',
'message' => 'r2'
)
expect(top['wiki_page']).to include('id' => page.id, 'title' => 'spec_wiki_title')
expect(top['user']).to include('id' => user.id, 'name' => user.name)
expect(top).to have_key('timestamp')

# order desc をざっくり担保
ids = json.map { |x| x['revision_id'] }
expect(ids).to eq(ids.sort.reverse)
end

it 'returns empty array when page has no revisions and filtered by id' do
# 別ページを作って revision 無し
tn2 = TagName.create!(name: 'spec_no_rev')
p2 = WikiPage.create!(tag_name: tn2, created_user: user, updated_user: user)

get "/wiki/changes?id=#{p2.id}"
expect(response).to have_http_status(:ok)
expect(json).to eq([])
end
end

describe 'GET /wiki/title/:title/exists' do
it 'returns 204 when exists' do
get "/wiki/title/#{CGI.escape('spec_wiki_title')}/exists"
expect(response).to have_http_status(:no_content)
expect(response.body).to be_empty
end

it 'returns 404 when not exists' do
get "/wiki/title/#{CGI.escape('nope')}/exists"
expect(response).to have_http_status(:not_found)
end
end

describe 'GET /wiki/:id/exists' do
it 'returns 204 when exists' do
get "/wiki/#{page.id}/exists"
expect(response).to have_http_status(:no_content)
expect(response.body).to be_empty
end

it 'returns 404 when not exists' do
get "/wiki/99999999/exists"
expect(response).to have_http_status(:not_found)
end
end

describe 'GET /wiki/:id/diff' do
let!(:rev_a) do
Wiki::Commit.content!(page: page, body: "a\nb\nc", created_user: user, message: 'A')
page.current_revision
end

let!(:rev_b) do
Wiki::Commit.content!(page: page, body: "a\nx\nc", created_user: user, message: 'B')
page.current_revision
end

it 'returns diff json between revisions' do
get "/wiki/#{page.id}/diff?from=#{rev_a.id}&to=#{rev_b.id}"

expect(response).to have_http_status(:ok)

expect(json).to include(
'wiki_page_id' => page.id,
'title' => 'spec_wiki_title',
'older_revision_id' => rev_a.id,
'newer_revision_id' => rev_b.id
)
expect(json['diff']).to be_an(Array)
# ざっくり「b が消えて x が増えた」が含まれることを確認
types = json['diff'].map { |x| x['type'] }
expect(types).to include('removed', 'added').or include('removed').and include('added')
end

it 'uses latest as "to" when to is omitted' do
get "/wiki/#{page.id}/diff?from=#{rev_a.id}"

expect(response).to have_http_status(:ok)
expect(json['older_revision_id']).to eq(rev_a.id)
expect(json['newer_revision_id']).to eq(page.current_revision.id)
end

it 'returns 422 when "to" is redirect revision' do
# redirect revision を作る
tn2 = TagName.create!(name: 'redirect_target')
target = WikiPage.create!(tag_name: tn2, created_user: user, updated_user: user)

Wiki::Commit.redirect!(page: page, redirect_page: target, created_user: user, message: 'redir')
redirect_rev = page.current_revision
expect(redirect_rev).to be_redirect

get "/wiki/#{page.id}/diff?from=#{rev_a.id}&to=#{redirect_rev.id}"
expect(response).to have_http_status(:unprocessable_entity)
end

it 'returns 422 when "from" is redirect revision' do
tn2 = TagName.create!(name: 'redirect_target2')
target = WikiPage.create!(tag_name: tn2, created_user: user, updated_user: user)

Wiki::Commit.redirect!(page: page, redirect_page: target, created_user: user, message: 'redir2')
redirect_rev = page.current_revision

get "/wiki/#{page.id}/diff?from=#{redirect_rev.id}&to=#{rev_b.id}"
expect(response).to have_http_status(:unprocessable_entity)
end
end
end

Loading…
Cancel
Save