Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,18 @@
module CsvSpecHelpers
# Generates a Rack::Test::UploadedFile object from an array of arrays
# data: Accepts an array of arrays as the only argument
def generate_csv_file(data)
# Create a temporary file
temp_file = Tempfile.new(['data', '.csv'])
# Write the array of arrays to the temporary file as CSV
CSV.open(temp_file.path, 'wb') do |csv|
data.each do |row|
csv << row
end
end
# Create and return a Rack::Test::UploadedFile object
Rack::Test::UploadedFile.new(temp_file.path, 'text/csv')
end
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
RSpec.shared_examples 'encrypted external credential' do |factory:, attribute:, value: 'secret-token'|
before do
skip('encryption keys missing; see run_mfa_spec workflow') unless Chatwoot.encryption_configured?
if defined?(Facebook::Messenger::Subscriptions)
allow(Facebook::Messenger::Subscriptions).to receive(:subscribe).and_return(true)
allow(Facebook::Messenger::Subscriptions).to receive(:unsubscribe).and_return(true)
end
end
it "encrypts #{attribute} at rest" do
record = create(factory, attribute => value)
raw_stored_value = record.reload.read_attribute_before_type_cast(attribute).to_s
expect(raw_stored_value).to be_present
expect(raw_stored_value).not_to include(value)
expect(record.public_send(attribute)).to eq(value)
expect(record.encrypted_attribute?(attribute)).to be(true)
end
end

View File

@@ -0,0 +1,9 @@
module FileUploadHelpers
def get_blob_for(file_path, content_type)
ActiveStorage::Blob.create_and_upload!(
io: File.open(file_path, 'rb'),
filename: File.basename(file_path),
content_type: content_type
)
end
end

View File

@@ -0,0 +1,20 @@
module InstagramSpecHelpers
def create_instagram_contact_for_sender(sender_id, inbox)
contact = Contact.find_by(identifier: sender_id)
if contact.nil?
contact = create(:contact, identifier: sender_id, name: 'Jane Dae')
create(:contact_inbox, contact_id: contact.id, inbox_id: inbox.id, source_id: sender_id)
end
contact
end
def instagram_user_response_object_for(sender_id, account_id)
{
name: 'Jane',
id: sender_id,
account_id: account_id,
profile_pic: 'https://chatwoot-assets.local/sample.png',
username: 'some_user_name'
}
end
end

View File

@@ -0,0 +1,3 @@
# "not_change" is needed to support chaining "change" matchers
# see https://stackoverflow.com/a/34969429/58876
RSpec::Matchers.define_negated_matcher :not_change, :change

View File

@@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'net/http'
require 'json'
RSpec.configure do |config|
# Run OpenSearch connectivity check only for specs tagged with :opensearch
config.before(:context, :opensearch) do
opensearch_url = ENV.fetch('OPENSEARCH_URL', nil)
next if opensearch_url.blank?
puts "\n==== OpenSearch Connectivity Check ===="
puts "OPENSEARCH_URL: #{opensearch_url}"
begin
uri = URI.parse("#{opensearch_url}/_cluster/health")
response = Net::HTTP.get_response(uri)
raise "OpenSearch is not reachable at #{opensearch_url}. HTTP Status: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
health = JSON.parse(response.body)
status = health['status']
puts "Cluster status: #{status}"
puts "Cluster name: #{health['cluster_name']}"
puts "Number of nodes: #{health['number_of_nodes']}"
raise "OpenSearch cluster is not healthy. Status: #{status}" unless %w[green yellow].include?(status)
puts '✓ OpenSearch is healthy and ready for tests'
puts "========================================\n\n"
rescue StandardError => e
puts "\n❌ ERROR: Failed to connect to OpenSearch"
puts "Message: #{e.message}"
puts "========================================\n\n"
raise "OpenSearch connectivity check failed: #{e.message}"
end
end
end

View File

@@ -0,0 +1,120 @@
module SlackStubs
def slack_url_verification_stub
{
token: 'Jhj5dZrVaK7ZwHHjRyZWjbDl',
challenge: '3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P',
type: 'url_verification'
}
end
def slack_message_stub
{
token: '[FILTERED]',
team_id: 'TLST3048H',
api_app_id: 'A012S5UETV4',
event: message_event,
type: 'event_callback',
event_id: 'Ev013QUX3WV6',
event_time: 1_588_623_033,
authed_users: '[FILTERED]',
webhook: {}
}
end
def slack_attachment_stub
slack_message_stub.merge({ event: message_event_without_blocks })
end
def slack_message_stub_without_thread_ts
{
token: '[FILTERED]',
team_id: '',
api_app_id: '',
event: {
type: 'message',
client_msg_id: 'ffc6e64e-6f0c-4a3d-b594-faa6b44e48ab',
text: 'this is test',
user: 'ULYPAKE5S',
ts: '1588623033.006000',
team: 'TLST3048H'
},
type: 'event_callback',
event_id: '',
event_time: 1_588_623_033,
authed_users: '[FILTERED]',
webhook: {}
}
end
def message_event
{
client_msg_id: 'ffc6e64e-6f0c-4a3d-b594-faa6b44e48ab',
type: 'message',
text: 'this is test <https://chatwoot.com> Hey <@U019KT237LP|Sojan> Test again',
user: 'ULYPAKE5S',
ts: '1588623033.006000',
team: 'TLST3048H',
blocks: message_blocks,
files: file_stub,
thread_ts: '1588623023.005900',
channel: 'G01354F6A6Q',
event_ts: '1588623033.006000',
channel_type: 'group'
}
end
def file_stub
[
{
mimetype: 'image/png',
url_private: 'https://chatwoot-assets.local/sample.png',
name: 'name_of_the_file',
title: 'title_of_the_file',
filetype: 'png',
url_private_download: 'https://chatwoot-assets.local/sample.png'
}
]
end
def message_blocks
[
{
type: 'rich_text',
block_id: 'jaIv3',
elements: [
{
type: 'rich_text_section',
elements: [{
type: 'text',
text: 'this is test'
}]
}
]
}
]
end
def message_event_without_blocks
{
client_msg_id: 'ffc6e64e-6f0c-4a3d-b594-faa6b44e48ab',
type: 'message',
text: 'this is test <https://chatwoot.com> Hey <@U019KT237LP|Sojan> Test again',
user: 'ULYPAKE5S',
ts: '1588623033.006000',
files: file_stub,
thread_ts: '1588623023.005900',
channel: 'G01354F6A6Q'
}
end
def link_shared_event
{
type: 'link_shared',
user: 'ULYPAKE5S',
source: 'conversations_history',
unfurl_id: 'C7NQEAE5Q.1695111587.937099.7e240338c6d2053fb49f56808e7c1f619f6ef317c39ebc59fc4af1cc30dce49b',
channel: 'G01354F6A6Q'
}
end
end