BlackBox specs #2524 (#2542)

* Specs for FuctionCaller

* Fix accepting nil values from AWS lambda

* Specs for the black_box #2524

* Spec for BlackBox#comment_quality_score

* Remove vcr cassette usage from the CouponGenerator spec
This commit is contained in:
Anna Buianova 2019-04-25 17:19:11 +03:00 committed by Ben Halpern
parent 012b2a482c
commit d57f8eec4a
7 changed files with 136 additions and 21 deletions

View file

@ -1,8 +1,6 @@
class BlackBox
class << self
def article_hotness_score(article)
return (article.featured_number || 10_000) / 10_000 unless Rails.env.production?
def article_hotness_score(article, function_caller = FunctionCaller)
usable_date = article.crossposted_at || article.published_at
reaction_points = article.score
super_super_recent_bonus = usable_date > 1.hour.ago ? 28 : 0
@ -11,8 +9,8 @@ class BlackBox
today_bonus = usable_date > 26.hours.ago ? 395 : 0
two_day_bonus = usable_date > 48.hours.ago ? 330 : 0
four_day_bonus = usable_date > 96.hours.ago ? 330 : 0
FunctionCaller.new("blackbox-production-articleHotness",
{ article: article, user: article.user }.to_json).call +
function_caller.call("blackbox-production-articleHotness",
{ article: article, user: article.user }.to_json).to_i +
reaction_points + recency_bonus + super_recent_bonus + super_super_recent_bonus + today_bonus + two_day_bonus + four_day_bonus
end
@ -24,13 +22,12 @@ class BlackBox
(rep_points + descendants_points + bonus_points - spaminess_rating).to_i
end
def calculate_spaminess(story)
def calculate_spaminess(story, function_caller = FunctionCaller)
# accepts comment or article as story
return 0 unless Rails.env.production?
return 100 unless story.user
FunctionCaller.new("blackbox-production-spamScore",
{ story: story, user: story.user }.to_json).call
function_caller.call("blackbox-production-spamScore",
{ story: story, user: story.user }.to_json).to_i
end
private

View file

@ -1,12 +1,20 @@
class FunctionCaller
attr_accessor :function_name, :payload
def initialize(function_name, payload)
def initialize(function_name, payload, aws_lambda_client = AWS_LAMBDA)
@function_name = function_name
@payload = payload
@aws_lambda_client = aws_lambda_client
end
def self.call(*args)
new(*args).call
end
def call
response = AWS_LAMBDA.invoke(function_name: function_name, payload: payload)
response = aws_lambda_client.invoke(function_name: function_name, payload: payload)
JSON.parse(JSON.parse(response.payload.as_json[0])["body"])["message"]
end
private
attr_reader :function_name, :payload, :aws_lambda_client
end

View file

@ -0,0 +1,10 @@
# Fake Aws client is used in non-production environments to prevent actual calls to AWS lambda
module Aws
class FakeClient
def initialize; end
def invoke(*)
OpenStruct.new(payload: [{ body: { message: 0 }.to_json }.to_json])
end
end
end

View file

@ -1,5 +1,9 @@
AWS_LAMBDA = Aws::Lambda::Client.new(
region: ApplicationConfig["AWS_DEFAULT_REGION"],
access_key_id: ApplicationConfig["AWS_SDK_KEY"],
secret_access_key: ApplicationConfig["AWS_SDK_SECRET"],
)
AWS_LAMBDA = if Rails.env.production?
Aws::Lambda::Client.new(
region: ApplicationConfig["AWS_DEFAULT_REGION"],
access_key_id: ApplicationConfig["AWS_SDK_KEY"],
secret_access_key: ApplicationConfig["AWS_SDK_SECRET"],
)
else
AWS_LAMBDA = Aws::FakeClient.new
end

View file

@ -0,0 +1,77 @@
require "rails_helper"
RSpec.describe BlackBox do
let!(:function_caller) { double }
describe "#article_hotness_score" do
let!(:article) { create(:article, published_at: Time.current) }
it "calls function caller" do
allow(function_caller).to receive(:call).and_return(5)
described_class.article_hotness_score(article, function_caller)
expect(function_caller).to have_received(:call).once
end
it "doesn't fail when function caller returns nil" do
allow(function_caller).to receive(:call).and_return(nil)
described_class.article_hotness_score(article, function_caller)
end
it "returns the correct value" do
article.update_column(:score, 99)
allow(function_caller).to receive(:call).and_return(5)
# recent bonuses (28 + 31 + 80 + 395 + 330 + 330 = 1194)
# + score (99)
# + value from the function caller (5)
score = described_class.article_hotness_score(article, function_caller)
expect(score).to eq(1298)
end
end
describe "#comment_quality_score" do
let(:comment) { create(:comment, commentable: create(:article), body_markdown: "```#{'hello, world! ' * 20}```") }
before do
reaction = create(:reaction, reactable: comment)
reaction.update_column(:points, 20)
reaction2 = create(:reaction, reactable: comment)
reaction2.update_column(:points, 2)
end
it "returns the correct score" do
# rep_points + descendants_points + bonus_points - spaminess_rating
# rep_points - 22
# descendants_points - 0
# bonus_points - 2 + 1 = 3
# spaminess_rating - 0
# 22 + 0 + 3 - 0 = 25
expect(described_class.comment_quality_score(comment)).to eq(25)
end
end
describe "#calculate_spaminess" do
let(:user) { build(:user) }
let(:comment) { build(:comment, user: user) }
before do
allow(function_caller).to receive(:call).and_return(1)
end
it "returns 100 if there is no user" do
story = instance_double("Comment", user: nil)
expect(described_class.calculate_spaminess(story, function_caller)).to eq(100)
expect(function_caller).not_to have_received(:call)
end
it "calls the function_caller" do
described_class.calculate_spaminess(comment, function_caller)
expect(function_caller).to have_received(:call).with("blackbox-production-spamScore",
{ story: comment, user: user }.to_json).once
end
it "returns the value that the caller returns" do
spaminess = described_class.calculate_spaminess(comment, function_caller)
expect(spaminess).to eq(1)
end
end
end

View file

@ -5,10 +5,8 @@ RSpec.describe CouponGenerator, vcr: {} do
describe "#expect" do
it "generates code with proper prefix ( [version]_[code] )" do
VCR.use_cassette "coupon_generator_1" do
version = versions.sample
expect(described_class.new(1, version).generate).to include("#{version}_")
end
version = versions.sample
expect(described_class.new(1, version).generate).to include("#{version}_")
end
end
end

View file

@ -0,0 +1,21 @@
require "rails_helper"
RSpec.describe FunctionCaller do
let(:payload) { { user_id: 1, name: "hello" } }
let(:dummy_client) { double }
let(:result) { OpenStruct.new(payload: [{ body: { message: "hi" }.to_json }.to_json]) }
before do
allow(dummy_client).to receive(:invoke).and_return(result)
end
it "calls the aws_lambda_client" do
described_class.call("some_function", payload, dummy_client)
expect(dummy_client).to have_received(:invoke).with(function_name: "some_function", payload: payload)
end
it "returns parsed value" do
value = described_class.call("some_function", payload, dummy_client)
expect(value).to eq("hi")
end
end