diff --git a/backend/app/controllers/wiki_assets_controller.rb b/backend/app/controllers/wiki_assets_controller.rb index c3861c2..e22f3c2 100644 --- a/backend/app/controllers/wiki_assets_controller.rb +++ b/backend/app/controllers/wiki_assets_controller.rb @@ -7,7 +7,7 @@ class WikiAssetsController < ApplicationController page = WikiPage.find_by(id: page_id) return head :not_found unless page - render json: page.assets + render json: WikiAssetRepr.many(page.assets) end def create @@ -34,6 +34,6 @@ class WikiAssetsController < ApplicationController page.update!(next_asset_no: no + 1) end - render json: asset.as_json(only: [:wiki_page_id, :no], methods: [:url]) + render json: WikiAssetRepr.base(asset) end end diff --git a/backend/app/representations/wiki_asset_repr.rb b/backend/app/representations/wiki_asset_repr.rb new file mode 100644 index 0000000..6a1e90f --- /dev/null +++ b/backend/app/representations/wiki_asset_repr.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + + +module WikiAssetRepr + BASE = { only: [:wiki_page_id, :no], methods: [:url] }.freeze + + module_function + + def base wiki_asset + wiki_asset.as_json(BASE) + end + + def many wiki_assets + wiki_assets.map { |a| base(a) } + end +end diff --git a/backend/spec/requests/wiki_assets_spec.rb b/backend/spec/requests/wiki_assets_spec.rb new file mode 100644 index 0000000..d0afbbf --- /dev/null +++ b/backend/spec/requests/wiki_assets_spec.rb @@ -0,0 +1,191 @@ +require 'digest' +require 'rails_helper' +require 'stringio' + + +RSpec.describe 'WikiAssets API', type: :request do + def dummy_upload(content = 'dummy-image', filename: 'dummy.png', content_type: 'image/png') + Rack::Test::UploadedFile.new(StringIO.new(content), + content_type, + original_filename: filename) + end + + let(:member) { create(:user, :member, name: 'member user') } + let(:guest) { create(:user, name: 'guest user') } + + let!(:tag_name) { TagName.create!(name: 'spec_wiki_asset_page') } + let!(:page) do + WikiPage.create!(tag_name: tag_name, created_user: member, updated_user: member).tap do |p| + Wiki::Commit.content!(page: p, body: 'init', created_user: member, message: 'init') + end + end + + describe 'GET /wiki/:wiki_page_id/assets' do + subject(:do_request) do + get "/wiki/#{wiki_page_id}/assets" + end + + let(:wiki_page_id) { page.id } + + let!(:asset) do + WikiAsset.new(wiki_page: page, + no: 1, + alt_text: 'spec alt', + sha256: Digest::SHA256.digest('asset-1'), + created_by_user: member).tap do |record| + record.file.attach(dummy_upload('asset-1')) + record.save! + end + end + + context 'when wiki page exists' do + it 'returns assets for the page' do + do_request + + expect(response).to have_http_status(:ok) + expect(json).to be_an(Array) + expect(json.size).to eq(1) + + expect(json.first).to include( + 'wiki_page_id' => page.id, + 'no' => 1) + end + + it 'does not include assets from other pages' do + other_tag_name = TagName.create!(name: 'spec_other_wiki_asset_page') + other_page = WikiPage.create!(tag_name: other_tag_name, + created_user: member, + updated_user: member) + Wiki::Commit.content!(page: other_page, body: 'other', created_user: member, message: 'other') + + WikiAsset.new(wiki_page: other_page, + no: 1, + alt_text: 'other alt', + sha256: Digest::SHA256.digest('asset-2'), + created_by_user: member).tap do |record| + record.file.attach(dummy_upload('asset-2', filename: 'other.png')) + record.save! + end + + do_request + + expect(response).to have_http_status(:ok) + expect(json.size).to eq(1) + expect(json.first['wiki_page_id']).to eq(page.id) + end + end + + context 'when wiki page does not exist' do + let(:wiki_page_id) { 999_999_999 } + + it 'returns 404' do + do_request + expect(response).to have_http_status(:not_found) + end + end + end + + describe 'POST /wiki/:wiki_page_id/assets' do + subject(:do_request) do + post "/wiki/#{wiki_page_id}/assets", params: params + end + + let(:wiki_page_id) { page.id } + let(:params) do + { file: dummy_upload(upload_content), + alt_text: 'uploaded alt' } + end + let(:upload_content) { 'uploaded-image-binary' } + + context 'when not logged in' do + it 'returns 401' do + sign_out + do_request + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when logged in but not member' do + it 'returns 403' do + sign_in_as(guest) + do_request + expect(response).to have_http_status(:forbidden) + end + end + + context 'when wiki page does not exist' do + let(:wiki_page_id) { 999_999_999 } + + it 'returns 404' do + sign_in_as(member) + do_request + expect(response).to have_http_status(:not_found) + end + end + + context 'when file is blank' do + let(:params) { { alt_text: 'uploaded alt' } } + + it 'returns 400' do + sign_in_as(member) + do_request + expect(response).to have_http_status(:bad_request) + end + end + + context 'when success' do + before do + sign_in_as(member) + end + + it 'creates asset, attaches file, increments next_asset_no, and returns json' do + expect { do_request } + .to change(WikiAsset, :count).by(1) + + expect(response).to have_http_status(:ok) + + asset = WikiAsset.order(:id).last + expect(asset.wiki_page_id).to eq(page.id) + expect(asset.no).to eq(1) + expect(asset.alt_text).to eq('uploaded alt') + expect(asset.sha256).to eq(Digest::SHA256.digest(upload_content)) + expect(asset.created_by_user_id).to eq(member.id) + expect(asset.file).to be_attached + expect(asset.file.download).to eq(upload_content) + + expect(page.reload.next_asset_no).to eq(2) + + expect(json).to include( + 'wiki_page_id' => page.id, + 'no' => 1, + 'url' => asset.url + ) + end + + it 'uses the next page-local number when assets already exist' do + existing = WikiAsset.new(wiki_page: page, + no: 1, + alt_text: 'existing alt', + sha256: Digest::SHA256.digest('existing'), + created_by_user: member) + existing.file.attach(dummy_upload('existing', filename: 'existing.png')) + existing.save! + page.update!(next_asset_no: 2) + + do_request + + expect(response).to have_http_status(:ok) + + asset = WikiAsset.order(:id).last + expect(asset.no).to eq(2) + expect(page.reload.next_asset_no).to eq(3) + + expect(json).to include( + 'wiki_page_id' => page.id, + 'no' => 2, + 'url' => asset.url + ) + end + end + end +end diff --git a/backend/spec/requests/wiki_spec.rb b/backend/spec/requests/wiki_spec.rb index 4541501..bc92f1c 100644 --- a/backend/spec/requests/wiki_spec.rb +++ b/backend/spec/requests/wiki_spec.rb @@ -97,13 +97,14 @@ RSpec.describe 'Wiki API', type: :request 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) + .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') + expect(json.fetch('body')).to eq("a\nb\nc") page = WikiPage.find(page_id) rev = page.current_revision @@ -111,30 +112,11 @@ RSpec.describe 'Wiki API', type: :request do 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