- <% if can?(:create, User.new(account: current_account)) %>
+ <% if params[:status].blank? && can?(:create, User.new(account: current_account)) %>
<%= render 'users/extra_buttons' %>
<% if content_for(:add_user_button) %>
<%= content_for(:add_user_button) %>
@@ -57,17 +63,17 @@
<%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %>
- <% if can?(:update, user) && user.archived_at.blank? %>
+ <% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %>
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %>
<%= t('edit') %>
<% end %>
<% end %>
- <% if params[:status].blank? && can?(:destroy, user) && user != current_user %>
+ <% if params[:status] != 'archived' && can?(:destroy, user) && user != current_user %>
<%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %>
<%= t('remove') %>
<% end %>
<% end %>
- <% if params[:status].present? && can?(:manage, user) && user != current_user %>
+ <% if params[:status] == 'archived' && can?(:manage, user) && user != current_user && user.archived_at? %>
<%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %>
<%= t('unarchive') %>
<% end %>
@@ -78,22 +84,11 @@
- <% view_archived_html = capture do %>
- <% if current_account.users.archived.exists? %>
-
- <% end %>
- <% end %>
<% if @pagy.pages > 1 %>
- <%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: view_archived_html %>
+ <%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: render('bottom_links') %>
<% else %>
- <%= view_archived_html %>
+ <%= render 'bottom_links' %>
<% end %>
diff --git a/config/initializers/migrate.rb b/config/initializers/migrate.rb
index 4f309349..1e0704ed 100644
--- a/config/initializers/migrate.rb
+++ b/config/initializers/migrate.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
Rails.configuration.to_prepare do
- ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
+ ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production' && ENV['RUN_MIGRATIONS'] != 'false'
end
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml
index 1cf346cf..9655b0d9 100644
--- a/config/locales/i18n.yml
+++ b/config/locales/i18n.yml
@@ -134,6 +134,7 @@ en: &en
require_signing_reason: Require signing reason
allow_typed_text_signatures: Allow typed text signatures
allow_to_resubmit_completed_forms: Allow to resubmit completed forms
+ allow_to_decline_documents: Allow to decline documents
remember_and_pre_fill_signatures: Remember and pre-fill signatures
require_authentication_for_file_download_links: Require authentication for file download links
combine_completed_documents_and_audit_log: Combine completed documents and Audit Log
@@ -473,7 +474,6 @@ en: &en
back: Back
add_secret: Add Secret
submission_example_payload: Submission example payload
- status_users: '%{status} Users'
there_are_no_signatures: There are no signatures
signed_with_trusted_certificate: Signed with trusted certificate
signed_with_external_certificate: Signed with external certificate
@@ -618,6 +618,10 @@ en: &en
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
submitter_cannot_be_updated: Submitter cannot be updated.
at_least_one_field_must_be_filled: At least one field must be filled.
+ archived_users: Archived Users
+ embedding_users: Embedding Users
+ view_embedding_users: View Embedding Users
+ view_users: View Users
submission_event_names:
send_email_to_html: 'Email sent to %{submitter_name}'
send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}'
@@ -770,6 +774,7 @@ es: &es
require_signing_reason: Requerir motivo de firma
allow_typed_text_signatures: Permitir firmas de texto mecanografiadas
allow_to_resubmit_completed_forms: Permitir reenviar formularios completados
+ allow_to_decline_documents: Permitir rechazar documentos
remember_and_pre_fill_signatures: Recordar y prellenar firmas
require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos
combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría
@@ -1109,7 +1114,6 @@ es: &es
back: Atrás
add_secret: Agregar secreto
submission_example_payload: Ejemplo de payload de envío
- status_users: '%{status} Usuarios'
there_are_no_signatures: No hay firmas
signed_with_trusted_certificate: Firmado con certificado de confianza
signed_with_external_certificate: Firmado con certificado externo
@@ -1254,6 +1258,9 @@ es: &es
use_international_format_1xxx_: 'Usa el formato internacional: +1xxx...'
submitter_cannot_be_updated: El remitente no puede ser actualizado.
at_least_one_field_must_be_filled: Al menos un campo debe estar completo.
+ archived_users: Usuarios Archivados
+ embedding_users: Usuarios Integrados
+ view_embedding_users: Ver Usuarios Integrado
submission_event_names:
send_email_to_html: 'Correo electrónico enviado a %{submitter_name}'
send_reminder_email_to_html: 'Correo de recordatorio enviado a %{submitter_name}'
@@ -1406,6 +1413,7 @@ it: &it
require_signing_reason: Richiedere il motivo della firma
allow_typed_text_signatures: Permettere firme con testo digitato
allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati
+ allow_to_decline_documents: Permettere di rifiutare i documenti
remember_and_pre_fill_signatures: Ricordare e precompilare le firme
require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file"
combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit
@@ -1745,7 +1753,6 @@ it: &it
back: Indietro
add_secret: Aggiungi segreto
submission_example_payload: Esempio di payload di invio
- status_users: '%{status} Utenti'
there_are_no_signatures: Non ci sono firme
signed_with_trusted_certificate: Firmato con certificato affidabile
signed_with_external_certificate: Firmato con certificato esterno
@@ -1890,6 +1897,9 @@ it: &it
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
+ archived_users: Utenti Archiviati
+ embedding_users: Utenti Incorporati
+ view_embedding_users: Visualizza Utenti Incorporati
submission_event_names:
send_email_to_html: 'E-mail inviato a %{submitter_name}'
send_reminder_email_to_html: 'E-mail di promemoria inviato a %{submitter_name}'
@@ -2043,6 +2053,7 @@ fr: &fr
require_signing_reason: Demander une raison pour la signature
allow_typed_text_signatures: Autoriser les signatures en texte tapé
allow_to_resubmit_completed_forms: Autoriser la resoumission des formulaires complétés
+ allow_to_decline_documents: Autoriser le refus des documents
remember_and_pre_fill_signatures: Se souvenir et préremplir les signatures
require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement des fichiers
combine_completed_documents_and_audit_log: "Combiner les documents complétés et le journal d'audit"
@@ -2382,7 +2393,6 @@ fr: &fr
back: Retour
add_secret: Ajouter un secret
submission_example_payload: Exemple de payload de soumission
- status_users: '%{status} Utilisateurs'
there_are_no_signatures: "Il n'y a pas de signatures"
signed_with_trusted_certificate: Signé avec un certificat de confiance
signed_with_external_certificate: Signé avec un certificat externe
@@ -2527,6 +2537,9 @@ fr: &fr
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
+ archived_users: Utilisateurs Archivés
+ embedding_users: Utilisateurs Intégrés
+ view_embedding_users: Voir les Utilisateurs Intégrés
submission_event_names:
send_email_to_html: 'E-mail envoyé à %{submitter_name}'
send_reminder_email_to_html: 'E-mail de rappel envoyé à %{submitter_name}'
@@ -2679,6 +2692,7 @@ pt: &pt
require_signing_reason: Requerer motivo para assinatura
allow_typed_text_signatures: Permitir assinaturas digitadas
allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos
+ allow_to_decline_documents: Permitir recusar documentos
remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente
require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos
combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria
@@ -3018,7 +3032,6 @@ pt: &pt
back: Voltar
add_secret: Adicionar segredo
submission_example_payload: Exemplo de payload de submissão
- status_users: '%{status} Usuários'
there_are_no_signatures: Não há assinaturas
signed_with_trusted_certificate: Assinado com certificado confiável
signed_with_external_certificate: Assinado com certificado externo
@@ -3163,6 +3176,9 @@ pt: &pt
use_international_format_1xxx_: 'Use o formato internacional: +1xxx...'
submitter_cannot_be_updated: O remetente não pode ser atualizado.
at_least_one_field_must_be_filled: Pelo menos um campo deve ser preenchido.
+ archived_users: Usuários Arquivados
+ embedding_users: Usuários Incorporados
+ view_embedding_users: Ver Usuários Incorporados
submission_event_names:
send_email_to_html: 'E-mail enviado para %{submitter_name}'
send_reminder_email_to_html: 'E-mail de lembrete enviado para %{submitter_name}'
@@ -3315,6 +3331,7 @@ de: &de
require_signing_reason: Grund für die Signatur erforderlich
allow_typed_text_signatures: Unterschriften mit getipptem Text zulassen
allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen
+ allow_to_decline_documents: Erlauben, Dokumente abzulehnen
remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen
require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich
combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren
@@ -3654,7 +3671,6 @@ de: &de
back: Zurück
add_secret: Geheimnis hinzufügen
submission_example_payload: Beispiel-Payload für Einreichung
- status_users: '%{status} Benutzer'
there_are_no_signatures: Es gibt keine Unterschriften
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
signed_with_external_certificate: Signiert mit externem Zertifikat
@@ -3799,6 +3815,9 @@ de: &de
use_international_format_1xxx_: 'Verwenden Sie das internationale Format: +1xxx...'
submitter_cannot_be_updated: Der Absender kann nicht aktualisiert werden.
at_least_one_field_must_be_filled: Mindestens ein Feld muss ausgefüllt werden.
+ archived_users: Archivierte Benutzer
+ embedding_users: Einbettende Benutzer
+ view_embedding_users: Einbettende Benutzer anzeigen
submission_event_names:
send_email_to_html: 'E-Mail gesendet an %{submitter_name}'
send_reminder_email_to_html: 'Erinnerungs-E-Mail gesendet an %{submitter_name}'
diff --git a/config/routes.rb b/config/routes.rb
index 3d10d04d..389d6f50 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -153,15 +153,17 @@ Rails.application.routes.draw do
scope '/settings', as: :settings do
unless Docuseal.multitenant?
resources :storage, only: %i[index create], controller: 'storage_settings'
- resources :email, only: %i[index create], controller: 'email_smtp_settings'
resources :sms, only: %i[index], controller: 'sms_settings'
end
+ resources :email, only: %i[index create], controller: 'email_smtp_settings'
resources :sso, only: %i[index], controller: 'sso_settings'
resources :notifications, only: %i[index create], controller: 'notifications_settings'
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
resources :users, only: %i[index]
resources :archived_users, only: %i[index], path: 'users/:status', controller: 'users',
defaults: { status: :archived }
+ resources :integration_users, only: %i[index], path: 'users/:status', controller: 'users',
+ defaults: { status: :integration }
resource :personalization, only: %i[show create], controller: 'personalization_settings'
resources :api, only: %i[index create], controller: 'api_settings'
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
diff --git a/db/migrate/20241018115034_create_completed_submitters_and_documents.rb b/db/migrate/20241018115034_create_completed_submitters_and_documents.rb
new file mode 100644
index 00000000..eb917606
--- /dev/null
+++ b/db/migrate/20241018115034_create_completed_submitters_and_documents.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
+ def change
+ create_table :completed_submitters do |t|
+ t.bigint :submitter_id, null: false, index: true
+ t.bigint :submission_id, null: false
+ t.bigint :account_id, null: false, index: true
+ t.bigint :template_id, null: false
+ t.string :source, null: false
+ t.integer :sms_count, null: false
+ t.datetime :completed_at, null: false
+
+ t.timestamps
+ end
+
+ create_table :completed_documents do |t|
+ t.bigint :submitter_id, null: false, index: true
+ t.string :sha256, null: false, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20241022125135_populate_completed_submitters_and_documents.rb b/db/migrate/20241022125135_populate_completed_submitters_and_documents.rb
new file mode 100644
index 00000000..c2c3dd57
--- /dev/null
+++ b/db/migrate/20241022125135_populate_completed_submitters_and_documents.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
+ disable_ddl_transaction
+
+ class MigrationSubmitter < ApplicationRecord
+ self.table_name = 'submitters'
+
+ belongs_to :submission, class_name: 'MigrationSubmission'
+ has_many :submission_sms_events, -> { where(event_type: %w[send_sms send_2fa_sms]) },
+ class_name: 'MigrationSubmissionEvent', foreign_key: :submitter_id
+ end
+
+ class MigrationSubmission < ApplicationRecord
+ self.table_name = 'submissions'
+ end
+
+ class MigrationSubmissionEvent < ApplicationRecord
+ self.table_name = 'submission_events'
+ end
+
+ class MigrationCompletedSubmitter < ApplicationRecord
+ self.table_name = 'completed_submitters'
+ end
+
+ class MigrationCompletedDocument < ApplicationRecord
+ self.table_name = 'completed_documents'
+ end
+
+ def up
+ submitters = MigrationSubmitter.where.not(completed_at: nil)
+ .preload(:submission, :submission_sms_events)
+
+ count = submitters.count
+
+ puts "Updating the database - it might take ~#{(count / 1000 * 3) + 1} seconds" if count > 2000
+
+ submitters.find_each do |submitter|
+ completed_submitter = MigrationCompletedSubmitter.find_or_initialize_by(submitter_id: submitter.id)
+
+ next if completed_submitter.persisted?
+
+ submission = submitter.submission
+
+ completed_submitter.assign_attributes(
+ submission_id: submitter.submission_id,
+ account_id: submission.account_id,
+ template_id: submission.template_id,
+ source: submission.source,
+ sms_count: submitter.submission_sms_events.size,
+ completed_at: submitter.completed_at,
+ created_at: submitter.completed_at,
+ updated_at: submitter.completed_at
+ )
+
+ completed_submitter.save!
+ end
+
+ attachments = ActiveStorage::Attachment.where(record_type: 'Submitter', name: 'documents').preload(:blob)
+
+ attachments.find_each do |attachment|
+ sha256 = attachment.metadata['sha256']
+
+ next if sha256.blank?
+
+ completed_document = MigrationCompletedDocument.find_or_initialize_by(submitter_id: attachment.record_id, sha256:)
+
+ next if completed_document.persisted?
+
+ completed_document.assign_attributes(created_at: attachment.created_at, updated_at: attachment.created_at)
+
+ completed_document.save!
+ end
+ end
+
+ def down
+ nil
+ end
+end
diff --git a/db/migrate/20241026161207_add_unique_index_on_completed_submitters.rb b/db/migrate/20241026161207_add_unique_index_on_completed_submitters.rb
new file mode 100644
index 00000000..30b47c98
--- /dev/null
+++ b/db/migrate/20241026161207_add_unique_index_on_completed_submitters.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexOnCompletedSubmitters < ActiveRecord::Migration[7.2]
+ def change
+ remove_index :completed_submitters, :submitter_id
+ add_index :completed_submitters, :submitter_id, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8a3123bc..3fd0ab11 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) do
+ActiveRecord::Schema[7.2].define(version: 2024_10_26_161207) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -89,6 +89,29 @@ ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) do
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
+ create_table "completed_documents", force: :cascade do |t|
+ t.bigint "submitter_id", null: false
+ t.string "sha256", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["sha256"], name: "index_completed_documents_on_sha256"
+ t.index ["submitter_id"], name: "index_completed_documents_on_submitter_id"
+ end
+
+ create_table "completed_submitters", force: :cascade do |t|
+ t.bigint "submitter_id", null: false
+ t.bigint "submission_id", null: false
+ t.bigint "account_id", null: false
+ t.bigint "template_id", null: false
+ t.string "source", null: false
+ t.integer "sms_count", null: false
+ t.datetime "completed_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["account_id"], name: "index_completed_submitters_on_account_id"
+ t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
+ end
+
create_table "document_generation_events", force: :cascade do |t|
t.bigint "submitter_id", null: false
t.string "event_name", null: false
diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb
index 0eeddfa7..f823fc3d 100644
--- a/lib/action_mailer_configs_interceptor.rb
+++ b/lib/action_mailer_configs_interceptor.rb
@@ -18,25 +18,25 @@ module ActionMailerConfigsInterceptor
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
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/')
- )
+ if from.match?(User::FULL_EMAIL_REGEXP)
+ message[:from] = message[:from].to_s.sub(User::EMAIL_REGEXP, from)
+ else
+ message.from = from
end
return message
end
- email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
+ unless Docuseal.multitenant?
+ email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
- if email_configs
- message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
+ if email_configs
+ message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
- message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
- else
- message.delivery_method(:test)
+ message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
+ else
+ message.delivery_method(:test)
+ end
end
message
diff --git a/lib/docuseal.rb b/lib/docuseal.rb
index 9ea6f35e..48ef452d 100644
--- a/lib/docuseal.rb
+++ b/lib/docuseal.rb
@@ -20,19 +20,19 @@ module Docuseal
elsif ENV['MULTITENANT'] == 'true'
"https://console.#{HOST}"
else
- 'https://console.docuseal.co'
+ 'https://console.docuseal.com'
end
CLOUD_URL = if Rails.env.development?
'http://localhost:3000'
else
- 'https://docuseal.co'
+ 'https://docuseal.com'
end
CDN_URL = if Rails.env.development?
'http://localhost:3000'
elsif ENV['MULTITENANT'] == 'true'
"https://cdn.#{HOST}"
else
- 'https://cdn.docuseal.co'
+ 'https://cdn.docuseal.com'
end
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb
index 4c2571e8..29c1985f 100644
--- a/lib/params/base_validator.rb
+++ b/lib/params/base_validator.rb
@@ -2,7 +2,7 @@
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
+ EMAIL_REGEXP = User::FULL_EMAIL_REGEXP
InvalidParameterError = Class.new(StandardError)
@@ -70,13 +70,14 @@ module Params
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) }
+ return if params[key].to_s.include?('<')
- 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")
+ if params[key].to_s.strip.split(/\s*[;,]\s*/).compact_blank
+ .all? { |email| EmailTypo::DotCom.call(email).match?(EMAIL_REGEXP) }
+ return
end
+
+ raise_error(message || "#{key} must follow the email format: '#{params[key]}'")
end
def unique_value(params, key, message: nil)
diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb
index 5e0ab509..d0af550c 100644
--- a/lib/submissions/create_from_submitters.rb
+++ b/lib/submissions/create_from_submitters.rb
@@ -2,8 +2,11 @@
module Submissions
module CreateFromSubmitters
+ BaseError = Class.new(StandardError)
+
module_function
+ # rubocop:disable Metrics/BlockLength
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
preferences = Submitters.normalize_preferences(user.account, user, params)
@@ -37,6 +40,10 @@ module Submissions
preferences: preferences.merge(submission_preferences))
end
+ if submission.submitters.size > template.submitters.size
+ raise BaseError, 'Defined more signing parties than in template'
+ end
+
next if submission.submitters.blank?
maybe_add_invite_submitters(submission, template)
@@ -44,6 +51,7 @@ module Submissions
submission.tap(&:save!)
end
end
+ # rubocop:enable Metrics/BlockLength
def maybe_add_invite_submitters(submission, template)
template.submitters.each do |item|
diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb
index df72ce23..2f02d5e4 100644
--- a/lib/submissions/generate_audit_trail.rb
+++ b/lib/submissions/generate_audit_trail.rb
@@ -393,7 +393,7 @@ module Submissions
end
def sign_reason
- 'Signed with DocuSeal.co'
+ 'Signed with DocuSeal.com'
end
def maybe_add_background(_canvas, _submission, _page_size); end
diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb
index 94c836c3..fdcc2861 100644
--- a/lib/submissions/generate_preview_attachments.rb
+++ b/lib/submissions/generate_preview_attachments.rb
@@ -8,12 +8,11 @@ module Submissions
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)
+ configs = submission.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY,
+ AccountConfig::WITH_SIGNATURE_ID])
- is_flatten =
- submission.account.account_configs
- .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
+ 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
pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten)
diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb
index 6aba2594..2d1ec0ee 100644
--- a/lib/submitters/form_configs.rb
+++ b/lib/submitters/form_configs.rb
@@ -7,6 +7,7 @@ module Submitters
AccountConfig::FORM_WITH_CONFETTI_KEY,
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
AccountConfig::WITH_SIGNATURE_ID,
+ AccountConfig::ALLOW_TO_DECLINE_KEY,
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
AccountConfig::REUSE_SIGNATURE_KEY,
AccountConfig::ALLOW_TYPED_SIGNATURE].freeze
@@ -22,6 +23,7 @@ module Submitters
with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false
prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false
reuse_signature = find_safe_value(configs, AccountConfig::REUSE_SIGNATURE_KEY) != false
+ with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false
with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true
require_signing_reason = find_safe_value(configs, AccountConfig::REQUIRE_SIGNING_REASON_KEY) == true
@@ -30,6 +32,7 @@ module Submitters
with_typed_signature:,
with_confetti:,
reuse_signature:,
+ with_decline:,
completed_message:,
require_signing_reason:,
prefill_signature:,
diff --git a/lib/templates/find_acro_fields.rb b/lib/templates/find_acro_fields.rb
index 002f7cc1..b6ae50ce 100644
--- a/lib/templates/find_acro_fields.rb
+++ b/lib/templates/find_acro_fields.rb
@@ -25,6 +25,8 @@ module Templates
media_box_start = [media_box[0], media_box[1]]
crop_shift = [crop_box[0] - media_box[0], crop_box[1] - media_box[1]]
+ next unless child_field[:Rect]
+
x0, y0, x1, y1 = child_field[:Rect]
x0, y0 = correct_coordinates(x0, y0, crop_shift, media_box_start)
@@ -202,6 +204,7 @@ module Templates
field = annot[:Parent]
field = field[:Parent] while field[:Parent]
+ annots_index[field.hash] ||= page
fields_index[field.hash] ||= HexaPDF::Type::AcroForm::Field.wrap(pdf, field)
end
end
diff --git a/spec/factories/accounts.rb b/spec/factories/accounts.rb
index 785a37b4..b0551e06 100644
--- a/spec/factories/accounts.rb
+++ b/spec/factories/accounts.rb
@@ -5,5 +5,14 @@ FactoryBot.define do
name { Faker::Company.name }
locale { 'en-US' }
timezone { 'UTC' }
+
+ trait :with_testing_account do
+ after(:create) do |account|
+ testing_account = account.dup.tap { |a| a.name = "Testing - #{account.name}" }
+ testing_account.uuid = SecureRandom.uuid
+ account.testing_accounts << testing_account
+ account.save!
+ end
+ end
end
end
diff --git a/spec/factories/completed_documents.rb b/spec/factories/completed_documents.rb
new file mode 100644
index 00000000..d670e9a3
--- /dev/null
+++ b/spec/factories/completed_documents.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :completed_document do
+ submitter
+ sha256 { SecureRandom.hex(32) }
+ end
+end
diff --git a/spec/jobs/process_submitter_completion_job_spec.rb b/spec/jobs/process_submitter_completion_job_spec.rb
new file mode 100644
index 00000000..632003b3
--- /dev/null
+++ b/spec/jobs/process_submitter_completion_job_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ProcessSubmitterCompletionJob do
+ let(:account) { create(:account) }
+ let(:user) { create(:user, account:) }
+ let(:template) { create(:template, account:, author: user) }
+ let(:submission) { create(:submission, template:, created_by_user: user) }
+ let(:submitter) { create(:submitter, submission:, uuid: SecureRandom.uuid, completed_at: Time.current) }
+
+ before do
+ create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
+ value: GenerateCertificate.call.transform_values(&:to_pem))
+ end
+
+ describe '#perform' do
+ it 'creates a completed submitter' do
+ expect do
+ described_class.new.perform('submitter_id' => submitter.id)
+ end.to change(CompletedSubmitter, :count).by(1)
+
+ completed_submitter = CompletedSubmitter.last
+ submitter.reload
+
+ expect(completed_submitter.submitter_id).to eq(submitter.id)
+ expect(completed_submitter.submission_id).to eq(submitter.submission_id)
+ expect(completed_submitter.account_id).to eq(submitter.submission.account_id)
+ expect(completed_submitter.template_id).to eq(submitter.submission.template_id)
+ expect(completed_submitter.source).to eq(submitter.submission.source)
+ end
+
+ it 'creates a completed document' do
+ expect do
+ described_class.new.perform('submitter_id' => submitter.id)
+ end.to change(CompletedDocument, :count).by(1)
+
+ completed_document = CompletedDocument.last
+
+ expect(completed_document.submitter_id).to eq(submitter.id)
+ expect(completed_document.sha256).to be_present
+ expect(completed_document.sha256).to eq(submitter.documents.first.metadata['sha256'])
+ end
+
+ it 'raises an error if the submitter is not found' do
+ expect do
+ described_class.new.perform('submitter_id' => 'invalid_id')
+ end.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+end
diff --git a/spec/lib/params/base_validator_spec.rb b/spec/lib/params/base_validator_spec.rb
index 193b9b33..5a00e188 100644
--- a/spec/lib/params/base_validator_spec.rb
+++ b/spec/lib/params/base_validator_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Params::BaseValidator do
emails = [
' john.doe@example.com ',
'john.doe@example.com',
+ 'Test ',
'jane+newsletter@domain.org',
'mike_smith@company.net',
'lisa-wong@sub.example.co.uk',
@@ -34,50 +35,21 @@ RSpec.describe Params::BaseValidator do
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'
+ 'tricky.email@sub.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.to raise_error(described_class::InvalidParameterError, "email must follow the email format: '#{email}'")
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',
@@ -106,9 +78,7 @@ RSpec.describe Params::BaseValidator do
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',
@@ -126,7 +96,7 @@ RSpec.describe Params::BaseValidator do
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.to raise_error(described_class::InvalidParameterError, "email must follow the email format: '#{email}'")
end
end
diff --git a/spec/requests/submissions_spec.rb b/spec/requests/submissions_spec.rb
index 3a9590d8..c5d02f4b 100644
--- a/spec/requests/submissions_spec.rb
+++ b/spec/requests/submissions_spec.rb
@@ -3,11 +3,17 @@
require 'rails_helper'
describe 'Submission API', type: :request do
- let!(:account) { create(:account) }
- 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:) }
+ let(:account) { create(:account, :with_testing_account) }
+ let(:testing_account) { account.testing_accounts.first }
+ let(:author) { create(:user, account:) }
+ let(:testing_author) { create(:user, account: testing_account) }
+ let(:folder) { create(:template_folder, account:) }
+ let(:testing_folder) { create(:template_folder, account: testing_account) }
+ let(:templates) { create_list(:template, 2, account:, author:, folder:) }
+ let(:multiple_submitters_template) { create(:template, submitter_count: 3, account:, author:, folder:) }
+ let(:testing_templates) do
+ create_list(:template, 2, account: testing_account, author: testing_author, folder: testing_folder)
+ end
describe 'GET /api/submissions' do
it 'returns a list of submissions' do
@@ -41,6 +47,31 @@ describe 'Submission API', type: :request do
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to eq(JSON.parse(show_submission_body(submission).to_json))
end
+
+ it 'returns an authorization error if test account API token is used with a production submission' do
+ submission = create(:submission, :with_submitters, :with_events, template: templates[0], created_by_user: author)
+
+ get "/api/submissions/#{submission.id}", headers: { 'x-auth-token': testing_author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Submission #{submission.id} not found using testing API key; " \
+ 'Use production API key to access production submissions.' }.to_json)
+ )
+ end
+
+ it 'returns an authorization error if production account API token is used with a test submission' do
+ submission = create(:submission, :with_submitters, :with_events, template: testing_templates[0],
+ created_by_user: testing_author)
+
+ get "/api/submissions/#{submission.id}", headers: { 'x-auth-token': author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Submission #{submission.id} not found using production API key; " \
+ 'Use testing API key to access testing submissions.' }.to_json)
+ )
+ end
end
describe 'POST /api/submissions' do
@@ -48,7 +79,7 @@ describe 'Submission API', type: :request 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.doe@example.com' }]
+ submitters: [{ role: 'First Party', email: 'john.doe@example.com' }]
}.to_json
expect(response).to have_http_status(:ok)
@@ -63,7 +94,7 @@ describe 'Submission API', type: :request do
template_id: multiple_submitters_template.id,
send_email: true,
submitters: [
- { role: 'First Role', email: 'john.doe@example.com' },
+ { role: 'First Party', email: 'john.doe@example.com' },
{ email: 'jane.doe@example.com' },
{ email: 'mike.doe@example.com' }
]
@@ -88,7 +119,7 @@ describe 'Submission API', type: :request do
template_id: templates[0].id,
send_email: true,
submitters: [
- { role: 'First Role', email: 'john@example' }
+ { role: 'First Party', email: 'john@example' }
]
}.to_json
@@ -103,7 +134,7 @@ describe 'Submission API', type: :request 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.doe@example.com' }]
+ submitters: [{ role: 'First Party', email: 'john.doe@example.com' }]
}.to_json
expect(response).to have_http_status(:unprocessable_entity)
@@ -115,14 +146,28 @@ describe 'Submission API', type: :request do
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' }
+ { role: 'First Party', email: 'john.doe@example.com' },
+ { role: 'First Party', 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
+
+ it 'returns an error if number of submitters more than in the template' do
+ post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
+ template_id: templates[0].id,
+ send_email: true,
+ submitters: [
+ { email: 'jane.doe@example.com' },
+ { role: 'First Party', email: 'john.doe@example.com' }
+ ]
+ }.to_json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body).to eq({ 'error' => 'Defined more signing parties than in template' })
+ end
end
describe 'POST /api/submissions/emails' do
diff --git a/spec/requests/submitters_spec.rb b/spec/requests/submitters_spec.rb
index 37ceecce..ffe35cd9 100644
--- a/spec/requests/submitters_spec.rb
+++ b/spec/requests/submitters_spec.rb
@@ -3,10 +3,16 @@
require 'rails_helper'
describe 'Submitter API', type: :request do
- let!(:account) { create(:account) }
- let!(:author) { create(:user, account:) }
- let!(:folder) { create(:template_folder, account:) }
- let!(:templates) { create_list(:template, 2, account:, author:, folder:) }
+ let(:account) { create(:account, :with_testing_account) }
+ let(:testing_account) { account.testing_accounts.first }
+ let(:author) { create(:user, account:) }
+ let(:testing_author) { create(:user, account: testing_account) }
+ let(:folder) { create(:template_folder, account:) }
+ let(:testing_folder) { create(:template_folder, account: testing_account) }
+ let(:templates) { create_list(:template, 2, account:, author:, folder:) }
+ let(:testing_templates) do
+ create_list(:template, 2, account: testing_account, author: testing_author, folder: testing_folder)
+ end
describe 'GET /api/submitters' do
it 'returns a list of submitters' do
@@ -42,6 +48,34 @@ describe 'Submitter API', type: :request do
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to eq(JSON.parse(submitter_body(submitter).to_json))
end
+
+ it 'returns an authorization error if test account API token is used with a production submitter' do
+ submitter = create(:submission, :with_submitters, :with_events,
+ template: templates[0],
+ created_by_user: author).submitters.first
+
+ get "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': testing_author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Submitter #{submitter.id} not found using " \
+ 'testing API key; Use production API key to access production submitters.' }.to_json)
+ )
+ end
+
+ it 'returns an authorization error if production account API token is used with a test submitter' do
+ submitter = create(:submission, :with_submitters, :with_events,
+ template: testing_templates[0],
+ created_by_user: testing_author).submitters.first
+
+ get "/api/submitters/#{submitter.id}", headers: { 'x-auth-token': author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Submitter #{submitter.id} not found using production API key; " \
+ 'Use testing API key to access testing submitters.' }.to_json)
+ )
+ end
end
describe 'PUT /api/submitters' do
diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb
index 67430ed8..26bdb546 100644
--- a/spec/requests/templates_spec.rb
+++ b/spec/requests/templates_spec.rb
@@ -3,10 +3,12 @@
require 'rails_helper'
describe 'Templates API', type: :request do
- let!(:account) { create(:account) }
- let!(:author) { create(:user, account:) }
- let!(:folder) { create(:template_folder, account:) }
- let!(:template_preferences) { { 'request_email_subject' => 'Subject text', 'request_email_body' => 'Body Text' } }
+ let(:account) { create(:account, :with_testing_account) }
+ let(:testing_account) { account.testing_accounts.first }
+ let(:author) { create(:user, account:) }
+ let(:testing_author) { create(:user, account: testing_account) }
+ let(:folder) { create(:template_folder, account:) }
+ let(:template_preferences) { { 'request_email_subject' => 'Subject text', 'request_email_body' => 'Body Text' } }
describe 'GET /api/templates' do
it 'returns a list of templates' do
@@ -48,6 +50,38 @@ describe 'Templates API', type: :request do
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to eq(JSON.parse(template_body(template).to_json))
end
+
+ it 'returns an authorization error if test account API token is used with a production template' do
+ template = create(:template, account:,
+ author:,
+ folder:,
+ external_id: SecureRandom.base58(10),
+ preferences: template_preferences)
+
+ get "/api/templates/#{template.id}", headers: { 'x-auth-token': testing_author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Template #{template.id} not found using testing API key; " \
+ 'Use production API key to access production templates.' }.to_json)
+ )
+ end
+
+ it 'returns an authorization error if production account API token is used with a test template' do
+ template = create(:template, account: testing_account,
+ author: testing_author,
+ folder:,
+ external_id: SecureRandom.base58(10),
+ preferences: template_preferences)
+
+ get "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body).to eq(
+ JSON.parse({ error: "Template #{template.id} not found using production API key; " \
+ 'Use testing API key to access testing templates.' }.to_json)
+ )
+ end
end
describe 'PUT /api/templates' do
diff --git a/spec/requests/tools_spec.rb b/spec/requests/tools_spec.rb
new file mode 100644
index 00000000..b51f771c
--- /dev/null
+++ b/spec/requests/tools_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Tools API', type: :request do
+ let(:account) { create(:account) }
+ let(:author) { create(:user, account:) }
+ let(:file_path) { Rails.root.join('spec/fixtures/sample-document.pdf') }
+
+ before do
+ create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
+ value: GenerateCertificate.call.transform_values(&:to_pem))
+ end
+
+ describe 'POST /api/tools/verify' do
+ it 'returns a verification result' do
+ template = create(:template, account:, author:)
+ submission = create(:submission, :with_submitters, :with_events, template:, created_by_user: author)
+ blob = ActiveStorage::Blob.create_and_upload!(
+ io: file_path.open,
+ filename: 'sample-document.pdf',
+ content_type: 'application/pdf'
+ )
+ create(:completed_document, submitter: submission.submitters.first,
+ sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(blob.download)))
+
+ ActiveStorage::Attachment.create!(
+ blob:,
+ name: :documents,
+ record: submission.submitters.first
+ )
+
+ post '/api/tools/verify', headers: { 'x-auth-token': author.access_token.token }, params: {
+ file: Base64.encode64(File.read(file_path))
+ }.to_json
+
+ expect(response).to have_http_status(:ok)
+ expect(response.parsed_body['checksum_status']).to eq('verified')
+ end
+ end
+end
diff --git a/spec/system/api_settings_spec.rb b/spec/system/api_settings_spec.rb
index 4870964c..648e202d 100644
--- a/spec/system/api_settings_spec.rb
+++ b/spec/system/api_settings_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe 'API Settings' do
it 'shows verify signed PDF page' do
expect(page).to have_content('API')
- expect(page).to have_field('X-Auth-Token', with: user.access_token.token)
+ token = user.access_token.token
+ expect(page).to have_field('X-Auth-Token', with: token.sub(token[5..], '*' * token[5..].size))
end
end
diff --git a/spec/system/submit_form_spec.rb b/spec/system/submit_form_spec.rb
index da4c5463..7c2f3223 100644
--- a/spec/system/submit_form_spec.rb
+++ b/spec/system/submit_form_spec.rb
@@ -3,9 +3,9 @@
require 'rails_helper'
RSpec.describe 'Submit Form' do
- let!(:account) { create(:account) }
- let!(:user) { create(:user, account:) }
- let!(:template) { create(:template, account:, author: user) }
+ let(:account) { create(:account) }
+ let(:user) { create(:user, account:) }
+ let(:template) { create(:template, account:, author: user) }
before do
sign_in(user)
@@ -48,8 +48,8 @@ RSpec.describe 'Submit Form' do
end
context 'when initialized by shared email address' do
- let!(:submission) { create(:submission, template:, created_by_user: user) }
- let!(:submitters) { template.submitters.map { |s| create(:submitter, submission:, uuid: s['uuid']) } }
+ let(:submission) { create(:submission, template:, created_by_user: user) }
+ let(:submitters) { template.submitters.map { |s| create(:submitter, submission:, uuid: s['uuid']) } }
let(:submitter) { submitters.first }
before do
diff --git a/spec/system/team_settings_spec.rb b/spec/system/team_settings_spec.rb
index c3c35832..3472917a 100644
--- a/spec/system/team_settings_spec.rb
+++ b/spec/system/team_settings_spec.rb
@@ -18,13 +18,19 @@ RSpec.describe 'Team Settings' do
visit settings_users_path
end
- it 'shows all users' do
+ it 'shows only active users' do
within '.table' do
users.each do |user|
expect(page).to have_content(user.full_name)
expect(page).to have_content(user.email)
- expect(page).to have_no_content(other_user.email)
+ expect(page).to have_link('Edit', href: edit_user_path(user))
end
+
+ expect(page).to have_button('Remove')
+ expect(page).to have_no_button('Unarchive')
+
+ expect(page).to have_no_content(other_user.full_name)
+ expect(page).to have_no_content(other_user.email)
end
end
@@ -89,4 +95,58 @@ RSpec.describe 'Team Settings' do
expect(page).to have_no_content('User has been removed')
end
end
+
+ context 'when some users are archived' do
+ let!(:users) { create_list(:user, 2, account:) }
+ let!(:archived_users) { create_list(:user, 2, account:, archived_at: Time.current) }
+ let!(:other_user) { create(:user) }
+
+ it 'shows only active users' do
+ visit settings_users_path
+
+ within '.table' do
+ users.each do |user|
+ expect(page).to have_content(user.full_name)
+ expect(page).to have_content(user.email)
+ end
+
+ archived_users.each do |user|
+ expect(page).to have_no_content(user.full_name)
+ expect(page).to have_no_content(user.email)
+ end
+
+ expect(page).to have_no_content(other_user.full_name)
+ expect(page).to have_no_content(other_user.email)
+ end
+
+ expect(page).to have_link('View Archived', href: settings_archived_users_path)
+ end
+
+ it 'shows only archived users' do
+ visit settings_archived_users_path
+
+ within '.table' do
+ archived_users.each do |user|
+ expect(page).to have_content(user.full_name)
+ expect(page).to have_content(user.email)
+ expect(page).to have_no_link('Edit', href: edit_user_path(user))
+ end
+
+ users.each do |user|
+ expect(page).to have_no_content(user.full_name)
+ expect(page).to have_no_content(user.email)
+ expect(page).to have_no_link('Edit', href: edit_user_path(user))
+ end
+
+ expect(page).to have_button('Unarchive')
+ expect(page).to have_no_button('Remove')
+
+ expect(page).to have_no_content(other_user.full_name)
+ expect(page).to have_no_content(other_user.email)
+ end
+
+ expect(page).to have_content('Archived Users')
+ expect(page).to have_link('View Active', href: settings_users_path)
+ end
+ end
end
|