diff --git a/backend/spec/requests/posts_spec.rb b/backend/spec/requests/posts_spec.rb index 4366a64..a65a935 100644 --- a/backend/spec/requests/posts_spec.rb +++ b/backend/spec/requests/posts_spec.rb @@ -15,11 +15,11 @@ RSpec.describe 'Posts API', type: :request do expect(response).to have_http_status(:ok) 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 expect(tags[0]).to have_key('name') diff --git a/backend/spec/requests/tags_spec.rb b/backend/spec/requests/tags_spec.rb index 255a300..7e26538 100644 --- a/backend/spec/requests/tags_spec.rb +++ b/backend/spec/requests/tags_spec.rb @@ -11,23 +11,50 @@ RSpec.describe 'Tags API', type: :request do get '/tags' expect(response).to have_http_status(:ok) - json = JSON.parse(response.body) - expect(json).to be_a(Array) + expect(json).to be_an(Array) expect(json).not_to be_empty expect(json[0]).to have_key('name') expect(json.map { |t| t['name'] }).to include('spec_tag') end end + describe 'GET /tags/:id' do + subject(:request) do + get "/tags/#{ tag_id }" + end + + let(:tag_id) { tag.id } + + context 'when tag exists' do + it 'returns tag with name' do + request + expect(response).to have_http_status(:ok) + + expect(json).to include( + 'id' => tag.id, + 'name' => 'spec_tag', + 'category' => 'general') + end + end + + context 'when tag does not exist' do + let(:tag_id) { 9_999_999 } + + it 'returns 404' do + request + expect(response).to have_http_status(:not_found) + end + end + end + describe 'GET /tags/autocomplete' do it 'returns matching tags by q' do get '/tags/autocomplete', params: { q: 'spec' } expect(response).to have_http_status(:ok) - json = JSON.parse(response.body) - expect(json).to be_a(Array) + expect(json).to be_an(Array) expect(json.map { |t| t['name'] }).to include('spec_tag') end end @@ -37,7 +64,6 @@ RSpec.describe 'Tags API', type: :request do get "/tags/name/#{ CGI.escape('spec_tag') }" expect(response).to have_http_status(:ok) - json = JSON.parse(response.body) expect(json).to have_key('id') expect(json).to have_key('name') diff --git a/backend/spec/requests/wiki_spec.rb b/backend/spec/requests/wiki_spec.rb index 3f2b981..cf3dc07 100644 --- a/backend/spec/requests/wiki_spec.rb +++ b/backend/spec/requests/wiki_spec.rb @@ -16,9 +16,8 @@ RSpec.describe 'Wiki API', type: :request do get '/wiki' expect(response).to have_http_status(:ok) - json = JSON.parse(response.body) - expect(json).to be_a(Array) + expect(json).to be_an(Array) expect(json).not_to be_empty expect(json[0]).to have_key('title') @@ -26,12 +25,225 @@ RSpec.describe 'Wiki API', type: :request do end end + describe 'GET /wiki/:id' do + subject(:request) do + get "/wiki/#{ page_id }" + end + + let(:page_id) { page.id } + + context 'when wiki page exists' do + it 'returns wiki page with title' do + request + expect(response).to have_http_status(:ok) + + expect(json).to include( + 'id' => page.id, + 'title' => 'spec_wiki_title') + end + end + + context 'when wiki page does not exist' do + let(:page_id) { 9_999_999 } + + it 'returns 404' do + request + expect(response).to have_http_status(:not_found) + end + end + end + + describe 'POST /wiki' do + let(:endpoint) { '/wiki' } + + let(:member) { create(:user, role: 'member') } + let(:guest) { create(:user, role: 'guest') } + + def auth_headers(user) + { 'X-Transfer-Code' => user.inheritance_code } + end + + context 'when not logged in' do + it 'returns 401' do + post endpoint, params: { title: 'Test', body: 'Hello' } + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when logged in but not member' do + it 'returns 403' do + post endpoint, params: { title: 'Test', body: 'Hello' }, headers: auth_headers(guest) + expect(response).to have_http_status(:forbidden) + end + end + + context 'when params invalid' do + it 'returns 422 when title blank' do + post endpoint, params: { title: '', body: 'Hello' }, headers: auth_headers(member) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'returns 422 when body blank' do + post endpoint, params: { title: 'Test', body: '' }, headers: auth_headers(member) + expect(response).to have_http_status(:unprocessable_entity) + end + end + + context 'when success' do + it 'creates wiki_page and first content revision' do + expect do + post endpoint, params: { title: 'TestPage', body: "a\nb\nc", message: 'init' }, + headers: auth_headers(member) + end + .to change(WikiPage, :count).by(1) + .and change(WikiRevision, :count).by(1) + + expect(response).to have_http_status(:created) + + page_id = json.fetch('id') + expect(json.fetch('title')).to eq('TestPage') + + page = WikiPage.find(page_id) + rev = page.current_revision + expect(rev).to be_present + expect(rev).to be_content + expect(rev.message).to eq('init') + + # body が復元できること + expect(page.body).to eq("a\nb\nc") + + # 行数とリレーションの整合 + expect(rev.lines_count).to eq(3) + expect(rev.wiki_revision_lines.order(:position).pluck(:position)).to eq([0, 1, 2]) + expect(rev.wiki_lines.pluck(:body)).to match_array(%w[a b c]) + end + + it 'reuses existing WikiLine rows by sha256' do + # 先に同じ行を作っておく + WikiLine.create!(sha256: Digest::SHA256.hexdigest('a'), body: 'a', created_at: Time.current, updated_at: Time.current) + + post endpoint, + params: { title: 'Reuse', body: "a\na" }, + headers: auth_headers(member) + + page = WikiPage.find(JSON.parse(response.body).fetch('id')) + rev = page.current_revision + expect(rev.lines_count).to eq(2) + + # "a" の WikiLine が増殖しない(1行のはず) + expect(WikiLine.where(body: 'a').count).to eq(1) + end + end + end + + describe 'PUT /wiki/:id' do + let(:member) { create(:user, role: 'member', inheritance_code: SecureRandom.hex(16)) } + let(:guest) { create(:user, role: 'guest', inheritance_code: SecureRandom.hex(16)) } + + def auth_headers(user) + { 'X-Transfer-Code' => user.inheritance_code } + end + + #let!(:page) { create(:wiki_page, title: 'TestPage') } + let!(:page) do + build(:wiki_page, title: 'TestPage').tap do |p| + puts p.errors.full_messages unless p.valid? + p.save! + end + end + + before do + # 初期版を 1 つ作っておく(更新が“2版目”になるように) + Wiki::Commit.content!(page: page, body: "a\nb", created_user: member, message: 'init') + end + + context 'when not logged in' do + it 'returns 401' do + put "/wiki/#{page.id}", params: { title: 'TestPage', body: 'x' } + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when logged in but not member' do + it 'returns 403' do + put "/wiki/#{page.id}", + params: { title: 'TestPage', body: 'x' }, + headers: auth_headers(guest) + expect(response).to have_http_status(:forbidden) + end + end + + context 'when params invalid' do + it 'returns 422 when body blank' do + put "/wiki/#{page.id}", + params: { title: 'TestPage', body: '' }, + headers: auth_headers(member) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'returns 422 when title mismatched (if you forbid rename here)' do + put "/wiki/#{page.id}", + params: { title: 'OtherTitle', body: 'x' }, + headers: auth_headers(member) + # 君の controller 例だと title 変更は 422 にしてた + expect(response).to have_http_status(:unprocessable_entity) + end + end + + context 'when success' do + it 'creates new revision and returns 200' do + current_id = page.wiki_revisions.maximum(:id) + + expect do + put "/wiki/#{page.id}", + params: { title: 'TestPage', body: "x\ny", message: 'edit', base_revision_id: current_id }, + headers: auth_headers(member) + end.to change(WikiRevision, :count).by(1) + + expect(response).to have_http_status(:ok) + + page.reload + rev = page.current_revision + expect(rev).to be_content + expect(rev.message).to eq('edit') + expect(page.body).to eq("x\ny") + expect(rev.base_revision_id).to eq(current_id) + end + end + + # TODO: コンフリクト未実装のため,実装したらコメント外す. + # context 'when conflict' do + # it 'returns 409 when base_revision_id mismatches' do + # # 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める + # Wiki::Commit.content!(page: page, body: "zzz", created_user: member, message: 'other edit') + # page.reload + + # stale_id = page.wiki_revisions.order(:id).first.id # わざと古い id + # put "/wiki/#{page.id}", + # params: { title: 'TestPage', body: 'x', base_revision_id: stale_id }, + # headers: auth_headers(member) + + # expect(response).to have_http_status(:conflict) + # json = JSON.parse(response.body) + # expect(json['error']).to eq('conflict') + # end + # end + + context 'when page not found' do + it 'returns 404' do + put "/wiki/99999999", + params: { title: 'X', body: 'x' }, + headers: auth_headers(member) + expect(response).to have_http_status(:not_found) + end + end + end + describe 'GET /wiki/title/:title' do it 'returns wiki page by title' do get "/wiki/title/#{CGI.escape('spec_wiki_title')}" expect(response).to have_http_status(:ok) - json = JSON.parse(response.body) expect(json).to have_key('id') expect(json).to have_key('title')