mirror of
https://github.com/discourse/discourse.git
synced 2025-05-21 18:12:32 +08:00
Merge discourse-narrative-bot into core plugins.
This commit is contained in:
@ -0,0 +1,658 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
||||
let(:discobot_user) { User.find(-2) }
|
||||
let(:first_post) { Fabricate(:post, user: discobot_user) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
let(:topic) do
|
||||
Fabricate(:private_message_topic, first_post: first_post,
|
||||
topic_allowed_users: [
|
||||
Fabricate.build(:topic_allowed_user, user: discobot_user),
|
||||
Fabricate.build(:topic_allowed_user, user: user),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
let(:narrative) { described_class.new }
|
||||
let(:other_topic) { Fabricate(:topic) }
|
||||
let(:other_post) { Fabricate(:post, topic: other_topic) }
|
||||
let(:skip_trigger) { DiscourseNarrativeBot::TrackSelector.skip_trigger }
|
||||
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
|
||||
|
||||
before do
|
||||
SiteSetting.discourse_narrative_bot_enabled = true
|
||||
end
|
||||
|
||||
describe '.can_start?' do
|
||||
describe 'when user is a moderator' do
|
||||
it 'should return true' do
|
||||
user.update!(moderator: true)
|
||||
|
||||
expect(described_class.can_start?(user)).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notify_timeout' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_poll,
|
||||
topic_id: topic.id,
|
||||
last_post_id: post.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'should create the right message' do
|
||||
expect { narrative.notify_timeout(user) }.to change { Post.count }.by(1)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.timeout.message',
|
||||
username: user.username,
|
||||
skip_trigger: skip_trigger,
|
||||
reset_trigger: "#{reset_trigger} #{described_class.reset_trigger}",
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reset_bot' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_images, topic_id: topic.id)
|
||||
end
|
||||
|
||||
context 'when trigger is initiated in a PM' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
let(:topic) do
|
||||
topic_allowed_user = Fabricate.build(:topic_allowed_user, user: user)
|
||||
bot = Fabricate.build(:topic_allowed_user, user: discobot_user)
|
||||
Fabricate(:private_message_topic, topic_allowed_users: [topic_allowed_user, bot])
|
||||
end
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
it 'should reset the bot' do
|
||||
narrative.reset_bot(user, post)
|
||||
|
||||
expected_raw = I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.start_message',
|
||||
username: user.username
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{expected_raw}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.edit.instructions')}
|
||||
RAW
|
||||
|
||||
new_post = Post.offset(1).last
|
||||
|
||||
expect(narrative.get_data(user)).to eq({
|
||||
"topic_id" => topic.id,
|
||||
"state" => "tutorial_edit",
|
||||
"last_post_id" => new_post.id,
|
||||
"track" => described_class.to_s,
|
||||
"tutorial_edit" => {
|
||||
"post_id" => Post.last.id
|
||||
}
|
||||
})
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(new_post.topic.id).to eq(topic.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trigger is not initiated in a PM' do
|
||||
it 'should start the new track in a PM' do
|
||||
narrative.reset_bot(user, other_post)
|
||||
|
||||
expected_raw = I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.start_message',
|
||||
username: user.username
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{expected_raw}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.edit.instructions')}
|
||||
RAW
|
||||
|
||||
new_post = Post.offset(1).last
|
||||
|
||||
expect(narrative.get_data(user)).to eq({
|
||||
"topic_id" => new_post.topic.id,
|
||||
"state" => "tutorial_edit",
|
||||
"last_post_id" => new_post.id,
|
||||
"track" => described_class.to_s,
|
||||
"tutorial_edit" => {
|
||||
"post_id" => Post.last.id
|
||||
}
|
||||
})
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(new_post.topic.id).to_not eq(topic.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#input" do
|
||||
context 'edit tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_edit,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s,
|
||||
tutorial_edit: {
|
||||
post_id: first_post.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_edit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the post' do
|
||||
it 'should create the right reply' do
|
||||
post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).once
|
||||
|
||||
expect { narrative.input(:reply, user, post: post) }
|
||||
.to change { Post.count }.by(1)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.edit.not_found',
|
||||
url: first_post.url
|
||||
))
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "@#{discobot_user.username} #{skip_trigger}")
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.delete.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_delete)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user edits the right post' do
|
||||
let(:post_2) { Fabricate(:post, user: post.user, topic: post.topic) }
|
||||
|
||||
it 'should create the right reply' do
|
||||
post_2
|
||||
|
||||
expect do
|
||||
PostRevisor.new(post_2).revise!(post_2.user, raw: 'something new')
|
||||
end.to change { Post.count }.by(1)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.edit.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.delete.instructions')}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_delete)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'delete tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_delete,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user).once
|
||||
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.delete.not_found'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_delete)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.offset(1).last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.recover.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_recover)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user destroys a post in a different topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
PostDestroyer.new(user, other_post).destroy
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_delete)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user deletes a post in the right topic' do
|
||||
it 'should create the right reply' do
|
||||
post
|
||||
|
||||
expect { PostDestroyer.new(user, post).destroy }
|
||||
.to change { Post.count }.by(2)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.delete.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.recover.instructions')}
|
||||
RAW
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_recover)
|
||||
expect(Post.offset(1).last.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
it 'should create the right reply' do
|
||||
post
|
||||
user.update!(admin: true)
|
||||
|
||||
expect { PostDestroyer.new(user, post).destroy }
|
||||
.to_not change { Post.count }
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.delete.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.recover.instructions')}
|
||||
RAW
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_recover)
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'undelete post tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_recover,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.set_data(user, narrative.get_data(user).merge(
|
||||
tutorial_recover: { post_id: '1' }
|
||||
))
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user).once
|
||||
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.recover.not_found'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_recover)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
parent_category = Fabricate(:category, name: 'a')
|
||||
category = Fabricate(:category, parent_category: parent_category, name: 'b')
|
||||
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.category_hashtag.instructions',
|
||||
category: "#a:b"
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_category_hashtag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user recovers a post in a different topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
PostDestroyer.new(user, other_post).destroy
|
||||
PostDestroyer.new(user, other_post).recover
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_recover)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user recovers a post in the right topic' do
|
||||
it 'should create the right reply' do
|
||||
parent_category = Fabricate(:category, name: 'a')
|
||||
category = Fabricate(:category, parent_category: parent_category, name: 'b')
|
||||
post
|
||||
|
||||
PostDestroyer.new(user, post).destroy
|
||||
|
||||
expect { PostDestroyer.new(user, post).recover }
|
||||
.to change { Post.count }.by(1)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.recover.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.category_hashtag.instructions', category: "#a:b")}
|
||||
RAW
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_category_hashtag)
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'category hashtag tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_category_hashtag,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }
|
||||
.to_not change { Post.count }
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym)
|
||||
.to eq(:tutorial_category_hashtag)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.category_hashtag.not_found'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_category_hashtag)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.instructions'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_change_topic_notification_level)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
category = Fabricate(:category)
|
||||
|
||||
post.update!(raw: "Check out this ##{category.slug}")
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.category_hashtag.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.instructions')}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_change_topic_notification_level)
|
||||
end
|
||||
end
|
||||
|
||||
context 'topic notification level tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_change_topic_notification_level,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when notification level is changed for another topic' do
|
||||
it 'should not do anything' do
|
||||
other_topic
|
||||
user
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect do
|
||||
TopicUser.change(
|
||||
user.id,
|
||||
other_topic.id,
|
||||
notification_level: TopicUser.notification_levels[:tracking]
|
||||
)
|
||||
end.to_not change { Post.count }
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_change_topic_notification_level)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.not_found'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_change_topic_notification_level)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.poll.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_poll)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user changed the topic notification level' do
|
||||
it 'should create the right reply' do
|
||||
TopicUser.change(
|
||||
user.id,
|
||||
topic.id,
|
||||
notification_level: TopicUser.notification_levels[:tracking]
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.change_topic_notification_level.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.poll.instructions')}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_poll)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'poll tutorial' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_poll,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_poll)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t('discourse_narrative_bot.advanced_user_narrative.poll.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_poll)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.details.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_details)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "[poll]\n* 1\n* 2\n[/poll]\n")
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.poll.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.advanced_user_narrative.details.instructions')}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_details)
|
||||
end
|
||||
end
|
||||
|
||||
context "details tutorial" do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_details,
|
||||
topic_id: topic.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_details)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.advanced_user_narrative.details.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_details)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
|
||||
expect do
|
||||
DiscourseNarrativeBot::TrackSelector.new(
|
||||
:reply, user, post_id: post.id
|
||||
).select
|
||||
end.to change { Post.count }.by(1)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "[details=\"This is a test\"]wooohoo[/details]")
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.offset(1).last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.advanced_user_narrative.details.reply'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)).to eq({
|
||||
"state" => "end",
|
||||
"topic_id" => topic.id,
|
||||
"track" => described_class.to_s
|
||||
})
|
||||
|
||||
expect(user.badges.where(name: DiscourseNarrativeBot::AdvancedUserNarrative::BADGE_NAME).exists?)
|
||||
.to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,895 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe DiscourseNarrativeBot::NewUserNarrative do
|
||||
let!(:welcome_topic) { Fabricate(:topic, title: 'Welcome to Discourse') }
|
||||
let(:discobot_user) { User.find(-2) }
|
||||
let(:first_post) { Fabricate(:post, user: discobot_user) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
let(:topic) do
|
||||
Fabricate(:private_message_topic, first_post: first_post,
|
||||
topic_allowed_users: [
|
||||
Fabricate.build(:topic_allowed_user, user: discobot_user),
|
||||
Fabricate.build(:topic_allowed_user, user: user),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
let(:narrative) { described_class.new }
|
||||
let(:other_topic) { Fabricate(:topic) }
|
||||
let(:other_post) { Fabricate(:post, topic: other_topic) }
|
||||
let(:profile_page_url) { "#{Discourse.base_url}/users/#{user.username}" }
|
||||
let(:skip_trigger) { DiscourseNarrativeBot::TrackSelector.skip_trigger }
|
||||
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
|
||||
|
||||
before do
|
||||
SiteSetting.discourse_narrative_bot_enabled = true
|
||||
end
|
||||
|
||||
describe '#notify_timeout' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_images,
|
||||
topic_id: topic.id,
|
||||
last_post_id: post.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'should create the right message' do
|
||||
expect { narrative.notify_timeout(user) }.to change { Post.count }.by(1)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.timeout.message',
|
||||
username: user.username,
|
||||
skip_trigger: skip_trigger,
|
||||
reset_trigger: "#{reset_trigger} #{described_class.reset_trigger}",
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reset_bot' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_images, topic_id: topic.id)
|
||||
end
|
||||
|
||||
context 'when trigger is initiated in a PM' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
let(:topic) do
|
||||
topic_allowed_user = Fabricate.build(:topic_allowed_user, user: user)
|
||||
bot = Fabricate.build(:topic_allowed_user, user: discobot_user)
|
||||
Fabricate(:private_message_topic, topic_allowed_users: [topic_allowed_user, bot])
|
||||
end
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
it 'should reset the bot' do
|
||||
narrative.reset_bot(user, post)
|
||||
|
||||
expected_raw = I18n.t('discourse_narrative_bot.new_user_narrative.hello.message',
|
||||
username: user.username, title: SiteSetting.title
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{expected_raw}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.bookmark.instructions', profile_page_url: profile_page_url)}
|
||||
RAW
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(narrative.get_data(user)).to eq({
|
||||
"topic_id" => topic.id,
|
||||
"state" => "tutorial_bookmark",
|
||||
"last_post_id" => new_post.id,
|
||||
"track" => described_class.to_s
|
||||
})
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(new_post.topic.id).to eq(topic.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trigger is not initiated in a PM' do
|
||||
it 'should start the new track in a PM' do
|
||||
narrative.reset_bot(user, other_post)
|
||||
|
||||
expected_raw = I18n.t('discourse_narrative_bot.new_user_narrative.hello.message',
|
||||
username: user.username, title: SiteSetting.title
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{expected_raw}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.bookmark.instructions', profile_page_url: profile_page_url)}
|
||||
RAW
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(narrative.get_data(user)).to eq({
|
||||
"topic_id" => new_post.topic.id,
|
||||
"state" => "tutorial_bookmark",
|
||||
"last_post_id" => new_post.id,
|
||||
"track" => described_class.to_s
|
||||
})
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(new_post.topic.id).to_not eq(topic.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#input' do
|
||||
before do
|
||||
SiteSetting.title = "This is an awesome site!"
|
||||
narrative.set_data(user, state: :begin)
|
||||
end
|
||||
|
||||
describe 'when an error occurs' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_flag, topic_id: topic.id)
|
||||
end
|
||||
|
||||
it 'should revert to the previous state' do
|
||||
narrative.expects(:send).with('init_tutorial_search').raises(StandardError.new('some error'))
|
||||
narrative.expects(:send).with(:reply_to_flag).returns(post)
|
||||
|
||||
expect { narrative.input(:flag, user, post: post) }.to raise_error(StandardError, 'some error')
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when input does not have a valid transition from current state' do
|
||||
before do
|
||||
narrative.set_data(user, state: :begin)
|
||||
end
|
||||
|
||||
it 'should raise the right error' do
|
||||
expect(narrative.input(:something, user, post: post)).to eq(nil)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:begin)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when [:begin, :init]' do
|
||||
it 'should create the right post' do
|
||||
narrative.expects(:enqueue_timeout_job).never
|
||||
|
||||
narrative.input(:init, user, post: nil)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = I18n.t('discourse_narrative_bot.new_user_narrative.hello.message',
|
||||
username: user.username, title: SiteSetting.title
|
||||
)
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{expected_raw}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.bookmark.instructions', profile_page_url: profile_page_url)}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym)
|
||||
.to eq(:tutorial_bookmark)
|
||||
end
|
||||
end
|
||||
|
||||
describe "bookmark tutorial" do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_bookmark, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post.update!(user_id: -2)
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:bookmark, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_bookmark)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when bookmark is not on bot's post" do
|
||||
it 'should not do anything' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
post
|
||||
|
||||
expect { narrative.input(:bookmark, user, post: post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_bookmark)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user).once
|
||||
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.bookmark.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_bookmark)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "@#{discobot_user.username} #{skip_trigger}")
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.onebox.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_onebox)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(user: described_class.discobot_user)
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
narrative.input(:bookmark, user, post: post)
|
||||
new_post = Post.last
|
||||
profile_page_url = "#{Discourse.base_url}/u/#{user.username}"
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.bookmark.reply', profile_page_url: profile_page_url)}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.onebox.instructions')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_onebox)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'onebox tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_onebox, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_onebox)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain onebox' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.onebox.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_onebox)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when user has not liked bot's post" do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.onebox.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_onebox)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.emoji.instructions')
|
||||
)
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_emoji)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'https://en.wikipedia.org/wiki/ROT13')
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.onebox.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.emoji.instructions')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_emoji)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'images tutorial' do
|
||||
let(:post_2) { Fabricate(:post, topic: topic) }
|
||||
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_images,
|
||||
topic_id: topic.id,
|
||||
last_post_id: post_2.id,
|
||||
track: described_class.to_s
|
||||
)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_images)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.flag.instructions',
|
||||
guidelines_url: Discourse.base_url + '/guidelines',
|
||||
about_url: Discourse.base_url + '/about'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when image is not found' do
|
||||
it 'should create the right replies' do
|
||||
PostAction.act(user, post_2, PostActionType.types[:like])
|
||||
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.images.not_found',
|
||||
image_url: "#{Discourse.base_url}/images/dog-walk.gif"
|
||||
))
|
||||
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
new_post = Fabricate(:post,
|
||||
user: user,
|
||||
topic: topic,
|
||||
raw: "<img src='https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg'>"
|
||||
)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: new_post.id).select
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.images.reply')}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.flag.instructions',
|
||||
guidelines_url: "#{Discourse.base_url}/guidelines",
|
||||
about_url: "#{Discourse.base_url}/about"
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
|
||||
post_action = PostAction.last
|
||||
|
||||
expect(post_action.post_action_type_id).to eq(PostActionType.types[:like])
|
||||
expect(post_action.user).to eq(described_class.discobot_user)
|
||||
expect(post_action.post).to eq(new_post)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right replies' do
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.images.not_found',
|
||||
image_url: "#{Discourse.base_url}/images/dog-walk.gif"
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_images)
|
||||
|
||||
new_post = Fabricate(:post,
|
||||
user: user,
|
||||
topic: topic,
|
||||
raw: "<img src='https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg'>"
|
||||
)
|
||||
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: new_post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.images.like_not_found',
|
||||
url: post_2.url
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_images)
|
||||
|
||||
expect(narrative.get_data(user)[:tutorial_images][:post_id])
|
||||
.to eq(new_post.id)
|
||||
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
PostAction.act(user, post_2, PostActionType.types[:like])
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.images.reply')}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.flag.instructions',
|
||||
guidelines_url: "#{Discourse.base_url}/guidelines",
|
||||
about_url: "#{Discourse.base_url}/about"
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
|
||||
post_action = PostAction.last
|
||||
|
||||
expect(post_action.post_action_type_id).to eq(PostActionType.types[:like])
|
||||
expect(post_action.user).to eq(described_class.discobot_user)
|
||||
expect(post_action.post).to eq(new_post)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'fomatting tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_formatting, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_formatting)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain any formatting' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.formatting.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_formatting)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.quoting.instructions',
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_quote)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
["**bold**", "__italic__", "[b]bold[/b]", "[i]italic[/i]"].each do |raw|
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: raw)
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.formatting.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.quoting.instructions')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_quote)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'quote tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_quote, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_quote)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain any quotes' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.quoting.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_quote)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.images.instructions',
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_images)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(
|
||||
raw: '[quote="#{post.user}, post:#{post.post_number}, topic:#{topic.id}"]\n:monkey: :fries:\n[/quote]'
|
||||
)
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.quoting.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.images.instructions')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_images)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'emoji tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_emoji, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_emoji)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain any emoji' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.emoji.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_emoji)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.mention.instructions',
|
||||
discobot_username: discobot_user.username
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_mention)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(
|
||||
raw: ':monkey: :fries:'
|
||||
)
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.emoji.reply')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.mention.instructions',
|
||||
discobot_username: discobot_user.username
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_mention)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'mention tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_mention, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_mention)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain any mentions' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.mention.not_found',
|
||||
username: user.username,
|
||||
discobot_username: discobot_user.username
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_mention)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.formatting.instructions',
|
||||
discobot_username: discobot_user.username
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_formatting)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(
|
||||
raw: '@discobot hello how are you doing today?'
|
||||
)
|
||||
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.mention.reply')}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.formatting.instructions'
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_formatting)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'flag tutorial' do
|
||||
let(:post) { Fabricate(:post, user: described_class.discobot_user, topic: topic) }
|
||||
let(:flag) { Fabricate(:flag, post: post, user: user) }
|
||||
let(:other_post) { Fabricate(:post, user: user, topic: topic) }
|
||||
|
||||
before do
|
||||
flag
|
||||
narrative.set_data(user, state: :tutorial_flag, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post flagged is not for the right topic' do
|
||||
it 'should not do anything' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
flag.update!(post: other_post)
|
||||
|
||||
expect { narrative.input(:flag, user, post: flag.post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post being flagged does not belong to discobot ' do
|
||||
it 'should not do anything' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
flag.update!(post: other_post)
|
||||
|
||||
expect { narrative.input(:flag, user, post: flag.post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user replies to the topic' do
|
||||
it 'should create the right reply' do
|
||||
narrative.input(:reply, user, post: other_post)
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t('discourse_narrative_bot.new_user_narrative.flag.not_found'))
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_flag)
|
||||
end
|
||||
|
||||
describe 'when reply contains the skip trigger' do
|
||||
it 'should create the right reply' do
|
||||
other_post.update!(raw: skip_trigger)
|
||||
described_class.any_instance.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: other_post.id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.search.instructions'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_search)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
|
||||
expect { narrative.input(:flag, user, post: flag.post) }.to change { PostAction.count }.by(-1)
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.new_user_narrative.flag.reply')}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.search.instructions'
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_search)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'search tutorial' do
|
||||
before do
|
||||
narrative.set_data(user, state: :tutorial_search, topic_id: topic.id)
|
||||
end
|
||||
|
||||
describe 'when post is not in the right topic' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
narrative.expects(:enqueue_timeout_job).with(user).never
|
||||
|
||||
expect { narrative.input(:reply, user, post: other_post) }.to_not change { Post.count }
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_search)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post does not contain the right answer' do
|
||||
it 'should create the right reply' do
|
||||
narrative.expects(:enqueue_timeout_job).with(user)
|
||||
narrative.input(:reply, user, post: post)
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.search.not_found'
|
||||
))
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_search)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post contain the right answer' do
|
||||
let(:post) { Fabricate(:post, user: described_class.discobot_user, topic: topic) }
|
||||
let(:flag) { Fabricate(:flag, post: post, user: user) }
|
||||
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_flag,
|
||||
topic_id: topic.id
|
||||
)
|
||||
|
||||
DiscourseNarrativeBot::TrackSelector.new(:flag, user, post_id: flag.post_id).select
|
||||
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:tutorial_search)
|
||||
|
||||
expect(post.reload.topic.first_post.raw).to include(I18n.t(
|
||||
"discourse_narrative_bot.new_user_narrative.search.hidden_message"
|
||||
))
|
||||
end
|
||||
|
||||
it 'should clean up if the tutorial is skipped' do
|
||||
post.update!(raw: skip_trigger)
|
||||
|
||||
expect do
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
end.to change { Post.count }.by(1)
|
||||
|
||||
expect(first_post.reload.raw).to eq('Hello world')
|
||||
expect(narrative.get_data(user)[:state].to_sym).to eq(:end)
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(
|
||||
raw: "#{described_class::SEARCH_ANSWER} this is a capybara"
|
||||
)
|
||||
|
||||
expect do
|
||||
DiscourseNarrativeBot::TrackSelector.new(:reply, user, post_id: post.id).select
|
||||
end.to change { Post.count }.by(2)
|
||||
|
||||
new_post = Post.offset(1).last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.new_user_narrative.search.reply',
|
||||
search_url: "#{Discourse.base_url}/search"
|
||||
).chomp)
|
||||
|
||||
expect(first_post.reload.raw).to eq('Hello world')
|
||||
|
||||
expect(narrative.get_data(user)).to include({
|
||||
"state" => "end",
|
||||
"topic_id" => new_post.topic_id,
|
||||
"track" => described_class.to_s,
|
||||
})
|
||||
|
||||
expect(user.badges.where(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME).exists?)
|
||||
.to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,28 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe DiscourseNarrativeBot::Store do
|
||||
describe '.set' do
|
||||
it 'should set the right value in the plugin store' do
|
||||
key = 'somekey'
|
||||
described_class.set(key, 'yay')
|
||||
plugin_store_row = PluginStoreRow.last
|
||||
|
||||
expect(plugin_store_row.value).to eq('yay')
|
||||
expect(plugin_store_row.plugin_name).to eq(DiscourseNarrativeBot::PLUGIN_NAME)
|
||||
expect(plugin_store_row.key).to eq(key)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get' do
|
||||
it 'should get the right value from the plugin store' do
|
||||
PluginStoreRow.create!(
|
||||
plugin_name: DiscourseNarrativeBot::PLUGIN_NAME,
|
||||
key: 'somekey',
|
||||
value: 'yay',
|
||||
type_name: 'string'
|
||||
)
|
||||
|
||||
expect(described_class.get('somekey')).to eq('yay')
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,704 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe DiscourseNarrativeBot::TrackSelector do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:discobot_user) { described_class.discobot_user }
|
||||
let(:narrative) { DiscourseNarrativeBot::NewUserNarrative.new }
|
||||
|
||||
let(:random_mention_reply) do
|
||||
I18n.t('discourse_narrative_bot.track_selector.random_mention.reply',
|
||||
discobot_username: discobot_user.username,
|
||||
help_trigger: described_class.help_trigger
|
||||
)
|
||||
end
|
||||
|
||||
let(:help_message) do
|
||||
discobot_username = discobot_user.username
|
||||
|
||||
end_message = <<~RAW
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.track_selector.random_mention.tracks',
|
||||
discobot_username: discobot_username,
|
||||
default_track: DiscourseNarrativeBot::NewUserNarrative.reset_trigger,
|
||||
reset_trigger: described_class.reset_trigger,
|
||||
tracks: DiscourseNarrativeBot::NewUserNarrative.reset_trigger
|
||||
)}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.track_selector.random_mention.bot_actions',
|
||||
discobot_username: discobot_username,
|
||||
dice_trigger: described_class.dice_trigger,
|
||||
quote_trigger: described_class.quote_trigger,
|
||||
magic_8_ball_trigger: described_class.magic_8_ball_trigger
|
||||
)}
|
||||
RAW
|
||||
|
||||
end_message.chomp
|
||||
end
|
||||
|
||||
describe '#select' do
|
||||
context 'in a PM with discobot' do
|
||||
let(:first_post) { Fabricate(:post, user: discobot_user) }
|
||||
|
||||
let(:topic) do
|
||||
Fabricate(:private_message_topic, first_post: first_post,
|
||||
topic_allowed_users: [
|
||||
Fabricate.build(:topic_allowed_user, user: discobot_user),
|
||||
Fabricate.build(:topic_allowed_user, user: user),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
|
||||
context 'during a tutorial track' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :tutorial_formatting,
|
||||
topic_id: topic.id,
|
||||
track: "DiscourseNarrativeBot::NewUserNarrative"
|
||||
)
|
||||
end
|
||||
|
||||
context 'when bot is mentioned' do
|
||||
it 'should select the right track' do
|
||||
post.update!(raw: '@discobot show me what you can do')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
"discourse_narrative_bot.new_user_narrative.formatting.not_found"
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bot is replied to' do
|
||||
it 'should select the right track' do
|
||||
post.update!(
|
||||
raw: 'show me what you can do',
|
||||
reply_to_post_number: first_post.post_number
|
||||
)
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
"discourse_narrative_bot.new_user_narrative.formatting.not_found"
|
||||
))
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.track_selector.do_not_understand.first_response',
|
||||
reset_trigger: "#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}",
|
||||
)}
|
||||
|
||||
#{I18n.t(
|
||||
'discourse_narrative_bot.track_selector.do_not_understand.track_response',
|
||||
reset_trigger: "#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}",
|
||||
skip_trigger: described_class.skip_trigger
|
||||
)}
|
||||
RAW
|
||||
|
||||
expect(Post.last.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user thank the bot' do
|
||||
it 'should like the post' do
|
||||
post.update!(raw: 'thanks!')
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to change { PostAction.count }.by(1)
|
||||
|
||||
post_action = PostAction.last
|
||||
|
||||
expect(post_action.post).to eq(post)
|
||||
expect(post_action.post_action_type_id).to eq(PostActionType.types[:like])
|
||||
|
||||
post = Post.last
|
||||
|
||||
expect(Post.last).to eq(post)
|
||||
|
||||
expect(DiscourseNarrativeBot::NewUserNarrative.new.get_data(user)['state'])
|
||||
.to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reply contains a reset trigger' do
|
||||
it 'should reset the track' do
|
||||
post.update!(
|
||||
raw: "#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}"
|
||||
)
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(DiscourseNarrativeBot::NewUserNarrative.new.get_data(user)['state'])
|
||||
.to eq("tutorial_bookmark")
|
||||
end
|
||||
|
||||
describe 'reset trigger in surrounded by quotes' do
|
||||
it 'should reset the track' do
|
||||
post.update!(
|
||||
raw: "'#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}'"
|
||||
)
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(DiscourseNarrativeBot::NewUserNarrative.new.get_data(user)['state'])
|
||||
.to eq("tutorial_bookmark")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'reset trigger in a middle of a sentence' do
|
||||
describe 'when post is less than reset trigger exact match limit' do
|
||||
it 'should reset the track' do
|
||||
post.update!(
|
||||
raw: "I would like to #{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger} now"
|
||||
)
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(DiscourseNarrativeBot::NewUserNarrative.new.get_data(user)['state'])
|
||||
.to eq("tutorial_bookmark")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when post exceeds reset trigger exact match limit' do
|
||||
it 'should not reset the track' do
|
||||
post.update!(
|
||||
raw: "I would like to #{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger} now #{'a' * described_class::RESET_TRIGGER_EXACT_MATCH_LENGTH}"
|
||||
)
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to change { Post.count }.by(1)
|
||||
|
||||
expect(DiscourseNarrativeBot::NewUserNarrative.new.get_data(user)['state'])
|
||||
.to eq("tutorial_formatting")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'start/reset advanced track' do
|
||||
before do
|
||||
post.update!(
|
||||
raw: "@#{discobot_user.username} #{described_class.reset_trigger} #{DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger}"
|
||||
)
|
||||
end
|
||||
|
||||
context 'when new user track has not been completed' do
|
||||
it 'should not start the track' do
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(DiscourseNarrativeBot::Store.get(user.id)['track'])
|
||||
.to eq(DiscourseNarrativeBot::NewUserNarrative.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when new user track has been completed' do
|
||||
it 'should start the track' do
|
||||
BadgeGranter.grant(
|
||||
Badge.find_by(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME),
|
||||
user
|
||||
)
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(DiscourseNarrativeBot::Store.get(user.id)['track'])
|
||||
.to eq(DiscourseNarrativeBot::AdvancedUserNarrative.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'at the end of a tutorial track' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :end,
|
||||
topic_id: topic.id,
|
||||
track: "DiscourseNarrativeBot::NewUserNarrative"
|
||||
)
|
||||
end
|
||||
|
||||
context 'generic replies' do
|
||||
after do
|
||||
$redis.del("#{described_class::GENERIC_REPLIES_COUNT_PREFIX}#{user.id}")
|
||||
end
|
||||
|
||||
it 'should create the right generic do not understand responses' do
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.track_selector.do_not_understand.first_response',
|
||||
reset_trigger: "#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}",
|
||||
))
|
||||
|
||||
described_class.new(:reply, user, post_id: Fabricate(:post,
|
||||
topic: new_post.topic,
|
||||
user: user,
|
||||
reply_to_post_number: new_post.post_number
|
||||
).id).select
|
||||
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.track_selector.do_not_understand.second_response',
|
||||
reset_trigger: "#{described_class.reset_trigger} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}",
|
||||
))
|
||||
|
||||
new_post = Fabricate(:post,
|
||||
topic: new_post.topic,
|
||||
user: user,
|
||||
reply_to_post_number: new_post.post_number
|
||||
)
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: new_post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when discobot is mentioned at the end of a track' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'Show me what you can do @discobot')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(random_mention_reply)
|
||||
end
|
||||
|
||||
describe 'when asking discobot for help' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'show me what you can do @discobot display help')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to include(help_message)
|
||||
end
|
||||
|
||||
describe 'as an admin or moderator' do
|
||||
it 'should include the commands to start the advanced user track' do
|
||||
user.update!(moderator: true)
|
||||
post.update!(raw: 'Show me what you can do @discobot display help')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to include(
|
||||
DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'as a user that has completed the new user track' do
|
||||
it 'should include the commands to start the advanced user track' do
|
||||
narrative.set_data(user,
|
||||
state: :end,
|
||||
topic_id: post.topic.id,
|
||||
track: "DiscourseNarrativeBot::NewUserNarrative",
|
||||
)
|
||||
|
||||
BadgeGranter.grant(
|
||||
Badge.find_by(name: DiscourseNarrativeBot::NewUserNarrative::BADGE_NAME),
|
||||
user
|
||||
)
|
||||
|
||||
post.update!(raw: 'Show me what you can do @discobot display help')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to include(
|
||||
DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when discobot is asked to roll dice' do
|
||||
before do
|
||||
narrative.set_data(user,
|
||||
state: :end,
|
||||
topic_id: topic.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'roll 2d1')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(I18n.t(
|
||||
"discourse_narrative_bot.dice.results", results: '1, 1'
|
||||
))
|
||||
end
|
||||
|
||||
describe 'when range of dice request is too high' do
|
||||
before do
|
||||
srand(1)
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
stub_request(:get, "https://www.wired.com/2016/05/mathematical-challenge-of-designing-the-worlds-most-complex-120-sided-dice")
|
||||
.to_return(status: 200, body: "", headers: {})
|
||||
|
||||
post.update!(raw: "roll 1d#{DiscourseNarrativeBot::Dice::MAXIMUM_RANGE_OF_DICE + 1}")
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.dice.out_of_range')}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.dice.results', results: '38')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when number of dice to roll is too high' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "roll #{DiscourseNarrativeBot::Dice::MAXIMUM_NUM_OF_DICE + 1}d1")
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expected_raw = <<~RAW
|
||||
#{I18n.t('discourse_narrative_bot.dice.not_enough_dice', num_of_dice: DiscourseNarrativeBot::Dice::MAXIMUM_NUM_OF_DICE)}
|
||||
|
||||
#{I18n.t('discourse_narrative_bot.dice.results', results: '1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1')}
|
||||
RAW
|
||||
|
||||
expect(new_post.raw).to eq(expected_raw.chomp)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when dice combination is invalid' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: "roll 0d1")
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t(
|
||||
'discourse_narrative_bot.dice.invalid'
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in a normal PM with discobot' do
|
||||
describe 'when discobot is replied to' do
|
||||
it 'should create the right reply' do
|
||||
SiteSetting.discourse_narrative_bot_disable_public_replies = true
|
||||
post.update!(raw: 'Show me what you can do @discobot')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(random_mention_reply)
|
||||
end
|
||||
|
||||
it 'should not rate limit help message' do
|
||||
post.update!(raw: '@discobot')
|
||||
other_post = Fabricate(:post, raw: 'discobot', topic: post.topic)
|
||||
|
||||
[post, other_post].each do |reply|
|
||||
described_class.new(:reply, user, post_id: reply.id).select
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'random discobot mentions' do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
|
||||
describe 'when discobot public replies are disabled' do
|
||||
before do
|
||||
SiteSetting.discourse_narrative_bot_disable_public_replies = true
|
||||
end
|
||||
|
||||
describe 'when discobot is mentioned' do
|
||||
it 'should not reply' do
|
||||
post.update!(raw: 'Show me what you can do @discobot')
|
||||
|
||||
expect do
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
end.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when discobot is mentioned' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'Show me what you can do @discobot')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
expect(new_post.raw).to eq(random_mention_reply)
|
||||
end
|
||||
|
||||
describe 'rate limiting random reply message in public topic' do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:other_post) { Fabricate(:post, raw: '@discobot show me something', topic: topic) }
|
||||
let(:post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
after do
|
||||
$redis.flushall
|
||||
end
|
||||
|
||||
describe 'when random reply massage has been displayed in the last 6 hours' do
|
||||
it 'should not do anything' do
|
||||
$redis.set(
|
||||
"#{described_class::PUBLIC_DISPLAY_BOT_HELP_KEY}:#{other_post.topic_id}",
|
||||
post.post_number - 11
|
||||
)
|
||||
|
||||
$redis.class.any_instance.expects(:ttl).returns(19.hours.to_i)
|
||||
|
||||
user
|
||||
post.update!(raw: "Show me what you can do @discobot")
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when random reply message has not been displayed in the last 6 hours' do
|
||||
it 'should create the right reply' do
|
||||
$redis.set(
|
||||
"#{described_class::PUBLIC_DISPLAY_BOT_HELP_KEY}:#{other_post.topic_id}",
|
||||
post.post_number - 11
|
||||
)
|
||||
|
||||
$redis.class.any_instance.expects(:ttl).returns(7.hours.to_i)
|
||||
|
||||
user
|
||||
post.update!(raw: "Show me what you can do @discobot")
|
||||
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when random reply message has been displayed in the last 10 replies' do
|
||||
it 'should not do anything' do
|
||||
described_class.new(:reply, user, post_id: other_post.id).select
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
|
||||
expect($redis.get(
|
||||
"#{described_class::PUBLIC_DISPLAY_BOT_HELP_KEY}:#{other_post.topic_id}"
|
||||
).to_i).to eq(other_post.post_number.to_i)
|
||||
|
||||
user
|
||||
post.update!(raw: "Show me what you can do @discobot")
|
||||
|
||||
expect do
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
end.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when asking discobot for help' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: '@discobot display help')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(help_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when asking discobot to start new user track' do
|
||||
describe 'invalid text' do
|
||||
it 'should not trigger the bot' do
|
||||
post.update!(raw: '`@discobot start new user track`')
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when discobot is asked to roll dice' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: '@discobot roll 2d1')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(
|
||||
I18n.t("discourse_narrative_bot.dice.results",
|
||||
results: '1, 1'
|
||||
))
|
||||
end
|
||||
|
||||
describe 'when dice roll is requested incorrectly' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'roll 2d1 @discobot')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when roll dice command is present inside a quote' do
|
||||
it 'should ignore the command' do
|
||||
user
|
||||
post.update!(raw: '[quote="Donkey, post:6, topic:1"]@discobot roll 2d1[/quote]')
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a quote is requested' do
|
||||
it 'should create the right reply' do
|
||||
Excon.stub({ method: :get, hostname: 'api.forismatic.com' },
|
||||
status: 200,
|
||||
body: "{\"quoteText\":\"Be Like Water\",\"quoteAuthor\":\"Bruce Lee\"}"
|
||||
)
|
||||
|
||||
['@discobot quote', 'hello @discobot quote there'].each do |raw|
|
||||
post.update!(raw: raw)
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
new_post = Post.last
|
||||
|
||||
expect(new_post.raw).to eq(
|
||||
I18n.t("discourse_narrative_bot.quote.results",
|
||||
quote: "Be Like Water", author: "Bruce Lee"
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when quote is requested incorrectly' do
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: 'quote @discobot')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when quote command is present inside a onebox or quote' do
|
||||
it 'should ignore the command' do
|
||||
user
|
||||
post.update!(raw: '[quote="Donkey, post:6, topic:1"]@discobot quote[/quote]')
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user requesting quote has a preferred locale' do
|
||||
before do
|
||||
SiteSetting.allow_user_locale = true
|
||||
user.update!(locale: 'it')
|
||||
srand(1)
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: '@discobot quote')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
key = "discourse_narrative_bot.quote.6"
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.quote.results',
|
||||
quote: I18n.t("#{key}.quote"), author: I18n.t("#{key}.author")
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when magic 8 ball is requested' do
|
||||
before do
|
||||
srand(1)
|
||||
end
|
||||
|
||||
it 'should create the right reply' do
|
||||
post.update!(raw: '@discobot fortune')
|
||||
described_class.new(:reply, user, post_id: post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(I18n.t('discourse_narrative_bot.magic_8_ball.result',
|
||||
result: I18n.t("discourse_narrative_bot.magic_8_ball.answers.6")
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when bot is asked to reset/start a track' do
|
||||
describe 'when user likes a post containing a reset trigger' do
|
||||
it 'should not start the track' do
|
||||
another_post = Fabricate(:post,
|
||||
user: Fabricate(:user),
|
||||
topic: topic,
|
||||
raw: "@discobot start new user"
|
||||
)
|
||||
|
||||
user
|
||||
|
||||
expect do
|
||||
PostAction.act(user, another_post, PostActionType.types[:like])
|
||||
end.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pm to self' do
|
||||
let(:other_topic) do
|
||||
topic_allowed_user = Fabricate.build(:topic_allowed_user, user: user)
|
||||
Fabricate(:private_message_topic, topic_allowed_users: [topic_allowed_user])
|
||||
end
|
||||
|
||||
let(:other_post) { Fabricate(:post, topic: other_topic) }
|
||||
|
||||
describe 'when a new message is made' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: other_post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pms to bot' do
|
||||
let(:other_topic) do
|
||||
topic_allowed_user = Fabricate.build(:topic_allowed_user, user: user)
|
||||
bot = Fabricate.build(:topic_allowed_user, user: discobot_user)
|
||||
Fabricate(:private_message_topic, topic_allowed_users: [topic_allowed_user, bot])
|
||||
end
|
||||
|
||||
let(:other_post) { Fabricate(:post, topic: other_topic) }
|
||||
|
||||
describe 'when a new like is made' do
|
||||
it 'should not do anything' do
|
||||
other_post
|
||||
expect { described_class.new(:like, user, post_id: other_post.id).select }
|
||||
.to_not change { Post.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a new message is made' do
|
||||
it 'should create the right reply' do
|
||||
described_class.new(:reply, user, post_id: other_post.id).select
|
||||
|
||||
expect(Post.last.raw).to eq(random_mention_reply)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when user thanks the bot' do
|
||||
it 'should like the post' do
|
||||
other_post.update!(raw: 'thanks!')
|
||||
|
||||
expect { described_class.new(:reply, user, post_id: other_post.id).select }
|
||||
.to change { PostAction.count }.by(1)
|
||||
|
||||
post_action = PostAction.last
|
||||
|
||||
expect(post_action.post).to eq(other_post)
|
||||
expect(post_action.post_action_type_id).to eq(PostActionType.types[:like])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user