private
+ # Retrieve a comma-separated list of tags from the environment variable
+ # `ALWAYS_TRENDING_TAGS`, which will always be reported as trending by
+ # {#index}.
+ #
+ # `ALWAYS_TRENDING_TAGS` ought to match something like
+ # `/^(?:[^[:space]]+,)*[^[:space]]+,?$/i`, but we do not (yet?) enforce
+ # this. `ALWAYS_TRENDING_TAGS=Lune,Yuki,Baron,` is parsed as
+ # `['Lune', 'Yuki', 'Baron']`, the same as without the final comma.
+ #
+ # @return [ActiveRecord::Relation] The tags that will always be trending
+ def always_trending
+ # TODO: do we need to sanitize ALWAYS_TRENDING_TAGS?
+ # TODO: should we log when ALWAYS_TRENDING_TAGS includes a tag that does not exist?
+ # TODO: can we get the empty Relation without searching for (what we hope is) an impossible id?
+ ENV['ALWAYS_TRENDING_TAGS'].to_s.split(',')
+ .reduce(Tag.none) { |relation, tag_name| relation.or(Tag.where(name: tag_name)) }
+ end
+
+ # Determine the tags that will be reported as trending, overriding the
+ # `limit` param if {#always_trending} renders it necessary.
+ #
+ # Note that having too many tags always trending will render {#index}
+ # completely deterministic (as per {#BaseController::limit_param})!
+ # TODO: is that desirable? should we log a warning in that case?
def set_tags
- @tags = begin
- if Setting.trends
- Trends.tags.query.allowed.offset(offset_param).limit(limit_param(DEFAULT_TAGS_LIMIT))
- else
- []
- end
- end
+ @tags = if !Setting.trends
+ []
+ else
+ guaranteed_tags = always_trending
+ # TODO: how does the query handle negative limits? is this necessary?
+ limit_considering_guaranteed = [0, limit_param(DEFAULT_TAGS_LIMIT) - guaranteed_tags.size].max
+
+ guaranteed_tags | Trends.tags.query.allowed.offset(offset_param).limit(limit_considering_guaranteed)
+ end
end
def insert_pagination_headers
RSpec.describe Api::V1::Trends::TagsController, type: :controller do
render_views
+ # TODO: return "actually" trending tags that are not the always trending tags
+ # TODO: this is WET
describe 'GET #index' do
- before do
- Fabricate.times(10, :tag).each do |tag|
- 10.times { |i| Trends.tags.add(tag, i) }
- end
+ let(:forced_tags) { Fabricate.times(10, :tag) }
+ it 'returns http success' do
get :index
+ expect(response).to have_http_status(200)
end
- it 'returns http success' do
- expect(response).to have_http_status(200)
+ context 'with always trending tags' do
+ before do
+ forced_tags_relation = forced_tags.reduce(Tag.where(id: -1)) { |relation, forced_tag| relation.or(Tag.where(name: forced_tag.name)) }
+ allow(controller).to receive(:always_trending).and_return(forced_tags_relation)
+ end
+
+ context 'with fewer tags always trending than the limit parameter' do
+ before do
+ get :index, params: { limit: 11 }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'respects the limit parameter' do
+ expect(response).to have_http_status(200)
+ expect(JSON.parse(response.body).size).to be <= 11
+ end
+
+ it 'responds with all always trending tags' do
+ response_tags = JSON.parse(response.body)
+ expect(forced_tags.all? { |forced_tag| response_tags.any? { |response_tag| forced_tag.name == response_tag['name'] } }).to be true
+ end
+ end
+
+ context 'with more tags always trending than the limit parameter' do
+ before do
+ get :index, params: { limit: 2 }
+ end
+
+ it 'overrides the limit parameter' do
+ expect(response).to have_http_status(200)
+ expect(JSON.parse(response.body).size).to eq(forced_tags.size)
+ end
+
+ it 'responds with only always trending tags' do
+ response_tags = JSON.parse(response.body)
+ expect(response_tags.all? { |response_tag| forced_tags.any? { |forced_tag| response_tag['name'] == forced_tag.name } }).to be true
+ end
+ end
end
end
end