Commit iniziale di eminuxCRM: CRM Rails con pipeline, campagne email e Docker.
CI / scan_ruby (push) Failing after 11m20s
CI / scan_js (push) Successful in 10m35s
CI / lint (push) Has been cancelled

Include autenticazione, progetti isolati, mail marketing HTML con SMTP, test A/B e editor WYSIWYG.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 23:01:48 +02:00
co-authored by Cursor
commit c4e5f289cf
258 changed files with 11293 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
require "test_helper"
class AuthenticationTest < ActionDispatch::IntegrationTest
test "requires login" do
get root_path
assert_redirected_to login_path
end
test "login and logout" do
login_as users(:admin)
assert_response :redirect
follow_redirect!
assert_response :success
delete logout_path
assert_redirected_to login_path
end
test "non admin cannot create users" do
login_as users(:marco)
follow_redirect! if response.redirect?
post users_path, params: {
user: { email: "x@example.com", first_name: "X", last_name: "Y", role: "user", password: "password123", password_confirmation: "password123" }
}
assert_response :redirect
end
test "project space is isolated by url" do
login_as users(:admin)
follow_redirect! if response.redirect?
get project_root_path(project_code: "matchlivetv")
assert_response :success
assert_match(/MatchLiveTV/, response.body)
get project_root_path(project_code: "riskmeter")
assert_response :success
assert_match(/RiskMeter/, response.body)
end
test "user cannot open disabled project" do
login_as users(:marco)
follow_redirect! if response.redirect?
get project_root_path(project_code: "riskmeter")
assert_redirected_to root_path
end
test "disabled user cannot login" do
admin = users(:admin)
User.create!(
email: "keeper@example.com",
first_name: "Keep",
last_name: "Admin",
role: "admin",
password: "password123",
password_confirmation: "password123"
)
assert admin.update(active: false)
post login_path, params: { email: admin.email, password: "password123" }
assert_response :unprocessable_entity
assert_match(/Email o password non validi/i, response.body)
end
test "admin can open platform settings" do
login_as users(:admin)
follow_redirect! if response.redirect?
get admin_path
assert_response :success
assert_match(/Impostazioni piattaforma/, response.body)
assert_match(/Utenti/, response.body)
end
test "non admin cannot open platform settings" do
login_as users(:marco)
follow_redirect! if response.redirect?
get admin_path
assert_redirected_to root_path
end
test "admin home has new project and settings actions" do
login_as users(:admin)
follow_redirect! if response.redirect?
get root_path
assert_response :success
assert_match(/Nuovo progetto/, response.body)
assert_match(/Impostazioni/, response.body)
assert_no_match(/Amministrazione progetti/, response.body)
end
end
@@ -0,0 +1,37 @@
require "test_helper"
class MailIdentitiesControllerTest < ActionDispatch::IntegrationTest
test "non admin cannot manage smtp accounts" do
login_as users(:marco)
follow_redirect! if response.redirect?
get mail_identities_path
assert_redirected_to root_path
end
test "admin can create an smtp account" do
login_as users(:admin)
follow_redirect! if response.redirect?
assert_difference "MailIdentity.count", 1 do
post mail_identities_path, params: {
mail_identity: {
name: "MatchLiveTV",
from_name: "MatchLiveTV",
from_email: "hello@matchlivetv.test",
smtp_host: "smtp.test",
smtp_port: 587,
smtp_username: "hello@matchlivetv.test",
smtp_password: "super-secret",
smtp_authentication: "plain",
encryption: "starttls",
verify_ssl: "1",
active: "1"
}
}
end
assert_redirected_to mail_identities_path
identity = MailIdentity.find_by!(from_email: "hello@matchlivetv.test")
assert_equal "super-secret", identity.smtp_password
end
end
@@ -0,0 +1,44 @@
require "test_helper"
require "base64"
class MailImagesControllerTest < ActionDispatch::IntegrationTest
PNG = Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
test "admin can upload an image for the editor" do
login_as users(:admin)
follow_redirect! if response.redirect?
upload = png_upload
assert_difference -> { ActiveStorage::Blob.count }, 1 do
post mail_images_path(project_code: "matchlivetv"), params: { file: upload }
end
assert_response :success
json = JSON.parse(response.body)
assert json["url"].present?
assert_match(/active_storage/, json["url"])
end
test "rejects non image files" do
login_as users(:admin)
follow_redirect! if response.redirect?
file = Tempfile.new(["note", ".txt"])
file.write("not-an-image")
file.rewind
post mail_images_path(project_code: "matchlivetv"), params: {
file: Rack::Test::UploadedFile.new(file.path, "text/plain")
}
assert_response :unprocessable_entity
end
private
def png_upload
file = Tempfile.new(["pixel", ".png"])
file.binmode
file.write(PNG)
file.rewind
Rack::Test::UploadedFile.new(file.path, "image/png")
end
end
@@ -0,0 +1,107 @@
require "test_helper"
class MailingsControllerTest < ActionDispatch::IntegrationTest
setup do
@project = projects(:matchlivetv)
@identity = create_mail_identity
opportunities(:deal).update!(send_status: "to_send", pipeline_stage: "to_contact")
end
test "lists mailings in project space" do
login_as users(:admin)
follow_redirect! if response.redirect?
get mailings_path(project_code: @project.code)
assert_response :success
assert_match(/Email/, response.body)
end
test "creates a draft and builds recipients" do
login_as users(:admin)
follow_redirect! if response.redirect?
assert_difference -> { Mailing.count } => 1, -> { MailingRecipient.count } => 1 do
post mailings_path(project_code: @project.code), params: {
mailing: {
name: "Lancio",
mail_identity_id: @identity.id,
audience: "to_send",
subject: "Ciao {{societa}}",
body_html: "<p>Ciao {{contatto_nome}}</p>",
interval_seconds: 0
}
}
end
mailing = Mailing.last
assert_redirected_to mailing_path(mailing, project_code: @project.code)
assert_equal "pending", mailing.mailing_recipients.first.status
end
test "check recipients then queue send" do
login_as users(:admin)
follow_redirect! if response.redirect?
mailing = create_mailing(identity: @identity, audience: "to_send")
mailing.rebuild_recipients!
recipient = mailing.mailing_recipients.first
patch update_recipients_mailing_path(mailing, project_code: @project.code), params: { pending_ids: [recipient.id] }
assert_redirected_to mailing_path(mailing, project_code: @project.code)
assert_equal "pending", recipient.reload.status
perform_enqueued_jobs do
post queue_mailing_path(mailing, project_code: @project.code)
end
assert_equal "sent", mailing.reload.status
assert_equal "sent", recipient.reload.status
assert_equal "sent", opportunities(:deal).reload.send_status
assert_equal "contacted", opportunities(:deal).pipeline_stage
assert_equal 1, ActionMailer::Base.deliveries.size
assert_equal "Ciao ASD Test Calcio", ActionMailer::Base.deliveries.last.subject
end
test "test send goes to current user" do
login_as users(:admin)
follow_redirect! if response.redirect?
mailing = create_mailing(identity: @identity, audience: "all")
mailing.rebuild_recipients!
assert_emails 1 do
post test_send_mailing_path(mailing, project_code: @project.code)
end
mail = ActionMailer::Base.deliveries.last
assert_equal [users(:admin).email], mail.to
assert_match(/\[TEST\]/, mail.subject)
end
test "creates an A/B mailing and sends variant B as test" do
login_as users(:admin)
follow_redirect! if response.redirect?
post mailings_path(project_code: @project.code), params: {
mailing: {
name: "AB lancio",
mail_identity_id: @identity.id,
audience: "to_send",
ab_test: "1",
ab_assignment: "from_record",
subject: "Oggetto A {{societa}}",
body_html: "<p>Versione A</p>",
subject_b: "Oggetto B {{societa}}",
body_html_b: "<p>Versione B</p>",
interval_seconds: 0
}
}
mailing = Mailing.last
assert mailing.ab_test?
assert_equal "A", mailing.mailing_recipients.first.ab_variant
assert_emails 1 do
post test_send_mailing_path(mailing, project_code: @project.code, variant: "B")
end
mail = ActionMailer::Base.deliveries.last
assert_match(/\[TEST B\] Oggetto B ASD Test Calcio/, mail.subject)
body = (mail.html_part || mail).body.to_s
assert_match(/Versione B/, body)
end
end
+117
View File
@@ -0,0 +1,117 @@
require "test_helper"
class UsersControllerTest < ActionDispatch::IntegrationTest
test "non admin cannot list or create users" do
login_as users(:marco)
follow_redirect! if response.redirect?
get users_path
assert_redirected_to root_path
post users_path, params: {
user: { email: "x@example.com", first_name: "X", last_name: "Y", role: "user", password: "password123", password_confirmation: "password123" }
}
assert_redirected_to root_path
end
test "admin can create a user" do
login_as users(:admin)
follow_redirect! if response.redirect?
assert_difference "User.count", 1 do
post users_path, params: {
user: {
email: "nuovo@example.com",
first_name: "Nuovo",
last_name: "Utente",
role: "user",
password: "password123",
password_confirmation: "password123"
},
project_ids: [ projects(:matchlivetv).id ]
}
end
assert_redirected_to users_path
user = User.find_by!(email: "nuovo@example.com")
assert user.project_enabled?(projects(:matchlivetv))
assert_not user.project_enabled?(projects(:riskmeter))
end
test "admin can disable another admin when a second admin exists" do
login_as users(:admin)
follow_redirect! if response.redirect?
other = User.create!(
email: "emiliano@example.com",
first_name: "Emiliano",
last_name: "Admin",
role: "admin",
password: "password123",
password_confirmation: "password123"
)
patch user_path(other), params: {
user: { first_name: other.first_name, last_name: other.last_name, email: other.email, role: "admin", active: "0" }
}
assert_redirected_to users_path
assert_not other.reload.active?
end
test "admin cannot disable last active admin" do
login_as users(:admin)
follow_redirect! if response.redirect?
admin = users(:admin)
patch user_path(admin), params: {
user: { first_name: admin.first_name, last_name: admin.last_name, email: admin.email, role: "admin", active: "0" }
}
assert_response :unprocessable_entity
assert admin.reload.active?
assert_match(/almeno un amministratore attivo/i, response.body)
end
test "admin cannot delete self" do
login_as users(:admin)
follow_redirect! if response.redirect?
assert_no_difference "User.count" do
delete user_path(users(:admin))
end
assert_redirected_to users_path
follow_redirect!
assert_match(/Non puoi eliminare il tuo account/i, response.body)
end
test "admin can delete another admin when a second remains" do
login_as users(:admin)
follow_redirect! if response.redirect?
other = User.create!(
email: "other-admin@example.com",
first_name: "Other",
last_name: "Admin",
role: "admin",
password: "password123",
password_confirmation: "password123"
)
assert_difference "User.count", -1 do
delete user_path(other)
end
assert_redirected_to users_path
end
test "admin can delete another user and nullify activities" do
login_as users(:admin)
follow_redirect! if response.redirect?
marco = users(:marco)
activity = activities(:note)
assert_equal marco.id, activity.user_id
assert_difference "User.count", -1 do
delete user_path(marco)
end
assert_redirected_to users_path
assert_nil activity.reload.user_id
end
end