97 行
4.3 KiB
Ruby
97 行
4.3 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe 'Counts API', type: :request do
|
|
let!(:actor) { create(:user, :member) }
|
|
let!(:post) { create(:post) }
|
|
let!(:tag) { create(:tag, primary_name: 'counts tag', category: :general) }
|
|
|
|
before do
|
|
snapshot = { tag_id: tag.id, version_no: 1, name: 'counts tag',
|
|
category: 'general', sections: [] }
|
|
[1, 2, 3].each do |number|
|
|
time = Time.utc(2024, number == 3 ? 2 : 1, number, 0, 0)
|
|
PostVersion.create!(post:, version_no: number,
|
|
event_type: number == 1 ? 'create' : 'update',
|
|
tags: '', url: post.url, title: "Snapshot #{ number }", parent_post_ids: '',
|
|
tags_json: number == 1 ? [snapshot] : [],
|
|
created_by_user: number == 3 ? nil : actor, created_at: time)
|
|
TagVersion.create!(tag:, version_no: number,
|
|
event_type: number == 1 ? 'create' : 'update',
|
|
name: 'counts tag', category: 'general', aliases: '', parent_tag_ids: '',
|
|
names: { ja: { name: 'counts tag', script_code: 'Jpan',
|
|
auto_generated: false, aliases: [] } },
|
|
created_by_user: number == 3 ? nil : actor, created_at: time)
|
|
end
|
|
end
|
|
|
|
['posts', 'tags'].each do |resource|
|
|
context resource do
|
|
let(:path) { "/counts/#{ resource }" }
|
|
let(:id) { resource == 'posts' ? post.id : tag.id }
|
|
let(:filters) { { resource_id: id, user_id: actor.id,
|
|
from: '2024-01-01T00:00Z', before: '2024-02-01T00:00Z' } }
|
|
|
|
it 'uses the same AND filters for SQL totals, groups and details' do
|
|
get path, params: filters.merge(axis: 'event_type')
|
|
expect(response).to have_http_status(:ok)
|
|
summary = response.parsed_body
|
|
expect(summary['count']).to eq(2)
|
|
expect(summary['groups'].sum { _1['count'] }).to eq(2)
|
|
expect(summary['groups'].map { _1['key'] }).to eq(['create', 'update'])
|
|
|
|
get path, params: filters.merge(mode: 'details')
|
|
expect(response.parsed_body['count']).to eq(2)
|
|
expect(response.parsed_body['versions'].map { _1['version_no'] }).to eq([2, 1])
|
|
end
|
|
|
|
it 'paginates details and groups independently from totals' do
|
|
get path, params: { resource_id: id, mode: 'details', limit: 1, page: 2 }
|
|
expect(response.parsed_body['count']).to eq(3)
|
|
expect(response.parsed_body['versions'].sole['version_no']).to eq(2)
|
|
get path, params: { resource_id: id, axis: 'month', limit: 1, page: 2 }
|
|
expect(response.parsed_body['group_count']).to eq(2)
|
|
expect(response.parsed_body['groups'].sole).to include('key' => '2024-02', 'count' => 1)
|
|
end
|
|
|
|
it 'groups null actors and supports their drill-down filter' do
|
|
get path, params: { resource_id: id, axis: 'user' }
|
|
expect(response.parsed_body['groups']).to include(
|
|
{ 'key' => nil, 'name' => nil, 'count' => 1 })
|
|
get path, params: { resource_id: id, mode: 'details', user_id: 'none' }
|
|
expect(response.parsed_body['count']).to eq(1)
|
|
end
|
|
|
|
it 'uses an inclusive start and exclusive end' do
|
|
get path, params: { resource_id: id,
|
|
from: '2024-01-01T00:00Z', before: '2024-01-02T00:00Z' }
|
|
expect(response.parsed_body['count']).to eq(1)
|
|
end
|
|
|
|
it 'rejects unsupported or malformed query values' do
|
|
[
|
|
{ axis: 'SQL' }, { limit: 101 }, { page: '1x' }, { user_id: '-1' },
|
|
{ event_type: 'invalid' }, { columns: 'password' }, { order: 'id' },
|
|
{ from: '2024-02-30T00:00Z' }, { from: '2024-01-01' },
|
|
{ user_id: ['1'] }, { mode: 'invalid' }].each do |invalid|
|
|
get path, params: invalid
|
|
expect(response).to have_http_status(:bad_request)
|
|
end
|
|
end
|
|
|
|
it 'retains the existing banned-user boundary' do
|
|
actor.update!(banned_at: Time.current)
|
|
sign_in_as(actor)
|
|
get path
|
|
expect(response).to have_http_status(:forbidden)
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'matches removed tags from the previous snapshot, not current post_tags' do
|
|
get '/counts/posts', params: { tag: tag.id, mode: 'details' }
|
|
expect(response.parsed_body['count']).to eq(2)
|
|
expect(response.parsed_body['versions'].map { _1['version_no'] }).to eq([2, 1])
|
|
expect(response.parsed_body['versions'].first['tags'].sole['type']).to eq('removed')
|
|
end
|
|
end
|