# frozen_string_literal: true RSpec.describe Report do let(:user) { Fabricate(:user) } let(:category_1) { Fabricate(:category, user: user) } let(:category_2) { Fabricate(:category, parent_category: category_1, user: user) } # id: 2 let(:category_3) { Fabricate(:category, user: user) } shared_examples "no data" do context "with no data" do it "returns an empty report" do expect(report.data).to be_blank end end end shared_examples "category filtering" do it "returns the filtered data" do expect(report.total).to eq 1 end end shared_examples "category filtering on subcategories" do it "returns the filtered data" do expect(report.total).to eq(1) end end shared_examples "with data x/y" do it "returns today's data" do expect(report.data.select { |v| v[:x].today? }).to be_present end it "returns correct data for period" do expect(report.data[0][:y]).to eq 3 end it "returns total" do expect(report.total).to eq 4 end it "returns previous 30 day’s data" do expect(report.prev30Days).to be_present end end describe "counting" do describe "requests" do subject(:json) { Report.find("http_total_reqs").as_json } before do freeze_time_safe # today, an incomplete day: application_requests = [ { date: 0.days.ago.to_time, req_type: ApplicationRequest.req_types["http_total"], count: 1, }, ] # 60 complete days: 30.times.each do |i| application_requests.concat( [ { date: (i + 1).days.ago.to_time, req_type: ApplicationRequest.req_types["http_total"], count: 10, }, ], ) end 30.times.each do |i| application_requests.concat( [ { date: (31 + i).days.ago.to_time, req_type: ApplicationRequest.req_types["http_total"], count: 100, }, ], ) end ApplicationRequest.insert_all(application_requests) end it "counts the correct records" do expect(json[:data].size).to eq(31) # today and 30 full days expect(json[:data][0..-2].sum { |d| d[:y] }).to eq(300) expect(json[:prev30Days]).to eq(3000) end end describe "topics" do before do Report.clear_cache freeze_time_safe user = Fabricate(:user) topics = ((0..32).to_a + [60, 61, 62, 63]).map do |i| date = i.days.ago { user_id: user.id, last_post_user_id: user.id, title: "topic #{i}", category_id: SiteSetting.uncategorized_category_id, bumped_at: date, created_at: date, updated_at: date, } end Topic.insert_all(topics) end it "counts the correct records" do json = Report.find("topics").as_json expect(json[:data].size).to eq(31) expect(json[:prev30Days]).to eq(3) # lets make sure we can ask for the correct options for the report json = Report.find( "topics", start_date: 5.days.ago.beginning_of_day, end_date: 1.day.ago.end_of_day, facets: [:prev_period], ).as_json expect(json[:prev_period]).to eq(5) expect(json[:data].length).to eq(5) expect(json[:prev30Days]).to eq(nil) end end end describe "visits report" do let(:report) { Report.find("visits") } include_examples "no data" context "with visits" do let(:user) { Fabricate(:user) } it "returns a report with data" do freeze_time_safe user.user_visits.create(visited_at: 1.hour.from_now) user.user_visits.create(visited_at: 1.day.ago) user.user_visits.create(visited_at: 2.days.ago, mobile: true) user.user_visits.create(visited_at: 45.days.ago) user.user_visits.create(visited_at: 46.days.ago, mobile: true) expect(report.data).to be_present expect(report.data.count).to eq(3) expect(report.data.select { |v| v[:x].today? }).to be_present expect(report.prev30Days).to eq(2) end end end describe "mobile visits report" do let(:report) { Report.find("mobile_visits") } include_examples "no data" context "with visits" do let(:user) { Fabricate(:user) } it "returns a report with data" do freeze_time_safe user.user_visits.create(visited_at: 1.hour.from_now) user.user_visits.create(visited_at: 2.days.ago, mobile: true) user.user_visits.create(visited_at: 45.days.ago) user.user_visits.create(visited_at: 46.days.ago, mobile: true) expect(report.data).to be_present expect(report.data.count).to eq(1) expect(report.data.select { |v| v[:x].today? }).not_to be_present expect(report.prev30Days).to eq(1) end end end %i[signup topic post flag like email].each do |arg| describe "#{arg} report" do pluralized = arg.to_s.pluralize let(:report) { Report.find(pluralized) } context "with no #{pluralized}" do it "returns an empty report" do expect(report.data).to be_blank end end context "with #{pluralized}" do before(:each) do freeze_time_safe if arg == :flag user = Fabricate(:user, refresh_auto_groups: true) topic = Fabricate(:topic, user: user) builder = ->(dt) do PostActionCreator.create( user, Fabricate(:post, topic: topic, user: user), :spam, created_at: dt, ) end elsif arg == :signup builder = ->(dt) { Fabricate(:user, created_at: dt) } else user = Fabricate(:user) factories = { email: :email_log } builder = ->(dt) { Fabricate(factories[arg] || arg, created_at: dt, user: user) } end [ DateTime.now, 1.hour.ago, 1.hour.ago, 1.day.ago, 2.days.ago, 30.days.ago, 35.days.ago, ].each(&builder) end it "returns today's, total and previous 30 day's data" do expect(report.data.select { |v| v[:x].today? }).to be_present expect(report.total).to eq 7 expect(report.prev30Days).to be_present end end end end %i[ http_total http_2xx http_background http_3xx http_4xx http_5xx page_view_crawler page_view_logged_in page_view_anon ].each do |request_type| describe "#{request_type} request reports" do let(:report) do Report.find("#{request_type}_reqs", start_date: 10.days.ago.to_time, end_date: Time.now) end context "with no #{request_type} records" do it "returns an empty report" do expect(report.data).to be_blank end end context "with #{request_type}" do before do freeze_time_safe application_requests = [ { date: 35.days.ago.to_time, req_type: ApplicationRequest.req_types[request_type.to_s], count: 35, }, { date: 7.days.ago.to_time, req_type: ApplicationRequest.req_types[request_type.to_s], count: 8, }, { date: Time.now, req_type: ApplicationRequest.req_types[request_type.to_s], count: 1 }, { date: 1.day.ago.to_time, req_type: ApplicationRequest.req_types[request_type.to_s], count: 2, }, { date: 2.days.ago.to_time, req_type: ApplicationRequest.req_types[request_type.to_s], count: 3, }, ] ApplicationRequest.insert_all(application_requests) end it "returns a report with data" do # expected number of records expect(report.data.count).to eq 4 # sorts the data from oldest to latest dates expect(report.data[0][:y]).to eq(8) # 7 days ago expect(report.data[1][:y]).to eq(3) # 2 days ago expect(report.data[2][:y]).to eq(2) # 1 day ago expect(report.data[3][:y]).to eq(1) # today # today's data expect(report.data.find { |value| value[:x] == Date.today }).to be_present # total data expect(report.total).to eq 49 #previous 30 days of data expect(report.prev30Days).to eq 35 end end end end describe "page_view_total_reqs" do before do freeze_time(Time.now.at_midnight) Theme.clear_default! end let(:report) { Report.find("page_view_total_reqs") } context "with no data" do it "works" do expect(report.data).to be_empty end end context "with data" do before do CachedCounting.reset CachedCounting.enable ApplicationRequest.enable end after do CachedCounting.reset ApplicationRequest.disable CachedCounting.disable end context "when use_legacy_pageviews is true" do before { SiteSetting.use_legacy_pageviews = true } it "works and does not count browser or mobile pageviews" do 3.times { ApplicationRequest.increment!(:page_view_crawler) } 8.times { ApplicationRequest.increment!(:page_view_logged_in) } 6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) } 2.times { ApplicationRequest.increment!(:page_view_logged_in_mobile) } 2.times { ApplicationRequest.increment!(:page_view_anon) } 1.times { ApplicationRequest.increment!(:page_view_anon_browser) } 4.times { ApplicationRequest.increment!(:page_view_anon_mobile) } CachedCounting.flush expect(report.data.sum { |r| r[:y] }).to eq(13) end end context "when use_legacy_pageviews is false" do before { SiteSetting.use_legacy_pageviews = false } it "works and does not count mobile pageviews, and only counts browser pageviews" do 3.times { ApplicationRequest.increment!(:page_view_crawler) } 8.times { ApplicationRequest.increment!(:page_view_logged_in) } 6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) } 2.times { ApplicationRequest.increment!(:page_view_logged_in_mobile) } 2.times { ApplicationRequest.increment!(:page_view_anon) } 1.times { ApplicationRequest.increment!(:page_view_anon_browser) } 4.times { ApplicationRequest.increment!(:page_view_anon_mobile) } CachedCounting.flush expect(report.data.sum { |r| r[:y] }).to eq(7) end end end end describe "user to user private messages with replies" do let(:report) { Report.find("user_to_user_private_messages_with_replies") } let(:user) { Fabricate(:user) } let(:topic) { Fabricate(:topic, created_at: 1.hour.ago, user: user) } it "topic report).to not include private messages" do Fabricate(:private_message_topic, created_at: 1.hour.ago, user: user) topic report = Report.find("topics") expect(report.data[0][:y]).to eq(1) expect(report.total).to eq(1) end it "post report).to not include private messages" do Fabricate(:private_message_post, created_at: 1.hour.ago) Fabricate(:post) report = Report.find("posts") expect(report.data[0][:y]).to eq 1 expect(report.total).to eq 1 end context "with no private messages" do it "returns an empty report" do expect(report.data).to be_blank end context "with some public posts" do it "returns an empty report" do Fabricate(:post, topic: topic, user: user) Fabricate(:post, topic: topic, user: user) expect(report.data).to be_blank expect(report.total).to eq 0 end end end context "with some private messages" do before do Fabricate(:private_message_post, created_at: 25.hours.ago, user: user) Fabricate(:private_message_post, created_at: 1.hour.ago, user: user) Fabricate(:private_message_post, created_at: 1.hour.ago, user: user) end it "returns correct data" do expect(report.data[0][:y]).to eq 1 expect(report.data[1][:y]).to eq 2 expect(report.total).to eq 3 end context "with some public posts" do before do Fabricate(:post, user: user, topic: topic) Fabricate(:post, user: user, topic: topic) end it "returns correct data" do expect(report.data[0][:y]).to eq 1 expect(report.data[1][:y]).to eq 2 expect(report.total).to eq 3 end end end context "with private message from system user" do before do Fabricate(:private_message_post, created_at: 1.hour.ago, user: Discourse.system_user) end it "does not include system users" do expect(report.data).to be_blank expect(report.total).to eq 0 end end end describe "user to user private messages" do let(:report) { Report.find("user_to_user_private_messages") } context "with private message from system user" do before do Fabricate(:private_message_post, created_at: 1.hour.ago, user: Discourse.system_user) end it "does not include system users" do expect(report.data).to be_blank expect(report.total).to eq 0 end end end describe "users by trust level report" do let(:report) { Report.find("users_by_trust_level") } include_examples "no data" context "with users at different trust levels" do before do 3.times { Fabricate(:user, trust_level: TrustLevel[0]) } 2.times { Fabricate(:user, trust_level: TrustLevel[2]) } Fabricate(:user, trust_level: TrustLevel[4]) end it "returns a report with data" do expect(report.data).to be_present expect(report.data.find { |d| d[:x] == TrustLevel[0] }[:y]).to eq 3 expect(report.data.find { |d| d[:x] == TrustLevel[2] }[:y]).to eq 2 expect(report.data.find { |d| d[:x] == TrustLevel[4] }[:y]).to eq 1 expect( report.data.find { |d| d[:x] == TrustLevel[0] }[:url], ).to eq "/admin/users/list/newuser" end end end describe "new contributors report" do let(:report) { Report.find("new_contributors") } include_examples "no data" context "with contributors" do before do jeff = Fabricate(:user) jeff.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 1.day.ago) regis = Fabricate(:user) regis.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 2.days.ago) hawk = Fabricate(:user) hawk.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 2.days.ago) end it "returns a report with data" do expect(report.data).to be_present expect(report.data[0][:y]).to eq 2 expect(report.data[1][:y]).to eq 1 end end end describe "users by types level report" do let(:report) { Report.find("users_by_type") } include_examples "no data" context "with users at different trust levels" do before do 3.times { Fabricate(:user, admin: true) } 2.times { Fabricate(:user, moderator: true) } UserSilencer.silence(Fabricate(:user, refresh_auto_groups: true), Fabricate.build(:admin)) Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) end it "returns a report with data" do expect(report.data).to be_present label = Proc.new { |key| I18n.t("reports.users_by_type.xaxis_labels.#{key}") } expect(report.data.find { |d| d[:x] == label.call("admin") }[:y]).to eq 3 expect(report.data.find { |d| d[:x] == label.call("moderator") }[:y]).to eq 2 expect(report.data.find { |d| d[:x] == label.call("silenced") }[:y]).to eq 1 expect(report.data.find { |d| d[:x] == label.call("suspended") }[:y]).to eq 1 end end end describe "trending search report" do let(:report) { Report.find("trending_search") } include_examples "no data" context "with different searches" do before do SearchLog.log(term: "ruby", search_type: :header, ip_address: "127.0.0.1") SearchLog.create!( term: "ruby", search_result_id: 1, search_type: 1, ip_address: "127.0.0.1", user_id: Fabricate(:user).id, ) SearchLog.log(term: "ruby", search_type: :header, ip_address: "127.0.0.2") SearchLog.log(term: "php", search_type: :header, ip_address: "127.0.0.1") end after { SearchLog.clear_debounce_cache! } it "returns a report with data" do expect(report.data[0][:term]).to eq("ruby") expect(report.data[0][:searches]).to eq(3) expect(report.data[0][:ctr]).to eq(33.4) expect(report.data[1][:term]).to eq("php") expect(report.data[1][:searches]).to eq(1) end end end describe "DAU/MAU report" do let(:report) { Report.find("dau_by_mau") } include_examples "no data" context "with different users/visits" do before do freeze_time_safe arpit = Fabricate(:user) arpit.user_visits.create(visited_at: 1.day.ago) sam = Fabricate(:user) sam.user_visits.create(visited_at: 2.days.ago) robin = Fabricate(:user) robin.user_visits.create(visited_at: 2.days.ago) michael = Fabricate(:user) michael.user_visits.create(visited_at: 35.days.ago) gerhard = Fabricate(:user) gerhard.user_visits.create(visited_at: 45.days.ago) end it "returns a report with data" do expect(report.data.first[:y]).to eq(100) expect(report.data.last[:y]).to eq(33.34) expect(report.prev30Days).to eq(75) end end end describe "Daily engaged users" do let(:report) { Report.find("daily_engaged_users") } include_examples "no data" context "with different activities" do before do freeze_time_safe UserActionManager.enable arpit = Fabricate(:user) sam = Fabricate(:user) jeff = Fabricate(:user, created_at: 1.day.ago, refresh_auto_groups: true) post = create_post(user: jeff, created_at: 1.day.ago) PostActionCreator.like(arpit, post) PostActionCreator.like(sam, post) end it "returns a report with data" do expect(report.data.first[:y]).to eq(1) expect(report.data.last[:y]).to eq(2) end end end describe "posts counts" do it "only counts regular posts" do post = Fabricate(:post) Fabricate(:moderator_post, topic: post.topic) Fabricate.build(:post, post_type: Post.types[:whisper], topic: post.topic) post.topic.add_small_action(Fabricate(:admin), "invited_group", "coolkids") r = Report.find("posts") expect(r.total).to eq(1) expect(r.data[0][:y]).to eq(1) end end describe "flags_status" do let(:report) { Report.find("flags_status") } include_examples "no data" context "with flags" do let(:flagger) { Fabricate(:user, refresh_auto_groups: true) } let(:post) { Fabricate(:post, user: flagger) } before { freeze_time } it "returns a report with data" do result = PostActionCreator.new(flagger, post, PostActionType.types[:spam], message: "bad").perform expect(result.success).to eq(true) expect(report.data).to be_present row = report.data[0] expect(row[:post_type]).to eq("spam") expect(row[:staff_username]).to eq(nil) expect(row[:staff_id]).to eq(nil) expect(row[:poster_username]).to eq(post.user.username) expect(row[:poster_id]).to eq(post.user.id) expect(row[:poster_avatar_template]).to be_present expect(row[:flagger_id]).to eq(flagger.id) expect(row[:flagger_username]).to eq(flagger.username) expect(row[:flagger_avatar_template]).to be_present expect(row[:resolution]).to eq("No action") expect(row[:response_time]).to eq(nil) end end end describe "post_edits" do let(:report) { Report.find("post_edits") } include_examples "no data" context "with edits" do let(:editor) { Fabricate(:user) } let(:post) { Fabricate(:post) } before do freeze_time post.revise( post.user, { raw: "updated body by author", edit_reason: "not cool" }, force_new_version: true, ) post.revise(editor, raw: "updated body", edit_reason: "not cool") end it "returns a report with data" do expect(report.data).to be_present expect(report.data.count).to be(1) row = report.data[0] expect(row[:editor_id]).to eq(editor.id) expect(row[:editor_username]).to eq(editor.username) expect(row[:editor_avatar_template]).to be_present expect(row[:author_id]).to eq(post.user.id) expect(row[:author_username]).to eq(post.user.username) expect(row[:author_avatar_template]).to be_present expect(row[:edit_reason]).to eq("not cool") expect(row[:post_raw]).to eq("updated body") expect(row[:post_number]).to eq(post.post_number) expect(row[:topic_id]).to eq(post.topic.id) end end context "with editor filter" do fab!(:posts) { Fabricate.times(3, :post) } fab!(:editor_with_two_edits) do Fabricate(:user).tap do |user| 2.times { |i| posts[i].revise(user, { raw: "edit #{i + 1}" }) } end end fab!(:editor_with_one_edit) do Fabricate(:user).tap { |user| posts.last.revise(user, { raw: "edit 3" }) } end let(:report_with_one_edit) do Report.find("post_edits", { filters: { "editor" => editor_with_one_edit.username } }) end let(:report_with_two_edits) do Report.find("post_edits", { filters: { "editor" => editor_with_two_edits.username } }) end it "returns a report for a given editor" do expect(report_with_one_edit.data.count).to be(1) expect(report_with_two_edits.data.count).to be(2) end end end describe "moderator activity" do let(:report) { Report.find("moderators_activity") } let(:sam) { Fabricate(:user, moderator: true, username: "sam") } let(:jeff) { Fabricate(:user, moderator: true, username: "jeff", refresh_auto_groups: true) } include_examples "no data" context "with moderators" do before { freeze_time(Date.today) } context "with moderators order" do before do Fabricate(:post, user: sam) Fabricate(:post, user: jeff) end it "returns the moderators in alphabetical order" do expect(report.data[0][:username]).to eq("jeff") expect(report.data[1][:username]).to eq("sam") end end context "with time read" do before do sam.user_visits.create(visited_at: 2.days.ago, time_read: 200) sam.user_visits.create(visited_at: 1.day.ago, time_read: 100) jeff.user_visits.create(visited_at: 2.days.ago, time_read: 1000) jeff.user_visits.create(visited_at: 1.day.ago, time_read: 2000) Fabricate(:topic, created_at: 1.day.ago) end it "returns the correct read times" do expect(report.data[0][:username]).to eq("jeff") expect(report.data[0][:time_read]).to eq(3000) expect(report.data[1][:username]).to eq("sam") expect(report.data[1][:time_read]).to eq(300) end end context "with flags" do before do flagged_post = Fabricate(:post) result = PostActionCreator.off_topic(jeff, flagged_post) result.reviewable.perform(jeff, :agree_and_keep) end it "returns the correct flag counts" do expect(report.data.count).to eq(1) expect(report.data[0][:flag_count]).to eq(1) expect(report.data[0][:username]).to eq("jeff") end end context "with topics" do before do Fabricate(:topic, user: sam) Fabricate(:topic, user: sam) Fabricate(:topic, user: jeff) end it "returns the correct topic count" do expect(report.data[0][:topic_count]).to eq(1) expect(report.data[0][:username]).to eq("jeff") expect(report.data[1][:topic_count]).to eq(2) expect(report.data[1][:username]).to eq("sam") end context "with private messages" do before { Fabricate(:private_message_topic, user: jeff) } it "doesn’t count private topic" do expect(report.data[0][:topic_count]).to eq(1) expect(report.data[1][:topic_count]).to eq(2) end end end context "with posts" do before do Fabricate(:post, user: sam) Fabricate(:post, user: sam) Fabricate(:post, user: jeff) end it "returns the correct topic count" do expect(report.data[0][:topic_count]).to eq(1) expect(report.data[0][:username]).to eq("jeff") expect(report.data[1][:topic_count]).to eq(2) expect(report.data[1][:username]).to eq("sam") end context "with private messages" do before { Fabricate(:private_message_post, user: jeff) } it "doesn’t count private post" do expect(report.data[0][:post_count]).to eq(1) expect(report.data[1][:post_count]).to eq(2) end end end context "with private messages" do before do Fabricate(:post, user: sam) Fabricate(:post, user: jeff) Fabricate(:private_message_post, user: jeff) end it "returns the correct topic count" do expect(report.data[0][:pm_count]).to eq(1) expect(report.data[0][:username]).to eq("jeff") expect(report.data[1][:pm_count]).to be_blank expect(report.data[1][:username]).to eq("sam") end end context "with revisions" do before do post = Fabricate(:post) post.revise(sam, raw: "updated body", edit_reason: "not cool") end it "returns the correct revisions count" do expect(report.data[0][:revision_count]).to eq(1) expect(report.data[0][:username]).to eq("sam") end context "when revising own post" do before do post = Fabricate(:post, user: sam) post.revise(sam, raw: "updated body") end it "doesn't count a revision on your own post" do expect(report.data[0][:revision_count]).to eq(1) expect(report.data[0][:username]).to eq("sam") end end end context "with previous data" do before { Fabricate(:topic, user: sam, created_at: 1.year.ago) } it "doesn’t count old data" do expect(report.data[0][:topic_count]).to be_blank expect(report.data[0][:username]).to eq("sam") end end end end describe "flags" do let(:report) { Report.find("flags") } include_examples "no data" context "with data" do include_examples "with data x/y" before(:each) do user = Fabricate(:user, refresh_auto_groups: true) topic = Fabricate(:topic, user: user) post0 = Fabricate(:post, topic: topic, user: user) post1 = Fabricate(:post, topic: Fabricate(:topic, category: category_2, user: user), user: user) post2 = Fabricate(:post, topic: topic, user: user) post3 = Fabricate(:post, topic: topic, user: user) PostActionCreator.off_topic(user, post0) PostActionCreator.off_topic(user, post1) PostActionCreator.off_topic(user, post2) PostActionCreator.create(user, post3, :off_topic, created_at: 45.days.ago) end context "with category filtering" do let(:report) { Report.find("flags", filters: { category: category_2.id }) } include_examples "category filtering" context "with subcategories" do let(:report) do Report.find("flags", filters: { category: category_1.id, include_subcategories: true }) end include_examples "category filtering on subcategories" end end end end describe "topics" do let(:report) { Report.find("topics") } include_examples "no data" context "with data" do include_examples "with data x/y" before(:each) do user = Fabricate(:user) Fabricate(:topic, user: user) Fabricate(:topic, category: category_2, user: user) Fabricate(:topic, user: user) Fabricate(:topic, created_at: 45.days.ago, user: user) end context "with category filtering" do let(:report) { Report.find("topics", filters: { category: category_2.id }) } include_examples "category filtering" context "with subcategories" do let(:report) do Report.find("topics", filters: { category: category_1.id, include_subcategories: true }) end include_examples "category filtering on subcategories" end end end end describe "exception report" do before(:each) { Report.stubs(:report_exception_test).raises(Exception) } it "returns a report with an exception error" do report = Report.find("exception_test", wrap_exceptions_in_test: true) expect(report.error).to eq(:exception) end end describe "timeout report" do before(:each) { Report.stubs(:report_timeout_test).raises(ActiveRecord::QueryCanceled) } it "returns a report with a timeout error" do report = Report.find("timeout_test") expect(report.error).to eq(:timeout) end end describe "unexpected error on report initialization" do before do @orig_logger = Rails.logger Rails.logger = @fake_logger = FakeLogger.new end after { Rails.logger = @orig_logger } it "returns no report" do class ReportInitError < StandardError end Report.stubs(:new).raises(ReportInitError.new("x")) report = Report.find("signups", wrap_exceptions_in_test: true) expect(report).to be_nil expect(@fake_logger.errors).to eq(["Couldn’t create report `signups`: "]) end end describe "posts" do let(:report) { Report.find("posts") } include_examples "no data" context "with data" do include_examples "with data x/y" before(:each) do user = Fabricate(:user) topic = Fabricate(:topic, user: user) topic_with_category_id = Fabricate(:topic, category: category_2, user: user) Fabricate(:post, topic: topic, user: user) Fabricate(:post, topic: topic_with_category_id, user: user) Fabricate(:post, topic: topic, user: user) Fabricate(:post, created_at: 45.days.ago, topic: topic, user: user) end context "with category filtering" do let(:report) { Report.find("posts", filters: { category: category_2.id }) } include_examples "category filtering" context "with subcategories" do let(:report) do Report.find("posts", filters: { category: category_1.id, include_subcategories: true }) end include_examples "category filtering on subcategories" end end end end # TODO: time_to_first_response describe "topics_with_no_response" do let(:report) { Report.find("topics_with_no_response") } include_examples "no data" context "with data" do include_examples "with data x/y" before(:each) do user = Fabricate(:user) Fabricate(:topic, category: category_2, user: user) Fabricate(:post, topic: Fabricate(:topic, user: user), user: user) Fabricate(:topic, user: user) Fabricate(:topic, created_at: 45.days.ago, user: user) end context "with category filtering" do let(:report) do Report.find("topics_with_no_response", filters: { category: category_2.id }) end include_examples "category filtering" context "with subcategories" do let(:report) do Report.find( "topics_with_no_response", filters: { category: category_1.id, include_subcategories: true, }, ) end include_examples "category filtering on subcategories" end end end end describe "likes" do let(:report) { Report.find("likes") } include_examples "no data" context "with data" do include_examples "with data x/y" before(:each) do topic = Fabricate(:topic, category: category_2) post = Fabricate(:post, topic: topic) PostActionCreator.like(Fabricate(:user), post) topic = Fabricate(:topic, category: category_3) post = Fabricate(:post, topic: topic) PostActionCreator.like(Fabricate(:user), post) PostActionCreator.like(Fabricate(:user), post) PostActionCreator .like(Fabricate(:user), post) .post_action .tap { |pa| pa.created_at = 45.days.ago } .save! end context "with category filtering" do let(:report) { Report.find("likes", filters: { category: category_2.id }) } include_examples "category filtering" context "with subcategories" do let(:report) do Report.find("likes", filters: { category: category_1.id, include_subcategories: true }) end include_examples "category filtering on subcategories" end end end end describe "user_flagging_ratio" do let(:joffrey) { Fabricate(:user, username: "joffrey", refresh_auto_groups: true) } let(:robin) { Fabricate(:user, username: "robin", refresh_auto_groups: true) } let(:moderator) { Fabricate(:moderator) } let(:user) { Fabricate(:user) } context "with data" do it "it works" do topic = Fabricate(:topic, user: user) 2.times do post_disagreed = Fabricate(:post, topic: topic, user: user) result = PostActionCreator.spam(joffrey, post_disagreed) result.reviewable.perform(moderator, :disagree) end 3.times do post_disagreed = Fabricate(:post, topic: topic, user: user) result = PostActionCreator.spam(robin, post_disagreed) result.reviewable.perform(moderator, :disagree) end post_agreed = Fabricate(:post, user: user, topic: topic) result = PostActionCreator.off_topic(robin, post_agreed) result.reviewable.perform(moderator, :agree_and_keep) report = Report.find("user_flagging_ratio") first = report.data[0] expect(first[:username]).to eq("joffrey") expect(first[:score]).to eq(2) expect(first[:agreed_flags]).to eq(0) expect(first[:disagreed_flags]).to eq(2) second = report.data[1] expect(second[:username]).to eq("robin") expect(second[:agreed_flags]).to eq(1) expect(second[:disagreed_flags]).to eq(3) end end end describe "report_suspicious_logins" do let(:joffrey) { Fabricate(:user, username: "joffrey") } let(:robin) { Fabricate(:user, username: "robin") } context "with data" do it "works" do SiteSetting.verbose_auth_token_logging = true UserAuthToken.log(action: "suspicious", user_id: joffrey.id, created_at: 2.hours.ago) UserAuthToken.log(action: "suspicious", user_id: joffrey.id, created_at: 3.hours.ago) UserAuthToken.log(action: "suspicious", user_id: robin.id, created_at: 1.hour.ago) report = Report.find("suspicious_logins") expect(report.data.length).to eq(3) expect(report.data[0][:username]).to eq("robin") expect(report.data[1][:username]).to eq("joffrey") expect(report.data[2][:username]).to eq("joffrey") end end end describe "report_staff_logins" do let(:joffrey) { Fabricate(:admin, username: "joffrey") } let(:robin) { Fabricate(:admin, username: "robin") } let(:james) { Fabricate(:user, username: "james") } context "with data" do it "works" do freeze_time_safe ip = [81, 2, 69, 142] DiscourseIpInfo.open_db(File.join(Rails.root, "spec", "fixtures", "mmdb")) Resolv::DNS .any_instance .stubs(:getname) .with(ip.join(".")) .returns("ip-#{ip.join("-")}.example.com") UserAuthToken.log( action: "generate", user_id: robin.id, client_ip: ip.join("."), created_at: 1.hour.ago, ) UserAuthToken.log(action: "generate", user_id: joffrey.id, client_ip: "1.2.3.4") UserAuthToken.log( action: "generate", user_id: joffrey.id, client_ip: ip.join("."), created_at: 2.hours.ago, ) UserAuthToken.log(action: "generate", user_id: james.id) report = Report.find("staff_logins") expect(report.data.length).to eq(3) expect(report.data[0][:username]).to eq("joffrey") expect(report.data[1][:username]).to eq("robin") expect(report.data[1][:location]).to eq("London, England, United Kingdom") expect(report.data[2][:username]).to eq("joffrey") end end end describe "report_top_uploads" do let(:report) { Report.find("top_uploads") } let(:tarek) { Fabricate(:admin, username: "tarek") } let(:khalil) { Fabricate(:admin, username: "khalil") } context "with data" do let!(:tarek_upload) do Fabricate( :upload, user: tarek, url: "/uploads/default/original/1X/tarek.jpg", extension: "jpg", original_filename: "tarek.jpg", filesize: 1000, ) end let!(:khalil_upload) do Fabricate( :upload, user: khalil, url: "/uploads/default/original/1X/khalil.png", extension: "png", original_filename: "khalil.png", filesize: 2000, ) end it "works" do expect(report.data.length).to eq(2) expect_uploads_report_data_to_be_equal(report.data, khalil, khalil_upload) expect_uploads_report_data_to_be_equal(report.data, tarek, tarek_upload) end end def expect_uploads_report_data_to_be_equal(data, user, upload) row = data.find { |r| r[:author_id] == user.id } expect(row[:author_id]).to eq(user.id) expect(row[:author_username]).to eq(user.username) expect(row[:author_avatar_template]).to eq( User.avatar_template(user.username, user.uploaded_avatar_id), ) expect(row[:filesize]).to eq(upload.filesize) expect(row[:extension]).to eq(upload.extension) expect(row[:file_url]).to eq(Discourse.store.cdn_url(upload.url)) expect(row[:file_name]).to eq(upload.original_filename.truncate(25)) end include_examples "no data" end describe "report_top_ignored_users" do let(:report) { Report.find("top_ignored_users") } let(:tarek) { Fabricate(:user, username: "tarek") } let(:john) { Fabricate(:user, username: "john") } let(:matt) { Fabricate(:user, username: "matt") } context "with data" do before do Fabricate(:ignored_user, user: tarek, ignored_user: john) Fabricate(:ignored_user, user: tarek, ignored_user: matt) end it "works" do expect(report.data.length).to eq(2) expect_ignored_users_report_data_to_be_equal(report.data, john, 1, 0) expect_ignored_users_report_data_to_be_equal(report.data, matt, 1, 0) end context "when muted users exist" do before do Fabricate(:muted_user, user: tarek, muted_user: john) Fabricate(:muted_user, user: tarek, muted_user: matt) end it "works" do expect(report.data.length).to eq(2) expect_ignored_users_report_data_to_be_equal(report.data, john, 1, 1) expect_ignored_users_report_data_to_be_equal(report.data, matt, 1, 1) end end end def expect_ignored_users_report_data_to_be_equal(data, user, ignores, mutes) row = data.find { |r| r[:ignored_user_id] == user.id } expect(row).to be_present expect(row[:ignored_user_id]).to eq(user.id) expect(row[:ignored_username]).to eq(user.username) expect(row[:ignored_user_avatar_template]).to eq( User.avatar_template(user.username, user.uploaded_avatar_id), ) expect(row[:ignores_count]).to eq(ignores) expect(row[:mutes_count]).to eq(mutes) end include_examples "no data" end describe "consolidated_page_views_browser_detection" do before do freeze_time(Time.now.at_midnight) Theme.clear_default! end let(:reports) { Report.find("consolidated_page_views_browser_detection") } context "with no data" do it "works" do reports.data.each { |report| expect(report[:data]).to be_empty } end end context "with data" do before do CachedCounting.reset CachedCounting.enable ApplicationRequest.enable SiteSetting.use_legacy_pageviews = true end after do CachedCounting.reset ApplicationRequest.disable CachedCounting.disable end it "works" do 3.times { ApplicationRequest.increment!(:page_view_crawler) } 8.times { ApplicationRequest.increment!(:page_view_logged_in) } 6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) } 2.times { ApplicationRequest.increment!(:page_view_anon) } 1.times { ApplicationRequest.increment!(:page_view_anon_browser) } CachedCounting.flush page_view_crawler_report = reports.data.find { |r| r[:req] == "page_view_crawler" } page_view_logged_in_browser_report = reports.data.find { |r| r[:req] == "page_view_logged_in_browser" } page_view_anon_browser_report = reports.data.find { |r| r[:req] == "page_view_anon_browser" } page_view_other_report = reports.data.find { |r| r[:req] == "page_view_other" } expect(page_view_crawler_report[:data][0][:y]).to eql(3) expect(page_view_logged_in_browser_report[:data][0][:y]).to eql(6) expect(page_view_anon_browser_report[:data][0][:y]).to eql(1) expect(page_view_other_report[:data][0][:y]).to eql(3) end it "gives the same total as page_view_total_reqs" do 3.times { ApplicationRequest.increment!(:page_view_crawler) } 8.times { ApplicationRequest.increment!(:page_view_logged_in) } 6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) } 2.times { ApplicationRequest.increment!(:page_view_anon) } 1.times { ApplicationRequest.increment!(:page_view_anon_browser) } CachedCounting.flush total_consolidated = reports.data.sum { |r| r[:data][0][:y] } total_page_views = Report.find("page_view_total_reqs").data[0][:y] expect(total_consolidated).to eq(total_page_views) end it "does not include any data before the first recorded browser page view (anon or logged in)" do freeze_time DateTime.parse("2024-02-10") 3.times { ApplicationRequest.increment!(:page_view_logged_in) } 2.times { ApplicationRequest.increment!(:page_view_anon) } CachedCounting.flush freeze_time DateTime.parse("2024-03-10") 3.times { ApplicationRequest.increment!(:page_view_logged_in) } 2.times { ApplicationRequest.increment!(:page_view_anon) } CachedCounting.flush freeze_time DateTime.parse("2024-04-10") 3.times { ApplicationRequest.increment!(:page_view_crawler) } 8.times { ApplicationRequest.increment!(:page_view_logged_in) } 6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) } 2.times { ApplicationRequest.increment!(:page_view_anon) } 1.times { ApplicationRequest.increment!(:page_view_anon_browser) } CachedCounting.flush report_in_range = Report.find( "consolidated_page_views_browser_detection", start_date: DateTime.parse("2024-02-10").beginning_of_day, end_date: DateTime.parse("2024-04-11").beginning_of_day, ) page_view_crawler_report = report_in_range.data.find { |r| r[:req] == "page_view_crawler" } page_view_logged_in_browser_report = report_in_range.data.find { |r| r[:req] == "page_view_logged_in_browser" } page_view_anon_browser_report = report_in_range.data.find { |r| r[:req] == "page_view_anon_browser" } page_view_other_report = report_in_range.data.find { |r| r[:req] == "page_view_other" } expect(page_view_crawler_report[:data].sum { |d| d[:y] }).to eql(3) expect(page_view_logged_in_browser_report[:data].sum { |d| d[:y] }).to eql(6) expect(page_view_anon_browser_report[:data].sum { |d| d[:y] }).to eql(1) expect(page_view_other_report[:data].sum { |d| d[:y] }).to eql(3) end end end describe "consolidated_page_views" do before do freeze_time(Time.now.at_midnight) Theme.clear_default! end let(:reports) { Report.find("consolidated_page_views") } context "with no data" do it "works" do reports.data.each { |report| expect(report[:data]).to be_empty } end end context "with data" do before do CachedCounting.reset CachedCounting.enable ApplicationRequest.enable end after do CachedCounting.reset ApplicationRequest.disable CachedCounting.disable end it "works" do 3.times { ApplicationRequest.increment!(:page_view_crawler) } 2.times { ApplicationRequest.increment!(:page_view_logged_in) } ApplicationRequest.increment!(:page_view_anon) CachedCounting.flush page_view_crawler_report = reports.data.find { |r| r[:req] == "page_view_crawler" } page_view_logged_in_report = reports.data.find { |r| r[:req] == "page_view_logged_in" } page_view_anon_report = reports.data.find { |r| r[:req] == "page_view_anon" } expect(page_view_crawler_report[:color]).to eql("#721D8D") expect(page_view_crawler_report[:data][0][:y]).to eql(3) expect(page_view_logged_in_report[:color]).to eql("#1EB8D1") expect(page_view_logged_in_report[:data][0][:y]).to eql(2) expect(page_view_anon_report[:color]).to eql("#9BC53D") expect(page_view_anon_report[:data][0][:y]).to eql(1) end end end describe ".report_consolidated_api_requests" do before do freeze_time(Time.now.at_midnight) Theme.clear_default! end let(:reports) { Report.find("consolidated_api_requests") } context "with no data" do it "works" do reports.data.each { |report| expect(report[:data]).to be_empty } end end context "with data" do before do CachedCounting.reset CachedCounting.enable ApplicationRequest.enable end after do ApplicationRequest.disable CachedCounting.disable end it "works" do 2.times { ApplicationRequest.increment!(:api) } ApplicationRequest.increment!(:user_api) CachedCounting.flush api_report = reports.data.find { |r| r[:req] == "api" } user_api_report = reports.data.find { |r| r[:req] == "user_api" } expect(api_report[:color]).to eql("#1EB8D1") expect(api_report[:data][0][:y]).to eql(2) expect(user_api_report[:color]).to eql("#9BC53D") expect(user_api_report[:data][0][:y]).to eql(1) end end end describe "trust_level_growth" do before do freeze_time(Time.now.at_midnight) Theme.clear_default! end let(:reports) { Report.find("trust_level_growth") } context "with no data" do it "works" do reports.data.each { |report| expect(report[:data]).to be_empty } end end context "with data" do fab!(:gwen) { Fabricate(:user) } fab!(:martin) { Fabricate(:user) } before do UserHistory.create( action: UserHistory.actions[:auto_trust_level_change], target_user_id: gwen.id, new_value: TrustLevel[2], previous_value: 1, ) UserHistory.create( action: UserHistory.actions[:change_trust_level], target_user_id: martin.id, new_value: TrustLevel[4], previous_value: 0, ) end it "works" do tl1_reached = reports.data.find { |r| r[:req] == "tl1_reached" } tl2_reached = reports.data.find { |r| r[:req] == "tl2_reached" } tl3_reached = reports.data.find { |r| r[:req] == "tl3_reached" } tl4_reached = reports.data.find { |r| r[:req] == "tl4_reached" } expect(tl1_reached[:data][0][:y]).to eql(0) expect(tl2_reached[:data][0][:y]).to eql(1) expect(tl3_reached[:data][0][:y]).to eql(0) expect(tl4_reached[:data][0][:y]).to eql(1) end end end describe ".cache" do let(:exception_report) { Report.find("exception_test", wrap_exceptions_in_test: true) } let(:valid_report) { Report.find("valid_test", wrap_exceptions_in_test: true) } before(:each) do Report.stubs(:report_exception_test).raises(Exception) Report.stubs(:report_valid_test) end it "caches exception reports for 1 minute" do Discourse .cache .expects(:write) .with(Report.cache_key(exception_report), exception_report.as_json, expires_in: 1.minute) Report.cache(exception_report) end it "caches valid reports for 35 minutes" do Discourse .cache .expects(:write) .with(Report.cache_key(valid_report), valid_report.as_json, expires_in: 35.minutes) Report.cache(valid_report) end end describe "top_uploads" do context "with no data" do it "works" do report = Report.find("top_uploads") expect(report.data).to be_empty end end context "with data" do fab!(:jpg_upload) { Fabricate(:upload, extension: :jpg) } fab!(:png_upload) { Fabricate(:upload, extension: :png) } it "works" do report = Report.find("top_uploads") expect(report.data.length).to eq(2) expect(report.data.map { |row| row[:extension] }).to contain_exactly("jpg", "png") end it "works with filters" do report = Report.find("top_uploads", filters: { file_extension: "jpg" }) expect(report.data.length).to eq(1) expect(report.data[0][:extension]).to eq("jpg") end end end describe "top_users_by_likes_received" do let(:report) { Report.find("top_users_by_likes_received") } include_examples "no data" context "with data" do before do user_1 = Fabricate(:user, username: "jonah") user_2 = Fabricate(:user, username: "jake") user_3 = Fabricate(:user, username: "john") 3.times { UserAction.create!(user_id: user_1.id, action_type: UserAction::WAS_LIKED) } 9.times { UserAction.create!(user_id: user_2.id, action_type: UserAction::WAS_LIKED) } 6.times { UserAction.create!(user_id: user_3.id, action_type: UserAction::WAS_LIKED) } end it "with category filtering" do report = Report.find("top_users_by_likes_received") expect(report.data.length).to eq(3) expect(report.data[0][:username]).to eq("jake") expect(report.data[1][:username]).to eq("john") expect(report.data[2][:username]).to eq("jonah") end end end describe "top_users_by_likes_received_from_a_variety_of_people" do let(:report) { Report.find("top_users_by_likes_received_from_a_variety_of_people") } include_examples "no data" context "with data" do before do user_1 = Fabricate(:user, username: "jonah") user_2 = Fabricate(:user, username: "jake") user_3 = Fabricate(:user, username: "john") user_4 = Fabricate(:user, username: "joseph") user_5 = Fabricate(:user, username: "joanne") user_6 = Fabricate(:user, username: "jerome") topic_1 = Fabricate(:topic, user: user_1) topic_2 = Fabricate(:topic, user: user_2) topic_3 = Fabricate(:topic, user: user_3) post_1 = Fabricate(:post, topic: topic_1, user: user_1) post_2 = Fabricate(:post, topic: topic_2, user: user_2) post_3 = Fabricate(:post, topic: topic_3, user: user_3) 3.times do UserAction.create!( user_id: user_4.id, target_post_id: post_1.id, action_type: UserAction::LIKE, ) end 6.times do UserAction.create!( user_id: user_5.id, target_post_id: post_2.id, action_type: UserAction::LIKE, ) end 9.times do UserAction.create!( user_id: user_6.id, target_post_id: post_3.id, action_type: UserAction::LIKE, ) end end it "with category filtering" do report = Report.find("top_users_by_likes_received_from_a_variety_of_people") expect(report.data.length).to eq(3) expect(report.data[0][:username]).to eq("jonah") expect(report.data[1][:username]).to eq("jake") expect(report.data[2][:username]).to eq("john") end end end describe "top_users_by_likes_received_from_inferior_trust_level" do let(:report) { Report.find("top_users_by_likes_received_from_inferior_trust_level") } include_examples "no data" context "with data" do before do user_1 = Fabricate(:user, username: "jonah", trust_level: 2) user_2 = Fabricate(:user, username: "jake", trust_level: 2) user_3 = Fabricate(:user, username: "john", trust_level: 2) user_4 = Fabricate(:user, username: "joseph", trust_level: 1) user_5 = Fabricate(:user, username: "joanne", trust_level: 1) user_6 = Fabricate(:user, username: "jerome", trust_level: 2) topic_1 = Fabricate(:topic, user: user_1) topic_2 = Fabricate(:topic, user: user_2) topic_3 = Fabricate(:topic, user: user_3) post_1 = Fabricate(:post, topic: topic_1, user: user_1) post_2 = Fabricate(:post, topic: topic_2, user: user_2) post_3 = Fabricate(:post, topic: topic_3, user: user_3) 3.times do UserAction.create!( user_id: user_4.id, target_post_id: post_1.id, action_type: UserAction::LIKE, ) end 6.times do UserAction.create!( user_id: user_5.id, target_post_id: post_2.id, action_type: UserAction::LIKE, ) end 9.times do UserAction.create!( user_id: user_6.id, target_post_id: post_3.id, action_type: UserAction::LIKE, ) end end it "with category filtering" do report = Report.find("top_users_by_likes_received_from_inferior_trust_level") expect(report.data.length).to eq(2) expect(report.data[0][:username]).to eq("jake") expect(report.data[1][:username]).to eq("jonah") end end end describe "topic_view_stats" do let(:report) { Report.find("topic_view_stats") } fab!(:topic_1) { Fabricate(:topic) } fab!(:topic_2) { Fabricate(:topic) } include_examples "no data" context "with data" do before do freeze_time_safe Fabricate( :topic_view_stat, topic: topic_1, anonymous_views: 4, logged_in_views: 2, viewed_at: Time.zone.now - 5.days, ) Fabricate( :topic_view_stat, topic: topic_1, anonymous_views: 5, logged_in_views: 18, viewed_at: Time.zone.now - 3.days, ) Fabricate( :topic_view_stat, topic: topic_2, anonymous_views: 14, logged_in_views: 21, viewed_at: Time.zone.now - 5.days, ) Fabricate( :topic_view_stat, topic: topic_2, anonymous_views: 9, logged_in_views: 13, viewed_at: Time.zone.now - 1.days, ) Fabricate( :topic_view_stat, topic: Fabricate(:topic), anonymous_views: 1, logged_in_views: 34, viewed_at: Time.zone.now - 40.days, ) end it "works" do expect(report.data.length).to eq(2) expect(report.data[0]).to include( topic_id: topic_2.id, topic_title: topic_2.title, total_anonymous_views: 23, total_logged_in_views: 34, total_views: 57, ) expect(report.data[1]).to include( topic_id: topic_1.id, topic_title: topic_1.title, total_anonymous_views: 9, total_logged_in_views: 20, total_views: 29, ) end context "with category filtering" do let(:report) { Report.find("topic_view_stats", filters: { category: category_1.id }) } before { topic_1.update!(category: category_1) } it "filters topics to that category" do expect(report.data.length).to eq(1) expect(report.data[0]).to include( topic_id: topic_1.id, topic_title: topic_1.title, total_anonymous_views: 9, total_logged_in_views: 20, total_views: 29, ) end end end end end