mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-02-19 18:04:51 +00:00
Initial implementation allowed internet wide sharing of AI conversations, on sites that require login. This feature can be an anti feature for private sites cause they can not share conversations internally. For now we are removing support for public sharing on login required sites, if the community need the feature we can consider adding a setting.
281 lines
11 KiB
Ruby
281 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails_helper"
|
|
|
|
RSpec.describe DiscourseAi::AiBot::SharedAiConversationsController do
|
|
before do
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
|
|
SiteSetting.ai_bot_enabled = true
|
|
SiteSetting.ai_bot_allowed_groups = "10"
|
|
SiteSetting.ai_bot_public_sharing_allowed_groups = "10"
|
|
end
|
|
|
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
|
fab!(:topic)
|
|
fab!(:pm) { Fabricate(:private_message_topic) }
|
|
fab!(:user_pm) { Fabricate(:private_message_topic, recipient: user) }
|
|
|
|
fab!(:bot_user) do
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
|
|
SiteSetting.ai_bot_enabled = true
|
|
SiteSetting.ai_bot_allowed_groups = "10"
|
|
SiteSetting.ai_bot_public_sharing_allowed_groups = "10"
|
|
User.find(DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)
|
|
end
|
|
|
|
fab!(:user_pm_share) do
|
|
pm_topic = Fabricate(:private_message_topic, user: user, recipient: bot_user)
|
|
# a different unknown user
|
|
Fabricate(:post, topic: pm_topic, user: user)
|
|
Fabricate(:post, topic: pm_topic, user: bot_user)
|
|
Fabricate(:post, topic: pm_topic, user: user)
|
|
pm_topic
|
|
end
|
|
|
|
let(:path) { "/discourse-ai/ai-bot/shared-ai-conversations" }
|
|
let(:shared_conversation) { SharedAiConversation.share_conversation(user, user_pm_share) }
|
|
|
|
def share_error(key)
|
|
I18n.t("discourse_ai.share_ai.errors.#{key}")
|
|
end
|
|
|
|
describe "POST create" do
|
|
context "when logged in" do
|
|
before { sign_in(user) }
|
|
|
|
it "denies creating a new shared conversation on public topics" do
|
|
post "#{path}.json", params: { topic_id: topic.id }
|
|
expect(response).not_to have_http_status(:success)
|
|
|
|
expect(response.parsed_body["errors"]).to eq([share_error(:not_allowed)])
|
|
expect(response.parsed_body["errors"].to_s).not_to include("Translation missing")
|
|
end
|
|
|
|
it "denies creating a new shared conversation for a random PM" do
|
|
post "#{path}.json", params: { topic_id: pm.id }
|
|
expect(response).not_to have_http_status(:success)
|
|
|
|
expect(response.parsed_body["errors"]).to eq([share_error(:not_allowed)])
|
|
expect(response.parsed_body["errors"].to_s).not_to include("Translation missing")
|
|
end
|
|
|
|
it "denies creating a shared conversation for my PMs not with bots" do
|
|
post "#{path}.json", params: { topic_id: user_pm.id }
|
|
expect(response).not_to have_http_status(:success)
|
|
expect(response.parsed_body["errors"]).to eq([share_error(:other_people_in_pm)])
|
|
expect(response.parsed_body["errors"].to_s).not_to include("Translation missing")
|
|
end
|
|
|
|
it "denies creating a shared conversation for my PMs with bots that also have other users" do
|
|
pm_topic = Fabricate(:private_message_topic, user: user, recipient: bot_user)
|
|
# a different unknown user
|
|
Fabricate(:post, topic: pm_topic)
|
|
post "#{path}.json", params: { topic_id: pm_topic.id }
|
|
expect(response).not_to have_http_status(:success)
|
|
|
|
expect(response.parsed_body["errors"]).to eq([share_error(:other_content_in_pm)])
|
|
expect(response.parsed_body["errors"].to_s).not_to include("Translation missing")
|
|
end
|
|
|
|
it "allows creating a shared conversation for my PMs with bots only" do
|
|
post "#{path}.json", params: { topic_id: user_pm_share.id }
|
|
expect(response).to have_http_status(:success)
|
|
end
|
|
|
|
context "when secure uploads are enabled" do
|
|
let(:upload_1) { Fabricate(:s3_image_upload, user: bot_user, secure: true) }
|
|
let(:upload_2) { Fabricate(:s3_image_upload, user: bot_user, secure: true) }
|
|
let(:post_with_upload_1) { Fabricate(:post, topic: user_pm_share, user: bot_user) }
|
|
let(:post_with_upload_2) { Fabricate(:post, topic: user_pm_share, user: bot_user) }
|
|
|
|
before do
|
|
enable_secure_uploads
|
|
stub_s3_store
|
|
SiteSetting.secure_uploads_pm_only = true
|
|
FileStore::S3Store.any_instance.stubs(:update_upload_ACL).returns(true)
|
|
Jobs.run_immediately!
|
|
|
|
upload_1.update!(
|
|
access_control_post: post_with_upload_1,
|
|
sha1: SecureRandom.hex(20),
|
|
original_sha1: upload_1.sha1,
|
|
)
|
|
upload_2.update!(
|
|
access_control_post: post_with_upload_2,
|
|
sha1: SecureRandom.hex(20),
|
|
original_sha1: upload_2.sha1,
|
|
)
|
|
post_with_upload_1.update!(
|
|
raw: "This is a post with a cool AI generated picture ",
|
|
)
|
|
post_with_upload_2.update!(
|
|
raw:
|
|
"Another post that has been birthed by AI with a picture ",
|
|
)
|
|
end
|
|
|
|
it "marks all of those uploads as not secure when sharing the topic" do
|
|
post "#{path}.json", params: { topic_id: user_pm_share.id }
|
|
expect(response).to have_http_status(:success)
|
|
expect(upload_1.reload.secure).to eq(false)
|
|
expect(upload_2.reload.secure).to eq(false)
|
|
end
|
|
|
|
it "rebakes any posts in the topic with uploads attached when sharing the topic so image urls become non-secure" do
|
|
post_1_cooked = post_with_upload_1.cooked
|
|
post_2_cooked = post_with_upload_2.cooked
|
|
|
|
post "#{path}.json", params: { topic_id: user_pm_share.id }
|
|
expect(response).to have_http_status(:success)
|
|
|
|
expect(post_with_upload_1.reload.cooked).not_to eq(post_1_cooked)
|
|
expect(post_with_upload_1.reload.cooked).not_to include("secure-uploads")
|
|
expect(post_with_upload_2.reload.cooked).not_to eq(post_2_cooked)
|
|
expect(post_with_upload_2.reload.cooked).not_to include("secure-uploads")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when not logged in" do
|
|
it "requires login" do
|
|
post "#{path}.json", params: { topic_id: topic.id }
|
|
expect(response).not_to have_http_status(:success)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "DELETE destroy" do
|
|
context "when logged in" do
|
|
before { sign_in(user) }
|
|
|
|
it "deletes the shared conversation" do
|
|
delete "#{path}/#{shared_conversation.share_key}.json"
|
|
expect(response).to have_http_status(:success)
|
|
expect(SharedAiConversation.exists?(shared_conversation.id)).to be_falsey
|
|
end
|
|
|
|
it "returns an error if the shared conversation is not found" do
|
|
delete "#{path}/123.json"
|
|
expect(response).not_to have_http_status(:success)
|
|
end
|
|
|
|
context "when secure uploads are enabled" do
|
|
let(:upload_1) { Fabricate(:s3_image_upload, user: bot_user, secure: false) }
|
|
let(:upload_2) { Fabricate(:s3_image_upload, user: bot_user, secure: false) }
|
|
|
|
before do
|
|
enable_secure_uploads
|
|
stub_s3_store
|
|
SiteSetting.secure_uploads_pm_only = true
|
|
FileStore::S3Store.any_instance.stubs(:update_upload_ACL).returns(true)
|
|
Jobs.run_immediately!
|
|
|
|
upload_1.update!(
|
|
access_control_post: shared_conversation.target.posts.first,
|
|
sha1: SecureRandom.hex(20),
|
|
original_sha1: upload_1.sha1,
|
|
)
|
|
upload_2.update!(
|
|
access_control_post: shared_conversation.target.posts.second,
|
|
sha1: SecureRandom.hex(20),
|
|
original_sha1: upload_2.sha1,
|
|
)
|
|
shared_conversation.target.posts.first.update!(
|
|
raw: "This is a post with a cool AI generated picture ",
|
|
)
|
|
shared_conversation.target.posts.second.update!(
|
|
raw:
|
|
"Another post that has been birthed by AI with a picture ",
|
|
)
|
|
end
|
|
|
|
it "marks all uploads in the PM back as secure when unsharing the conversation" do
|
|
delete "#{path}/#{shared_conversation.share_key}.json"
|
|
expect(response).to have_http_status(:success)
|
|
expect(upload_1.reload.secure).to eq(true)
|
|
expect(upload_2.reload.secure).to eq(true)
|
|
end
|
|
|
|
it "rebakes any posts in the topic with uploads attached when sharing the topic so image urls become secure" do
|
|
post_1_cooked = shared_conversation.target.posts.first.cooked
|
|
post_2_cooked = shared_conversation.target.posts.second.cooked
|
|
|
|
delete "#{path}/#{shared_conversation.share_key}.json"
|
|
expect(response).to have_http_status(:success)
|
|
|
|
expect(shared_conversation.target.posts.first.reload.cooked).not_to eq(post_1_cooked)
|
|
expect(shared_conversation.target.posts.first.reload.cooked).to include("secure-uploads")
|
|
expect(shared_conversation.target.posts.second.reload.cooked).not_to eq(post_2_cooked)
|
|
expect(shared_conversation.target.posts.second.reload.cooked).to include("secure-uploads")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when not logged in" do
|
|
it "requires login" do
|
|
delete "#{path}/#{shared_conversation.share_key}.json"
|
|
expect(response).not_to have_http_status(:success)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET preview" do
|
|
it "denies preview from logged out users" do
|
|
get "#{path}/preview/#{user_pm_share.id}.json"
|
|
expect(response).not_to have_http_status(:success)
|
|
end
|
|
|
|
context "when logged in" do
|
|
before { sign_in(user) }
|
|
|
|
it "renders the shared conversation" do
|
|
get "#{path}/preview/#{user_pm_share.id}.json"
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body["llm_name"]).to eq("Claude-2")
|
|
expect(response.parsed_body["error"]).to eq(nil)
|
|
expect(response.parsed_body["share_key"]).to eq(nil)
|
|
expect(response.parsed_body["context"].length).to eq(3)
|
|
|
|
shared_conversation
|
|
get "#{path}/preview/#{user_pm_share.id}.json"
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body["share_key"]).to eq(shared_conversation.share_key)
|
|
|
|
SiteSetting.ai_bot_public_sharing_allowed_groups = ""
|
|
get "#{path}/preview/#{user_pm_share.id}.json"
|
|
expect(response).not_to have_http_status(:success)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET show" do
|
|
it "redirects to home page if site require login" do
|
|
SiteSetting.login_required = true
|
|
get "#{path}/#{shared_conversation.share_key}"
|
|
expect(response).to redirect_to("/login")
|
|
end
|
|
|
|
it "renders the shared conversation" do
|
|
get "#{path}/#{shared_conversation.share_key}"
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.headers["Cache-Control"]).to eq("max-age=60, public")
|
|
expect(response.headers["X-Robots-Tag"]).to eq("noindex")
|
|
expect(response.body).not_to include("Translation missing")
|
|
end
|
|
|
|
it "is also able to render in json format" do
|
|
get "#{path}/#{shared_conversation.share_key}.json"
|
|
expect(response.parsed_body["llm_name"]).to eq("Claude-2")
|
|
expect(response.headers["X-Robots-Tag"]).to eq("noindex")
|
|
end
|
|
|
|
it "returns an error if the shared conversation is not found" do
|
|
get "#{path}/123"
|
|
expect(response).to have_http_status(:not_found)
|
|
end
|
|
end
|
|
end
|