From ee679a64361605d8475548128728d8323194ec24 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sat, 11 Apr 2026 17:01:53 +0900 Subject: [PATCH] #264 --- backend/spec/models/post_version_spec.rb | 41 ++++++++ backend/spec/requests/posts_spec.rb | 123 +++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 backend/spec/models/post_version_spec.rb diff --git a/backend/spec/models/post_version_spec.rb b/backend/spec/models/post_version_spec.rb new file mode 100644 index 0000000..d35ab4c --- /dev/null +++ b/backend/spec/models/post_version_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe PostVersion, type: :model do + let!(:tag_name) { TagName.create!(name: 'post_version_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/post-version-spec').tap do |post| + PostTag.create!(post: post, tag: tag) + end + end + + let!(:post_version) do + PostVersion.create!( + post: post_record, + version_no: 1, + event_type: 'create', + title: post_record.title, + url: post_record.url, + thumbnail_base: post_record.thumbnail_base, + tags: post_record.snapshot_tag_names.join(' '), + parent: post_record.parent, + original_created_from: post_record.original_created_from, + original_created_before: post_record.original_created_before, + created_at: Time.current, + created_by_user: nil + ) + end + + it 'is read only after create' do + expect do + post_version.update!(title: 'changed') + end.to raise_error(ActiveRecord::ReadOnlyRecord) + end + + it 'cannot be destroyed' do + expect do + post_version.destroy! + end.to raise_error(ActiveRecord::ReadOnlyRecord) + end +end diff --git a/backend/spec/requests/posts_spec.rb b/backend/spec/requests/posts_spec.rb index 120a221..1295165 100644 --- a/backend/spec/requests/posts_spec.rb +++ b/backend/spec/requests/posts_spec.rb @@ -795,4 +795,127 @@ RSpec.describe 'Posts API', type: :request do expect(user.reload.viewed?(post_record)).to be(false) end end + + describe 'post versioning' do + let(:member) { create(:user, :member) } + + def snapshot_tags(post) + post.snapshot_tag_names.join(' ') + end + + def create_post_version_for!(post) + PostVersion.create!( + post: post, + version_no: 1, + event_type: 'create', + title: post.title, + url: post.url, + thumbnail_base: post.thumbnail_base, + tags: snapshot_tags(post), + parent: post.parent, + original_created_from: post.original_created_from, + original_created_before: post.original_created_before, + created_at: post.created_at, + created_by_user: post.uploaded_user + ) + end + + it 'creates version 1 on POST /posts' do + sign_in_as(member) + + expect do + post '/posts', params: { + title: 'versioned post', + url: 'https://example.com/versioned-post', + tags: 'spec_tag', + thumbnail: dummy_upload + } + end.to change(PostVersion, :count).by(1) + + expect(response).to have_http_status(:created) + + created_post = Post.find(json.fetch('id')) + version = PostVersion.find_by!(post: created_post, version_no: 1) + + expect(version.event_type).to eq('create') + expect(version.title).to eq('versioned post') + expect(version.url).to eq('https://example.com/versioned-post') + expect(version.created_by_user_id).to eq(member.id) + expect(version.tags).to eq(snapshot_tags(created_post)) + end + + it 'creates next version on PUT /posts/:id when snapshot changes' do + sign_in_as(member) + create_post_version_for!(post_record) + + tag_name2 = TagName.create!(name: 'spec_tag_2') + Tag.create!(tag_name: tag_name2, category: :general) + + expect do + put "/posts/#{post_record.id}", params: { + title: 'updated title', + tags: 'spec_tag_2' + } + end.to change(PostVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + version = post_record.reload.post_versions.order(:version_no).last + expect(version.version_no).to eq(2) + expect(version.event_type).to eq('update') + expect(version.title).to eq('updated title') + expect(version.created_by_user_id).to eq(member.id) + expect(version.tags).to eq(snapshot_tags(post_record.reload)) + end + + it 'does not create a new version on PUT /posts/:id when snapshot is unchanged' do + sign_in_as(member) + create_post_version_for!(post_record) + + expect do + put "/posts/#{post_record.id}", params: { + title: post_record.title, + tags: 'spec_tag' + } + end.not_to change(PostVersion, :count) + + expect(response).to have_http_status(:ok) + + version = post_record.reload.post_versions.order(:version_no).last + expect(version.version_no).to eq(1) + expect(version.event_type).to eq('create') + expect(version.tags).to eq(snapshot_tags(post_record)) + end + + it 'does not create a version when POST /posts is invalid' do + sign_in_as(member) + + expect do + post '/posts', params: { + title: 'invalid post', + url: 'ぼざクリタグ広場', + tags: 'spec_tag', + thumbnail: dummy_upload + } + end.not_to change(PostVersion, :count) + + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'does not create a version when PUT /posts/:id is invalid' do + sign_in_as(member) + create_post_version_for!(post_record) + + expect do + put "/posts/#{post_record.id}", params: { + title: 'updated title', + tags: 'spec_tag', + original_created_from: Time.zone.local(2020, 1, 2, 0, 0, 0).iso8601, + original_created_before: Time.zone.local(2020, 1, 1, 0, 0, 0).iso8601 + } + end.not_to change(PostVersion, :count) + + expect(response).to have_http_status(:unprocessable_entity) + end + end end