Reviewed-on: #449 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #449 でマージされました。
このコミットが含まれているのは:
@@ -0,0 +1,129 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Post history privacy', type: :request do
|
||||
let(:origin) { Time.utc(2020, 1, 1) }
|
||||
let!(:post_record) do
|
||||
create(:post, title: 'current', created_at: origin, updated_at: origin)
|
||||
end
|
||||
let!(:other_post) do
|
||||
create(:post, created_at: origin, updated_at: origin + 3.days + 1.hour)
|
||||
end
|
||||
let!(:tag) { create(:tag, primary_name: 'private-history-tag') }
|
||||
let!(:external) { create(:external_tag, name: 'external-history-tag') }
|
||||
|
||||
before do
|
||||
['create', 'discard'].each_with_index do |event, index|
|
||||
TagVersion.create!(tag:, version_no: index + 1, event_type: event,
|
||||
name: 'private-history-tag', category: 'general', aliases: '', parent_tag_ids: '',
|
||||
names: { ja: { name: 'private-history-tag', script_code: 'Jpan',
|
||||
auto_generated: false, aliases: [] } },
|
||||
created_at: origin + index.hours)
|
||||
end
|
||||
NicoTagVersion.create!(external_tag: external, version_no: 1, event_type: 'create',
|
||||
name: 'nico:external-history-tag', linked_tags: 'private-history-tag',
|
||||
created_at: origin)
|
||||
['create', 'discard', 'restore', 'update'].each_with_index do |event, index|
|
||||
snapshots =
|
||||
if index < 2
|
||||
[{ tag_id: tag.id, version_no: 1, name: 'private-history-tag', sections: [] }]
|
||||
elsif index == 3
|
||||
[{ external_tag_id: external.id }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
PostVersion.create!(post: post_record, version_no: index + 1, event_type: event,
|
||||
title: "version #{ index + 1 }", url: post_record.url, tags: '',
|
||||
tags_json: snapshots, parent_post_ids: '', created_at: origin + (index + 1).days)
|
||||
end
|
||||
end
|
||||
|
||||
it 'evaluates each reference condition against the restored snapshot alone' do
|
||||
HistoryAccess.set(user: nil) do
|
||||
restored = PostVersion.where(post_id: post_record.id, version_no: 3)
|
||||
HistoryAccess.reference_sql('post_versions', 'post_versions').each do |condition|
|
||||
expect(restored.where(Arel.sql(condition)).pluck(:version_no)).to eq([3])
|
||||
end
|
||||
expect(HistoryAccess.visible(PostVersion.where(post_id: post_record.id))
|
||||
.pluck(:version_no)).to eq([3])
|
||||
end
|
||||
end
|
||||
|
||||
it 'keeps every version through a later discard private after another restore' do
|
||||
['discard', 'restore'].each_with_index do |event, index|
|
||||
PostVersion.create!(post: post_record, version_no: index + 5, event_type: event,
|
||||
title: 'later snapshot', url: post_record.url, tags: '', tags_json: [],
|
||||
parent_post_ids: '', created_at: origin + (index + 5).days)
|
||||
end
|
||||
|
||||
get '/posts/versions', params: { post: post_record.id }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.parsed_body.fetch('versions').pluck('version_no')).to eq([6])
|
||||
expect(response.parsed_body.fetch('count')).to eq(1)
|
||||
expect(response.parsed_body.fetch('versions').first.fetch('title').fetch('prev')).to be_nil
|
||||
end
|
||||
|
||||
['anonymous', 'member', 'admin'].each do |role|
|
||||
context "as #{ role }" do
|
||||
before do
|
||||
sign_in_as(create(:user, role.to_sym)) unless role == 'anonymous'
|
||||
end
|
||||
|
||||
it 'uses only authorised history timestamps for display' do
|
||||
get '/posts'
|
||||
expect(response).to have_http_status(:ok)
|
||||
row = response.parsed_body.fetch('posts').find { _1.fetch('id') == post_record.id }
|
||||
expected = origin + (role == 'admin' ? 4 : 3).days
|
||||
expect(Time.zone.parse(row.fetch('updated_at'))).to eq(expected)
|
||||
end
|
||||
|
||||
it 'uses the same visibility for date bounds and count' do
|
||||
get '/posts', params: { updated_from: (origin + 4.days).iso8601,
|
||||
updated_to: (origin + 4.days).iso8601 }
|
||||
expect(response).to have_http_status(:ok)
|
||||
ids = response.parsed_body.fetch('posts').pluck('id')
|
||||
expect(ids).to eq(role == 'admin' ? [post_record.id] : [])
|
||||
expect(response.parsed_body.fetch('count')).to eq(ids.length)
|
||||
end
|
||||
|
||||
it 'sorts using authorised timestamps in both directions' do
|
||||
['asc', 'desc'].each do |direction|
|
||||
get '/posts', params: { order: "updated_at:#{ direction }" }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expected = role == 'admin' ? [other_post.id, post_record.id] :
|
||||
[post_record.id, other_post.id]
|
||||
expected.reverse! if direction == 'desc'
|
||||
expect(response.parsed_body.fetch('posts').pluck('id')).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
it 'applies authorised timestamp ordering before pagination' do
|
||||
get '/posts', params: { order: 'updated_at:desc', limit: 1 }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expected = role == 'admin' ? post_record.id : other_post.id
|
||||
expect(response.parsed_body.fetch('posts').pluck('id')).to eq([expected])
|
||||
expect(response.parsed_body.fetch('count')).to eq(2)
|
||||
end
|
||||
|
||||
it 'counts only visible versions and masks a private previous version' do
|
||||
get '/posts/versions', params: { post: post_record.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = response.parsed_body
|
||||
expected = role == 'admin' ? [4, 3, 2, 1] : [3]
|
||||
expect(body.fetch('count')).to eq(expected.length)
|
||||
expect(body.fetch('versions').pluck('version_no')).to eq(expected)
|
||||
restored = body.fetch('versions').find { _1.fetch('version_no') == 3 }
|
||||
expect(restored.fetch('title').fetch('prev')).to eq(role == 'admin' ? 'version 2' : nil)
|
||||
end
|
||||
|
||||
it 'uses only an authorised previous version for tag filtering' do
|
||||
get '/posts/versions', params: { post: post_record.id, tag: tag.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expected = role == 'admin' ? [3, 2, 1] : []
|
||||
expect(response.parsed_body.fetch('versions').pluck('version_no')).to eq(expected)
|
||||
expect(response.parsed_body.fetch('count')).to eq(expected.length)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -629,6 +629,37 @@ RSpec.describe 'Posts API', type: :request do
|
||||
create(:post_tag, post: plain_post, tag:, created_at: t3)
|
||||
end
|
||||
|
||||
it 'displays history time after pagination without joining history to posts or count' do
|
||||
queries = []
|
||||
subscriber = ->(event) { queries << event.payload.fetch(:sql) }
|
||||
ActiveSupport::Notifications.subscribed(subscriber, 'sql.active_record') do
|
||||
get '/posts', params: { url: '/version-time/', order: 'url:asc', limit: 1 }
|
||||
end
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('posts').pluck('id')).to eq([history_post.id])
|
||||
expect(Time.zone.parse(json.fetch('posts').first.fetch('updated_at'))).to eq(t2)
|
||||
expect(json.fetch('count')).to eq(3)
|
||||
post_queries = queries.select { _1.include?('FROM `posts`') }
|
||||
expect(post_queries).not_to be_empty
|
||||
expect(post_queries.grep(/post_versions/)).to be_empty
|
||||
end
|
||||
|
||||
it 'omits the history aggregate from count when only sorting by update time' do
|
||||
queries = []
|
||||
subscriber = ->(event) { queries << event.payload.fetch(:sql) }
|
||||
ActiveSupport::Notifications.subscribed(subscriber, 'sql.active_record') do
|
||||
get '/posts', params: { url: '/version-time/', order: 'updated_at:desc', limit: 1 }
|
||||
end
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('posts').pluck('id')).to eq([newer_post.id])
|
||||
expect(json.fetch('count')).to eq(3)
|
||||
count_queries = queries.select { _1.include?('FROM `posts`') && _1.match?(/COUNT\(/i) }
|
||||
expect(count_queries).not_to be_empty
|
||||
expect(count_queries.grep(/post_versions/)).to be_empty
|
||||
end
|
||||
|
||||
['asc', 'desc'].each do |direction|
|
||||
it "sorts by the later of post update and latest version time (#{ direction })" do
|
||||
get '/posts', params: { url: '/version-time/', order: "updated_at:#{ direction }" }
|
||||
@@ -658,6 +689,16 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(json.fetch('posts').map { |item| item.fetch('id') }).to eq([history_post.id])
|
||||
expect(json.fetch('count')).to eq(1)
|
||||
end
|
||||
|
||||
it 'counts every timestamp match before pagination' do
|
||||
get '/posts', params: { url: '/version-time/', order: 'updated_at:asc',
|
||||
updated_from: t1.iso8601, updated_to: t3.iso8601,
|
||||
limit: 1, page: 2 }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('posts').pluck('id')).to eq([history_post.id])
|
||||
expect(json.fetch('count')).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when original_created_from/original_created_to are provided' do
|
||||
|
||||
@@ -221,9 +221,15 @@ RSpec.describe CountsExportDownload::ResponseBody do
|
||||
body = described_class.new(source) { |state| states << state; true }
|
||||
response = ActionDispatch::Response.new
|
||||
response.body = body
|
||||
deferred = response.stream.to_ary
|
||||
|
||||
_, _, rack_body = response.to_a
|
||||
|
||||
expect(states).to eq([])
|
||||
expect(deferred.to_a.join).to eq(payload)
|
||||
|
||||
chunks = []
|
||||
rack_body.each { |chunk| chunks << chunk }
|
||||
|
||||
expect(chunks.join).to eq(payload)
|
||||
expect(states.count('started')).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする