Merge branch 'develop' into feat/new-auth-screens

This commit is contained in:
Sivin Varghese 2022-10-03 10:19:31 +05:30 committed by GitHub
commit 100f0b07ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 284 additions and 96 deletions

View file

@ -31,7 +31,7 @@ class ContactInboxBuilder
return unless @contact.phone_number return unless @contact.phone_number
# whatsapp doesn't want the + in e164 format # whatsapp doesn't want the + in e164 format
"#{@contact.phone_number}.delete('+')" @contact.phone_number.delete('+').to_s
end end
def twilio_source_id def twilio_source_id

View file

@ -51,6 +51,6 @@ class Platform::Api::V1::UsersController < PlatformController
end end
def user_params def user_params
params.permit(:name, :email, :password, custom_attributes: {}) params.permit(:name, :display_name, :email, :password, custom_attributes: {})
end end
end end

View file

@ -28,7 +28,7 @@ import {
suggestionsPlugin, suggestionsPlugin,
triggerCharacters, triggerCharacters,
} from '@chatwoot/prosemirror-schema/src/mentions/plugin'; } from '@chatwoot/prosemirror-schema/src/mentions/plugin';
import { EditorState } from 'prosemirror-state'; import { EditorState, Selection } from 'prosemirror-state';
import { defaultMarkdownParser } from 'prosemirror-markdown'; import { defaultMarkdownParser } from 'prosemirror-markdown';
import { wootWriterSetup } from '@chatwoot/prosemirror-schema'; import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
@ -61,23 +61,28 @@ export default {
mixins: [eventListenerMixins], mixins: [eventListenerMixins],
props: { props: {
value: { type: String, default: '' }, value: { type: String, default: '' },
editorId: { type: String, default: '' },
placeholder: { type: String, default: '' }, placeholder: { type: String, default: '' },
isPrivate: { type: Boolean, default: false }, isPrivate: { type: Boolean, default: false },
isFormatMode: { type: Boolean, default: false },
enableSuggestions: { type: Boolean, default: true }, enableSuggestions: { type: Boolean, default: true },
}, },
data() { data() {
return { return {
lastValue: null,
showUserMentions: false, showUserMentions: false,
showCannedMenu: false, showCannedMenu: false,
mentionSearchKey: '', mentionSearchKey: '',
cannedSearchTerm: '', cannedSearchTerm: '',
editorView: null, editorView: null,
range: null, range: null,
state: undefined,
}; };
}, },
computed: { computed: {
contentFromEditor() {
return addMentionsToMarkdownSerializer(
defaultMarkdownSerializer
).serialize(this.editorView.state.doc);
},
plugins() { plugins() {
if (!this.enableSuggestions) { if (!this.enableSuggestions) {
return []; return [];
@ -102,7 +107,6 @@ export default {
onExit: () => { onExit: () => {
this.mentionSearchKey = ''; this.mentionSearchKey = '';
this.showUserMentions = false; this.showUserMentions = false;
this.editorView = null;
return false; return false;
}, },
onKeyDown: ({ event }) => { onKeyDown: ({ event }) => {
@ -131,7 +135,6 @@ export default {
onExit: () => { onExit: () => {
this.cannedSearchTerm = ''; this.cannedSearchTerm = '';
this.showCannedMenu = false; this.showCannedMenu = false;
this.editorView = null;
return false; return false;
}, },
onKeyDown: ({ event }) => { onKeyDown: ({ event }) => {
@ -149,54 +152,57 @@ export default {
this.$emit('toggle-canned-menu', !this.isPrivate && updatedValue); this.$emit('toggle-canned-menu', !this.isPrivate && updatedValue);
}, },
value(newValue = '') { value(newValue = '') {
if (newValue !== this.lastValue) { if (newValue !== this.contentFromEditor) {
const { tr } = this.state; this.reloadState();
if (this.isFormatMode) {
this.state = createState(
newValue,
this.placeholder,
this.plugins,
this.isFormatMode
);
} else {
tr.insertText(newValue, 0, tr.doc.content.size);
this.state = this.view.state.apply(tr);
}
this.view.updateState(this.state);
} }
}, },
editorId() {
this.reloadState();
},
isPrivate() {
this.reloadState();
},
}, },
created() { created() {
this.state = createState(this.value, this.placeholder, this.plugins); this.state = createState(this.value, this.placeholder, this.plugins);
}, },
mounted() { mounted() {
this.view = new EditorView(this.$refs.editor, { this.createEditorView();
state: this.state, this.editorView.updateState(this.state);
dispatchTransaction: tx => {
this.state = this.state.apply(tx);
this.emitOnChange();
},
handleDOMEvents: {
keyup: () => {
this.onKeyup();
},
focus: () => {
this.onFocus();
},
blur: () => {
this.onBlur();
},
paste: (view, event) => {
const data = event.clipboardData.files;
if (data.length > 0) {
event.preventDefault();
}
},
},
});
this.focusEditorInputField(); this.focusEditorInputField();
}, },
methods: { methods: {
reloadState() {
this.state = createState(this.value, this.placeholder, this.plugins);
this.editorView.updateState(this.state);
this.focusEditorInputField();
},
createEditorView() {
this.editorView = new EditorView(this.$refs.editor, {
state: this.state,
dispatchTransaction: tx => {
this.state = this.state.apply(tx);
this.emitOnChange();
},
handleDOMEvents: {
keyup: () => {
this.onKeyup();
},
focus: () => {
this.onFocus();
},
blur: () => {
this.onBlur();
},
paste: (view, event) => {
const data = event.clipboardData.files;
if (data.length > 0) {
event.preventDefault();
}
},
},
});
},
handleKeyEvents(e) { handleKeyEvents(e) {
if (hasPressedAltAndPKey(e)) { if (hasPressedAltAndPKey(e)) {
this.focusEditorInputField(); this.focusEditorInputField();
@ -206,47 +212,59 @@ export default {
} }
}, },
focusEditorInputField() { focusEditorInputField() {
this.$refs.editor.querySelector('div.ProseMirror-woot-style').focus(); const { tr } = this.editorView.state;
const selection = Selection.atEnd(tr.doc);
this.editorView.dispatch(tr.setSelection(selection));
this.editorView.focus();
}, },
insertMentionNode(mentionItem) { insertMentionNode(mentionItem) {
if (!this.view) { if (!this.editorView) {
return null; return null;
} }
const node = this.view.state.schema.nodes.mention.create({ const node = this.editorView.state.schema.nodes.mention.create({
userId: mentionItem.key, userId: mentionItem.key,
userFullName: mentionItem.label, userFullName: mentionItem.label,
}); });
const tr = this.view.state.tr.replaceWith( const tr = this.editorView.state.tr.replaceWith(
this.range.from, this.range.from,
this.range.to, this.range.to,
node node
); );
this.state = this.view.state.apply(tr); this.state = this.editorView.state.apply(tr);
return this.emitOnChange(); return this.emitOnChange();
}, },
insertCannedResponse(cannedItem) { insertCannedResponse(cannedItem) {
if (!this.view) { if (!this.editorView) {
return null; return null;
} }
const tr = this.view.state.tr.insertText( const tr = this.editorView.state.tr.insertText(
cannedItem, cannedItem,
this.range.from, this.range.from,
this.range.to this.range.to
); );
this.state = this.view.state.apply(tr); this.state = this.editorView.state.apply(tr);
return this.emitOnChange(); this.emitOnChange();
// Hacky fix for #5501
this.state = createState(
this.contentFromEditor,
this.placeholder,
this.plugins
);
this.editorView.updateState(this.state);
return false;
}, },
emitOnChange() { emitOnChange() {
this.view.updateState(this.state); this.editorView.updateState(this.state);
this.lastValue = addMentionsToMarkdownSerializer(
defaultMarkdownSerializer this.$emit('input', this.contentFromEditor);
).serialize(this.state.doc);
this.$emit('input', this.lastValue);
}, },
hideMentions() { hideMentions() {
this.showUserMentions = false; this.showUserMentions = false;
}, },

View file

@ -56,6 +56,7 @@
<woot-message-editor <woot-message-editor
v-else v-else
v-model="message" v-model="message"
:editor-id="editorStateId"
class="input" class="input"
:is-private="isOnPrivateNote" :is-private="isOnPrivateNote"
:placeholder="messagePlaceHolder" :placeholder="messagePlaceHolder"
@ -429,6 +430,13 @@ export default {
profilePath() { profilePath() {
return frontendURL(`accounts/${this.accountId}/profile/settings`); return frontendURL(`accounts/${this.accountId}/profile/settings`);
}, },
conversationId() {
return this.currentChat.id;
},
editorStateId() {
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
return key;
},
}, },
watch: { watch: {
currentChat(conversation) { currentChat(conversation) {

View file

@ -1,6 +1,7 @@
export const LOCAL_STORAGE_KEYS = { export const LOCAL_STORAGE_KEYS = {
DISMISSED_UPDATES: 'dismissedUpdates', DISMISSED_UPDATES: 'dismissedUpdates',
WIDGET_BUILDER: 'widgetBubble_', WIDGET_BUILDER: 'widgetBubble_',
DRAFT_MESSAGES: 'draftMessages',
}; };
export const LocalStorage = { export const LocalStorage = {

View file

@ -46,6 +46,7 @@ export default {
return { return {
articleTitle: '', articleTitle: '',
articleContent: '', articleContent: '',
saveArticle: () => {},
}; };
}, },
mounted() { mounted() {

View file

@ -23,7 +23,7 @@
# #
# index_articles_on_associated_article_id (associated_article_id) # index_articles_on_associated_article_id (associated_article_id)
# index_articles_on_author_id (author_id) # index_articles_on_author_id (author_id)
# index_articles_on_slug (slug) # index_articles_on_slug (slug) UNIQUE
# #
class Article < ApplicationRecord class Article < ApplicationRecord
include PgSearch::Model include PgSearch::Model
@ -45,6 +45,8 @@ class Article < ApplicationRecord
belongs_to :author, class_name: 'User' belongs_to :author, class_name: 'User'
before_validation :ensure_account_id before_validation :ensure_account_id
before_validation :ensure_article_slug
validates :account_id, presence: true validates :account_id, presence: true
validates :category_id, presence: true validates :category_id, presence: true
validates :author_id, presence: true validates :author_id, presence: true
@ -112,4 +114,8 @@ class Article < ApplicationRecord
def ensure_account_id def ensure_account_id
self.account_id = portal&.account_id self.account_id = portal&.account_id
end end
def ensure_article_slug
self.slug ||= "#{Time.now.utc.to_i}-#{title.underscore.parameterize(separator: '-')}" if title.present?
end
end end

View file

@ -22,6 +22,7 @@
class ContactInbox < ApplicationRecord class ContactInbox < ApplicationRecord
include Pubsubable include Pubsubable
include RegexHelper
validates :inbox_id, presence: true validates :inbox_id, presence: true
validates :contact_id, presence: true validates :contact_id, presence: true
validates :source_id, presence: true validates :source_id, presence: true
@ -51,10 +52,10 @@ class ContactInbox < ApplicationRecord
def validate_twilio_source_id def validate_twilio_source_id
# https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164 # https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164
if inbox.channel.medium == 'sms' && !/\+[1-9]\d{1,14}\z/.match?(source_id) if inbox.channel.medium == 'sms' && !TWILIO_CHANNEL_SMS_REGEX.match?(source_id)
errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /\+[1-9]\d{1,14}\z/') errors.add(:source_id, "invalid source id for twilio sms inbox. valid Regex #{TWILIO_CHANNEL_SMS_REGEX}")
elsif inbox.channel.medium == 'whatsapp' && !/whatsapp:\+[1-9]\d{1,14}\z/.match?(source_id) elsif inbox.channel.medium == 'whatsapp' && !TWILIO_CHANNEL_WHATSAPP_REGEX.match?(source_id)
errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /whatsapp:\+[1-9]\d{1,14}\z/') errors.add(:source_id, "invalid source id for twilio whatsapp inbox. valid Regex #{TWILIO_CHANNEL_WHATSAPP_REGEX}")
end end
end end
@ -62,8 +63,15 @@ class ContactInbox < ApplicationRecord
errors.add(:source_id, "invalid source id for Email inbox. valid Regex #{Devise.email_regexp}") unless Devise.email_regexp.match?(source_id) errors.add(:source_id, "invalid source id for Email inbox. valid Regex #{Devise.email_regexp}") unless Devise.email_regexp.match?(source_id)
end end
def validate_whatsapp_source_id
return if WHATSAPP_CHANNEL_REGEX.match?(source_id)
errors.add(:source_id, "invalid source id for whatsapp inbox. valid Regex #{WHATSAPP_CHANNEL_REGEX}")
end
def valid_source_id_format? def valid_source_id_format?
validate_twilio_source_id if inbox.channel_type == 'Channel::TwilioSms' validate_twilio_source_id if inbox.channel_type == 'Channel::TwilioSms'
validate_email_source_id if inbox.channel_type == 'Channel::Email' validate_email_source_id if inbox.channel_type == 'Channel::Email'
validate_whatsapp_source_id if inbox.channel_type == 'Channel::Whatsapp'
end end
end end

View file

@ -241,7 +241,7 @@ class Message < ApplicationRecord
end end
def validate_attachments_limit(_attachment) def validate_attachments_limit(_attachment)
errors.add(attachments: 'exceeded maximum allowed') if attachments.size >= NUMBER_OF_PERMITTED_ATTACHMENTS errors.add(:attachments, message: 'exceeded maximum allowed') if attachments.size >= NUMBER_OF_PERMITTED_ATTACHMENTS
end end
def set_conversation_activity def set_conversation_activity

View file

@ -0,0 +1,6 @@
class AddUniqueIndexToSlug < ActiveRecord::Migration[6.1]
def change
remove_index :articles, :slug
add_index :articles, :slug, unique: true
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_09_26_164441) do ActiveRecord::Schema.define(version: 2022_09_30_025317) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements" enable_extension "pg_stat_statements"
@ -134,7 +134,7 @@ ActiveRecord::Schema.define(version: 2022_09_26_164441) do
t.string "slug", null: false t.string "slug", null: false
t.index ["associated_article_id"], name: "index_articles_on_associated_article_id" t.index ["associated_article_id"], name: "index_articles_on_associated_article_id"
t.index ["author_id"], name: "index_articles_on_author_id" t.index ["author_id"], name: "index_articles_on_author_id"
t.index ["slug"], name: "index_articles_on_slug" t.index ["slug"], name: "index_articles_on_slug", unique: true
end end
create_table "attachments", id: :serial, force: :cascade do |t| create_table "attachments", id: :serial, force: :cascade do |t|

View file

@ -6,4 +6,8 @@ module RegexHelper
# shouldn't start with a underscore or hyphen # shouldn't start with a underscore or hyphen
UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z') UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE = Regexp.new('\A[\p{L}\p{N}]+[\p{L}\p{N}_-]+\Z')
MENTION_REGEX = Regexp.new('\[(@[\w_. ]+)\]\(mention://(?:user|team)/\d+/(.*?)+\)') MENTION_REGEX = Regexp.new('\[(@[\w_. ]+)\]\(mention://(?:user|team)/\d+/(.*?)+\)')
TWILIO_CHANNEL_SMS_REGEX = Regexp.new('^\+\d{1,14}\z')
TWILIO_CHANNEL_WHATSAPP_REGEX = Regexp.new('^whatsapp:\+\d{1,14}\z')
WHATSAPP_CHANNEL_REGEX = Regexp.new('^\d{1,14}\z')
end end

View file

@ -17,7 +17,7 @@ describe ::ContactInboxBuilder do
source_id: contact.phone_number source_id: contact.phone_number
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -27,7 +27,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -38,8 +38,8 @@ describe ::ContactInboxBuilder do
source_id: '+224213223422' source_id: '+224213223422'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('+224213223422') expect(contact_inbox.source_id).to eq('+224213223422')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -48,7 +48,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.phone_number) expect(contact_inbox.source_id).to eq(contact.phone_number)
end end
end end
@ -64,7 +64,7 @@ describe ::ContactInboxBuilder do
source_id: "whatsapp:#{contact.phone_number}" source_id: "whatsapp:#{contact.phone_number}"
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -74,7 +74,7 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -85,8 +85,8 @@ describe ::ContactInboxBuilder do
source_id: 'whatsapp:+555555' source_id: 'whatsapp:+555555'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('whatsapp:+55555') expect(contact_inbox.source_id).to eq('whatsapp:+555555')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -95,7 +95,53 @@ describe ::ContactInboxBuilder do
inbox_id: twilio_inbox.id inbox_id: twilio_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be("whatsapp:#{contact.phone_number}") expect(contact_inbox.source_id).to eq("whatsapp:#{contact.phone_number}")
end
end
describe 'whatsapp inbox' do
let(:whatsapp_inbox) { create(:channel_whatsapp, account: account, sync_templates: false, validate_provider_config: false).inbox }
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id,
source_id: contact.phone_number&.delete('+')
).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id)
end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id
).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id)
end
it 'creates a new contact inbox when different source id is provided' do
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: contact.phone_number&.delete('+'))
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id,
source_id: '555555'
).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('555555')
end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
contact_inbox = described_class.new(
contact_id: contact.id,
inbox_id: whatsapp_inbox.id
).perform
expect(contact_inbox.source_id).to eq(contact.phone_number&.delete('+'))
end end
end end
@ -111,7 +157,7 @@ describe ::ContactInboxBuilder do
source_id: contact.phone_number source_id: contact.phone_number
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
@ -121,7 +167,7 @@ describe ::ContactInboxBuilder do
inbox_id: sms_inbox.id inbox_id: sms_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -132,8 +178,8 @@ describe ::ContactInboxBuilder do
source_id: '+224213223422' source_id: '+224213223422'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('+224213223422') expect(contact_inbox.source_id).to eq('+224213223422')
end end
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
@ -142,7 +188,7 @@ describe ::ContactInboxBuilder do
inbox_id: sms_inbox.id inbox_id: sms_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.phone_number) expect(contact_inbox.source_id).to eq(contact.phone_number)
end end
end end
@ -158,7 +204,7 @@ describe ::ContactInboxBuilder do
source_id: contact.email source_id: contact.email
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do
@ -168,7 +214,7 @@ describe ::ContactInboxBuilder do
inbox_id: email_inbox.id inbox_id: email_inbox.id
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -179,8 +225,8 @@ describe ::ContactInboxBuilder do
source_id: 'xyc@xyc.com' source_id: 'xyc@xyc.com'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('xyc@xyc.com') expect(contact_inbox.source_id).to eq('xyc@xyc.com')
end end
it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do
@ -189,7 +235,7 @@ describe ::ContactInboxBuilder do
inbox_id: email_inbox.id inbox_id: email_inbox.id
).perform ).perform
expect(contact_inbox.source_id).not_to be(contact.email) expect(contact_inbox.source_id).to eq(contact.email)
end end
end end
@ -205,7 +251,7 @@ describe ::ContactInboxBuilder do
source_id: 'test' source_id: 'test'
).perform ).perform
expect(contact_inbox.id).to be(existing_contact_inbox.id) expect(contact_inbox.id).to eq(existing_contact_inbox.id)
end end
it 'creates a new contact inbox when different source id is provided' do it 'creates a new contact inbox when different source id is provided' do
@ -216,8 +262,8 @@ describe ::ContactInboxBuilder do
source_id: 'test' source_id: 'test'
).perform ).perform
expect(contact_inbox.id).not_to be(existing_contact_inbox.id) expect(contact_inbox.id).not_to eq(existing_contact_inbox.id)
expect(contact_inbox.source_id).not_to be('test') expect(contact_inbox.source_id).to eq('test')
end end
it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do

View file

@ -96,15 +96,24 @@ RSpec.describe 'Platform Users API', type: :request do
it 'creates a new user and permissible for the user' do it 'creates a new user and permissible for the user' do
expect do expect do
post '/platform/api/v1/users/', params: { name: 'test', email: 'test@test.com', password: 'Password1!', post '/platform/api/v1/users/', params: { name: 'test', display_name: 'displaytest',
email: 'test@test.com', password: 'Password1!',
custom_attributes: { test: 'test_create' } }, custom_attributes: { test: 'test_create' } },
headers: { api_access_token: platform_app.access_token.token }, as: :json headers: { api_access_token: platform_app.access_token.token }, as: :json
end.not_to enqueue_mail end.not_to enqueue_mail
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
data = JSON.parse(response.body) data = JSON.parse(response.body)
expect(data['email']).to eq('test@test.com') expect(data).to match(
expect(data['custom_attributes']['test']).to eq('test_create') hash_including(
'name' => 'test',
'display_name' => 'displaytest',
'email' => 'test@test.com',
'custom_attributes' => {
'test' => 'test_create'
}
)
)
expect(platform_app.platform_app_permissibles.first.permissible_id).to eq data['id'] expect(platform_app.platform_app_permissibles.first.permissible_id).to eq data['id']
end end

View file

@ -3,8 +3,7 @@ FactoryBot.define do
account_id { 1 } account_id { 1 }
category_id { 1 } category_id { 1 }
author_id { 1 } author_id { 1 }
title { 'MyString' } title { Faker::Movie.title }
slug { 'MyString' }
content { 'MyText' } content { 'MyText' }
description { 'MyDescrption' } description { 'MyDescrption' }
status { 1 } status { 1 }

View file

@ -15,6 +15,8 @@ def generate_source_id(contact_inbox)
contact_inbox.inbox.channel.medium == 'sms' ? Faker::PhoneNumber.cell_phone_in_e164 : "whatsapp:#{Faker::PhoneNumber.cell_phone_in_e164}" contact_inbox.inbox.channel.medium == 'sms' ? Faker::PhoneNumber.cell_phone_in_e164 : "whatsapp:#{Faker::PhoneNumber.cell_phone_in_e164}"
when 'Channel::Email' when 'Channel::Email'
"#{SecureRandom.uuid}@acme.inc" "#{SecureRandom.uuid}@acme.inc"
when 'Channel::Whatsapp'
Faker::PhoneNumber.cell_phone_in_e164.delete('+')
else else
SecureRandom.uuid SecureRandom.uuid
end end

View file

@ -119,11 +119,23 @@ RSpec.describe Article, type: :model do
records = portal_1.articles.search(params) records = portal_1.articles.search(params)
expect(records.count).to eq(2) expect(records.count).to eq(2)
end end
it 'auto saves article slug' do
article = create(:article, category_id: category_1.id, title: 'the awesome article 1', content: 'This is the content', portal_id: portal_1.id,
author_id: user.id)
expect(article.slug).to include('the-awesome-article-1')
end
end end
context 'with pagination' do context 'with pagination' do
it 'returns paginated articles' do it 'returns paginated articles' do
create_list(:article, 30, category_id: category_2.id, slug: 'title-1', title: 'title 1', portal_id: portal_2.id, author_id: user.id) build_list(:article, 30) do |record, i|
record.category_id = category_2.id
record.title = "title #{i}"
record.portal_id = portal_2.id
record.author_id = user.id
record.save!
end
params = { category_slug: 'category_2' } params = { category_slug: 'category_2' }
records = portal_2.articles.search(params) records = portal_2.articles.search(params)
expect(records.count).to eq(25) expect(records.count).to eq(25)

View file

@ -37,4 +37,59 @@ RSpec.describe ContactInbox do
expect(obj.pubsub_token).to eq(new_token) expect(obj.pubsub_token).to eq(new_token)
end end
end end
describe 'validations' do
context 'when source_id' do
it 'validates whatsapp channel source_id' do
whatsapp_inbox = create(:channel_whatsapp, sync_templates: false, validate_provider_config: false).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '1234567890aaa')
ci_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: whatsapp_inbox, source_id: '+1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for whatsapp inbox. valid Regex (?-mix:^\\d{1,14}\\z)']
)
expect(ci_plus_in_source_id.valid?).to be(false)
expect(ci_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for whatsapp inbox. valid Regex (?-mix:^\\d{1,14}\\z)']
)
end
it 'validates twilio sms channel source_id' do
twilio_sms_inbox = create(:channel_twilio_sms).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '+1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '+1234567890aaa')
ci_without_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_sms_inbox, source_id: '1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio sms inbox. valid Regex (?-mix:^\\+\\d{1,14}\\z)']
)
expect(ci_without_plus_in_source_id.valid?).to be(false)
expect(ci_without_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio sms inbox. valid Regex (?-mix:^\\+\\d{1,14}\\z)']
)
end
it 'validates twilio whatsapp channel source_id' do
twilio_whatsapp_inbox = create(:channel_twilio_sms, medium: :whatsapp).inbox
contact = create(:contact)
valid_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:+1234567890')
ci_character_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:+1234567890aaa')
ci_without_plus_in_source_id = build(:contact_inbox, contact: contact, inbox: twilio_whatsapp_inbox, source_id: 'whatsapp:1234567890')
expect(valid_source_id.valid?).to be(true)
expect(ci_character_in_source_id.valid?).to be(false)
expect(ci_character_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio whatsapp inbox. valid Regex (?-mix:^whatsapp:\\+\\d{1,14}\\z)']
)
expect(ci_without_plus_in_source_id.valid?).to be(false)
expect(ci_without_plus_in_source_id.errors.full_messages).to eq(
['Source invalid source id for twilio whatsapp inbox. valid Regex (?-mix:^whatsapp:\\+\\d{1,14}\\z)']
)
end
end
end
end end

View file

@ -106,6 +106,19 @@ RSpec.describe Message, type: :model do
end end
end end
context 'when attachments size maximum' do
let(:message) { build(:message, content_type: nil, account: create(:account)) }
it 'add errors to message for attachment size is more than allowed limit' do
16.times.each do
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
attachment.file.attach(io: File.open(Rails.root.join('spec/assets/avatar.png')), filename: 'avatar.png', content_type: 'image/png')
end
expect(message.errors.messages).to eq({ attachments: ['exceeded maximum allowed'] })
end
end
context 'when email notifiable message' do context 'when email notifiable message' do
let(:message) { build(:message, content_type: nil, account: create(:account)) } let(:message) { build(:message, content_type: nil, account: create(:account)) }