diff --git a/config/routes.rb b/config/routes.rb
index a9e383cc..3d10d04d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -34,6 +34,7 @@ Rails.application.routes.draw do
resources :submitter_form_views, only: %i[create]
resources :submitters, only: %i[index show update]
resources :submissions, only: %i[index show create destroy] do
+ resources :documents, only: %i[index], controller: 'submission_documents'
collection do
resources :init, only: %i[create], controller: 'submissions'
resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails
diff --git a/lib/accounts.rb b/lib/accounts.rb
index d6088bab..644032b1 100644
--- a/lib/accounts.rb
+++ b/lib/accounts.rb
@@ -11,7 +11,7 @@ module Accounts
new_user.uuid = SecureRandom.uuid
new_user.account = new_account
new_user.encrypted_password = SecureRandom.hex
- new_user.email = "#{SecureRandom.hex}@docuseal.co"
+ new_user.email = "#{SecureRandom.hex}@docuseal.com"
account.templates.each do |template|
new_template = template.dup
diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb
index b868fc27..0eeddfa7 100644
--- a/lib/action_mailer_configs_interceptor.rb
+++ b/lib/action_mailer_configs_interceptor.rb
@@ -16,7 +16,15 @@ module ActionMailerConfigsInterceptor
end
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
- message.from = ENV.fetch('SMTP_FROM')
+ from = ENV.fetch('SMTP_FROM').to_s.split(',').sample
+
+ message.from = from
+
+ if from == 'DocuSeal '
+ message.body.instance_variable_set(
+ :@raw_source, message.body.raw_source.gsub('https://docuseal.co/', 'https://docuseal.com/')
+ )
+ end
return message
end
diff --git a/lib/action_mailer_events_observer.rb b/lib/action_mailer_events_observer.rb
index c7786bda..273564ac 100644
--- a/lib/action_mailer_events_observer.rb
+++ b/lib/action_mailer_events_observer.rb
@@ -22,7 +22,7 @@ module ActionMailerEventsObserver
emailable_type:,
event_type: :send,
email:,
- data: { method: mail.delivery_method.class.name.underscore },
+ data: { from: mail.from, method: mail.delivery_method.class.name.underscore },
event_datetime: Time.current
)
end
diff --git a/lib/docuseal.rb b/lib/docuseal.rb
index 2b3e283d..9ea6f35e 100644
--- a/lib/docuseal.rb
+++ b/lib/docuseal.rb
@@ -12,7 +12,7 @@ module Docuseal
TWITTER_URL = 'https://twitter.com/docusealco'
TWITTER_HANDLE = '@docusealco'
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
- SUPPORT_EMAIL = 'support@docuseal.co'
+ SUPPORT_EMAIL = 'support@docuseal.com'
HOST = ENV.fetch('HOST', 'localhost')
AATL_CERT_NAME = 'docuseal_aatl'
CONSOLE_URL = if Rails.env.development?
diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb
index cc037300..4c2571e8 100644
--- a/lib/params/base_validator.rb
+++ b/lib/params/base_validator.rb
@@ -2,6 +2,8 @@
module Params
class BaseValidator
+ EMAIL_REGEXP = /\A[a-z0-9][\.']?(?:(?:[a-z0-9_-]+[\.\+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[\.-])*[a-z0-9]+\.[a-z]{2,}\z/i
+
InvalidParameterError = Class.new(StandardError)
def self.call(...)
@@ -65,6 +67,27 @@ module Params
raise_error(message || "#{key} must follow the #{regexp.source} format")
end
+ def email_format(params, key, message: nil)
+ return if params.blank?
+ return if params[key].blank?
+ return if params[key].to_s.strip.split(/\s*[;,]\s*/).all? { |email| email.match?(EMAIL_REGEXP) }
+
+ if Rails.env.production?
+ Rollbar.error(message || "#{key} must follow the email format") if defined?(Rollbar)
+ else
+ raise_error(message || "#{key} must follow the email format")
+ end
+ end
+
+ def unique_value(params, key, message: nil)
+ return unless params.is_a?(Array)
+ return if params.none?
+ return if params.all? { |p| p[key].blank? }
+ return if params.pluck(key).compact_blank.uniq.size == params.pluck(key).compact_blank.size
+
+ raise_error(message || "#{key} must be unique")
+ end
+
def in_path(params, path = [])
old_path = @current_path
diff --git a/lib/params/submission_create_validator.rb b/lib/params/submission_create_validator.rb
index 7ea76401..eef14391 100644
--- a/lib/params/submission_create_validator.rb
+++ b/lib/params/submission_create_validator.rb
@@ -22,6 +22,7 @@ module Params
required(params, %i[emails email])
type(params, :emails, String)
+ email_format(params, :emails, message: 'emails are invalid')
boolean(params, :send_email)
type(params, :message, Hash)
@@ -43,8 +44,8 @@ module Params
type(params, :completed_redirect_url, String)
type(params, :bcc_completed, String)
type(params, :reply_to, String)
- format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
- format(params, :reply_to, /@/, message: 'reply_to email is invalid')
+ email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
+ email_format(params, :reply_to, message: 'reply_to email is invalid')
type(params, :message, Hash)
type(params, :submitters, Array)
@@ -60,6 +61,7 @@ module Params
if params[:submitters].present?
in_path(params, :submitters) do |submitters_params|
type(submitters_params, 0, Hash)
+ unique_value(submitters_params, :role)
end
end
@@ -74,8 +76,8 @@ module Params
type(submitter_params, :name, String)
type(submitter_params, :reply_to, String)
type(submitter_params, :email, String)
- format(submitter_params, :email, /@/, message: 'email is invalid')
- format(submitter_params, :reply_to, /@/, message: 'reply_to email is invalid')
+ email_format(submitter_params, :email, message: 'email is invalid')
+ email_format(submitter_params, :reply_to, message: 'reply_to email is invalid')
type(submitter_params, :phone, String)
format(submitter_params, :phone, /\A\+\d+\z/,
message: 'phone should start with + and contain only digits')
@@ -105,7 +107,7 @@ module Params
type(params, :order, String)
type(params, :completed_redirect_url, String)
type(params, :bcc_completed, String)
- format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
+ email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
type(params, :message, Hash)
in_path(params, :message) do |message_params|
diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb
new file mode 100644
index 00000000..94c836c3
--- /dev/null
+++ b/lib/submissions/generate_preview_attachments.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module Submissions
+ module GeneratePreviewAttachments
+ module_function
+
+ # rubocop:disable Metrics
+ def call(submission, values_hash: nil)
+ values_hash ||= build_values_hash(submission)
+
+ with_signature_id = submission.account.account_configs
+ .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true)
+
+ is_flatten =
+ submission.account.account_configs
+ .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
+
+ pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten)
+
+ submission.submitters.where(completed_at: nil).preload(attachments_attachments: :blob).each do |submitter|
+ GenerateResultAttachments.fill_submitter_fields(submitter, submission.account, pdfs_index,
+ with_signature_id:, is_flatten:)
+ end
+
+ template = submission.template
+
+ image_pdfs = []
+ original_documents = template.documents.preload(:blob)
+
+ result_attachments =
+ (submission.template_schema || template.schema).map do |item|
+ pdf = pdfs_index[item['attachment_uuid']]
+
+ if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
+ pdf = GenerateResultAttachments.normalize_image_pdf(pdf)
+
+ image_pdfs << pdf
+ end
+
+ build_pdf_attachment(pdf:, submission:,
+ uuid: item['attachment_uuid'],
+ values_hash:,
+ name: item['name'])
+ end
+
+ return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2
+
+ images_pdf =
+ image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
+ pdf.pages.each { |page| doc.pages << doc.import(page) }
+ end
+
+ images_pdf = GenerateResultAttachments.normalize_image_pdf(images_pdf)
+
+ images_pdf_attachment =
+ build_pdf_attachment(
+ pdf: images_pdf,
+ submission:,
+ uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)),
+ values_hash:,
+ name: template.name
+ )
+
+ ApplicationRecord.no_touching do
+ (result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) }
+ end
+ end
+
+ def build_values_hash(submission)
+ submission.submitters.reduce({}) { |acc, s| acc.merge(s.values) }.hash
+ end
+
+ def build_pdf_attachment(pdf:, submission:, uuid:, name:, values_hash:)
+ io = StringIO.new
+
+ begin
+ pdf.write(io, incremental: true, validate: false)
+ rescue HexaPDF::MalformedPDFError => e
+ Rollbar.error(e) if defined?(Rollbar)
+
+ pdf.write(io, incremental: false, validate: false)
+ end
+
+ ActiveStorage::Attachment.new(
+ blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename: "#{name}.pdf"),
+ metadata: { original_uuid: uuid,
+ values_hash:,
+ analyzed: true,
+ sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) },
+ name: 'preview_documents',
+ record: submission
+ )
+ end
+ # rubocop:enable Metrics
+ end
+end
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index c396ba31..f1ef7fb7 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -92,19 +92,13 @@ module Submissions
end
def generate_pdfs(submitter)
- cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
+ configs = submitter.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY,
+ AccountConfig::WITH_SIGNATURE_ID])
- with_signature_id = submitter.account.account_configs
- .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true)
+ with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true
+ is_flatten = configs.find { |c| c.key == AccountConfig::FLATTEN_RESULT_PDF_KEY }&.value != false
- is_flatten =
- submitter.account.account_configs
- .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
-
- account = submitter.account
- attachments_data_cache = {}
-
- pdfs_index = build_pdfs_index(submitter, flatten: is_flatten)
+ pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten)
if with_signature_id || submitter.account.testing?
pdfs_index.each_value do |pdf|
@@ -145,6 +139,16 @@ module Submissions
end
end
+ fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:)
+ end
+
+ def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:)
+ cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
+
+ attachments_data_cache = {}
+
+ return pdfs_index if submitter.submission.template_fields.blank?
+
submitter.submission.template_fields.each do |field|
next if field['submitter_uuid'] != submitter.uuid
@@ -514,13 +518,13 @@ module Submissions
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':'))
end
- def build_pdfs_index(submitter, flatten: true)
- latest_submitter = find_last_submitter(submitter)
+ def build_pdfs_index(submission, submitter: nil, flatten: true)
+ latest_submitter = find_last_submitter(submission, submitter:)
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
documents = latest_submitter&.documents&.preload(:blob).to_a.presence
- documents ||= submitter.submission.template_schema_documents.preload(:blob)
+ documents ||= submission.template_schema_documents.preload(:blob)
documents.to_h do |attachment|
pdf =
@@ -582,11 +586,11 @@ module Submissions
font_wrapper.custom_glyph(replace_with, character)
end
- def find_last_submitter(submitter)
- submitter.submission.submitters
- .select(&:completed_at?)
- .select { |e| e.id != submitter.id && e.completed_at <= submitter.completed_at }
- .max_by(&:completed_at)
+ def find_last_submitter(submission, submitter: nil)
+ submission.submitters
+ .select(&:completed_at?)
+ .select { |e| submitter.nil? ? true : e.id != submitter.id && e.completed_at <= submitter.completed_at }
+ .max_by(&:completed_at)
end
def build_pdf_from_image(attachment)
diff --git a/spec/factories/templates.rb b/spec/factories/templates.rb
index d1c433d2..f4f2ff62 100644
--- a/spec/factories/templates.rb
+++ b/spec/factories/templates.rb
@@ -7,7 +7,11 @@ FactoryBot.define do
author factory: %i[user]
name { Faker::Book.title }
- after(:create) do |template|
+ transient do
+ submitter_count { 1 }
+ end
+
+ after(:create) do |template, evaluator|
blob = ActiveStorage::Blob.create_and_upload!(
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
filename: 'sample-document.pdf',
@@ -22,39 +26,46 @@ FactoryBot.define do
Templates::ProcessDocument.call(attachment, attachment.download)
template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }]
- template.submitters = [
+ number_words = %w[first second third fourth fifth sixth seventh eighth ninth tenth]
+
+ template.submitters = Array.new(evaluator.submitter_count) do |i|
{
- 'name' => 'First Party',
- 'uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c'
+ 'name' => "#{number_words[i]&.capitalize} Party",
+ 'uuid' => SecureRandom.uuid
}
- ]
- template.fields = [
- {
- 'uuid' => '21637fc9-0655-45df-8952-04ec64949e85',
- 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
- 'name' => 'First Name',
- 'type' => 'text',
- 'required' => true,
- 'areas' => [
- {
- 'x' => 0.09027777777777778,
- 'y' => 0.1197252208047105,
- 'w' => 0.3069444444444444,
- 'h' => 0.03336604514229637,
- 'attachment_uuid' => attachment.uuid,
- 'page' => 0
- }
- ]
- },
- {
- 'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46',
- 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
- 'name' => '',
- 'type' => 'signature',
- 'required' => true,
- 'areas' => []
- }
- ]
+ end
+
+ template.fields = template.submitters.reduce([]) do |fields, submitter|
+ fields += [
+ {
+ 'uuid' => SecureRandom.uuid,
+ 'submitter_uuid' => submitter['uuid'],
+ 'name' => 'First Name',
+ 'type' => 'text',
+ 'required' => true,
+ 'areas' => [
+ {
+ 'x' => 0.09027777777777778,
+ 'y' => 0.1197252208047105,
+ 'w' => 0.3069444444444444,
+ 'h' => 0.03336604514229637,
+ 'attachment_uuid' => attachment.uuid,
+ 'page' => 0
+ }
+ ]
+ },
+ {
+ 'uuid' => SecureRandom.uuid,
+ 'submitter_uuid' => submitter['uuid'],
+ 'name' => '',
+ 'type' => 'signature',
+ 'required' => true,
+ 'areas' => []
+ }
+ ]
+
+ fields
+ end
template.save!
end
diff --git a/spec/lib/params/base_validator_spec.rb b/spec/lib/params/base_validator_spec.rb
new file mode 100644
index 00000000..193b9b33
--- /dev/null
+++ b/spec/lib/params/base_validator_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Params::BaseValidator do
+ let(:validator) { described_class.new({}) }
+
+ describe '#email_format' do
+ it 'when email is valid' do
+ emails = [
+ ' john.doe@example.com ',
+ 'john.doe@example.com',
+ 'jane+newsletter@domain.org',
+ 'mike_smith@company.net',
+ 'lisa-wong@sub.example.co.uk',
+ 'peter@webmail.com',
+ 'anna.jones123@my-domain.com',
+ 'contact@company.email',
+ 'info@my-company123.org',
+ 'hello.world@business.info',
+ 'feedback@new-domain.com',
+ 'alerts+user@localdomain.net',
+ 'webmaster@industry.biz',
+ 'services@agency.example',
+ 'george123@consultant.pro',
+ 'sales-team@company.io'
+ ]
+
+ emails.each do |email|
+ expect { validator.email_format({ email: }, :email) }.not_to raise_error
+ end
+ end
+
+ it 'when signle email is invalid' do
+ emails = [
+ 'jone.doe@',
+ 'mike.smith@',
+ 'jane.doe@@example.com',
+ '@example.com',
+ 'lisa.wong@example',
+ 'peter.parker..@example.com',
+ 'anna.jones@.com',
+ 'jack.brown@com',
+ 'john doe@example.com',
+ 'laura.martin@ example.com',
+ 'dave.clark@example .com',
+ 'susan.green@example,com',
+ 'chris.lee@example;com',
+ 'jenny.king@.example.com',
+ '.henry.ford@example.com',
+ 'amy.baker@sub_domain.com',
+ 'george.morris@-example.com',
+ 'nancy.davis@example..com',
+ 'kevin.white@.',
+ 'diana.robinson@.example..com',
+ 'oliver.scott@example.c',
+ 'email1@g.comemail@g.com',
+ 'user.name@subdomain.example@example.com',
+ 'double@at@sign.com',
+ 'user@@example.com',
+ 'email@123.123.123.123',
+ 'this...is@strange.but.valid.com',
+ 'mix-and.match@strangely-formed-email_address.com',
+ 'email@domain..com',
+ 'user@-weird-domain-.com',
+ 'user.name@[IPv6:2001:db8::1]',
+ 'tricky.email@sub.example-.com',
+ 'user@domain.c0m'
+ ]
+
+ emails.each do |email|
+ expect do
+ validator.email_format({ email: }, :email)
+ end.to raise_error(described_class::InvalidParameterError, 'email must follow the email format')
+ end
+ end
+
+ it 'when multiple emails are valid' do
+ emails = [
+
+ 'john.doe@example.com, jane.doe+newsletter@domain.org',
+ 'joshua@automobile.car ; chloe+fashion@food.delivery',
+ 'mike-smith@company.net;lisa.wong-sales@sub.example.co.uk',
+ 'peter.parker+info@webmail.com,laura.martin-office@company.co',
+ 'anna.jones123@my-domain.com, jack.brown+work@college.edu',
+ 'susan.green@business-info.org; dave.clark+personal@nonprofit.org',
+ 'chris.lee+team@new-domain.com;jenny.king.marketing@localdomain.net',
+ 'george.morris@consultant.pro; nancy.davis-office@company.io',
+ 'joshua-jones@automobile.car; chloe.taylor+fashion@food.delivery',
+ 'ryan.moore+alerts@music-band.com,isabella.walker.design@fashion.design',
+ 'support-team@company.com, contact.us@domain.org',
+ 'admin.office@industry.biz, hr.department@service.pro',
+ 'feedback@agency-example.org; hello.world@creative-studio.net',
+ 'sales-team@e-commerce.shop, support.department@technology.co',
+ 'media.contact@financial.servicesl; events-coordinator@food.delivery',
+ 'order@music-band.com; info.support@creative.example',
+ 'design.team@webmail.com , admin-office@company.co',
+ 'contact.sales@sub-example.co.uk, support+info@legal.gov',
+ 'support@media.group;subscribe-updates@concert.events'
+ ]
+
+ emails.each do |email|
+ expect { validator.email_format({ email: }, :email) }.not_to raise_error
+ end
+ end
+
+ it 'when multiple emails are invalid' do
+ emails = [
+ 'jone@gmail.com, ,mike@gmail.com',
+ 'john.doe@example.com dave@nonprofit.org',
+ '; oliver.scott@example.com',
+ 'amy.baker@ example.com, george.morris@ example.com',
+ 'jenny.king@example.com . diana.robinson@example.com',
+ 'nancy.davis@.com, henry.ford@.com',
+ 'jack.brown@example.com, laura.martin@example .com',
+ 'anna.jones@example,com lisa.wong@example.com',
+ 'dave.clark@example.com kevin.white@example;com',
+ 'susan.green@ example.com; john.doe@example.com',
+ 'amy.baker@sub_domain.com george.morris@-example.com',
+ 'nancy.davis@example..com john.doe@example.c',
+ 'peter.parker@example.com, .henry.ford@example.com',
+ 'diana.robinson@.example..com, mike.smith@.',
+ 'oliver.scott@example.com; laura.martin@ example.com, jane.doe@@example.com'
+ ]
+
+ emails.each do |email|
+ expect do
+ validator.email_format({ email: }, :email)
+ end.to raise_error(described_class::InvalidParameterError, 'email must follow the email format')
+ end
+ end
+
+ it 'when email is invalid with custom message' do
+ expect do
+ validator.email_format({ email: 'jone.doe@' }, :email, message: 'email is invalid')
+ end.to raise_error(described_class::InvalidParameterError, 'email is invalid')
+ end
+ end
+end
diff --git a/spec/requests/submissions_spec.rb b/spec/requests/submissions_spec.rb
index 81cdb6c2..3a9590d8 100644
--- a/spec/requests/submissions_spec.rb
+++ b/spec/requests/submissions_spec.rb
@@ -7,6 +7,7 @@ describe 'Submission API', type: :request do
let!(:author) { create(:user, account:) }
let!(:folder) { create(:template_folder, account:) }
let!(:templates) { create_list(:template, 2, account:, author:, folder:) }
+ let!(:multiple_submitters_template) { create(:template, submitter_count: 3, account:, author:, folder:) }
describe 'GET /api/submissions' do
it 'returns a list of submissions' do
@@ -56,13 +57,16 @@ describe 'Submission API', type: :request do
expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json))
end
- end
- describe 'POST /api/submissions/emails' do
- it 'creates a submission using email' do
+ it 'creates a submission when some submitter roles are not provided' do
post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
- template_id: templates[0].id,
- emails: 'john.doe@example.com'
+ template_id: multiple_submitters_template.id,
+ send_email: true,
+ submitters: [
+ { role: 'First Role', email: 'john.doe@example.com' },
+ { email: 'jane.doe@example.com' },
+ { email: 'mike.doe@example.com' }
+ ]
}.to_json
expect(response).to have_http_status(:ok)
@@ -70,6 +74,81 @@ describe 'Submission API', type: :request do
submission = Submission.last
expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json))
+ expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json))
+ expect(response.parsed_body[0]['role']).to eq('First Party')
+ expect(response.parsed_body[0]['email']).to eq('john.doe@example.com')
+ expect(response.parsed_body[1]['role']).to eq('Second Party')
+ expect(response.parsed_body[1]['email']).to eq('jane.doe@example.com')
+ expect(response.parsed_body[2]['role']).to eq('Third Party')
+ expect(response.parsed_body[2]['email']).to eq('mike.doe@example.com')
+ end
+
+ it 'returns an error if the submitter email is invalid' do
+ post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: templates[0].id,
+ send_email: true,
+ submitters: [
+ { role: 'First Role', email: 'john@example' }
+ ]
+ }.to_json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+
+ expect(response.parsed_body).to eq({ 'error' => 'email is invalid in `submitters[0]`.' })
+ end
+
+ it 'returns an error if the template fields are missing' do
+ templates[0].update(fields: [])
+
+ post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: templates[0].id,
+ send_email: true,
+ submitters: [{ role: 'First Role', email: 'john.doe@example.com' }]
+ }.to_json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body).to eq({ 'error' => 'Template does not contain fields' })
+ end
+
+ it 'returns an error if submitter roles are not unique' do
+ post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: multiple_submitters_template.id,
+ send_email: true,
+ submitters: [
+ { role: 'First Role', email: 'john.doe@example.com' },
+ { role: 'First Role', email: 'jane.doe@example.com' }
+ ]
+ }.to_json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body).to eq({ 'error' => 'role must be unique in `submitters`.' })
+ end
+ end
+
+ describe 'POST /api/submissions/emails' do
+ it 'creates a submission using email' do
+ post '/api/submissions/emails', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: templates[0].id,
+ emails: 'john.doe@example.com,jane.doe@example.com'
+ }.to_json
+
+ expect(response).to have_http_status(:ok)
+
+ submissions = Submission.last(2)
+ submissions_body = submissions.reduce([]) { |acc, submission| acc + create_submission_body(submission) }
+
+ expect(response.parsed_body).to eq(JSON.parse(submissions_body.to_json))
+ end
+
+ it 'returns an error if emails are invalid' do
+ post '/api/submissions/emails', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: templates[0].id,
+ emails: 'amy.baker@example.com, george.morris@example.com@gmail.com'
+ }.to_json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+
+ expect(response.parsed_body).to eq({ 'error' => 'emails are invalid' })
end
end
diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb
index aa4951bf..67430ed8 100644
--- a/spec/requests/templates_spec.rb
+++ b/spec/requests/templates_spec.rb
@@ -144,8 +144,8 @@ describe 'Templates API', type: :request do
name: template.name,
fields: [
{
- 'uuid' => '21637fc9-0655-45df-8952-04ec64949e85',
- 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
+ 'uuid' => template.fields[0]['uuid'],
+ 'submitter_uuid' => template.submitters[0]['uuid'],
'name' => 'First Name',
'type' => 'text',
'required' => true,
@@ -161,8 +161,8 @@ describe 'Templates API', type: :request do
]
},
{
- 'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46',
- 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
+ 'uuid' => template.fields[1]['uuid'],
+ 'submitter_uuid' => template.submitters[0]['uuid'],
'name' => '',
'type' => 'signature',
'required' => true,