diff --git a/.github/workflows/specs.yml b/.github/workflows/specs.yml new file mode 100644 index 000000000..3d33384fe --- /dev/null +++ b/.github/workflows/specs.yml @@ -0,0 +1,36 @@ +name: Specs + +on: push + +jobs: + specs: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install libvips and pdftoppm + run: | + sudo apt-get update -qq -o Acquire::Retries=3 + sudo apt-get install -y --fix-missing -qq -o Acquire::Retries=3 libvips poppler-utils + + - name: Install Ruby and run Bundler install with cache + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set timezone + uses: szenius/set-timezone@v2.0 + with: + timezoneLinux: Europe/Zurich + + - name: Run specs + env: + RAILS_ENV: test + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + run: | + bin/rails db:setup + bin/rails test:prepare + bin/rspec + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 128ce7760..271f2449d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ name: Tests on: push jobs: - test: + tests: runs-on: ubuntu-24.04 steps: @@ -25,11 +25,9 @@ jobs: with: timezoneLinux: Europe/Zurich - - name: Rails tests + - name: Run tests env: RAILS_ENV: test RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} run: | - bin/rails db:setup - bin/rails test:prepare - bin/rspec + bin/rails test:all diff --git a/Aptfile b/Aptfile deleted file mode 100644 index e297bd2a6..000000000 --- a/Aptfile +++ /dev/null @@ -1,2 +0,0 @@ -libvips -libvips-dev diff --git a/Gemfile b/Gemfile index 0b39febe6..0ffdd8728 100644 --- a/Gemfile +++ b/Gemfile @@ -65,7 +65,7 @@ gem "hexapdf" gem "public_suffix" gem "rubyXL" gem "rexml" -gem "rubyzip", require: "zip" +gem "rubyzip", "~> 2.4.rc1", require: "zip" gem "parallel" gem "postmark-rails" @@ -94,7 +94,6 @@ group :development, :test do gem "rspec-rails" gem "faker" gem "factory_bot_rails" - gem "brakeman" end group :development do @@ -114,8 +113,10 @@ group :development do gem "rubocop-rails-omakase", require: false gem "rubocop-slim", require: false - gem "cloudflare" - gem "resolv" + gem "brakeman", require: false + + gem "cloudflare", require: false + gem "resolv", require: false gem "prosopite" gem "pg_query" diff --git a/Gemfile.lock b/Gemfile.lock index b1a8c5215..9dd494f3b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -376,12 +376,12 @@ GEM net-smtp (0.5.0) net-ssh (7.3.0) nio4r (2.7.4) - nokogiri (1.18.0) + nokogiri (1.18.1) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.0-arm64-darwin) + nokogiri (1.18.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.0-x86_64-linux-gnu) + nokogiri (1.18.1-x86_64-linux-gnu) racc (~> 1.4) openssl (3.3.0) optimist (3.2.0) @@ -506,7 +506,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (3.8.0) + rbs (3.8.1) logger rchardet19 (1.3.7) rdoc (6.10.0) @@ -590,7 +590,7 @@ GEM rubyXL (3.4.33) nokogiri (>= 1.10.8) rubyzip (>= 1.3.0) - rubyzip (2.3.2) + rubyzip (2.4.rc1) securerandom (0.4.1) simple_form (5.3.1) actionpack (>= 5.2) @@ -603,7 +603,7 @@ GEM temple thor tilt - solid_queue (1.1.0) + solid_queue (1.1.2) activejob (>= 7.1) activerecord (>= 7.1) concurrent-ruby (>= 1.3.1) @@ -629,7 +629,7 @@ GEM attr_extras (>= 6.2.4) diff-lcs patience_diff - tailwindcss-rails (3.0.0) + tailwindcss-rails (3.1.0) railties (>= 7.0.0) tailwindcss-ruby tailwindcss-ruby (3.4.17) @@ -746,7 +746,7 @@ DEPENDENCIES ruby-lsp-rails ruby-lsp-rspec rubyXL - rubyzip + rubyzip (~> 2.4.rc1) simple_form slim solid_queue diff --git a/app/models/invoice.rb b/app/models/invoice.rb index ac3623032..4e6e91cff 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -170,12 +170,11 @@ def send! # Leave some time for the invoice PDF to be uploaded MailTemplate.deliver_later(:invoice_created, invoice: self) update!(sent_at: Time.current) - close_or_open! - rescue => e - Error.report(e, - invoice_id: id, - emails: member.emails, - member_id: member_id) + rescue => e + Error.report(e, + invoice_id: id, + emails: member.emails, + member_id: member_id) end def mark_as_sent! diff --git a/app/models/organization.rb b/app/models/organization.rb index 5722ec09f..68c18ac11 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -24,17 +24,18 @@ class Organization < ApplicationRecord INPUT_FORM_MODES = %w[hidden visible required] DELIVERY_PDF_MEMBER_INFOS = %w[none phones food_note] MEMBERS_SUBDOMAINS = %w[membres mitglieder soci members] + ACTIVITY_PARTICIPATIONS_DEMANDED_LOGIC_DEFAULT = <<-LIQUID + {% if member.salary_basket %} + 0 + {% else %} + {{ membership.baskets | divided_by: membership.full_year_deliveries | times: membership.full_year_activity_participations | round }} + {% endif %} + LIQUID attribute :shop_delivery_open_last_day_end_time, :time_only attribute :icalendar_auth_token, :string, default: -> { SecureRandom.hex(16) } attribute :activity_participations_demanded_logic, :string, default: -> { - <<~LIQUID - {% if member.salary_basket %} - 0 - {% else %} - {{ membership.baskets | divided_by: membership.full_year_deliveries | times: membership.full_year_activity_participations | round }} - {% endif %} - LIQUID + ACTIVITY_PARTICIPATIONS_DEMANDED_LOGIC_DEFAULT } translated_attributes :invoice_info, :invoice_footer diff --git a/app/models/permission.rb b/app/models/permission.rb index 90f450577..29e0aa190 100644 --- a/app/models/permission.rb +++ b/app/models/permission.rb @@ -5,7 +5,6 @@ class Permission < ApplicationRecord RIGHTS = %i[read write].freeze SUPERADMIN_ID = 1 - SUPERADMIN_FEATURES = translated_attributes :name, required: true diff --git a/app/views/active_admin/_site_footer.html.slim b/app/views/active_admin/_site_footer.html.slim index 6bcb0e9ec..881d6ca68 100644 --- a/app/views/active_admin/_site_footer.html.slim +++ b/app/views/active_admin/_site_footer.html.slim @@ -1,11 +1,11 @@ footer class="pt-40" ul class="flex justify-center" role="group" - li = link_to updates_path, class: "inline-flex items-center px-2 py-1 no-underline text-sm font-medium text-gray-400 dark:text-gray-600 bg-white border border-gray-200 rounded-s-lg hover:bg-gray-100 hover:text-green-600 focus:z-10 focus:ring-2 focus:ring-green-700 focus:text-green-700 dark:bg-transparent dark:border-gray-800 dark:hover:text-green-500 dark:hover:bg-gray-700 dark:focus:ring-green-500 dark:focus:text-white" do + li = link_to updates_path, class: "action-item-button px-2 py-1 rounded-e-none text-gray-400 dark:text-gray-500" do = icon "gift", class: "w-5 h-5 me-2" = t(".updates") - li = link_to handbook_path, class: "inline-flex items-center px-2 py-1 no-underline text-sm font-medium text-gray-400 dark:text-gray-600 bg-white border border-l-0 border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-green-600 focus:z-10 focus:ring-2 focus:ring-green-700 focus:text-green-700 dark:bg-transparent dark:border-gray-800 dark:hover:text-green-500 dark:hover:bg-gray-700 dark:focus:ring-green-500 dark:focus:text-white" do + li = link_to handbook_path, class: "action-item-button px-2 py-1 border-s-0 rounded-s-none text-gray-400 dark:text-gray-500" do = icon "book-open", class: "w-5 h-5 me-2" = t(".handbook") diff --git a/app/views/admin_mailer/new_activity_participation_email.en.liquid b/app/views/admin_mailer/new_activity_participation_email.en.liquid index 9131973c2..8e41b8848 100644 --- a/app/views/admin_mailer/new_activity_participation_email.en.liquid +++ b/app/views/admin_mailer/new_activity_participation_email.en.liquid @@ -4,7 +4,7 @@ {% highlight_list %} Date: {{ activity.date_long }} - Time: {{ activity.period }} + Schedule: {{ activity.period }} Activity: {{ activity.title }} {% if activity.description %} Description: {{ activity.description }} diff --git a/app/views/admin_mailer/new_registration_email.en.liquid b/app/views/admin_mailer/new_registration_email.en.liquid index 5bb9caf8a..f9b894dc1 100644 --- a/app/views/admin_mailer/new_registration_email.en.liquid +++ b/app/views/admin_mailer/new_registration_email.en.liquid @@ -8,5 +8,5 @@ {% endif %} {% button member.admin_url %} - Access the member's page + Access member page {% endbutton %} diff --git a/app/views/mail_templates/activity_participation_rejected.en.liquid b/app/views/mail_templates/activity_participation_rejected.en.liquid index 7884d374d..9204af3a7 100644 --- a/app/views/mail_templates/activity_participation_rejected.en.liquid +++ b/app/views/mail_templates/activity_participation_rejected.en.liquid @@ -2,7 +2,7 @@ {% highlight_list %} Date: {{ activity.date_long }} - Time: {{ activity.period }} + Schedule: {{ activity.period }} Activity: {{ activity.title }} {% if activity.description %} Description: {{ activity.description }} diff --git a/app/views/mail_templates/invoice_overdue_notice.en.liquid b/app/views/mail_templates/invoice_overdue_notice.en.liquid index 7ede6844a..d95ec747d 100644 --- a/app/views/mail_templates/invoice_overdue_notice.en.liquid +++ b/app/views/mail_templates/invoice_overdue_notice.en.liquid @@ -12,6 +12,6 @@

You can view your invoices and payments at any time from your member page.

{% button member.billing_url %} - Access My Member Page + Access my member page {% endbutton %} {% endunless %} diff --git a/bin/update b/bin/update deleted file mode 100755 index 58bfaed51..000000000 --- a/bin/update +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env ruby -require 'fileutils' -include FileUtils - -# path to your application root. -APP_ROOT = File.expand_path('..', __dir__) - -def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") -end - -chdir APP_ROOT do - # This script is a way to update your development environment automatically. - # Add necessary update steps to this file. - - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') - - # Install JavaScript dependencies if using Yarn - # system('bin/yarn') - - puts "\n== Updating database ==" - system! 'bin/rails db:migrate' - - puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' - - puts "\n== Restarting application server ==" - system! 'bin/rails restart' -end diff --git a/config/application.rb b/config/application.rb index 894693949..8e00a9c53 100644 --- a/config/application.rb +++ b/config/application.rb @@ -29,7 +29,7 @@ class Application < Rails::Application config.time_zone = "Europe/Zurich" config.i18n.available_locales = %w[en fr de it] - config.i18n.default_locale = :fr + config.i18n.default_locale = :en config.i18n.fallbacks = true # :time must not be included here because of the tod gem diff --git a/config/locales/mail_template.yml b/config/locales/mail_template.yml index 0be0ee911..0e658ca24 100644 --- a/config/locales/mail_template.yml +++ b/config/locales/mail_template.yml @@ -3,32 +3,32 @@ _: default_subjects: activity_participation_rejected: _de: "Abgelehnte Aktivität \U0001F62C" - _en: "Activity Rejected \U0001F62C" + _en: "Activity rejected \U0001F62C" _fr: "Activité refusée \U0001F62C" _it: "Attività rifiutata \U0001F62C" activity_participation_reminder: _de: Bevorstehende Aktivität ({{ activity.date }}) - _en: Upcoming Activity ({{ activity.date }}) + _en: Upcoming activity ({{ activity.date }}) _fr: Activité à venir ({{ activity.date }}) _it: Attività futura ({{ activity.date }}) activity_participation_validated: _de: "Bestätigte Aktivität \U0001F389" - _en: "Activity Confirmed \U0001F389" + _en: "Activity confirmed \U0001F389" _fr: "Activité validée \U0001F389" _it: "Attività confermata \U0001F389" invoice_cancelled: _de: 'Stornierte Rechnung #{{ invoice.number }}' - _en: 'Cancelled Invoice #{{ invoice.number }}' + _en: 'Cancelled invoice #{{ invoice.number }}' _fr: 'Facture annulée #{{ invoice.number }}' _it: 'Fattura annullata #{{ invoice.number }}' invoice_created: _de: 'Neue Rechnung #{{ invoice.number }}' - _en: 'New Invoice #{{ invoice.number }}' + _en: 'New invoice #{{ invoice.number }}' _fr: 'Nouvelle facture #{{ invoice.number }}' _it: 'Nuova fattura #{{ invoice.number }}' invoice_overdue_notice: _de: "Mahnung #{{ invoice.overdue_notices_count }} der Rechnung #{{ invoice.number }} \U0001F62C" - _en: "Overdue Notice #{{ invoice.overdue_notices_count }} for Invoice #{{ invoice.number }} \U0001F62C" + _en: "Overdue notice #{{ invoice.overdue_notices_count }} for invoice #{{ invoice.number }} \U0001F62C" _fr: "Rappel #{{ invoice.overdue_notices_count }} de la facture #{{ invoice.number }} \U0001F62C" _it: "Promemoria #{{ invoice.overdue_notices_count }} della fattura #{{ invoice.number }} \U0001F62C" member_activated: @@ -38,42 +38,42 @@ _: _it: Benvenuto/a! member_validated: _de: Registrierung validiert! - _en: Registration Validated! + _en: Registration validated! _fr: Inscription validée! _it: Iscrizione confermata! membership_final_basket: _de: Letzte Tasche! - _en: Last Basket! + _en: Last basket! _fr: Dernier panier! _it: Ultimo cestino! membership_first_basket: _de: Erste Tasche des Jahres! - _en: First Basket of the Year! + _en: First basket of the year! _fr: Premier panier de l'année! _it: Primo cestino dell'anno! membership_initial_basket: _de: Erste Tasche! - _en: First Basket! + _en: First basket! _fr: Premier panier! _it: Primo cestino! membership_last_basket: _de: Letzte Tasche des Jahres! - _en: Last Basket of the Year! + _en: Last basket of the year! _fr: Dernier panier de l'année! _it: Ultimo cestino dell'anno! membership_last_trial_basket: _de: Letzte Probe Tasche! - _en: Last Trial Basket! + _en: Last trial basket! _fr: Dernier panier à l'essai! _it: Ultimo cestino da provare! membership_renewal: _de: Ihr Abonnement verlängern - _en: Renew Your Membership + _en: Renew your membership _fr: Renouvellement de votre abonnement _it: Rinnovo del vostro abbonamento membership_renewal_reminder: _de: Ihr Abonnement verlängern (Mahnung) - _en: Renew Your Membership (Reminder) + _en: Renew your membership (reminder) _fr: Renouvellement de votre abonnement (Rappel) _it: Rinnovo del vostro abbonamento (promemoria) description: diff --git a/lib/tasks/hostname.rake b/lib/tasks/hostname.rake index 088c2abf6..4b8b957ba 100644 --- a/lib/tasks/hostname.rake +++ b/lib/tasks/hostname.rake @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "cloudflare" require "cloudflare-rails" namespace :hostname do diff --git a/lib/tenant.rb b/lib/tenant.rb index 5049551a8..7fb1e028c 100644 --- a/lib/tenant.rb +++ b/lib/tenant.rb @@ -66,7 +66,7 @@ def switch(tenant) end def connect(tenant) - raise "Only for use in Rails console" unless defined?(Rails::Console) + ensure_test_env_or_rails_console! enter(tenant) ActiveRecord::Base.connecting_to(shard: tenant.to_sym) @@ -74,7 +74,7 @@ def connect(tenant) # Only for use in console def disconnect - raise "Only for use in Rails console" unless defined?(Rails::Console) + ensure_test_env_or_rails_console! leave ActiveRecord::Base.connecting_to(shard: :default) @@ -112,4 +112,11 @@ def relevant_domain(domain) # Ignore tld locally Rails.env.local? ? domain.sld : domain.domain end + + def ensure_test_env_or_rails_console! + return if Rails.env.test? + return if defined?(Rails::Console) + + raise "Only for use in Rails console or test environment" + end end diff --git a/spec/jobs/billing/missing_activity_participations_invoicer_job_spec.rb b/spec/jobs/billing/missing_activity_participations_invoicer_job_spec.rb deleted file mode 100644 index d02d8502c..000000000 --- a/spec/jobs/billing/missing_activity_participations_invoicer_job_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Billing::MissingActivityParticipationsInvoicerJob do - before { Current.org.update!(activity_price: 90, trial_baskets_count: 0) } - - specify "noop if no activty price" do - Current.org.update!(activity_price: 0) - - membership = create(:membership, activity_participations_demanded_annually: 2) - - expect { described_class.perform_later(membership) } - .not_to change(Invoice, :count) - end - - specify "noop if no missing activity participations" do - membership = create(:membership, activity_participations_demanded_annually: 0) - - expect { described_class.perform_later(membership) } - .not_to change(Invoice, :count) - end - - specify "create invoice and send invoice" do - membership = create(:membership, activity_participations_demanded_annually: 2) - - expect { - perform_enqueued_jobs { described_class.perform_later(membership) } - } - .to change(Invoice, :count).by(1) - .and change { membership.reload.activity_participations_missing }.to(0) - .and change { InvoiceMailer.deliveries.size }.by(1) - - invoice = Invoice.last - expect(invoice).to have_attributes( - member_id: membership.member_id, - date: Date.today, - missing_activity_participations_count: 2, - missing_activity_participations_fiscal_year: membership.fiscal_year, - entity_type: "ActivityParticipation", - entity_id: nil, - amount: 2 * 90) - expect(invoice).to be_sent - end - - specify "create invoice for previous year membership" do - Current.org.update!(fiscal_year_start_month: 5) - membership = travel_to "2021-01-06" do - create(:membership, activity_participations_demanded_annually: 2) - end - - expect { - perform_enqueued_jobs { described_class.perform_later(membership) } - }.to change(Invoice, :count).by(1) - - invoice = Invoice.last - expect(invoice).to have_attributes( - member_id: membership.member_id, - date: Date.today, - missing_activity_participations_fiscal_year: Current.org.fiscal_year_for(2020), - entity_type: "ActivityParticipation", - entity_id: nil) - expect(invoice).to be_sent - end -end diff --git a/spec/jobs/concerns/tenant_context_spec.rb b/spec/jobs/concerns/tenant_context_spec.rb deleted file mode 100644 index 9be3cb175..000000000 --- a/spec/jobs/concerns/tenant_context_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe TenantContext do - specify "add current attributes and tenant last arguments" do - class DummyJob < ActiveJob::Base - include TenantContext - def perform(admin, name: nil) - admin.update!(name: name) - end - end - - admin = create(:admin, name: "Admin") - Current.session = create(:session, id: 42) - DummyJob.perform_later(admin, name: "Admin!") - - expect(enqueued_jobs.size).to eq(1) - job = enqueued_jobs.first - expect(job["arguments"]).to eq([ - { "_aj_globalid" => "gid://csa-admin/Admin/1" }, - { "name" => "Admin!", "_aj_ruby2_keywords" => [ "name" ] }, - { - "tenant" => "acme", - "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/42" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] - } - ]) - - perform_enqueued_jobs - expect(enqueued_jobs.size).to eq(0) - expect(admin.reload.name).to eq("Admin!") - end - - specify "retry with the same current attributes and tenant last arguments" do - class DummyExceptionJob < ActiveJob::Base - include TenantContext - retry_on Exception, wait: :polynomially_longer, attempts: 2 - - def perform(foo) - raise Exception - end - end - - Current.session = create(:session, id: 42) - DummyExceptionJob.perform_later("bar") - - expect(enqueued_jobs.size).to eq(1) - job = enqueued_jobs.first - expect(job["arguments"]).to eq([ - "bar", - { - "tenant" => "acme", - "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/42" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] - } - ]) - - # rescue Exception one time - perform_enqueued_jobs - - expect(enqueued_jobs.size).to eq(1) - job = enqueued_jobs.first - expect(job["arguments"]).to eq([ - "bar", - { - "tenant" => "acme", - "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/42" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] - } - ]) - - begin - perform_enqueued_jobs - rescue Exception - end - expect(enqueued_jobs.size).to eq(0) - end -end diff --git a/spec/jobs/membership_renewal_job_spec.rb b/spec/jobs/membership_renewal_job_spec.rb deleted file mode 100644 index c1d19e9f6..000000000 --- a/spec/jobs/membership_renewal_job_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe MembershipRenewalJob, freeze: "2022-01-01" do - let(:next_fy) { Current.org.fiscal_year_for(Date.today.year + 1) } - - it "raises when no next year deliveries" do - membership = create(:membership) - - expect(Delivery.between(next_fy.range).count).to be_zero - expect { - MembershipRenewalJob.perform_later(membership) - perform_enqueued_jobs - }.to raise_error(MembershipRenewal::MissingDeliveriesError) - end - - it "renews a membership without complements" do - create(:delivery, date: next_fy.beginning_of_year) - membership = create(:membership, - basket_quantity: 2, - basket_price: 42, - baskets_annual_price_change: 130, - depot_price: 3, - activity_participations_demanded_annually: 5, - activity_participations_annual_price_change: -60) - - membership.basket_size.update!(price: 41) - membership.depot.update!(price: 4) - - expect { - MembershipRenewalJob.perform_later(membership) - perform_enqueued_jobs - }.to change(Membership, :count).by(1) - - expect(Membership.last).to have_attributes( - member_id: membership.member_id, - basket_size_id: membership.basket_size_id, - basket_price: 41, - basket_quantity: 2, - baskets_annual_price_change: 130, - depot_id: membership.depot_id, - depot_price: 4, - activity_participations_demanded_annually: 5, - activity_participations_annual_price_change: -60, - started_on: next_fy.beginning_of_year, - ended_on: next_fy.end_of_year) - end -end diff --git a/spec/lib/tenant_spec.rb b/spec/lib/tenant_spec.rb deleted file mode 100644 index ee1f6695b..000000000 --- a/spec/lib/tenant_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Tenant do - specify ".find_by host" do - expect(Tenant.find_by(host: "admin.acme.test")).to eq "acme" - expect(Tenant.find_by(host: "foo.acme.test")).to eq "acme" - expect(Tenant.find_by(host: "admin.unknown.test")).to be nil - end - - specify ".domain" do - expect(Tenant.current).to eq "acme" - expect(Tenant.domain).to eq "acme.test" - end - - specify ".inside? / .outside?" do - expect(Tenant.current).to eq "acme" - expect(Tenant).to be_inside - expect(Tenant).not_to be_outside - end - - specify ".exists?" do - expect(Tenant.exists?("acme")).to be true - expect(Tenant.exists?("unknown")).to be false - end - - specify ".connect to unknown tenant" do - expect(Tenant.current).to eq "acme" - expect { - Tenant.switch("unknown") { } - }.to raise_error("Unknown tenant 'unknown'") - end -end diff --git a/spec/mailers/activity_mailer_spec.rb b/spec/mailers/activity_mailer_spec.rb deleted file mode 100644 index 830de98aa..000000000 --- a/spec/mailers/activity_mailer_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe ActivityMailer do - before { Current.org.update!(activity_phone: "+41 77 333 44 55") } - let(:member) { create(:member, emails: "example@csa-admin.org") } - let(:activity) { - create(:activity, - date: "24.03.2020", - start_time: Time.zone.parse("8:30"), - end_time: Time.zone.parse("12:00"), - place: "Thielle", - title: "Aide aux champs", - description: "Que du bonheur") - } - let(:participation) { - create(:activity_participation, - member: member, - activity: activity, - participants_count: 2) - } - - specify "#participation_reminder_email" do - template = MailTemplate.find_by(title: "activity_participation_reminder") - create(:activity_participation, :carpooling, - activity: activity, - member: create(:member, name: "Elea Asah"), - carpooling_phone: "+41765431243", - carpooling_city: "La Chaux-de-Fonds") - group = ActivityParticipationGroup.group([ participation ]).first - - mail = ActivityMailer.with( - template: template, - activity_participation_ids: group.ids, - ).participation_reminder_email - - expect(mail.subject).to eq("Activité à venir (24 mars 2020)") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("activity-participation-reminder") - expect(mail.body).to include("Date: mardi 24 mars 2020") - expect(mail.body).to include("Horaire: 8:30-12:00") - expect(mail.body).to include("Lieu: Thielle") - expect(mail.body).to include("Activité: Aide aux champs") - expect(mail.body).to include("Description: Que du bonheur") - expect(mail.body).to include("Participants: 2") - expect(mail.body).to include("+41 77 333 44 55") - expect(mail.body).to include("Elea Asah: +41 76 543 12 43 (La Chaux-de-Fonds)") - expect(mail.body).to include("https://membres.acme.test/activity_participations") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#participation_validated_email" do - template = MailTemplate.find_by(title: "activity_participation_validated") - - mail = ActivityMailer.with( - template: template, - activity_participation_ids: participation.id - ).participation_validated_email - - expect(mail.subject).to eq("Activité validée 🎉") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("activity-participation-validated") - expect(mail.body).to include("Date: mardi 24 mars 2020") - expect(mail.body).to include("Horaire: 8:30-12:00") - expect(mail.body).to include("Lieu: Thielle") - expect(mail.body).to include("Activité: Aide aux champs") - expect(mail.body).to include("Description: Que du bonheur") - expect(mail.body).to include("Participants: 2") - expect(mail.body).to include("+41 77 333 44 55") - expect(mail.body).to include("https://membres.acme.test/activity_participations") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#participation_rejected_email" do - template = MailTemplate.find_by(title: "activity_participation_rejected") - - mail = ActivityMailer.with( - template: template, - activity_participation_ids: participation.id - ).participation_rejected_email - - expect(mail.subject).to eq("Activité refusée 😬") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("activity-participation-rejected") - expect(mail.body).to include("Date: mardi 24 mars 2020") - expect(mail.body).to include("Horaire: 8:30-12:00") - expect(mail.body).to include("Lieu: Thielle") - expect(mail.body).to include("Activité: Aide aux champs") - expect(mail.body).to include("Description: Que du bonheur") - expect(mail.body).to include("Participants: 2") - expect(mail.body).to include("+41 77 333 44 55") - expect(mail.body).to include("https://membres.acme.test/activity_participations") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end -end diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb deleted file mode 100644 index 7067f3dcb..000000000 --- a/spec/mailers/admin_mailer_spec.rb +++ /dev/null @@ -1,373 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe AdminMailer do - specify "#depot_delivery_list_email", freeze: "2020-01-01" do - delivery = create(:delivery, - date: Date.new(2020, 11, 6)) - depot = create(:depot, - name: "Jardin de la Main", - language: I18n.locale, - emails: "respondent1@csa-admin.org, respondent2@csa-admin.org") - create(:membership, - member: create(:member, name: "Martha"), - basket_size: create(:basket_size, :small)) - create(:membership, - member: create(:member, name: "Charle"), - basket_size: create(:basket_size, :big)) - mail = AdminMailer.with( - depot: depot, - baskets: Basket.all, - delivery: delivery - ).depot_delivery_list_email - - expect(mail.subject).to eq("Liste livraison du 6 novembre 2020 (Jardin de la Main)") - expect(mail.to).to eq([ "respondent1@csa-admin.org", "respondent2@csa-admin.org" ]) - expect(mail.tag).to eq("admin-depot-delivery-list") - expect(mail[:from].decoded).to eq "Rage de Vert " - - body = mail.html_part.body - expect(body).to include("Voici la liste des membres:") - expect(body).to include("Charle, Grand") - expect(body).to include("Martha, Petit") - expect(body).to include("Voir les pièces jointes pour plus de détails, merci.") - expect(body).not_to include("Gérer mes notifications") - - expect(mail.attachments.size).to eq 2 - attachment1 = mail.attachments.first - expect(attachment1.filename).to eq "livraison-#1-20201106.xlsx" - expect(attachment1.content_type).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" - attachment2 = mail.attachments.second - expect(attachment2.filename).to eq "fiches-livraison-#1-20201106.pdf" - expect(attachment2.content_type).to eq "application/pdf" - end - - specify "#delivery_list_email", freeze: "2023-01-01" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - delivery = create(:delivery, - id: 1, - date: Date.new(2023, 11, 6)) - mail = AdminMailer.with( - admin: admin, - delivery: delivery - ).delivery_list_email - - expect(mail.subject).to eq("Liste livraison du 6 novembre 2023") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-delivery-list") - expect(mail[:from].decoded).to eq "Rage de Vert " - - body = mail.html_part.body - expect(body).to include("(XLSX)") - expect(body).to include("(PDF)") - expect(body).to include("Accéder à la page de la livraison") - expect(body).to include("https://admin.acme.test/deliveries/1") - expect(body).to include("Gérer mes notifications") - - expect(mail.attachments.size).to eq 2 - attachment1 = mail.attachments.first - expect(attachment1.filename).to eq "livraison-#1-20231106.xlsx" - expect(attachment1.content_type).to eq "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" - attachment2 = mail.attachments.second - expect(attachment2.filename).to eq "fiches-livraison-#1-20231106.pdf" - expect(attachment2.content_type).to eq "application/pdf" - end - - specify "#invitation_email" do - admin = Admin.new( - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - mail = AdminMailer.with( - admin: admin, - action_url: "https://admin.ragedevert.ch" - ).invitation_email - - expect(mail.subject).to eq("Invitation à l'admin de Rage de Vert") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-invitation") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("admin@csa-admin.org") - expect(mail.body).to include("Accéder à l'admin de Rage de Vert") - expect(mail.body).to include("https://admin.ragedevert.ch") - expect(mail.body).not_to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#invoice_overpaid_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - member = Member.new( - id: 2, - name: "Martha") - invoice = Invoice.new(id: 42) - mail = AdminMailer.with( - admin: admin, - member: member, - invoice: invoice - ).invoice_overpaid_email - - expect(mail.subject).to eq("Facture #42 payée en trop") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-invoice-overpaid") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("Facture #42") - expect(mail.body).to include("Martha") - expect(mail.body).to include("Accéder à la page du membre") - expect(mail.body).to include("https://admin.acme.test/members/2") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#invoice_third_overdue_notice_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - member = Member.new( - id: 2, - name: "Martha") - invoice = Invoice.new(id: 42, member: member) - mail = AdminMailer.with( - admin: admin, - invoice: invoice - ).invoice_third_overdue_notice_email - - expect(mail.subject).to eq("Facture #42, 3ᵉ rappel envoyé") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-invoice-third-overdue-notice") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("Le 3ᵉ rappel vient d'être envoyé pour la facture #42") - expect(mail.body).to include("Martha") - expect(mail.body).to include("Accéder à la page du membre") - expect(mail.body).to include("https://admin.acme.test/members/2") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#new_absence_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - member = Member.new(name: "Martha") - absence = Absence.new( - id: 1, - started_on: Date.new(2020, 11, 10), - ended_on: Date.new(2020, 11, 20), - note: "Une Super Remarque!") - mail = AdminMailer.with( - admin: admin, - member: member, - absence: absence - ).new_absence_email - - expect(mail.subject).to eq("Nouvelle absence") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-absence-created") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("Martha") - expect(mail.body).to include("10 novembre 2020 au 20 novembre 2020") - expect(mail.body).to include("Remarque du membre:
\r\n Une Super Remarque!") - expect(mail.body).to include("Accéder à la page de l'absence") - expect(mail.body).to include("https://admin.acme.test/absences/1") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#new_activity_participation_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - member = create(:member, name: "Martha", id: 1512) - activity = create(:activity, - date: "24.03.2020", - start_time: Time.zone.parse("8:30"), - end_time: Time.zone.parse("12:00"), - place: "Thielle", - title: "Aide aux champs", - description: "Que du bonheur") - participation = create(:activity_participation, :carpooling, - member: member, - activity: activity, - participants_count: 2, - note: "Une Super Remarque!", - carpooling_phone: "+41765431243", - carpooling_city: "La Chaux-de-Fonds") - group = ActivityParticipationGroup.group([ participation ]).first - - mail = AdminMailer.with( - admin: admin, - activity_participation_ids: group.ids, - ).new_activity_participation_email - - expect(mail.subject).to eq("Nouvelle participation à une ½ journée") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-activity-participation-created") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("Date: mardi 24 mars 2020") - expect(mail.body).to include("Horaire: 8:30-12:00") - expect(mail.body).to include("Lieu: Thielle") - expect(mail.body).to include("Activité: Aide aux champs") - expect(mail.body).to include("Description: Que du bonheur") - expect(mail.body).to include("Participants: 2") - expect(mail.body).to include("Covoiturage: +41 76 543 12 43 (La Chaux-de-Fonds)") - expect(mail.body).to include("Remarque du membre:
\r\n Une Super Remarque!") - expect(mail.body).to include("Accéder à la page des participations de ce membre") - expect(mail.body).to include("https://admin.acme.test/activity_participations?q%5Bmember_id_eq%5D=1512&scope=future") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#new_email_suppression_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - email_suppression = OpenStruct.new( - reason: "HardBounce", - email: "john@doe.com", - owners: [ - Member.new( - id: 2, - name: "Martha"), - Admin.new( - id: 4, - name: "Martha" - ) - ]) - mail = AdminMailer.with( - admin: admin, - email_suppression: email_suppression - ).new_email_suppression_email - - expect(mail.subject).to eq("Email rejeté (HardBounce)") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-email-suppression-created") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("L'email john@doe.com a été rejeté lors de l'envoi du dernier message à cause de la raison suivante: HardBounce.") - expect(mail.body).to include("Admin: Martha") - expect(mail.body).to include("https://admin.acme.test/admins/4") - expect(mail.body).to include("Membre: Martha") - expect(mail.body).to include("https://admin.acme.test/members/2") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#new_registration_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - member = Member.new( - id: 2, - name: "Martha") - mail = AdminMailer.with( - admin: admin, - member: member - ).new_registration_email - - expect(mail.subject).to eq("Nouvelle inscription") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-member-created") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("Martha") - expect(mail.body).to include("Accéder à la page du membre") - expect(mail.body).to include("https://admin.acme.test/members/2") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#memberships_renewal_pending_email" do - admin = Admin.new( - id: 1, - name: "John", - language: I18n.locale, - email: "admin@csa-admin.org") - membership_1 = Membership.new(id: 1) - membership_2 = Membership.new(id: 2) - membership_3 = Membership.new(id: 3) - mail = AdminMailer.with( - admin: admin, - pending_memberships: [ membership_1, membership_2 ], - opened_memberships: [ membership_3 ], - pending_action_url: "https://admin.example.com/memberships/pending", - opened_action_url: "https://admin.example.com/memberships/opened", - action_url: "https://admin.example.com/memberships" - ).memberships_renewal_pending_email - - expect(mail.subject).to eq("⚠️ Abonnement(s) en attente de renouvellement!") - expect(mail.to).to eq([ "admin@csa-admin.org" ]) - expect(mail.tag).to eq("admin-memberships-renewal-pending") - expect(mail.body).to include("Salut John,") - expect(mail.body).to include("2 abonnement(s)") - expect(mail.body).to include("https://admin.example.com/memberships/pending") - expect(mail.body).to include("1 demande(s)") - expect(mail.body).to include("https://admin.example.com/memberships/opened") - expect(mail.body).to include("Accéder aux abonnements") - expect(mail.body).to include("https://admin.example.com/memberships") - expect(mail.body).to include("https://admin.acme.test/admins/1/edit#notifications") - expect(mail.body).to include("Gérer mes notifications") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#memberships_renewal_pending_email (pending only)" do - admin = Admin.new(id: 1, language: I18n.locale, email: "admin@csa-admin.org") - membership_1 = Membership.new(id: 1) - membership_2 = Membership.new(id: 2) - mail = AdminMailer.with( - admin: admin, - pending_memberships: [ membership_1, membership_2 ], - opened_memberships: [], - pending_action_url: "https://admin.example.com/memberships/pending", - opened_action_url: "https://admin.example.com/memberships/opened", - action_url: "https://admin.example.com/memberships" - ).memberships_renewal_pending_email - - expect(mail.subject).to eq("⚠️ Abonnement(s) en attente de renouvellement!") - expect(mail.body).to include("2 abonnement(s)") - expect(mail.body).to include("https://admin.example.com/memberships/pending") - expect(mail.body).not_to include("demande(s)") - expect(mail.body).not_to include("https://admin.example.com/memberships/opened") - end - - specify "#memberships_renewal_pending_email (opened only)" do - admin = Admin.new(id: 1, language: I18n.locale, email: "admin@csa-admin.org") - membership_1 = Membership.new(id: 1) - membership_2 = Membership.new(id: 2) - mail = AdminMailer.with( - admin: admin, - pending_memberships: [], - opened_memberships: [ membership_1, membership_2 ], - pending_action_url: "https://admin.example.com/memberships/pending", - opened_action_url: "https://admin.example.com/memberships/opened", - action_url: "https://admin.example.com/memberships" - ).memberships_renewal_pending_email - - expect(mail.subject).to eq("⚠️ Abonnement(s) en attente de renouvellement!") - expect(mail.body).not_to include("abonnement(s)") - expect(mail.body).not_to include("https://admin.example.com/memberships/pending") - expect(mail.body).to include("2 demande(s)") - expect(mail.body).to include("https://admin.example.com/memberships/opened") - end -end diff --git a/spec/mailers/invoice_mailer_spec.rb b/spec/mailers/invoice_mailer_spec.rb deleted file mode 100644 index fba929357..000000000 --- a/spec/mailers/invoice_mailer_spec.rb +++ /dev/null @@ -1,235 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe InvoiceMailer do - specify "#created_email" do - template = MailTemplate.find_by(title: "invoice_created") - member = create(:member, emails: "example@csa-admin.org") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - annual_fee: 62) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).created_email - - expect(mail.subject).to eq("Nouvelle facture #42") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - body = mail.html_part.body - expect(body).to include("Voici votre nouvelle facture") - expect(body).to include("Accéder à ma page de membre") - expect(body).to include("https://membres.acme.test/billing") - expect(mail.tag).to eq("invoice-created") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - - expect(mail.attachments.size).to eq 1 - attachment = mail.attachments.first - expect(attachment.filename).to eq "facture-acme-42.pdf" - expect(attachment.content_type).to eq "application/pdf" - end - - specify "#created_email (closed)" do - template = MailTemplate.find_by(title: "invoice_created") - member = create(:member, emails: "example@csa-admin.org") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - annual_fee: 62) - create(:payment, member: member, amount: 100) - invoice.reload - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).created_email - - expect(mail.subject).to eq("Nouvelle facture #42") - body = mail.html_part.body - expect(body).to include("En tenant compte des paiements précédents, cette facture est considérée comme payée et est envoyée uniquement à titre d'information.") - end - - specify "#created_email (partially paid)" do - template = MailTemplate.find_by(title: "invoice_created") - member = create(:member, emails: "example@csa-admin.org") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - annual_fee: 62) - create(:payment, member: member, amount: 20) - invoice.reload - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).created_email - - expect(mail.subject).to eq("Nouvelle facture #42") - body = mail.html_part.body - expect(body).to include("En tenant compte des paiements précédents, le montant restant à payer est de: CHF 42.00") - end - - specify "#created_email (Shop::Order)" do - template = MailTemplate.find_by(title: "invoice_created") - member = create(:member, emails: "example@csa-admin.org") - order = create(:shop_order, :pending, id: 51235, member: member) - invoice = order.invoice! - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).created_email - - body = mail.html_part.body - expect(body).to include( - "Voici votre nouvelle facture, pour votre commande N° 51235, ") - - expect(mail.attachments.size).to eq 1 - expect(mail.attachments.first.content_type).to eq "application/pdf" - end - - specify "#created_email (billing_email)" do - template = MailTemplate.find_by(title: "invoice_created") - member = create(:member, - emails: "example@csa-admin.org", - billing_email: "john@doe.com") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - annual_fee: 62) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).created_email - - expect(mail.subject).to eq("Nouvelle facture #42") - expect(mail.to).to eq([ "john@doe.com" ]) - expect(mail.tag).to eq("invoice-created") - body = mail.html_part.body - expect(body).to include("Voici votre nouvelle facture") - expect(body).not_to include("Accéder à ma page de membre") - expect(body).not_to include("https://membres.acme.test/billing") - expect(mail[:from].decoded).to eq "Rage de Vert " - - expect(mail.attachments.size).to eq 1 - attachment = mail.attachments.first - expect(attachment.filename).to eq "facture-acme-42.pdf" - expect(attachment.content_type).to eq "application/pdf" - end - - specify "#cancelled_email" do - template = MailTemplate.find_by(title: "invoice_cancelled") - member = create(:member, emails: "example@csa-admin.org") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - annual_fee: 62) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).cancelled_email - - expect(mail.subject).to eq("Facture annulée #42") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("invoice-cancelled") - body = mail.body - expect(body) - .to include "Votre facture ##{invoice.id} du #{I18n.l(invoice.date)} vient d'être annulée." - expect(body).to include("Accéder à ma page de membre") - expect(body).to include("https://membres.acme.test/billing") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - - expect(mail.attachments.size).to be_zero - end - - specify "#overdue_notice_email" do - template = MailTemplate.find_by(title: "invoice_overdue_notice") - member = create(:member, emails: "example@csa-admin.org") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - overdue_notices_count: 2, - annual_fee: 62) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).overdue_notice_email - - expect(mail.subject).to eq("Rappel #2 de la facture #42 😬") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("invoice-overdue-notice") - body = mail.html_part.body - expect(body).to include("Le montant restant à payer est de: CHF 62") - expect(body).to include("Accéder à ma page de membre") - expect(body).to include("https://membres.acme.test/billing") - expect(mail[:from].decoded).to eq "Rage de Vert " - - expect(mail.attachments.size).to eq 1 - attachment = mail.attachments.first - expect(attachment.filename).to eq "facture-acme-42.pdf" - expect(attachment.content_type).to eq "application/pdf" - end - - specify "#overdue_notice_email (billing_email)" do - template = MailTemplate.find_by(title: "invoice_overdue_notice") - member = create(:member, - emails: "example@csa-admin.org", - billing_email: "john@doe.com") - invoice = create(:invoice, :annual_fee, :open, - member: member, - id: 42, - date: "24.03.2020", - overdue_notices_count: 2, - annual_fee: 62) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).overdue_notice_email - - expect(mail.subject).to eq("Rappel #2 de la facture #42 😬") - expect(mail.to).to eq([ "john@doe.com" ]) - expect(mail.tag).to eq("invoice-overdue-notice") - body = mail.html_part.body - expect(body).to include("Le montant restant à payer est de: CHF 62") - expect(body).not_to include("Accéder à ma page de membre") - expect(body).not_to include("https://membres.acme.test/billing") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - - expect(mail.attachments.size).to eq 1 - attachment = mail.attachments.first - expect(attachment.filename).to eq "facture-acme-42.pdf" - expect(attachment.content_type).to eq "application/pdf" - end - - specify "sanitize html from subject" do - template = MailTemplate.find_by(title: "invoice_overdue_notice") - template.update!(subject: 'Rappel #{{ invoice.overdue_notices_count }} 😬') - - member = create(:member, name: "John Doe") - invoice = create(:invoice, :annual_fee, :open, - member: member, - overdue_notices_count: 2) - - mail = InvoiceMailer.with( - template: template, - invoice: invoice, - ).overdue_notice_email - - expect(mail.subject).to eq("Rappel #2 😬") - end -end diff --git a/spec/mailers/member_mailer_spec.rb b/spec/mailers/member_mailer_spec.rb deleted file mode 100644 index f083097df..000000000 --- a/spec/mailers/member_mailer_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe MemberMailer do - specify "#activated_email", freeze: "2021-01-01" do - template = MailTemplate.find_by(title: "member_activated") - create_deliveries(2) - create(:basket_complement, name: "Pain", id: 1) - create(:basket_complement, name: "Oeuf", id: 2) - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, - member: member, - depot: create(:depot, id: 12, name: "Jardin de la main"), - basket_size: create(:basket_size, :small), - memberships_basket_complements_attributes: { - "0" => { basket_complement_id: 1 }, - "1" => { basket_complement_id: 2, quantity: 2 } - }) - mail = MemberMailer.with( - template: template, - member: member, - ).activated_email - - expect(mail.subject).to eq("Bienvenue!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("member-activated") - expect(mail.body).to include("Dépôt: Jardin de la main") - expect(mail.body).to include("Taille panier: Petit") - expect(mail.body).to include("Compléments: Oeuf et Pain") - expect(mail.body).to include("Accéder à ma page de membre") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#validated_email" do - template = MailTemplate.find_by(title: "member_validated") - member = create(:member, emails: "example@csa-admin.org") - mail = MemberMailer.with( - template: template, - member: member - ).validated_email - - expect(mail.subject).to eq("Inscription validée!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("member-validated") - expect(mail.body).to include("Position sur la liste d'attente: 1") - expect(mail.body).to include("Accéder à ma page de membre") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end -end diff --git a/spec/mailers/membership_mailer_spec.rb b/spec/mailers/membership_mailer_spec.rb deleted file mode 100644 index 06fbcf435..000000000 --- a/spec/mailers/membership_mailer_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe MembershipMailer, freeze: "2022-01-01" do - specify "#initial_basket_email" do - template = MailTemplate.find_by(title: "membership_initial_basket") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - basket = membership.baskets.first - mail = MembershipMailer.with( - template: template, - basket: basket, - ).initial_basket_email - - expect(mail.subject).to eq("Premier panier!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-initial-basket") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#final_basket_email" do - template = MailTemplate.find_by(title: "membership_final_basket") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - basket = membership.baskets.last - mail = MembershipMailer.with( - template: template, - basket: basket, - ).final_basket_email - - expect(mail.subject).to eq("Dernier panier!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-final-basket") - expect(mail.body). to include("https://membres.acme.test") - expect(mail[:from].decoded). to eq "Rage de Vert " - expect(mail[:message_stream].to_s). to eq "outbound" - end - - specify "#first_basket_email" do - template = MailTemplate.find_by(title: "membership_first_basket") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - basket = membership.baskets.first - mail = MembershipMailer.with( - template: template, - basket: basket, - ).first_basket_email - - expect(mail.subject).to eq("Premier panier de l'année!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-first-basket") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#last_basket_email" do - template = MailTemplate.find_by(title: "membership_last_basket") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - basket = membership.baskets.last - mail = MembershipMailer.with( - template: template, - basket: basket, - ).last_basket_email - - expect(mail.subject).to eq("Dernier panier de l'année!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-last-basket") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#last_trial_basket_email" do - template = MailTemplate.find_by(title: "membership_last_trial_basket") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - basket = membership.baskets.trial.last - mail = MembershipMailer.with( - template: template, - basket: basket, - ).last_trial_basket_email - - expect(mail.subject).to eq("Dernier panier à l'essai!") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-last-trial-basket") - expect(mail.body).to include("C'est le jour de votre dernier panier à l'essai...") - expect(mail.body).to include("https://membres.acme.test") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#renewal_email" do - template = MailTemplate.find_by(title: "membership_renewal") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - mail = MembershipMailer.with( - template: template, - membership: membership, - ).renewal_email - - expect(mail.subject).to eq("Renouvellement de votre abonnement") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-renewal") - expect(mail.body).to include("Accéder au formulaire de renouvellement") - expect(mail.body).to include("https://membres.acme.test/memberships#renewal") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end - - specify "#renewal_reminder_email" do - template = MailTemplate.find_by(title: "membership_renewal_reminder") - member = create(:member, emails: "example@csa-admin.org") - membership = create(:membership, member: member) - mail = MembershipMailer.with( - template: template, - membership: membership, - ).renewal_reminder_email - - expect(mail.subject).to eq("Renouvellement de votre abonnement (Rappel)") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("membership-renewal-reminder") - expect(mail.body).to include("Accéder au formulaire de renouvellement") - expect(mail.body).to include("https://membres.acme.test/memberships#renewal") - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "outbound" - end -end diff --git a/spec/mailers/newsletter_mailer_spec.rb b/spec/mailers/newsletter_mailer_spec.rb deleted file mode 100644 index 918daf6ca..000000000 --- a/spec/mailers/newsletter_mailer_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe NewsletterMailer do - let(:template) { - create(:newsletter_template, - content: <<~LIQUID) - Salut {{ member.name }}, - - {% content id: 'main', title: "Content Title" %} - Example Text {{ member.name }} - {% endcontent %} - LIQUID - } - - specify "#newsletter_email", freeze: "2022-01-01" do - member = create(:member, - name: "John Doe", - emails: "john@doe.com, jane@doe.com") - membership = create(:membership, member: member) - mail = NewsletterMailer.with( - tag: "newsletter-42", - template: template, - subject: "Ma Newsletter", - member: member, - to: "john@doe.com" - ).newsletter_email - - expect(mail.subject).to eq "Ma Newsletter" - expect(mail.to).to eq [ "john@doe.com" ] - expect(mail[:from].decoded).to eq "Rage de Vert " - expect(mail[:message_stream].to_s).to eq "broadcast" - expect(mail[:tag].to_s).to eq "newsletter-42" - - expect(mail.body).to include("Salut John Doe,") - expect(mail.body).to include('

Content Title

') - expect(mail.body).to include("Example Text John Doe") - expect(mail.body).to have_link("Désinscription", - href: %r{https://membres.acme.test/newsletters/unsubscribe/\w{32}}) - expect(mail["List-Unsubscribe-Post"].to_s).to eq "List-Unsubscribe=One-Click" - expect(mail["List-Unsubscribe"].to_s) - .to match %r{} - end - - specify "#newsletter_email with attachments" do - newsletter = create(:newsletter, template: template) - attachment = Newsletter::Attachment.new - attachment.file.attach( - io: File.open(file_fixture("qrcode-test.png")), - filename: "Un code \"QR\" stylé.png") - newsletter.update! attachments: [ attachment ] - - mail = NewsletterMailer.with( - template: template, - subject: "Ma Newsletter", - member: create(:member), - attachments: newsletter.attachments.to_a, - to: "john@doe.com" - ).newsletter_email - - expect(mail.attachments.size).to eq 1 - attachment = mail.attachments.first - expect(attachment.filename).to eq "Un code -QR- style.png" - expect(attachment.content_type).to eq "image/png" - end -end diff --git a/spec/mailers/session_mailer_spec.rb b/spec/mailers/session_mailer_spec.rb deleted file mode 100644 index 9dabecf2f..000000000 --- a/spec/mailers/session_mailer_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe SessionMailer do - specify "#new_member_session_email" do - session = Session.new( - member: Member.new(language: "fr"), - email: "example@csa-admin.org") - mail = SessionMailer.with( - session: session, - session_url: "https://example.com/session/token", - ).new_member_session_email - - expect(mail.subject).to eq("Connexion à votre compte") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("session-member") - - expect(mail.body).to include("Accéder à mon compte") - expect(mail.body).to include("https://example.com/session/token") - expect(mail[:from].decoded).to eq "Rage de Vert " - end - - specify "#new_admin_session_email" do - session = Session.new( - admin: Admin.new(language: "fr"), - email: "example@csa-admin.org") - mail = SessionMailer.with( - session: session, - session_url: "https://example.com/session/token", - ).new_admin_session_email - - expect(mail.subject).to eq("Connexion à votre compte admin") - expect(mail.to).to eq([ "example@csa-admin.org" ]) - expect(mail.tag).to eq("session-admin") - - expect(mail.body).to include("Accéder à mon compte admin") - expect(mail.body).to include("https://example.com/session/token") - expect(mail[:from].decoded).to eq "Rage de Vert " - end -end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb deleted file mode 100644 index 80439cef9..000000000 --- a/spec/models/ability_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Ability do - let(:ability) { Ability.new(admin) } - - context "superadmin" do - let(:admin) { create(:admin, permission: Permission.superadmin) } - - specify { expect(ability.can?(:read, Organization)).to be_truthy } - specify { expect(ability.can?(:update, Current.org)).to be_truthy } - - specify { expect(ability.can?(:manage, Admin)).to be_truthy } - specify { expect(ability.can?(:destroy, admin)).to be_falsey } - - specify { expect(ability.can?(:manage, ActiveAdmin::Comment)).to be_truthy } - - specify { expect(ability.can?(:create, Absence)).to be_truthy } - context "without absence feature" do - before { Current.org.update! features: [] } - specify { expect(ability.can?(:create, Absence)).to be_falsey } - end - end - - context "read-only" do - let(:admin) { create(:admin, permission: create(:permission, rights: {})) } - - specify { expect(ability.can?(:read, ActiveAdmin::Page)).to be_truthy } - specify { expect(ability.can?(:pdf, Invoice)).to be_truthy } - specify { expect(ability.can?(:read, Organization)).to be_falsey } - specify { expect(ability.can?(:manage, Admin)).to be_falsey } - specify { expect(ability.can?(:destroy, admin)).to be_falsey } - specify { expect(ability.can?(:update, admin)).to be_truthy } - specify { expect(ability.can?(:read, ActiveAdmin::Comment)).to be_truthy } - specify { expect(ability.can?(:create, ActiveAdmin::Comment)).to be_truthy } - specify { expect(ability.can?(:manage, create(:comment))).to be_falsey } - specify { expect(ability.can?(:manage, create(:comment, author: admin))).to be_truthy } - specify { expect(ability.can?(:batch_action, Member)).to be_falsey } - specify { expect(ability.can?(:batch_action, Membership)).to be_falsey } - specify { expect(ability.can?(:batch_action, Invoice)).to be_falsey } - specify { expect(ability.can?(:batch_action, Shop::Product)).to be_falsey } - end - - context "with member write permission" do - let(:admin) { create(:admin, permission: create(:permission, rights: { member: :write })) } - - specify { expect(ability.can?(:create, Member)).to be_truthy } - specify { expect(ability.can?(:update, Member)).to be_truthy } - specify { expect(ability.can?(:batch_action, Member)).to be_truthy } - - specify { expect(ability.can?(:become, Member)).to be_truthy } - specify { expect(ability.can?(:validate, Member.new(state: "pending"))).to be_truthy } - end - - context "with membership write permission" do - let(:admin) { create(:admin, permission: create(:permission, rights: { membership: :write })) } - - specify { expect(ability.can?(:create, Membership)).to be_truthy } - specify { expect(ability.can?(:update, Membership)).to be_truthy } - specify { expect(ability.can?(:update, Basket)).to be_truthy } - specify { expect(ability.can?(:batch_action, Membership)).to be_truthy } - - specify { expect(ability.can?(:renew_all, Membership)).to be_truthy } - specify { expect(ability.can?(:open_renewal_all, Membership)).to be_truthy } - specify { expect(ability.can?(:open_renewal, Membership)).to be_truthy } - specify { expect(ability.can?(:mark_renewal_as_pending, Membership)).to be_truthy } - specify { expect(ability.can?(:future_billing, Membership)).to be_truthy } - specify { expect(ability.can?(:renew, Membership)).to be_truthy } - specify { expect(ability.can?(:cancel, Membership)).to be_truthy } - end - - context "with billing write permission" do - let(:admin) { create(:admin, permission: create(:permission, rights: { billing: :write })) } - - specify { expect(ability.can?(:create, Invoice)).to be_truthy } - specify { expect(ability.can?(:update, Invoice)).to be_truthy } - specify { expect(ability.can?(:batch_action, Invoice)).to be_truthy } - specify { expect(ability.can?(:create, Payment)).to be_truthy } - specify { expect(ability.can?(:update, Payment)).to be_truthy } - specify { expect(ability.can?(:batch_action, Payment)).to be_truthy } - - specify { expect(ability.can?(:recurring_billing, Member)).to be_truthy } - specify { expect(ability.can?(:force_share_billing, Member)).to be_truthy } - specify { expect(ability.can?(:send_email, Invoice)).to be_truthy } - specify { expect(ability.can?(:cancel, Invoice)).to be_truthy } - specify { expect(ability.can?(:import, Payment)).to be_truthy } - specify "cannot send invoice when member has no emails" do - invoice = create(:invoice, :annual_fee, :open, :not_sent, - member: create(:member, emails: "")) - expect(ability.can?(:send_email, invoice)).to be_falsy - end - specify "can send invoice when member has only billing email" do - invoice = create(:invoice, :annual_fee, :open, :not_sent, - member: create(:member, emails: "", billing_email: "john@doe.com")) - expect(ability.can?(:send_email, invoice)).to be_truthy - end - end - - context "with billing shop permission" do - before { Current.org.update! features: [ :shop ] } - let(:admin) { create(:admin, permission: create(:permission, rights: { shop: :write })) } - - specify { expect(ability.can?(:create, Shop::Order)).to be_truthy } - specify { expect(ability.can?(:update, Shop::Order)).to be_truthy } - specify { expect(ability.can?(:batch_action, Shop::Order)).to be_truthy } - specify { expect(ability.can?(:create, Shop::Product)).to be_truthy } - specify { expect(ability.can?(:update, Shop::Product)).to be_truthy } - specify { expect(ability.can?(:batch_action, Shop::Product)).to be_truthy } - specify { expect(ability.can?(:create, Shop::Producer)).to be_truthy } - specify { expect(ability.can?(:update, Shop::Producer)).to be_truthy } - specify { expect(ability.can?(:create, Shop::Tag)).to be_truthy } - specify { expect(ability.can?(:update, Shop::Tag)).to be_truthy } - - specify { expect(ability.can?(:invoice, Shop::Order)).to be_truthy } - specify { expect(ability.can?(:cancel, Shop::Order)).to be_truthy } - end -end diff --git a/spec/models/admin_spec.rb b/spec/models/admin_spec.rb deleted file mode 100644 index be1ebb468..000000000 --- a/spec/models/admin_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Admin do - it "deletes sessions when destroyed" do - admin = create(:admin) - session = create(:session, admin: admin) - - expect { - admin.destroy! - }.to change(Session, :count).by(-1) - end - - it "sets latest_update_read on create" do - admin = create(:admin, latest_update_read: nil) - expect(admin.latest_update_read).to eq Update.all.first.name - end - - specify "email=" do - admin = Admin.new(email: "Thibaud@Thibaud.GG ") - expect(admin.email).to eq "thibaud@thibaud.gg" - end - - describe ".notify!" do - specify "with suppressed email" do - admin = create(:admin, notifications: [ "new_absence" ]) - create(:email_suppression, email: admin.email) - - expect { - Admin.notify!(:new_absence) - }.not_to change(ActionMailer::Base.deliveries, :count) - end - end -end diff --git a/spec/models/audit_spec.rb b/spec/models/audit_spec.rb deleted file mode 100644 index 0850b6831..000000000 --- a/spec/models/audit_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Auditable do - specify "save changes on audited attributes without session" do - member = create(:member, name: "Joe Doe") - - expect { - member.update!(name: "John Doe") - }.to change(Audit, :count).by(1) - - expect(member.audits.last).to have_attributes( - actor: System.instance, - session: nil, - audited_changes: { - "name" => [ "Joe Doe", "John Doe" ] - }) - end - - specify "save changes on audited attributes with current session" do - member = create(:member, name: "Joe Doe") - session = create(:session, member: member) - Current.session = session - - expect { - member.update!(name: "John Doe", note: "Doo") - }.to change(Audit, :count).by(1) - - expect(member.audits.last).to have_attributes( - actor: member, - session: session, - audited_changes: { - "name" => [ "Joe Doe", "John Doe" ], - "note" => [ nil, "Doo" ] - }) - end - - specify "ignore changes with no present value" do - member = create(:member, note: nil) - - expect { - member.update!(note: " ") - }.not_to change(Audit, :count) - end - - specify "ignore changes with similar values" do - member = create(:member, note: "Foo") - - expect { - member.update!(note: " Foo ") - }.not_to change(Audit, :count) - end -end diff --git a/spec/models/basket_size_spec.rb b/spec/models/basket_size_spec.rb deleted file mode 100644 index 9d77894d2..000000000 --- a/spec/models/basket_size_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe BasketSize do - def member_ordered_names - BasketSize.member_ordered.map(&:name) - end - - specify "#member_ordered" do - small_size = create(:basket_size, price: 10, name: "petit") - create(:basket_size, price: 20, name: "moyen", public_name: "") - create(:basket_size, price: 30, name: "grand") - - expect(member_ordered_names).to eq %w[grand moyen petit] - - Current.org.update! basket_sizes_member_order_mode: "price_asc" - expect(member_ordered_names).to eq %w[petit moyen grand] - - Current.org.update! basket_sizes_member_order_mode: "name_asc" - expect(member_ordered_names).to eq %w[grand moyen petit] - - small_size.update! member_order_priority: 0 - expect(member_ordered_names).to eq %w[petit grand moyen] - end -end diff --git a/spec/models/billing/camt_file_spec.rb b/spec/models/billing/camt_file_spec.rb deleted file mode 100644 index a89a4a4e2..000000000 --- a/spec/models/billing/camt_file_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Billing::CamtFile do - describe "#payments_data" do - it "returns payment data from CAMT file" do - file = described_class.new(file_fixture("camt054.xml")) - expect(file.payments_data).to eq([ - Billing::CamtFile::PaymentData.new( - invoice_id: 1, - member_id: 42, - amount: 1, - date: Date.new(2020, 11, 13, 11), - fingerprint: "2020-11-13-ZV20201113/371247/2-000000000000000420000000011") - ]) - end - - it "returns no payment data when REF has letter" do - file = described_class.new(file_fixture("camt054_ref_with_letters.xml")) - expect(file.payments_data).to be_empty - end - - it "raise for invalid CAMT namespace" do - file = described_class.new(file_fixture("camt_wrong.xml")) - expect { file.payments_data } - .to raise_error(Billing::CamtFile::UnsupportedFileError) - end - - it "raise for CAMT.053 format" do - file = described_class.new(file_fixture("camt053.xml")) - expect { file.payments_data } - .to raise_error(Billing::CamtFile::UnsupportedFileError) - end - - it "raise for invalid CAMT file" do - file = described_class.new(file_fixture("camt_invalid.xml")) - expect { file.payments_data } - .to raise_error(Billing::CamtFile::UnsupportedFileError) - end - end -end diff --git a/spec/models/delivery_spec.rb b/spec/models/delivery_spec.rb deleted file mode 100644 index a78002e98..000000000 --- a/spec/models/delivery_spec.rb +++ /dev/null @@ -1,306 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe Delivery do - it_behaves_like "bulk_dates_insert" - - it "validates bulk inserts" do - delivery = Delivery.create( - bulk_dates_starts_on: Date.today, - bulk_dates_wdays: [ 1 ], - date: Date.today) - - expect(delivery).not_to have_valid(:bulk_dates_starts_on) - expect(delivery).not_to have_valid(:bulk_dates_wdays) - end - - it "bulk inserts with basket_complements", freeze: "2018-01-01" do - create(:basket_complement, id: 1) - - Delivery.create( - bulk_dates_starts_on: Date.parse("2018-11-05"), - bulk_dates_ends_on: Date.parse("2018-11-11") + 1.month, - bulk_dates_weeks_frequency: 2, - bulk_dates_wdays: [ 1 ], - basket_complement_ids: [ 1 ]) - - expect(Delivery.count).to eq 3 - expect(Delivery.all.map(&:basket_complement_ids)).to eq [ [ 1 ], [ 1 ], [ 1 ] ] - end - - it "adds basket_complement on subscribed baskets", freeze: "2022-01-01" do - create(:basket_complement, id: 1, price: 3.2) - create(:basket_complement, id: 2, price: 4.5) - - create(:delivery) - perform_enqueued_jobs - - membership_1 = create(:membership, subscribed_basket_complement_ids: [ 1, 2 ]) - membership_2 = create(:membership, subscribed_basket_complement_ids: [ 2 ]) - membership_3 = create(:membership, subscribed_basket_complement_ids: [ 1 ]) - - delivery = create(:delivery, basket_complement_ids: [ 1, 2 ]) - perform_enqueued_jobs - - basket1 = delivery.baskets.find_by(membership: membership_1) - expect(basket1.complement_ids).to match_array [ 1, 2 ] - expect(basket1.complements_price).to eq 3.2 + 4.5 - - basket2 = delivery.baskets.find_by(membership: membership_2) - expect(basket2.complement_ids).to match_array [ 2 ] - expect(basket2.complements_price).to eq 4.5 - - basket3 = delivery.baskets.find_by(membership: membership_3) - basket3.update!(complement_ids: [ 1, 2 ]) - expect(basket3.complement_ids).to match_array [ 1, 2 ] - expect(basket3.complements_price).to eq 3.2 + 4.5 - end - - it "removes basket_complement on subscribed baskets", freeze: "2022-01-01" do - create(:basket_complement, id: 1, price: 3.2) - create(:basket_complement, id: 2, price: 4.5) - - create(:delivery) - perform_enqueued_jobs - - membership_1 = create(:membership, subscribed_basket_complement_ids: [ 1, 2 ]) - membership_2 = create(:membership, subscribed_basket_complement_ids: [ 2 ]) - membership_3 = create(:membership, subscribed_basket_complement_ids: [ 1 ]) - - delivery = create(:delivery, basket_complement_ids: [ 1, 2 ]) - perform_enqueued_jobs - - basket3 = delivery.baskets.find_by(membership: membership_3) - basket3.update!(complement_ids: [ 1, 2 ]) - - Current.org.update!(recurring_billing_wday: 1) - membership_1.update!(billing_year_division: 1) - invoice_1 = Billing::Invoicer.force_invoice!(membership_1.member) - invoice_1.process! - invoice_1.mark_as_sent! - membership_2.update!(billing_year_division: 4) - invoice_2 = Billing::Invoicer.force_invoice!(membership_2.member) - invoice_2.process! - invoice_2.mark_as_sent! - - expect { - delivery.update!(basket_complement_ids: [ 1 ]) - perform_enqueued_jobs - } - .to change { membership_1.reload.price }.by(-4.5) - .and change { membership_2.reload.price }.by(-4.5) - - basket1 = delivery.baskets.find_by(membership: membership_1) - expect(basket1.complement_ids).to match_array [ 1 ] - expect(basket1.complements_price).to eq 3.2 - - basket2 = delivery.baskets.find_by(membership: membership_2) - expect(basket2.complement_ids).to be_empty - expect(basket2.complements_price).to be_zero - - basket3.reload - expect(basket3.complement_ids).to match_array [ 1 ] - expect(basket3.complements_price).to eq 3.2 - end - - it "updated membership price when destroy", freeze: "2022-01-01" do - basket_size = create(:basket_size, price: 42) - membership = create(:membership, basket_size: basket_size, deliveries_count: 2) - delivery = membership.deliveries.last - - expect { - delivery.destroy! - perform_enqueued_jobs - } - .to change { membership.baskets.count }.by(-1) - .and change { membership.reload.price }.by(-42) - end - - it "updates all fiscal year delivery numbers", freeze: "2018-01-01" do - first = create(:delivery, date: "2018-02-01") - last = create(:delivery, date: "2018-11-01") - - expect(first.number).to eq 1 - expect(last.number).to eq 2 - - delivery = create(:delivery, date: "2018-06-01") - - expect(first.reload.number).to eq 1 - expect(delivery.reload.number).to eq 2 - expect(last.reload.number).to eq 3 - - delivery.update!(date: "2018-01-01") - - expect(delivery.reload.number).to eq 1 - expect(first.reload.number).to eq 2 - expect(last.reload.number).to eq 3 - end - - it "handles date change", freeze: "2020-01-01" do - delivery_1 = create(:delivery, date: "2020-02-01") - delivery_2 = create(:delivery, date: "2020-04-01") - - membership1 = create(:membership, started_on: "2020-01-01", ended_on: "2020-05-01") - membership2 = create(:membership, started_on: "2020-03-01", ended_on: "2020-08-01") - - expect { - delivery_1.update!(date: "2020-06-01") - perform_enqueued_jobs - } - .to change { membership1.reload.baskets.size }.from(2).to(1) - .and change { membership2.reload.baskets.size }.from(1).to(2) - .and change { membership1.reload.price }.from(60).to(30) - .and change { membership2.reload.price }.from(30).to(60) - end - - specify "handles new delivery change", freeze: "2020-01-01" do - create(:delivery, date: "2020-02-01") - create(:delivery, date: "2020-04-01") - - membership1 = create(:membership, started_on: "2020-01-01", ended_on: "2020-05-01") - membership2 = create(:membership, started_on: "2020-03-01", ended_on: "2020-08-01") - - expect { - expect { - create(:delivery, date: "2020-06-01") - perform_enqueued_jobs - } - .to change { membership2.reload.baskets.size }.from(1).to(2) - .and change { membership2.reload.price }.from(30).to(60) - }.not_to change { membership1.reload.baskets.size } - end - - it "flags basket when creating them", freeze: "2020-01-01" do - create(:delivery, date: "2020-02-01") - membership = create(:membership, started_on: "2020-01-01", ended_on: "2020-06-01") - absence = create(:absence, - member: membership.member, - started_on: "2020-01-15", - ended_on: "2020-02-15") - - expect(membership.baskets_count).to eq 1 - expect(membership.baskets.first).to be_absent - - delivery = create(:delivery, date: "2020-02-15") - perform_enqueued_jobs - membership.reload - - expect(membership.baskets_count).to eq 2 - expect(membership.baskets.last).to have_attributes( - state: "absent", - delivery_id: delivery.id, - absence: absence) - end - - specify "reset delivery_cycle cache after date change", freeze: "2023-01-01" do - cycle = create(:delivery_cycle, wdays: [ 0 ]) - - expect { create(:delivery, date: "2023-01-01") } - .to change { cycle.reload.deliveries_counts } - .from("2023" => 0, "2024" => 0) - .to("2023" => 1, "2024" => 0) - - expect { Delivery.last.update!(date: "2023-01-02") } - .to change { cycle.reload.deliveries_counts } - .from("2023" => 1, "2024" => 0) - .to("2023" => 0, "2024" => 0) - end - - specify "reset delivery_cycle cache after destroy", freeze: "2023-01-01" do - cycle = create(:delivery_cycle, wdays: [ 0 ]) - delivery = create(:delivery, date: "2023-01-01") - - expect { delivery.destroy! } - .to change { cycle.reload.deliveries_counts } - .from("2023" => 1, "2024" => 0) - .to("2023" => 0, "2024" => 0) - end - - specify "update baskets after date change", freeze: "2024-01-01" do - create(:delivery, date: "2024-11-01") - delivery = create(:delivery, date: "2024-12-01") - membership = create(:membership, started_on: "2024-01-01", ended_on: "2024-11-30") - - expect { - perform_enqueued_jobs do - delivery.update!(date: "2024-11-30") - end - }.to change { membership.reload.baskets.size }.from(1).to(2) - end - - specify "update baskets after destroy", freeze: "2024-01-01" do - create(:delivery, date: "2024-11-01") - delivery = create(:delivery, date: "2024-12-01") - membership = create(:membership) - - expect { - perform_enqueued_jobs do - delivery.destroy! - end - }.to change { membership.reload.baskets.size }.from(2).to(1) - end - - describe "#shop_open?" do - before { create(:depot) } - - specify "when shop_open is false" do - delivery = create(:delivery, shop_open: false) - - expect(delivery.shop_open?).to eq false - end - - specify "when shop_open is true and no other restriction" do - delivery = create(:delivery, shop_open: true) - - expect(delivery.shop_open?).to eq true - end - - specify "when Organization#shop_delivery_open_delay_in_days is set" do - Current.org.update!(shop_delivery_open_delay_in_days: 2) - - delivery = travel_to "2021-08-08" do - create(:delivery, date: "2021-08-10", shop_open: true) - end - - travel_to "2021-08-08 23:59:59 +02" do - expect(delivery.shop_open?).to eq true - end - travel_to "2021-08-09 00:00:00 +02" do - expect(delivery.shop_open?).to eq false - end - end - - specify "when Organization#shop_delivery_open_last_day_end_time is set" do - Current.org.update!(shop_delivery_open_last_day_end_time: "12:00") - - delivery = travel_to "2021-08-08" do - create(:delivery, date: "2021-08-10", shop_open: true) - end - - travel_to "2021-08-10 12:00:00 +02" do - expect(delivery.shop_open?).to eq true - end - travel_to "2021-08-10 12:00:01 +02" do - expect(delivery.shop_open?).to eq false - end - end - - specify "when both Organization#shop_delivery_open_delay_in_days and Organization#shop_delivery_open_last_day_end_time are set" do - Current.org.update!( - shop_delivery_open_delay_in_days: 1, - shop_delivery_open_last_day_end_time: "12:30") - - delivery = travel_to "2021-08-08" do - create(:delivery, date: "2021-08-10", shop_open: true) - end - - travel_to "2021-08-09 12:30:00 +02" do - expect(delivery.shop_open?).to eq true - end - travel_to "2021-08-09 12:30:01 +02" do - expect(delivery.shop_open?).to eq false - end - end - end -end diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index d7e371d47..fb8e3a463 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -711,6 +711,7 @@ end specify "whit one previous cancel invoiced" do + travel_to "2024-01-01" p = create(:activity_participation) m = p.member i1 = create(:invoice, :activity_participation, :open, member: m, entity: p, id: 1) diff --git a/spec/models/pdf/invoice_spec.rb b/spec/models/pdf/invoice_spec.rb index c9ebf3f2e..c4b29e9ac 100644 --- a/spec/models/pdf/invoice_spec.rb +++ b/spec/models/pdf/invoice_spec.rb @@ -1033,6 +1033,7 @@ end specify "includes previous cancelled invoices references" do + travel_to "2024-01-01" p = create(:activity_participation) m = p.member i1 = create(:invoice, :activity_participation, :open, member: m, entity: p, id: 1) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 2405f9742..1e3554b17 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -22,6 +22,7 @@ config.include ActiveJob::TestHelper config.before(:suite) do + I18n.locale = :fr Tenant.switch("acme") do FactoryBot.create(:organization) unless Organization.exists? end diff --git a/spec/system/members/sessions_spec.rb b/spec/system/members/sessions_spec.rb deleted file mode 100644 index ec166d9ba..000000000 --- a/spec/system/members/sessions_spec.rb +++ /dev/null @@ -1,203 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe "Member sessions" do - before { Capybara.app_host = "http://membres.acme.test" } - - it "creates a new session from email" do - member = create(:member, emails: "thibaud@thibaud.gg, john@doe.com") - - visit "/" - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - - fill_in "session_email", with: " thibaud@Thibaud.GG " - click_button "Envoyer" - perform_enqueued_jobs - - session = member.sessions.last - - expect(session.email).to eq "thibaud@thibaud.gg" - expect(SessionMailer.deliveries.size).to eq 1 - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci! Un email vient de vous être envoyé." - - open_email("thibaud@thibaud.gg") - current_email.click_link "Accéder à mon compte" - - expect(current_path).to eq "/activity_participations" - expect(page).to have_content "Vous êtes maintenant connecté." - - delete_session(member) - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - end - - it "redirects to billing when activity is not a feature" do - current_org.update!(features: []) - - login(create(:member)) - - expect(current_path).to eq "/billing" - end - - it "does not accept blank email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "session_email", with: "" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("span.error", text: "doit être rempli(e)") - end - - it "does not accept invalid email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "session_email", with: "foo@bar" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("span.error", text: "n'est pas valide") - end - - it "does not accept email with invalid character" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "session_email", with: "foo@gmail.com)" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("span.error", text: "n'est pas valide") - end - - it "does not accept partial email matching other" do - create(:member, emails: "thibaud@thibaud.gg, john@doe.com") - - visit "/" - expect(current_path).to eq "/login" - - fill_in "session_email", with: "hn@doe.com" - click_button "Envoyer" - - expect(SessionMailer.deliveries).to be_empty - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("span.error", text: "Email inconnu") - end - - it "does not accept unknown email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "session_email", with: "unknown@member.com" - click_button "Envoyer" - - expect(SessionMailer.deliveries).to be_empty - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("span.error", text: "Email inconnu") - end - - it "does not accept old session when not logged in" do - old_session = create(:session, :member, created_at: 1.hour.ago) - - visit "/sessions/#{old_session.token}" - - expect(current_path).to eq "/login" - expect(page).to have_content "Votre lien de connexion n'est plus valide, merci d'en demander un nouveau." - end - - it "handles old session when already logged in" do - member = create(:member) - login(member) - old_session = create(:session, member: member, created_at: 1.hour.ago) - - visit "/sessions/#{old_session.token}" - - expect(current_path).to eq "/activity_participations" - expect(page).to have_content "Vous êtes déjà connecté." - end - - it "logout session without email" do - member = create(:member) - login(member) - member.sessions.last.update!(email: nil) - - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - end - - it "logout expired session" do - member = create(:member) - login(member) - member.sessions.last.update!(created_at: 1.year.ago) - - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Votre session a expirée, merci de vous authentifier à nouveau." - - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - end - - it "update last usage column every hour when using the session" do - member = create(:member) - - travel_to Time.new(2018, 7, 6, 1) do - login(member) - - session = member.sessions.last - expect(session).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 1), - last_remote_addr: "127.0.0.1") - expect(session.last_user_agent.to_s).to eq "Other" - end - - travel_to Time.new(2018, 7, 6, 1, 59) do - visit "/" - expect(member.sessions.last).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 1)) - end - - travel_to Time.new(2018, 7, 6, 2, 0, 1) do - visit "/" - expect(member.sessions.last).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 2, 0, 1)) - end - end - - specify "revoke session on logout" do - member = create(:member) - login(member) - session = member.sessions.last - - visit "/" - - expect { click_button "Déconnexion" } - .to change { session.reload.revoked_at }.from(nil) - - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - end -end diff --git a/spec/system/sessions_spec.rb b/spec/system/sessions_spec.rb deleted file mode 100644 index 044374b75..000000000 --- a/spec/system/sessions_spec.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -describe "Admin sessions" do - it "creates a new session from email" do - admin = create(:admin, email: "thibaud@thibaud.gg") - - visit "/" - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Merci de vous authentifier pour accéder à votre compte." - - fill_in "Email", with: " Thibaud@thibaud.gg " - click_button "Envoyer" - perform_enqueued_jobs - - session = admin.sessions.last - - expect(session.email).to eq "thibaud@thibaud.gg" - expect(SessionMailer.deliveries.size).to eq 1 - - expect(current_path).to eq "/login" - expect(flash_notice).to eq "Merci! Un email vient de vous être envoyé." - - open_email("thibaud@thibaud.gg") - current_email.click_link "Accéder à mon compte admin" - - expect(current_path).to eq "/" - expect(flash_notice).to eq "Vous êtes maintenant connecté." - - delete_session(admin) - visit "/" - - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Merci de vous authentifier pour accéder à votre compte." - end - - it "does not accept blank email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "Email", with: "" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("p.inline-errors", text: "doit être rempli(e)") - end - - it "does not accept invalid email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "Email", with: "@foo" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("p.inline-errors", text: "n'est pas valide") - end - - it "does not accept unknown email" do - visit "/" - expect(current_path).to eq "/login" - - fill_in "Email", with: "unknown@admin.com" - click_button "Envoyer" - - expect(SessionMailer.deliveries.size).to eq 0 - - expect(current_path).to eq "/sessions" - expect(page).to have_selector("p.inline-errors", text: "Email inconnu") - end - - it "does not accept old session when not logged in" do - old_session = create(:session, :admin, created_at: 1.hour.ago) - - visit "/sessions/#{old_session.token}" - - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Votre lien de connexion n'est plus valide, merci d'en demander un nouveau." - end - - it "handles old session when already logged in" do - admin = create(:admin) - login(admin) - old_session = create(:session, admin: admin, created_at: 1.hour.ago) - - visit "/sessions/#{old_session.token}" - - expect(current_path).to eq "/" - expect(flash_notice).to eq "Vous êtes déjà connecté." - end - - it "logout session without email" do - admin = create(:admin) - login(admin) - admin.sessions.last.update!(email: nil) - - visit "/" - - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Merci de vous authentifier pour accéder à votre compte." - end - - it "logout expired session" do - admin = create(:admin) - login(admin) - admin.sessions.last.update!(created_at: 1.year.ago) - - visit "/" - - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Votre session a expirée, merci de vous authentifier à nouveau." - - visit "/" - - expect(current_path).to eq "/login" - expect(flash_alert).to eq "Merci de vous authentifier pour accéder à votre compte." - end - - it "update last usage column every hour when using the session" do - admin = create(:admin) - - travel_to Time.new(2018, 7, 6, 1) do - login(admin) - - session = admin.sessions.last - expect(session).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 1), - last_remote_addr: "127.0.0.1") - expect(session.last_user_agent.to_s).to eq "Other" - end - - travel_to Time.new(2018, 7, 6, 1, 59) do - visit "/" - expect(admin.sessions.last).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 1)) - end - - travel_to Time.new(2018, 7, 6, 2, 0, 1) do - visit "/" - expect(admin.sessions.last).to have_attributes( - last_used_at: Time.new(2018, 7, 6, 2, 0, 1)) - end - end - - specify "revoke session on logout" do - admin = create(:admin) - login(admin) - session = admin.sessions.last - - visit "/" - - expect { click_link "Déconnexion" } - .to change { session.reload.revoked_at }.from(nil) - - visit "/" - - expect(current_path).to eq "/login" - expect(page).to have_content "Merci de vous authentifier pour accéder à votre compte." - end -end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 000000000..49b51580b --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "test_helper" +require 'capybara/email' + +require "support/flash_messages_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + include ActiveJob::TestHelper + include Capybara::Email::DSL + + include FlashMessagesHelper + + driven_by :rack_test + + setup do |test| + subdomain = test.class.name.include?("Members::") ? "members" : "admin" + Capybara.app_host = "http://#{subdomain}.acme.test" + + clear_emails + end +end diff --git a/test/fixtures/absences.yml b/test/fixtures/absences.yml new file mode 100644 index 000000000..84f966b03 --- /dev/null +++ b/test/fixtures/absences.yml @@ -0,0 +1,5 @@ +jane_thursday_5: + member: jane + started_on: 2024-05-01 + ended_on: 2024-05-07 + note: Vacation diff --git a/test/fixtures/active_admin_comments.yml b/test/fixtures/active_admin_comments.yml new file mode 100644 index 000000000..7c3a780aa --- /dev/null +++ b/test/fixtures/active_admin_comments.yml @@ -0,0 +1,14 @@ +_fixture: + model_class: ActiveAdmin::Comment + +super_admin: + namespace: root + body: "This is a comment from the super admin" + resource: small (BasketSize) + author: super (Admin) + +external: + namespace: root + body: "This is a comment from the external consultant" + resource: small (BasketSize) + author: external (Admin) diff --git a/test/fixtures/activities.yml b/test/fixtures/activities.yml new file mode 100644 index 000000000..542f3833b --- /dev/null +++ b/test/fixtures/activities.yml @@ -0,0 +1,10 @@ +harvest_help: + date: 2024-07-01 + start_time: "08:30" + end_time: "12:00" + places: + en: Farm + titles: + en: Help with the harvest + descriptions: + en: Picking vegetables diff --git a/test/fixtures/activity_participations.yml b/test/fixtures/activity_participations.yml new file mode 100644 index 000000000..08dd3a111 --- /dev/null +++ b/test/fixtures/activity_participations.yml @@ -0,0 +1,12 @@ +john_harvest: + member: john + activity: harvest_help + participants_count: 1 + +jane_harvest: + member: jane + activity: harvest_help + participants_count: 1 + carpooling_phone: +41 79 123 45 67 + carpooling_city: La Chaux-de-Fonds + note: I will bring my own gloves diff --git a/test/fixtures/admins.yml b/test/fixtures/admins.yml new file mode 100644 index 000000000..17f3332b2 --- /dev/null +++ b/test/fixtures/admins.yml @@ -0,0 +1,10 @@ +super: + name: Thibaud + email: info@csa-admin.org + language: en + permission_id: 1 # super_admin + +external: + name: External Consultant + email: external@support.com + permission: read_only diff --git a/test/fixtures/basket_complements.yml b/test/fixtures/basket_complements.yml new file mode 100644 index 000000000..58c1723f1 --- /dev/null +++ b/test/fixtures/basket_complements.yml @@ -0,0 +1,14 @@ +bread: + names: + en: Bread + price: 4 + +cheese: + names: + en: Cheese + price: 5 + +eggs: + names: + en: Eggs + price: 6 diff --git a/test/fixtures/basket_sizes.yml b/test/fixtures/basket_sizes.yml new file mode 100644 index 000000000..aceacb993 --- /dev/null +++ b/test/fixtures/basket_sizes.yml @@ -0,0 +1,20 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +small: + names: + en: "Small" + price: 10 + +medium: + names: + en: "Medium" + price: 20 + +large: + names: + en: "Large" + price: 30 diff --git a/test/fixtures/baskets.yml b/test/fixtures/baskets.yml new file mode 100644 index 000000000..ec0f9cb32 --- /dev/null +++ b/test/fixtures/baskets.yml @@ -0,0 +1,22 @@ +<% 10.times do |i| %> + +john_<%= i + 1 %>: + membership: john + delivery: monday_<%= i + 1 %> + delivery_cycle_price: 0 + basket_size: medium + basket_price: 20 + depot: farm + depot_price: 0 + +jane_<%= i + 1 %>: + membership: jane + delivery: thursday_<%= i + 1 %> + state: <%= (i + 1) == 5 ? "absent" : "normal" %> + delivery_cycle_price: 0 + basket_size: large + basket_price: 30 + depot: bakery + depot_price: 4 + +<% end %> diff --git a/test/fixtures/baskets_basket_complements.yml b/test/fixtures/baskets_basket_complements.yml new file mode 100644 index 000000000..9e320476d --- /dev/null +++ b/test/fixtures/baskets_basket_complements.yml @@ -0,0 +1,8 @@ +<% 10.times do |i| %> + +jane_bread_<%= i + 1 %>: + basket: jane_<%= i + 1 %> + basket_complement: bread + price: 4 + +<% end %> diff --git a/test/fixtures/deliveries.yml b/test/fixtures/deliveries.yml new file mode 100644 index 000000000..27a2bfa08 --- /dev/null +++ b/test/fixtures/deliveries.yml @@ -0,0 +1,90 @@ +monday_1: + number: 1 + date: 2024-04-01 + +monday_2: + number: 3 + date: 2024-04-08 + +monday_3: + number: 5 + date: 2024-04-15 + +monday_4: + number: 7 + date: 2024-04-22 + +monday_5: + number: 9 + date: 2024-04-29 + +monday_6: + number: 11 + date: 2024-05-06 + +monday_7: + number: 13 + date: 2024-05-13 + +monday_8: + number: 15 + date: 2024-05-20 + +monday_9: + number: 17 + date: 2024-05-27 + +monday_10: + number: 19 + date: 2024-06-03 + + +thursday_1: + number: 2 + date: 2024-04-04 + basket_complements: [ bread, eggs ] + +thursday_2: + number: 4 + date: 2024-04-11 + basket_complements: [ bread, eggs ] + +thursday_3: + number: 6 + date: 2024-04-18 + basket_complements: [ bread, eggs ] + +thursday_4: + number: 8 + date: 2024-04-25 + basket_complements: [ bread, eggs ] + +thursday_5: + number: 10 + date: 2024-05-02 + basket_complements: [ bread, eggs ] + +thursday_6: + number: 12 + date: 2024-05-09 + basket_complements: [ bread, eggs ] + +thursday_7: + number: 14 + date: 2024-05-16 + basket_complements: [ bread, eggs ] + +thursday_8: + number: 16 + date: 2024-05-23 + basket_complements: [ bread, eggs ] + +thursday_9: + number: 18 + date: 2024-05-30 + basket_complements: [ bread, eggs ] + +thursday_10: + number: 20 + date: 2024-06-06 + basket_complements: [ bread, eggs ] diff --git a/test/fixtures/delivery_cycles.yml b/test/fixtures/delivery_cycles.yml new file mode 100644 index 000000000..5f1dcdbf3 --- /dev/null +++ b/test/fixtures/delivery_cycles.yml @@ -0,0 +1,19 @@ +mondays: + names: + en: Mondays + wdays: [1] + depots: + - home_delivery + - farm + - bakery + deliveries_counts: { "2024": 10, "2025": 0 } + +thursdays: + names: + en: Thursdays + wdays: [4] + depots: + - home_delivery + - farm + - bakery + deliveries_counts: { "2024": 10, "2025": 0 } diff --git a/test/fixtures/depots.yml b/test/fixtures/depots.yml new file mode 100644 index 000000000..a727dea72 --- /dev/null +++ b/test/fixtures/depots.yml @@ -0,0 +1,14 @@ +home_delivery: + name: Home Delivery + price: 9 + language: en + +farm: + name: Farm + price: 0 + language: en + +bakery: + name: Bakery + price: 4 + language: en diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/files/camt053.xml b/test/fixtures/files/camt053.xml new file mode 100644 index 000000000..4080cc4ca --- /dev/null +++ b/test/fixtures/files/camt053.xml @@ -0,0 +1,368 @@ + + + + + + 053D2013-12-27T22:05:03.0N130000005 + 2013-12-27T22:04:52.0+01:00 + + 1 + true + + + + 0352C5320131227220503 + 130000005 + 2013-12-27T22:04:52.0+01:00 + + + DE14740618130000033626 + + EUR + + Testkonto Nummer 1 + + + + GENODEF1PFK + VR-Bank Rottal-Inn eG + + DE 129267947 + UmsStId + + + + + + + + PRCD + + + 33.06 + CRDT +
+
2013-12-27
+ +
+ + + + CLBD + + + 23.06 + CRDT +
+
2013-12-27
+ +
+ + 2.00 + DBIT + BOOK + +
2013-12-27
+
+ +
2013-12-27
+
+ 2013122710583450000 + + + + + BankReference + EndToEndReference + MandateReference + PaymentIdentification + UniqueTransactionId + + AdditionalTransactionInformation + + + NTRF+020 + ZKA + + + + + Wayne Enterprises + + + + DE24302201900609832118 + + + + + + + CreditorIdentifier + + + + Testkonto Nummer 2 + + Berlin + Infinite Loop 2 + 12345 + + + + + DE09300606010012345671 + + + CACC + + + + + + + DAAEDEDDXXX + + + ABCDEF + + 1232344234234 + + + + + + DAAEDEDDXXX + + + ABCDEF + + 123456789 + + Bank + + Infinite Loop 1 + Berlin + + + + + + TEST BERWEISUNG MITTELS BLZUND KONTONUMMER - DTA + + + + Überweisungs-Gutschrift; GVC: SEPA Credit Transfer (Einzelbuchung-Haben) +
+ + 3.00 + DBIT + BOOK + +
2013-12-27
+
+ +
2013-12-27
+
+ 2013122710583600000 + + + + + CCTI/VRNWSW/b044f24cddb92a502b8a1b5 + NOTPROVIDED + + + + NMSC+201 + ZKA + + + + + Testkonto Nummer 1 + + + + DE14740618130000033626 + + + + keine Information vorhanden + + + Testkonto Nummer 2 + + + + DE58740618130100033626 + + + + keine Information vorhanden + + + + + + GENODEF1PFK + + + + + Test+berweisung mit BIC und IBAN SEPA IBAN: DE58740618130100033626 BIC: GENODEF1PFK + + + +
+ + 1.00 + CRDT + BOOK + +
2013-12-27
+
+ +
2013-12-27
+
+ 2013122711085260000 + + + + + + NMSC+051 + ZKA + + + + + Testkonto Nummer 2 + + + + + 740618130100033626 + + BBAN + + + + + + + R CKBUCHUNG + + + +
+ + 6.00 + DBIT + BOOK + +
2013-12-27
+
+ +
2013-12-27
+
+ 2013122711513230000 + + + + STZV-PmInf27122013-11:02-2 + 2 + + + + STZV-Msg27122013-11:02 + STZV-EtE27122013-11:02-1 + + + + 3.50 + + + + + NMSC+201 + ZKA + + + + + Testkonto Nummer 2 + + + + DE58740618130100033626 + + + + keine Information vorhanden + + + Testkonto Nummer 1 + + + + DE14740618130000033626 + + + + Testkonto + + + + Sammelueberwseisung 2. Zahlung TAN:283044 + + + + + STZV-Msg27122013-11:02 + STZV-EtE27122013-11:02-2 + + + + 2.50 + + + + + NMSC+201 + ZKA + + + + + Testkonto Nummer 2 + + + + DE58740618130100033626 + + + + keine Information vorhanden + + + Testkonto Nummer 1 + + + + DE14740618130000033626 + + + + Testkonto + + + + Sammelueberweisung 1. Zahlung TAN:283044 + + + +
+
+
+
diff --git a/test/fixtures/files/camt054.xml b/test/fixtures/files/camt054.xml new file mode 100644 index 000000000..379117c81 --- /dev/null +++ b/test/fixtures/files/camt054.xml @@ -0,0 +1,125 @@ + + + + + 2020112000085998 + 2020-11-20T10:27:59 + + 1 + true + + + + 2020112000085998 + 1 + 2020-11-20T10:27:59 + + 2020-08-21T00:00:00 + 2020-11-19T23:59:59 + + + OTHR + + + + CH120345006789100000 + + CHF + + Mule + + Route du chemin 3 + 2300 La Chaux-de-Fonds + + + + + ABSOCH22XXX + Alternative Bank Schweiz AG + + + + + CH120345006789100000 + 1 + CRDT + false + BOOK + +
2020-11-13
+
+ +
2020-11-13
+
+ + + PMNT + + RCDT + VCOM + + + + + + 1 + 1 + CRDT + + + + ZV20201113/371247/2 + NOTPROVIDED + + 1 + CRDT + + + 1 + + + + + PMNT + + RCDT + VCOM + + + + + + Monsieur Thibaud Guillaume-Gentil + + 2300 La Chaux-de-Fonds + + + + Mule + + Route du chemin 3 + 2300 La Chaux-de-Fonds + + + + + + + + + QRR + + + 000000000000000420000000011 + + Test QR + + + Crédit QR-facture + + + Crédit QR-facture +
+
+
+
diff --git a/test/fixtures/files/camt054_ref_with_letters.xml b/test/fixtures/files/camt054_ref_with_letters.xml new file mode 100644 index 000000000..3551a8b8f --- /dev/null +++ b/test/fixtures/files/camt054_ref_with_letters.xml @@ -0,0 +1,125 @@ + + + + + 2020112000085998 + 2020-11-20T10:27:59 + + 1 + true + + + + 2020112000085998 + 1 + 2020-11-20T10:27:59 + + 2020-08-21T00:00:00 + 2020-11-19T23:59:59 + + + OTHR + + + + CH120345006789100000 + + CHF + + Mule + + Route du chemin 3 + 2300 La Chaux-de-Fonds + + + + + ABSOCH22XXX + Alternative Bank Schweiz AG + + + + + CH120345006789100000 + 1 + CRDT + false + BOOK + +
2020-11-13
+
+ +
2020-11-13
+
+ + + PMNT + + RCDT + VCOM + + + + + + 1 + 1 + CRDT + + + + ZV20201113/371247/2 + NOTPROVIDED + + 1 + CRDT + + + 1 + + + + + PMNT + + RCDT + VCOM + + + + + + Monsieur Thibaud Guillaume-Gentil + + 2300 La Chaux-de-Fonds + + + + Mule + + Route du chemin 3 + 2300 La Chaux-de-Fonds + + + + + + + + + QRR + + + ABCD000000000000000001ABCD1 + + Test QR + + + Crédit QR-facture + + + Crédit QR-facture +
+
+
+
diff --git a/test/fixtures/files/camt_invalid.xml b/test/fixtures/files/camt_invalid.xml new file mode 100644 index 000000000..9127b7088 --- /dev/null +++ b/test/fixtures/files/camt_invalid.xml @@ -0,0 +1 @@ + diff --git a/test/fixtures/files/camt_wrong.xml b/test/fixtures/files/camt_wrong.xml new file mode 100644 index 000000000..8060a2a0f --- /dev/null +++ b/test/fixtures/files/camt_wrong.xml @@ -0,0 +1,13 @@ + + + + + 2020112000085998 + 2020-11-20T10:27:59 + + 1 + true + + + + diff --git a/test/fixtures/files/logo.png b/test/fixtures/files/logo.png new file mode 100644 index 000000000..b1e6dec30 Binary files /dev/null and b/test/fixtures/files/logo.png differ diff --git a/test/fixtures/files/mt940.mta b/test/fixtures/files/mt940.mta new file mode 100644 index 000000000..48270c974 --- /dev/null +++ b/test/fixtures/files/mt940.mta @@ -0,0 +1,6 @@ +:20:STARTUMS +:25:43060967/1328420900 +:28C:0 +:60F:C240403EUR21060,46 +:61:2404030403CR285,00NTRFKREF+ +:86:171?00SEPA LASTSCHRIFT KUNDE?10281?20KREF+EREF+TRX-0A4A47C3-F846-4729?21-8A1B-5DF620F?22:FOO:MREF+CAC97D2144174318A?23BF815BD4FB?24CRED+DE98ZZZ09999999999?25SVWZ+RF21 0000 0001 0000 0001FOO?30HYVEDEMMXXX?31HUkkbbbsssskcccccccccccccccx?32Peter Pan?99?34171 diff --git a/test/fixtures/files/qrcode-706.png b/test/fixtures/files/qrcode-706.png new file mode 100644 index 000000000..02c09da38 Binary files /dev/null and b/test/fixtures/files/qrcode-706.png differ diff --git a/test/fixtures/files/qrcode-test.png b/test/fixtures/files/qrcode-test.png new file mode 100644 index 000000000..888bb305a Binary files /dev/null and b/test/fixtures/files/qrcode-test.png differ diff --git a/test/fixtures/invoices.yml b/test/fixtures/invoices.yml new file mode 100644 index 000000000..106e2d1bd --- /dev/null +++ b/test/fixtures/invoices.yml @@ -0,0 +1,8 @@ +annual_fee: + state: open + member: support_annual_fee + entity_type: AnnualFee + amount: 30 + vat_rate: 0.0 + vat_amount: 0.0 + date: <%= Date.today %> diff --git a/test/fixtures/members.yml b/test/fixtures/members.yml new file mode 100644 index 000000000..9bd89c7ce --- /dev/null +++ b/test/fixtures/members.yml @@ -0,0 +1,29 @@ +john: + name: John Doe + emails: john@doe.com + annual_fee: 30 + language: en + country_code: CH + address: Nowhere 42 + zip: 1234 + city: City + +jane: + name: Jane Doe + emails: jane@doe.com + annual_fee: 30 + language: en + country_code: CH + address: Nowhere 42 + zip: 1234 + city: City + +support_annual_fee: + name: Martha + emails: support@annual.com + annual_fee: 30 + language: en + country_code: CH + address: Nowhere 42 + zip: 1234 + city: City diff --git a/test/fixtures/memberships.yml b/test/fixtures/memberships.yml new file mode 100644 index 000000000..d1df3df86 --- /dev/null +++ b/test/fixtures/memberships.yml @@ -0,0 +1,32 @@ +john: + member: john + price: 200 + renew: true + invoices_amount: 0 + baskets_count: 10 + basket_size: medium + basket_price: 20 + depot: farm + depot_price: 0 + delivery_cycle: mondays + delivery_cycle_price: 0 + absences_included_annually: 0 + started_on: <%= Date.new(Date.today.year, 1, 1) %> + ended_on: <%= Date.new(Date.today.year, 12, 31) %> + activity_participations_demanded_annually: 2 + +jane: + member: jane + price: 300 + invoices_amount: 0 + baskets_count: 10 + basket_size: large + basket_price: 30 + depot: bakery + depot_price: 4 + delivery_cycle: mondays + delivery_cycle_price: 0 + absences_included_annually: 0 + started_on: <%= Date.new(Date.today.year, 1, 1) %> + ended_on: <%= Date.new(Date.today.year, 12, 31) %> + activity_participations_demanded_annually: 2 diff --git a/test/fixtures/memberships_basket_complements.yml b/test/fixtures/memberships_basket_complements.yml new file mode 100644 index 000000000..39133992c --- /dev/null +++ b/test/fixtures/memberships_basket_complements.yml @@ -0,0 +1,5 @@ +jame_bread: + membership: jane + basket_complement: bread + quantity: 1 + price: 4 diff --git a/test/fixtures/newsletter/templates.yml b/test/fixtures/newsletter/templates.yml new file mode 100644 index 000000000..17e77596f --- /dev/null +++ b/test/fixtures/newsletter/templates.yml @@ -0,0 +1,9 @@ +simple: + title: Simple + contents: + en: | + Hello {{ member.name }}, + + {% content id: 'main', title: "Content Title" %} + Example Text {{ member.name }} + {% endcontent %} diff --git a/test/fixtures/newsletters.yml b/test/fixtures/newsletters.yml new file mode 100644 index 000000000..29e014bb8 --- /dev/null +++ b/test/fixtures/newsletters.yml @@ -0,0 +1,5 @@ +simple: + template: simple + audience: member_state::all + subjects: + en: "Simple Subject" diff --git a/test/fixtures/organizations.yml b/test/fixtures/organizations.yml new file mode 100644 index 000000000..8846ea8cf --- /dev/null +++ b/test/fixtures/organizations.yml @@ -0,0 +1,33 @@ +acme: + name: Acme + url: https://www.acme.test + country_code: CH + currency_code: CHF + languages: [en] + email: info@acme.test + phone: +41 76 449 59 38 + members_subdomain: members + email_default_from: info@acme.test + email_signatures: + en: "Best regards,\nAcme" + email_footers: + en: "In case of questions or remarks, simply reply to this email.\nAcme, Nowhere 42, 1234 City" + billing_year_divisions: [1, 4] + iban: CH4431999123000889012 + creditor_name: Acme + creditor_address: Nowhere 42 + creditor_city: City + creditor_zip: 1234 + invoice_infos: + en: Payable within 30 days, with our thanks. + invoice_footers: + en: Acme, Nowhere 42, 1234 City // info@acme.test + terms_of_service_urls: + en: https://www.acme.test/terms + features: + - absence + - activity + - shop + feature_flags: [] + activity_price: 50 + activity_participations_demanded_logic: "<%= Organization::ACTIVITY_PARTICIPATIONS_DEMANDED_LOGIC_DEFAULT %>" diff --git a/test/fixtures/permissions.yml b/test/fixtures/permissions.yml new file mode 100644 index 000000000..c7a12e734 --- /dev/null +++ b/test/fixtures/permissions.yml @@ -0,0 +1,8 @@ +super_admin: + id: 1 + names: + en: SuperAdmin + +read_only: + names: + en: ReadOnly diff --git a/test/fixtures/shop/order_items.yml b/test/fixtures/shop/order_items.yml new file mode 100644 index 000000000..7e47159d2 --- /dev/null +++ b/test/fixtures/shop/order_items.yml @@ -0,0 +1,6 @@ +john_bread_500: + order: john + product: bread + product_variant: bread_500 + item_price: 5 + quantity: 1 diff --git a/test/fixtures/shop/orders.yml b/test/fixtures/shop/orders.yml new file mode 100644 index 000000000..c6674469f --- /dev/null +++ b/test/fixtures/shop/orders.yml @@ -0,0 +1,4 @@ +john: + member: john + delivery: mondays_1 + state: pending diff --git a/test/fixtures/shop/producers.yml b/test/fixtures/shop/producers.yml new file mode 100644 index 000000000..27b5968cd --- /dev/null +++ b/test/fixtures/shop/producers.yml @@ -0,0 +1,2 @@ +farm: + name: Farm diff --git a/test/fixtures/shop/product_variants.yml b/test/fixtures/shop/product_variants.yml new file mode 100644 index 000000000..40105b3e8 --- /dev/null +++ b/test/fixtures/shop/product_variants.yml @@ -0,0 +1,11 @@ +bread_500: + product: bread + names: + en: 500g + price: 5 + +bread_1000: + product: bread + names: + en: 1kg + price: 10 diff --git a/test/fixtures/shop/products.yml b/test/fixtures/shop/products.yml new file mode 100644 index 000000000..fd90f54d4 --- /dev/null +++ b/test/fixtures/shop/products.yml @@ -0,0 +1,3 @@ +bread: + names: + en: Bread diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/jobs/billing/missing_activity_participations_invoicer_job_test.rb b/test/jobs/billing/missing_activity_participations_invoicer_job_test.rb new file mode 100644 index 000000000..d74760434 --- /dev/null +++ b/test/jobs/billing/missing_activity_participations_invoicer_job_test.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "test_helper" + +class Billing::MissingActivityParticipationsInvoicerJobTest < ActiveJob::TestCase + def perform(membership) + perform_enqueued_jobs do + Billing::MissingActivityParticipationsInvoicerJob.perform_later(membership) + end + end + + test "noop if no activity price" do + Current.org.update!(activity_price: 0) + membership = memberships(:john) + + assert_no_difference "Invoice.count" do + perform(membership) + end + end + + test "noop if no missing activity participations" do + membership = memberships(:john) + membership.update!(activity_participations_demanded_annually: 0) + + assert_no_difference "Invoice.count" do + perform(membership) + end + end + + test "create invoice and send invoice" do + mail_templates(:invoice_created) + membership = memberships(:john) + membership.update!(activity_participations_demanded_annually: 2) + + assert_difference [ "Invoice.count", "InvoiceMailer.deliveries.size" ], 1 do + assert_changes -> { membership.reload.activity_participations_missing }, to: 0 do + perform(membership) + end + end + + invoice = membership.member.invoices.last + assert_equal Date.today, invoice.date + assert_equal 2, invoice.missing_activity_participations_count + assert_equal membership.fiscal_year, invoice.missing_activity_participations_fiscal_year + assert_equal "ActivityParticipation", invoice.entity_type + assert_nil invoice.entity_id + assert_equal 2 * 50, invoice.amount + assert invoice.sent? + end + + test "create invoice for previous year membership" do + travel_to "2025-01-01" + + membership = memberships(:john) + membership.update!(activity_participations_demanded_annually: 2) + + assert_difference "Invoice.count", 1 do + perform(membership) + end + + invoice = membership.member.invoices.last + assert_equal Date.today, invoice.date + assert_equal FiscalYear.for(2024), invoice.missing_activity_participations_fiscal_year + assert_equal "ActivityParticipation", invoice.entity_type + assert_nil invoice.entity_id + assert invoice.sent? + end +end diff --git a/test/jobs/concerns/tenant_context_test.rb b/test/jobs/concerns/tenant_context_test.rb new file mode 100644 index 000000000..72e937a9e --- /dev/null +++ b/test/jobs/concerns/tenant_context_test.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "test_helper" + +class TenantContextTest < ActiveJob::TestCase + class DummyJob < ActiveJob::Base + include TenantContext + def perform(admin, name: nil) + admin.update!(name: name) + end + end + + class DummyExceptionJob < ActiveJob::Base + include TenantContext + retry_on Exception, wait: :polynomially_longer, attempts: 2 + + def perform(foo) + raise Exception + end + end + + test "add current attributes and tenant last arguments" do + admin = admins(:super) + Current.session = create_session(admin) + DummyJob.perform_later(admin, name: "Admin!") + + assert_equal 1, enqueued_jobs.size + job = enqueued_jobs.first + assert_equal [ + { "_aj_globalid" => "gid://csa-admin/Admin/#{admin.id}" }, + { "name" => "Admin!", "_aj_ruby2_keywords" => [ "name" ] }, + { + "tenant" => "acme", + "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/#{Current.session.id}" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] + } + ], job["arguments"] + + perform_enqueued_jobs + assert_equal 0, enqueued_jobs.size + assert_equal "Admin!", admin.reload.name + end + + test "retry with the same current attributes and tenant last arguments" do + Current.session = create_session(admins(:super)) + DummyExceptionJob.perform_later("bar") + + assert_equal 1, enqueued_jobs.size + job = enqueued_jobs.first + assert_equal [ + "bar", + { + "tenant" => "acme", + "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/#{Current.session.id}" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] + } + ], job["arguments"] + + # rescue Exception one time + perform_enqueued_jobs + + assert_equal 1, enqueued_jobs.size + job = enqueued_jobs.first + assert_equal [ + "bar", + { + "tenant" => "acme", + "current" => { "session" => { "_aj_globalid" => "gid://csa-admin/Session/#{Current.session.id}" }, "_aj_symbol_keys" => [ "session" ] }, "_aj_symbol_keys" => [] + } + ], job["arguments"] + + assert_raises(Exception) { perform_enqueued_jobs } + assert_equal 0, enqueued_jobs.size + end +end diff --git a/test/jobs/membership_renewal_job_test.rb b/test/jobs/membership_renewal_job_test.rb new file mode 100644 index 000000000..fa64b9a2f --- /dev/null +++ b/test/jobs/membership_renewal_job_test.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "test_helper" + +class MembershipRenewalJobTest < ActiveJob::TestCase + def next_fy + Current.org.next_fiscal_year + end + + def perform(membership) + perform_enqueued_jobs do + MembershipRenewalJob.perform_later(membership) + end + end + + test "raises when no next year deliveries" do + membership = memberships(:john) + assert_equal 0, Delivery.between(next_fy.range).count + + MembershipRenewalJob.perform_later(membership) + assert_raise(MembershipRenewal::MissingDeliveriesError) do + perform_enqueued_jobs + end + end + + test "renews a membership" do + travel_to "2024-01-01" + Delivery.create!(date: "2025-01-06") + membership = memberships(:john) + membership.update!( + basket_quantity: 2, + basket_price: 42, + baskets_annual_price_change: 130, + depot_price: 3, + activity_participations_demanded_annually: 5, + activity_participations_annual_price_change: -60) + + membership.basket_size.update!(price: 41) + membership.depot.update!(price: 4) + + assert_difference "Membership.count", 1 do + perform(membership) + end + + new_membership = Membership.last + assert_equal membership.member_id, new_membership.member_id + assert_equal membership.basket_size_id, new_membership.basket_size_id + assert_equal 41, new_membership.basket_price + assert_equal 2, new_membership.basket_quantity + assert_equal 130, new_membership.baskets_annual_price_change + assert_equal membership.depot_id, new_membership.depot_id + assert_equal 4, new_membership.depot_price + assert_equal 5, new_membership.activity_participations_demanded_annually + assert_equal -60, new_membership.activity_participations_annual_price_change + assert_equal next_fy.beginning_of_year, new_membership.started_on + assert_equal next_fy.end_of_year, new_membership.ended_on + end +end diff --git a/test/lib/tenant_test.rb b/test/lib/tenant_test.rb new file mode 100644 index 000000000..b44b9582a --- /dev/null +++ b/test/lib/tenant_test.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "test_helper" + +class TenantTest < ActiveSupport::TestCase + test "find_by host" do + assert_equal "acme", Tenant.find_by(host: "admin.acme.test") + assert_equal "acme", Tenant.find_by(host: "foo.acme.test") + assert_nil Tenant.find_by(host: "admin.unknown.test") + end + + test "domain" do + assert_equal "acme", Tenant.current + assert_equal "acme.test", Tenant.domain + end + + test "inside? / outside?" do + assert_equal "acme", Tenant.current + assert Tenant.inside? + assert_not Tenant.outside? + end + + test "exists?" do + assert Tenant.exists?("acme") + assert_not Tenant.exists?("unknown") + end + + test "connect to unknown tenant" do + assert_equal "acme", Tenant.current + assert_raises RuntimeError, match: /Unknown tenant 'unknown'/ do + Tenant.switch("unknown") { } + end + end +end diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/mailers/activity_mailer_test.rb b/test/mailers/activity_mailer_test.rb new file mode 100644 index 000000000..a85cde2e4 --- /dev/null +++ b/test/mailers/activity_mailer_test.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActivityMailerTest < ActionMailer::TestCase + test "participation_reminder_email" do + template = mail_template(:activity_participation_reminder) + participation = activity_participations(:john_harvest) + group = ActivityParticipationGroup.group([ participation ]).first + + mail = ActivityMailer.with( + template: template, + activity_participation_ids: group.ids, + ).participation_reminder_email + + assert_equal "Upcoming activity (1 July 2024)", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "activity-participation-reminder", mail.tag + assert_includes mail.body, "Date: Monday 1 July 2024" + assert_includes mail.body, "Schedule: 8:30-12:00" + assert_includes mail.body, "Activity: Help with the harvest" + assert_includes mail.body, "Description: Picking vegetables" + assert_includes mail.body, "Location: Farm" + assert_includes mail.body, "Participants: 1" + assert_includes mail.body, "Jane Doe: +41 79 123 45 67 (La Chaux-de-Fonds)" + assert_includes mail.body, "https://members.acme.test/activity_participations" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "participation_validated_email" do + template = mail_template(:activity_participation_validated) + participation = activity_participations(:john_harvest) + + mail = ActivityMailer.with( + template: template, + activity_participation_ids: participation.id + ).participation_validated_email + + assert_equal "Activity confirmed 🎉", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "activity-participation-validated", mail.tag + assert_includes mail.body, "Date: Monday 1 July 2024" + assert_includes mail.body, "Schedule: 8:30-12:00" + assert_includes mail.body, "Activity: Help with the harvest" + assert_includes mail.body, "Description: Picking vegetables" + assert_includes mail.body, "Location: Farm" + assert_includes mail.body, "Participants: 1" + assert_includes mail.body, "https://members.acme.test/activity_participations" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "participation_rejected_email" do + template = mail_template(:activity_participation_rejected) + participation = activity_participations(:john_harvest) + + mail = ActivityMailer.with( + template: template, + activity_participation_ids: participation.id + ).participation_rejected_email + + assert_equal "Activity rejected 😬", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "activity-participation-rejected", mail.tag + assert_includes mail.body, "Date: Monday 1 July 2024" + assert_includes mail.body, "Schedule: 8:30-12:00" + assert_includes mail.body, "Activity: Help with the harvest" + assert_includes mail.body, "Description: Picking vegetables" + assert_includes mail.body, "Location: Farm" + assert_includes mail.body, "Participants: 1" + assert_includes mail.body, "https://members.acme.test/activity_participations" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end +end diff --git a/test/mailers/admin_mailer_test.rb b/test/mailers/admin_mailer_test.rb new file mode 100644 index 000000000..6455a75b8 --- /dev/null +++ b/test/mailers/admin_mailer_test.rb @@ -0,0 +1,306 @@ +# frozen_string_literal: true + +require "test_helper" + +class AdminMailerTest < ActionMailer::TestCase + # setup do + # @admin = admins(:john) # Ensure there is a fixture named :john in admins.yml + # @delivery = deliveries(:one) # Ensure there is a fixture named :one in deliveries.yml + # @depot = depots(:jardin_de_la_main) # Ensure there is a fixture named :jardin_de_la_main in depots.yml + # @membership_small = memberships(:small_membership) # Fixture for small membership + # @membership_big = memberships(:big_membership) # Fixture for big membership + # @member_martha = members(:martha) # Fixture for member Martha + # @member_charle = members(:charle) # Fixture for member Charle + # @absence = absences(:one) # Ensure there is a fixture named :one in absences.yml + # @invoice = invoices(:one) # Ensure there is a fixture named :one in invoices.yml + # end + + test "depot_delivery_list_email" do + depot = depots(:farm) + depot.update!(emails: "respondent1@csa-admin.org, respondent2@csa-admin.org") + + mail = AdminMailer.with( + depot: depot, + baskets: Basket.all, + delivery: deliveries(:monday_1) + ).depot_delivery_list_email + + assert_equal "Delivery list of 1 April 2024 (Farm)", mail.subject + assert_equal [ "respondent1@csa-admin.org", "respondent2@csa-admin.org" ], mail.to + assert_equal "admin-depot-delivery-list", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.html_part.body + assert_includes body, "Here is the list of members:" + assert_includes body, "John Doe, Medium" + assert_includes body, "See the attachments for more details, thank you." + assert_not_includes body, "Manage my notifications" + + assert_equal 2, mail.attachments.size + attachment1 = mail.attachments.first + assert_equal "delivery-#1-20240401.xlsx", attachment1.filename + assert_equal "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", attachment1.content_type + attachment2 = mail.attachments.second + assert_equal "sheets-delivery-#1-20240401.pdf", attachment2.filename + assert_equal "application/pdf", attachment2.content_type + end + + test "delivery_list_email" do + delivery = deliveries(:monday_1) + mail = AdminMailer.with( + admin: admins(:super), + delivery: delivery + ).delivery_list_email + + assert_equal "Delivery list of 1 April 2024", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-delivery-list", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.html_part.body.to_s + assert_includes body, "(XLSX)" + assert_includes body, "(PDF)" + assert_includes body, "Access the delivery page" + assert_includes body, "https://admin.acme.test/deliveries/#{delivery.id}" + assert_includes body, "Manage my notifications" + + assert_equal 2, mail.attachments.size + attachment1 = mail.attachments.first + assert_equal "delivery-#1-20240401.xlsx", attachment1.filename + assert_equal "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", attachment1.content_type + attachment2 = mail.attachments.second + assert_equal "sheets-delivery-#1-20240401.pdf", attachment2.filename + assert_equal "application/pdf", attachment2.content_type + end + + test "invitation_email" do + admin = Admin.new( + name: "Bob", + language: "en", + email: "bob@csa-admin.org") + + mail = AdminMailer.with( + admin: admin, + action_url: "https://admin.acme.test" + ).invitation_email + + assert_equal "Invitation to the Acme admin", mail.subject + assert_equal [ "bob@csa-admin.org" ], mail.to + assert_equal "admin-invitation", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Bob," + assert_includes body, "bob@csa-admin.org" + assert_includes body, "Access the admin of Acme" + assert_includes body, "https://admin.acme.test" + assert_not_includes body, "Manage my notifications" + end + + test "invoice_overpaid_email" do + invoice = invoices(:annual_fee) + + mail = AdminMailer.with( + admin: admins(:super), + member: invoice.member, + invoice: invoice + ).invoice_overpaid_email + + assert_equal "Overpaid invoice ##{invoice.id}", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-invoice-overpaid", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "Overpaid invoice ##{invoice.id}" + assert_includes body, "Martha" + assert_includes body, "Access member page" + assert_includes body, "https://admin.acme.test/members/#{invoice.member_id}" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "invoice_third_overdue_notice_email" do + invoice = invoices(:annual_fee) + + mail = AdminMailer.with( + admin: admins(:super), + invoice: invoice + ).invoice_third_overdue_notice_email + + assert_equal "Invoice ##{invoice.id}, 3rd reminder sent", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-invoice-third-overdue-notice", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "The 3rd reminder has just been sent for invoice ##{invoice.id}" + assert_includes body, "Martha" + assert_includes body, "Access member page" + assert_includes body, "https://admin.acme.test/members/#{invoice.member_id}" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "new_absence_email" do + absence = absences(:jane_thursday_5) + + mail = AdminMailer.with( + admin: admins(:super), + member: absence.member, + absence: absence + ).new_absence_email + + assert_equal "New absence", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-absence-created", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "Jane" + assert_includes body, "from 1 May 2024 to 7 May 2024." + assert_includes body, "Member's note:
\n Vacation" + assert_includes body, "Access the absence page" + assert_includes body, "https://admin.acme.test/absences/#{absence.id}" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "new_activity_participation_email" do + mail = AdminMailer.with( + admin: admins(:super), + activity_participation_ids: [ activity_participations(:jane_harvest).id ] + ).new_activity_participation_email + + assert_equal "New participation in a ½ day", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-activity-participation-created", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "The member Jane Doe has registered for an activity" + assert_includes body, "Date: Monday 1 July 2024" + assert_includes body, "Schedule: 8:30-12:00" + assert_includes body, "Activity: Help with the harvest" + assert_includes body, "Description: Picking vegetables" + assert_includes body, "Location: Farm" + assert_includes body, "Participants: 1" + assert_includes body, "Carpooling: +41 79 123 45 67 (La Chaux-de-Fonds)" + assert_includes body, "Member's note:
\r\n I will bring my own gloves" + assert_includes body, "Access the member's participation page" + assert_includes body, "https://admin.acme.test/activity_participations?q%5Bmember_id_eq%5D=#{members(:jane).id}&scope=future" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "new_email_suppression_email" do + email_suppression = OpenStruct.new( + reason: "HardBounce", + email: "john@doe.com", + owners: [ members(:john) ] + ) + + mail = AdminMailer.with( + admin: admins(:super), + email_suppression: email_suppression + ).new_email_suppression_email + + assert_equal "Email rejected (HardBounce)", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-email-suppression-created", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "Email rejected (HardBounce)\n\n

Hello Thibaud,

\n\n

The email john@doe.com was rejected during the last message delivery due to the following reason: HardBounce" + assert_includes body, "Member: John Doe" + assert_includes body, "https://admin.acme.test/members/#{members(:john).id}" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "new_registration_email" do + mail = AdminMailer.with( + admin: admins(:super), + member: members(:john), + ).new_registration_email + + assert_equal "New registration", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-member-created", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "John Doe" + assert_includes body, "Access member page" + assert_includes body, "https://admin.acme.test/members/#{members(:john).id}" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "memberships_renewal_pending_email" do + mail = AdminMailer.with( + admin: admins(:super), + pending_memberships: [ memberships(:john), memberships(:jane) ], + opened_memberships: [ memberships(:jane) ], + pending_action_url: "https://admin.example.com/memberships/pending", + opened_action_url: "https://admin.example.com/memberships/opened", + action_url: "https://admin.example.com/memberships" + ).memberships_renewal_pending_email + + assert_equal "⚠️ Membership(s) pending renewal!", mail.subject + assert_equal [ "info@csa-admin.org" ], mail.to + assert_equal "admin-memberships-renewal-pending", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.body.to_s + assert_includes body, "Hello Thibaud," + assert_includes body, "2 membership(s)" + assert_includes body, "https://admin.example.com/memberships/pending" + assert_includes body, "1 open renewal request(s)" + assert_includes body, "https://admin.example.com/memberships/opened" + assert_includes body, "Access memberships" + assert_includes body, "https://admin.example.com/memberships" + assert_includes body, "https://admin.acme.test/admins/#{admins(:super).id}/edit#notifications" + assert_includes body, "Manage my notifications" + end + + test "memberships_renewal_pending_email_pending_only" do + mail = AdminMailer.with( + admin: admins(:super), + pending_memberships: [ memberships(:john), memberships(:jane) ], + opened_memberships: [], + pending_action_url: "https://admin.example.com/memberships/pending", + opened_action_url: "https://admin.example.com/memberships/opened", + action_url: "https://admin.example.com/memberships" + ).memberships_renewal_pending_email + + assert_equal "⚠️ Membership(s) pending renewal!", mail.subject + assert_includes mail.body.to_s, "2 membership(s)" + assert_includes mail.body.to_s, "https://admin.example.com/memberships/pending" + assert_not_includes mail.body.to_s, "request(s)" + assert_not_includes mail.body.to_s, "https://admin.example.com/memberships.opened" + end + + test "memberships_renewal_pending_email_opened_only" do + mail = AdminMailer.with( + admin: admins(:super), + pending_memberships: [], + opened_memberships: [ memberships(:john), memberships(:jane) ], + pending_action_url: "https://admin.example.com/memberships/pending", + opened_action_url: "https://admin.example.com/memberships/opened", + action_url: "https://admin.example.com/memberships" + ).memberships_renewal_pending_email + + assert_equal "⚠️ Membership(s) pending renewal!", mail.subject + assert_not_includes mail.body.to_s, "2 membership(s)" + assert_not_includes mail.body.to_s, "https://admin.example.com/memberships/pending" + assert_includes mail.body.to_s, "2 open renewal request(s)" + assert_includes mail.body.to_s, "https://admin.example.com/memberships/opened" + end +end diff --git a/test/mailers/invoice_mailer_test.rb b/test/mailers/invoice_mailer_test.rb new file mode 100644 index 000000000..900b363aa --- /dev/null +++ b/test/mailers/invoice_mailer_test.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "test_helper" + +class InvoiceMailerTest < ActionMailer::TestCase + test "created_email" do + travel_to "2024-01-01" + template = mail_template(:invoice_created) + invoice = invoices(:annual_fee) + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).created_email + + assert_equal "New invoice ##{invoice.id}", mail.subject + assert_equal [ "support@annual.com" ], mail.to + assert_equal "invoice-created", mail.tag + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + + body = mail.html_part.body.to_s + assert_includes body, "Here is your new invoice" + assert_includes body, "Access my member page" + assert_includes body, "https://members.acme.test/billing" + + assert_equal 1, mail.attachments.size + attachment = mail.attachments.first + assert_equal "invoice-acme-#{invoice.id}.pdf", attachment.filename + assert_equal "application/pdf", attachment.content_type + end + + test "created_email (closed)" do + travel_to "2024-01-01" + template = mail_template(:invoice_created) + invoice = invoices(:annual_fee) + invoice.state = "closed" + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).created_email + + assert_equal "New invoice ##{invoice.id}", mail.subject + body = mail.html_part.body.to_s + assert_includes body, "Considering previous payments, this invoice is considered paid and is sent for informational purposes only." + end + + test "created_email (partially paid)" do + travel_to "2024-01-01" + template = mail_template(:invoice_created) + invoice = invoices(:annual_fee) + Payment.create!(invoice: invoice, amount: 11, date: Date.yesterday) + invoice.reload + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).created_email + + assert_equal "New invoice ##{invoice.id}", mail.subject + body = mail.html_part.body.to_s + assert_includes body, "Considering previous payments, the remaining amount to be paid is: CHF 19.00" + end + + test "created_email (Shop::Order)" do + travel_to "2024-01-01" + template = mail_template(:invoice_created) + order = shop_orders(:john) + invoice = order.invoice! + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).created_email + + body = mail.html_part.body.to_s + assert_includes body, "Here is your new invoice for your order number #{order.id}, " + + assert_equal 1, mail.attachments.size + assert_equal "application/pdf", mail.attachments.first.content_type + end + + test "created_email (billing_email)" do + travel_to "2024-01-01" + template = mail_template(:invoice_created) + invoice = invoices(:annual_fee) + invoice.member.billing_email = "info@accounting.com" + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).created_email + + assert_equal "New invoice ##{invoice.id}", mail.subject + assert_equal [ "info@accounting.com" ], mail.to + assert_equal "invoice-created", mail.tag + body = mail.html_part.body.to_s + assert_includes body, "Here is your new invoice" + assert_not_includes body, "Access my member page" + assert_not_includes body, "https://members.acme.test/billing" + assert_equal "Acme ", mail[:from].decoded + + assert_equal 1, mail.attachments.size + attachment = mail.attachments.first + assert_equal "invoice-acme-#{invoice.id}.pdf", attachment.filename + assert_equal "application/pdf", attachment.content_type + end + + test "cancelled_email" do + travel_to "2024-01-01" + template = mail_template(:invoice_cancelled) + invoice = invoices(:annual_fee) + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).cancelled_email + + assert_equal "Cancelled invoice ##{invoice.id}", mail.subject + assert_equal [ "support@annual.com" ], mail.to + assert_equal "invoice-cancelled", mail.tag + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + + body = mail.body.to_s + assert_includes body, "Your invoice ##{invoice.id} from #{I18n.l(invoice.date)} has been cancelled." + assert_includes body, "Access my member page" + assert_includes body, "https://members.acme.test/billing" + + assert_equal 0, mail.attachments.size + end + + test "overdue_notice_email" do + travel_to "2024-01-01" + template = mail_template(:invoice_overdue_notice) + invoice = invoices(:annual_fee) + invoice.overdue_notices_count = 2 + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).overdue_notice_email + + assert_equal "Overdue notice #2 for invoice ##{invoice.id} 😬", mail.subject + assert_equal [ "support@annual.com" ], mail.to + assert_equal "invoice-overdue-notice", mail.tag + assert_equal "Acme ", mail[:from].decoded + + body = mail.html_part.body.to_s + assert_includes body, "The remaining amount to be paid is: CHF 30.00" + assert_includes body, "Access my member page" + assert_includes body, "https://members.acme.test/billing" + + assert_equal 1, mail.attachments.size + attachment = mail.attachments.first + assert_equal "invoice-acme-#{invoice.id}.pdf", attachment.filename + assert_equal "application/pdf", attachment.content_type + end + + test "overdue_notice_email (billing_email)" do + travel_to "2024-01-01" + template = mail_template(:invoice_overdue_notice) + invoice = invoices(:annual_fee) + invoice.overdue_notices_count = 2 + invoice.member.billing_email = "info@accounting.com" + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).overdue_notice_email + + assert_equal "Overdue notice #2 for invoice ##{invoice.id} 😬", mail.subject + assert_equal [ "info@accounting.com" ], mail.to + assert_equal "invoice-overdue-notice", mail.tag + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + + body = mail.html_part.body.to_s + assert_includes body, "The remaining amount to be paid is: CHF 30.00" + assert_not_includes body, "Access my member page" + assert_not_includes body, "https://members.acme.test/billing" + + assert_equal 1, mail.attachments.size + attachment = mail.attachments.first + assert_equal "invoice-acme-#{invoice.id}.pdf", attachment.filename + assert_equal "application/pdf", attachment.content_type + end + + test "sanitize html from subject" do + travel_to "2024-01-01" + template = mail_template(:invoice_overdue_notice) + template.update!(subject: 'Reminder #{{ invoice.overdue_notices_count }} 😬') + + invoice = invoices(:annual_fee) + invoice.overdue_notices_count = 2 + + mail = InvoiceMailer.with( + template: template, + invoice: invoice, + ).overdue_notice_email + + assert_equal "Reminder #2 😬", mail.subject + end +end diff --git a/test/mailers/member_mailer_test.rb b/test/mailers/member_mailer_test.rb new file mode 100644 index 000000000..5111ef391 --- /dev/null +++ b/test/mailers/member_mailer_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "test_helper" + +class MemberMailerTest < ActionMailer::TestCase + test "activated_email" do + travel_to "2024-01-01" + template = mail_template(:member_activated) + membership = memberships(:jane) + + mail = MemberMailer.with( + template: template, + member: membership.member, + ).activated_email + + assert_equal "Welcome!", mail.subject + assert_equal [ "jane@doe.com" ], mail.to + assert_equal "member-activated", mail.tag + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + + body = mail.body.to_s + assert_includes body, "Depot: Bakery" + assert_includes body, "Basket size: Large" + assert_includes body, "Complements: Bread" + assert_includes body, "Access my member page" + assert_includes body, "https://members.acme.test" + end + + test "validated_email" do + template = mail_template(:member_validated) + member = members(:john) + + mail = MemberMailer.with( + template: template, + member: member + ).validated_email + + assert_equal "Registration validated!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "member-validated", mail.tag + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + + body = mail.body.to_s + assert_includes body, "Waiting list position: 1" + assert_includes body, "Access my member page" + assert_includes body, "https://members.acme.test" + end +end diff --git a/test/mailers/membership_mailer_test.rb b/test/mailers/membership_mailer_test.rb new file mode 100644 index 000000000..9224e1e89 --- /dev/null +++ b/test/mailers/membership_mailer_test.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require "test_helper" + +class MembershipMailerTest < ActionMailer::TestCase + test "initial_basket_email" do + travel_to "2024-01-01" + template = mail_template(:membership_initial_basket) + membership = memberships(:john) + basket = membership.baskets.first + + mail = MembershipMailer.with( + template: template, + basket: basket, + ).initial_basket_email + + assert_equal "First basket!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-initial-basket", mail.tag + assert_includes mail.body.to_s, "https://members.acme.test" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "final_basket_email" do + travel_to "2024-01-01" + template = mail_template(:membership_final_basket) + membership = memberships(:john) + basket = membership.baskets.last + + mail = MembershipMailer.with( + template: template, + basket: basket, + ).final_basket_email + + assert_equal "Last basket!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-final-basket", mail.tag + assert_includes mail.body.to_s, "https://members.acme.test" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "first_basket_email" do + travel_to "2024-01-01" + template = mail_template(:membership_first_basket) + membership = memberships(:john) + basket = membership.baskets.first + + mail = MembershipMailer.with( + template: template, + basket: basket, + ).first_basket_email + + assert_equal "First basket of the year!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-first-basket", mail.tag + assert_includes mail.body.to_s, "https://members.acme.test" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "last_basket_email" do + travel_to "2024-01-01" + template = mail_template(:membership_last_basket) + membership = memberships(:john) + basket = membership.baskets.last + + mail = MembershipMailer.with( + template: template, + basket: basket, + ).last_basket_email + + assert_equal "Last basket of the year!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-last-basket", mail.tag + assert_includes mail.body.to_s, "https://members.acme.test" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "last_trial_basket_email" do + travel_to "2024-01-01" + template = mail_template(:membership_last_trial_basket) + membership = memberships(:john) + basket = membership.baskets.first + + mail = MembershipMailer.with( + template: template, + basket: basket, + ).last_trial_basket_email + + assert_equal "Last trial basket!", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-last-trial-basket", mail.tag + assert_includes mail.body.to_s, "It's the day of your last trial basket.." + assert_includes mail.body.to_s, "https://members.acme.test" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "renewal_email" do + travel_to "2024-01-01" + template = mail_template(:membership_renewal) + membership = memberships(:john) + + mail = MembershipMailer.with( + template: template, + membership: membership, + ).renewal_email + + assert_equal "Renew your membership", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-renewal", mail.tag + assert_includes mail.body.to_s, "Access the renewal form" + assert_includes mail.body.to_s, "https://members.acme.test/memberships#renewal" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end + + test "renewal_reminder_email" do + travel_to "2024-01-01" + template = mail_template(:membership_renewal_reminder) + membership = memberships(:john) + + mail = MembershipMailer.with( + template: template, + membership: membership, + ).renewal_reminder_email + + assert_equal "Renew your membership (reminder)", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "membership-renewal-reminder", mail.tag + assert_includes mail.body.to_s, "Access the renewal form" + assert_includes mail.body.to_s, "https://members.acme.test/memberships#renewal" + assert_equal "Acme ", mail[:from].decoded + assert_equal "outbound", mail[:message_stream].to_s + end +end diff --git a/test/mailers/newsletter_mailer_test.rb b/test/mailers/newsletter_mailer_test.rb new file mode 100644 index 000000000..0523bbc58 --- /dev/null +++ b/test/mailers/newsletter_mailer_test.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "test_helper" + +class NewsletterMailerTest < ActionMailer::TestCase + test "newsletter_email" do + template = newsletter_templates(:simple) + member = members(:john) + + mail = NewsletterMailer.with( + tag: "newsletter-42", + template: template, + template_contents: template.contents, # Avoid preview data usage + subject: "My Newsletter", + member: member, + to: "john@doe.com" + ).newsletter_email + + assert_equal "My Newsletter", mail.subject + assert_equal [ "john@doe.com" ], mail.to + assert_equal "Acme ", mail[:from].decoded + assert_equal "broadcast", mail[:message_stream].to_s + assert_equal "newsletter-42", mail[:tag].to_s + + body = mail.body.to_s + assert_includes body, "Hello John Doe," + assert_includes body, '

Content Title

' + assert_includes body, "Example Text John Doe" + assert_match %r{https://members.acme.test/newsletters/unsubscribe/\w{32}}, body + assert_equal "List-Unsubscribe=One-Click", mail["List-Unsubscribe-Post"].to_s + assert_match %r{}, + mail["List-Unsubscribe"].to_s + end + + test "newsletter_email with attachments" do + newsletter = newsletters(:simple) + member = members(:john) + + attachment = Newsletter::Attachment.new + attachment.file.attach( + io: File.open(file_fixture("qrcode-test.png")), + filename: 'A "stylish" QR code.png') + newsletter.update!(attachments: [ attachment ]) + + mail = NewsletterMailer.with( + template: newsletter.template, + subject: "My Newsletter", + member: member, + attachments: [ attachment ], + to: "john@doe.com" + ).newsletter_email + + assert_equal 1, mail.attachments.size + attachment = mail.attachments.first + assert_equal "A -stylish- QR code.png", attachment.filename + assert_equal "image/png", attachment.content_type + end +end diff --git a/test/mailers/session_mailer_test.rb b/test/mailers/session_mailer_test.rb new file mode 100644 index 000000000..d3507d890 --- /dev/null +++ b/test/mailers/session_mailer_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "test_helper" + +class SessionMailerTest < ActionMailer::TestCase + test "new member session email" do + session = Session.new( + member: Member.new(language: "fr"), + email: "example@csa-admin.org") + mail = SessionMailer.with( + session: session, + session_url: "https://example.com/session/token", + ).new_member_session_email + + assert_equal "Connexion à votre compte", mail.subject + assert_equal %w[ example@csa-admin.org ], mail.to + assert_equal "session-member", mail.tag + + assert_includes mail.body.to_s, "Accéder à mon compte" + assert_includes mail.body.to_s, "https://example.com/session/token" + assert_equal "Acme ", mail[:from].decoded + end + + test "new admin session email" do + session = Session.new( + admin: Admin.new(language: "fr"), + email: "example@csa-admin.org") + mail = SessionMailer.with( + session: session, + session_url: "https://example.com/session/token", + ).new_admin_session_email + + assert_equal "Connexion à votre compte admin", mail.subject + assert_equal %w[ example@csa-admin.org ], mail.to + assert_equal "session-admin", mail.tag + + assert_includes mail.body.to_s, "Accéder à mon compte admin" + assert_includes mail.body.to_s, "https://example.com/session/token" + assert_equal "Acme ", mail[:from].decoded + end +end diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/ability_test.rb b/test/models/ability_test.rb new file mode 100644 index 000000000..7acc45953 --- /dev/null +++ b/test/models/ability_test.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "test_helper" + +class AbilityTest < ActiveSupport::TestCase + test "superadmin permissions" do + ability = Ability.new(admins(:super)) + + assert ability.can?(:read, Organization) + assert ability.can?(:update, Current.org) + assert ability.can?(:manage, Admin) + assert_not ability.can?(:destroy, admins(:super)) + assert ability.can?(:manage, ActiveAdmin::Comment) + assert ability.can?(:create, Absence) + + Current.org.update!(features: []) + ability = Ability.new(admins(:super)) + + assert_not ability.can?(:create, Absence) + end + + test "read only permissions" do + admin = admins(:external) + ability = Ability.new(admin) + + assert ability.can?(:read, ActiveAdmin::Page) + assert ability.can?(:pdf, Invoice) + assert_not ability.can?(:read, Organization) + assert_not ability.can?(:manage, Admin) + assert_not ability.can?(:destroy, admin) + assert ability.can?(:update, admin) + assert ability.can?(:read, ActiveAdmin::Comment) + assert ability.can?(:create, ActiveAdmin::Comment) + assert_not ability.can?(:manage, active_admin_comments(:super_admin)) + assert ability.can?(:manage, active_admin_comments(:external)) + assert_not ability.can?(:batch_action, Member) + assert_not ability.can?(:batch_action, Membership) + assert_not ability.can?(:batch_action, Invoice) + assert_not ability.can?(:batch_action, Shop::Product) + end + + test "member write permissions" do + admin = admins(:external) + admin.permission.update!(rights: { member: :write }) + ability = Ability.new(admin) + + assert ability.can?(:create, Member) + assert ability.can?(:update, Member) + assert ability.can?(:batch_action, Member) + assert ability.can?(:become, Member) + assert ability.can?(:validate, Member.new(state: "pending")) + end + + test "membership write permissions" do + admin = admins(:external) + admin.permission.update!(rights: { membership: :write }) + ability = Ability.new(admin) + + assert ability.can?(:create, Membership) + assert ability.can?(:update, Membership) + assert ability.can?(:update, Basket) + assert ability.can?(:batch_action, Membership) + assert ability.can?(:renew_all, Membership) + assert ability.can?(:open_renewal_all, Membership) + assert ability.can?(:open_renewal, Membership) + assert ability.can?(:mark_renewal_as_pending, Membership) + assert ability.can?(:future_billing, Membership) + assert ability.can?(:renew, Membership) + assert ability.can?(:cancel, Membership) + end + + test "billing write permissions" do + admin = admins(:external) + admin.permission.update!(rights: { billing: :write }) + ability = Ability.new(admin) + + assert ability.can?(:create, Invoice) + assert ability.can?(:update, Invoice) + assert ability.can?(:batch_action, Invoice) + assert ability.can?(:create, Payment) + assert ability.can?(:update, Payment) + assert ability.can?(:batch_action, Payment) + assert ability.can?(:recurring_billing, Member) + assert ability.can?(:force_share_billing, Member) + assert ability.can?(:send_email, Invoice) + assert ability.can?(:cancel, Invoice) + assert ability.can?(:import, Payment) + + invoice = invoices(:annual_fee) + assert ability.can?(:send_email, invoice) + + invoice.member.update!(emails: "") + assert_not ability.can?(:send_email, invoice) + + invoice.member.update!(emails: "", billing_email: "billing@test.com") + assert ability.can?(:send_email, invoice) + end + + test "shop write permissions" do + Current.org.update!(features: [ :shop ]) + admin = admins(:external) + admin.permission.update!(rights: { shop: :write }) + ability = Ability.new(admin) + + assert ability.can?(:create, Shop::Order) + assert ability.can?(:update, Shop::Order) + assert ability.can?(:batch_action, Shop::Order) + assert ability.can?(:create, Shop::Product) + assert ability.can?(:update, Shop::Product) + assert ability.can?(:batch_action, Shop::Product) + assert ability.can?(:create, Shop::Producer) + assert ability.can?(:update, Shop::Producer) + assert ability.can?(:create, Shop::Tag) + assert ability.can?(:update, Shop::Tag) + assert ability.can?(:invoice, Shop::Order) + assert ability.can?(:cancel, Shop::Order) + end +end diff --git a/test/models/admin_test.rb b/test/models/admin_test.rb new file mode 100644 index 000000000..1ff78f25a --- /dev/null +++ b/test/models/admin_test.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "test_helper" + +class AdminTest < ActiveSupport::TestCase + test "deletes sessions when destroyed" do + admin = admins(:super) + session = create_session(admin) + + assert_difference "Session.count", -1 do + admin.destroy! + end + end + + test "sets latest_update_read on create" do + admin = Admin.create!( + name: "New Admin", + permission: Permission.superadmin, + email: "new_admin@example.com", + latest_update_read: nil) + + assert_equal Update.all.first.name, admin.latest_update_read + end + + test "email=" do + admin = Admin.new(email: "Thibaud@Thibaud.GG ") + + assert_equal "thibaud@thibaud.gg", admin.email + end + + test "notify! with suppressed email" do + admin = admins(:super) + admin.update!(notifications: [ "new_absence" ]) + EmailSuppression.suppress!(admin.email, + stream_id: "outbound", + origin: "Recipient", + reason: "HardBounce") + + assert_no_difference("ActionMailer::Base.deliveries.count") do + Admin.notify!(:new_absence) + end + end +end diff --git a/test/models/basket_size_test.rb b/test/models/basket_size_test.rb new file mode 100644 index 000000000..f5981d557 --- /dev/null +++ b/test/models/basket_size_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "test_helper" + +class BasketSizeTest < ActiveSupport::TestCase + def ordered_names + BasketSize.member_ordered.map(&:name) + end + + test "#member_ordered" do + assert_equal %w[Large Medium Small], ordered_names + + Current.org.update!(basket_sizes_member_order_mode: "price_asc") + assert_equal %w[Small Medium Large], ordered_names + + Current.org.update!(basket_sizes_member_order_mode: "name_asc") + assert_equal %w[Large Medium Small], ordered_names + + basket_sizes(:small).update!(member_order_priority: 0) + assert_equal %w[Small Large Medium], ordered_names + end +end diff --git a/test/models/billing/camt_file_test.rb b/test/models/billing/camt_file_test.rb new file mode 100644 index 000000000..fa00687ff --- /dev/null +++ b/test/models/billing/camt_file_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "test_helper" + +class Billing::CamtFileTest < ActiveSupport::TestCase + test "returns payment data from CAMT file" do + file = Billing::CamtFile.new(file_fixture("camt054.xml")) + assert_equal [ + Billing::CamtFile::PaymentData.new( + invoice_id: 1, + member_id: 42, + amount: 1, + date: Date.new(2020, 11, 13, 11), + fingerprint: "2020-11-13-ZV20201113/371247/2-000000000000000420000000011" + ) + ], file.payments_data + end + + test "returns no payment data when REF has letter" do + file = Billing::CamtFile.new(file_fixture("camt054_ref_with_letters.xml")) + assert_empty file.payments_data + end + + test "raises for invalid CAMT namespace" do + file = Billing::CamtFile.new(file_fixture("camt_wrong.xml")) + assert_raises(Billing::CamtFile::UnsupportedFileError) { file.payments_data } + end + + test "raises for CAMT.053 format" do + file = Billing::CamtFile.new(file_fixture("camt053.xml")) + assert_raises(Billing::CamtFile::UnsupportedFileError) { file.payments_data } + end + + test "raises for invalid CAMT file" do + file = Billing::CamtFile.new(file_fixture("camt_invalid.xml")) + assert_raises(Billing::CamtFile::UnsupportedFileError) { file.payments_data } + end +end diff --git a/test/models/concerns/auditable_test.rb b/test/models/concerns/auditable_test.rb new file mode 100644 index 000000000..6367e0d9d --- /dev/null +++ b/test/models/concerns/auditable_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require "test_helper" + +class AuditableTest < ActiveSupport::TestCase + test "save changes on audited attributes without session" do + member = members(:john) + member.update!(name: "Joe Doe") + + assert_difference("Audit.count", 1) do + member.update!(name: "John Doe") + end + + audit = member.audits.last + assert_equal System.instance, audit.actor + assert_nil audit.session + assert_equal({ "name" => [ "Joe Doe", "John Doe" ] }, audit.audited_changes) + end + + test "save changes on audited attributes with current session" do + member = members(:john) + session = create_session(member) + Current.session = session + + assert_difference("Audit.count", 1) do + member.update!(name: "Joe Doe", note: "Hello") + end + + audit = member.audits.last + assert_equal member, audit.actor + assert_equal session, audit.session + assert_equal({ "name" => [ "John Doe", "Joe Doe" ], "note" => [ nil, "Hello" ] }, audit.audited_changes) + end + + test "ignore changes with no present value" do + member = members(:john) + member.update!(note: nil) + + assert_no_difference("Audit.count") do + member.update!(note: " ") + end + end + + test "ignore changes with similar values" do + member = members(:john) + member.update!(note: "Foo") + + assert_no_difference("Audit.count") do + member.update!(note: " Foo ") + end + end +end diff --git a/test/models/delivery_test.rb b/test/models/delivery_test.rb new file mode 100644 index 000000000..b2e08251e --- /dev/null +++ b/test/models/delivery_test.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require "test_helper" +require "shared/bulk_dates_insert" + +class DeliveryTest < ActiveSupport::TestCase + include Shared::BulkDatesInsert + + test "validates bulk inserts" do + delivery = Delivery.create( + bulk_dates_starts_on: Date.today, + bulk_dates_wdays: [ 1 ], + date: Date.today) + + assert_not delivery.valid?(:bulk_dates_starts_on) + assert_not delivery.valid?(:bulk_dates_wdays) + end + + test "bulk inserts with basket_complements" do + travel_to "2020-01-01" + bread = basket_complements(:bread) + + assert_difference "Delivery.count", 2 do + Delivery.create( + bulk_dates_starts_on: "2020-01-01", + bulk_dates_ends_on: "2020-02-01", + bulk_dates_weeks_frequency: 2, + bulk_dates_wdays: [ 1 ], + basket_complements: [ bread ]) + end + + assert_equal [ [ bread ], [ bread ] ], Delivery.first(2).map(&:basket_complements) + end + + test "adds basket_complement on subscribed baskets" do + travel_to "2024-01-01" + bread = basket_complements(:bread) + eggs = basket_complements(:eggs) + + membership = memberships(:john) + membership.update!(subscribed_basket_complements: [ bread, eggs ]) + + delivery = membership.deliveries.first + delivery.update! basket_complements: [ eggs ] + + basket = membership.baskets.first + assert_equal [ eggs ], basket.complements + assert_equal eggs.price, basket.complements_price + + delivery.update! basket_complements: [ bread, eggs ] + + basket = membership.baskets.first + assert_equal [ eggs, bread ], basket.complements + assert_equal bread.price + eggs.price, basket.complements_price + end + + test "removes basket_complement on subscribed baskets" do + travel_to "2024-01-01" + bread = basket_complements(:bread) + + membership = memberships(:jane) + + basket = membership.baskets.first + assert_equal [ bread ], basket.complements + + delivery = membership.deliveries.first + delivery.update! basket_complement_ids: [] + + basket = membership.baskets.first + assert_equal [], basket.complements + assert_equal 0, basket.complements_price + end + + test "updates all fiscal year delivery numbers" do + travel_to "2024-01-01" + first = deliveries(:monday_1) + last = deliveries(:thursday_10) + assert_equal 1, first.number + assert_equal 20, last.number + + delivery = Delivery.create!(date: "2024-01-02") + assert_equal 1, delivery.reload.number + assert_equal 2, first.reload.number + assert_equal 21, last.reload.number + + delivery.update! date: first.date + 1.day + assert_equal 1, first.reload.number + assert_equal 2, delivery.reload.number + assert_equal 21, last.reload.number + end + + test "update membership when date created" do + travel_to "2024-01-01" + membership = memberships(:john) + basket = baskets(:john_1) + + assert_difference -> { membership.baskets.count } do + assert_difference -> { membership.reload.price }, basket.basket_price do + Delivery.create!(date: "2024-06-10") + perform_enqueued_jobs + end + end + end + + test "update membership when date updated" do + travel_to "2024-01-01" + membership = memberships(:john) + basket = baskets(:john_1) + delivery = deliveries(:monday_1) + + assert_difference -> { membership.baskets.count }, -1 do + assert_difference -> { membership.reload.price }, -basket.basket_price do + delivery.update!(date: delivery.date + 1.day) + perform_enqueued_jobs + end + end + end + + test "update membership when date removed" do + travel_to "2024-01-01" + membership = memberships(:john) + basket = baskets(:john_1) + + assert_difference -> { membership.baskets.count }, -1 do + assert_difference -> { membership.reload.price }, -basket.basket_price do + membership.deliveries.first.destroy! + perform_enqueued_jobs + end + end + end + + test "flags basket when creating them" do + travel_to "2024-01-01" + membership = memberships(:john) + absence = membership.member.absences.create!( + started_on: "2024-06-05", + ended_on: "2024-06-15") + + Delivery.create!(date: "2024-06-10") + perform_enqueued_jobs + + basket = membership.baskets.last + assert_equal "absent", basket.state + assert_equal absence, basket.absence + end + + test "reset delivery_cycle cache after date change" do + travel_to "2024-01-01" + cycle = delivery_cycles(:mondays) + + assert_changes -> { cycle.reload.deliveries_counts }, from: { "2024" => 10, "2025" => 0 }, to: { "2024" => 11, "2025" => 0 } do + Delivery.create!(date: "2024-06-10") + end + + assert_changes -> { cycle.reload.deliveries_counts }, from: { "2024" => 11, "2025" => 0 }, to: { "2024" => 10, "2025" => 0 } do + cycle.deliveries(2024).last.update!(date: "2024-06-11") + end + + assert_changes -> { cycle.reload.deliveries_counts }, from: { "2024" => 10, "2025" => 0 }, to: { "2024" => 9, "2025" => 0 } do + cycle.deliveries(2024).first.destroy! + end + end + + # + # Shop + # + test "#shop_open is true by default" do + travel_to "2024-01-01" + delivery = deliveries(:monday_1) + + assert delivery.shop_open? + + delivery.update!(shop_open: false) + assert_not delivery.shop_open? + end + + test "when Organization#shop_delivery_open_delay_in_days is set" do + Current.org.update!(shop_delivery_open_delay_in_days: 2) + delivery = deliveries(:monday_1) + + travel_to "2024-03-30 23:59:59" do + assert delivery.shop_open? + end + travel_to "2024-03-31" do + assert_not delivery.shop_open? + end + end + + test "when Organization#shop_delivery_open_last_day_end_time is set" do + Current.org.update!(shop_delivery_open_last_day_end_time: "12:00") + delivery = deliveries(:monday_1) + + travel_to "2024-04-01 11:59" do + assert delivery.shop_open? + end + travel_to "2024-04-01 12:00:01" do + assert_not delivery.shop_open? + end + end + + test "when both Organization#shop_delivery_open_delay_in_days and Organization#shop_delivery_open_last_day_end_time are set" do + Current.org.update!( + shop_delivery_open_delay_in_days: 1, + shop_delivery_open_last_day_end_time: "12:30") + delivery = deliveries(:monday_1) + + travel_to "2024-03-31 12:30" do + assert delivery.shop_open? + end + travel_to "2024-03-31 12:30:01" do + assert_not delivery.shop_open? + end + end +end diff --git a/test/models/newsletter/audience_test.rb b/test/models/newsletter/audience_test.rb new file mode 100644 index 000000000..e69de29bb diff --git a/test/shared/bulk_dates_insert.rb b/test/shared/bulk_dates_insert.rb new file mode 100644 index 000000000..2af0ce0d1 --- /dev/null +++ b/test/shared/bulk_dates_insert.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Shared + module BulkDatesInsert + extend ActiveSupport::Concern + + included do + def setup + super + shared_class = self.class.name.gsub(/Test$/, "").classify.constantize + @model = shared_class.new(date: nil) + end + + test "bulk_dates is nil with a date set" do + @model.date = Date.today + assert_nil @model.bulk_dates + end + + test "bulk_dates includes all the days between starts and ends dates" do + @model.bulk_dates_starts_on = Date.today + @model.bulk_dates_ends_on = Date.tomorrow + @model.bulk_dates_weeks_frequency = 1 + @model.bulk_dates_wdays = Array(0..6) + + assert_equal [ Date.today, Date.tomorrow ], @model.bulk_dates + end + + test "bulk_dates includes all the days between starts and ends dates following wdays" do + @model.bulk_dates_starts_on = Date.today.monday + @model.bulk_dates_ends_on = Date.today.sunday + @model.bulk_dates_weeks_frequency = 1 + @model.bulk_dates_wdays = [ 0, 1, 2 ] + + assert_equal [ + Date.today.monday, + Date.today.monday + 1.day, + Date.today.sunday + ], @model.bulk_dates + end + + test "bulk_dates includes all the days between starts and ends dates with frequency" do + @model.bulk_dates_starts_on = Date.today.monday + @model.bulk_dates_ends_on = Date.today.sunday + 1.month + @model.bulk_dates_weeks_frequency = 2 + @model.bulk_dates_wdays = [ 1 ] + + assert_equal [ + Date.today.monday, + Date.today.monday + 2.weeks, + Date.today.monday + 4.weeks + ], @model.bulk_dates + end + + test "save includes all the days between starts and ends dates following wdays" do + travel_to Date.parse("2018-01-01") do + @model.bulk_dates_starts_on = Date.parse("2018-11-05") + @model.bulk_dates_ends_on = Date.parse("2018-11-11") + 1.month + @model.bulk_dates_weeks_frequency = 2 + @model.bulk_dates_wdays = Array(0..6) + + assert_equal 21, @model.bulk_dates.size + assert_difference("#{@model.class.name}.count", 21) do + @model.save + end + end + end + end + end +end diff --git a/test/support/flash_messages_helper.rb b/test/support/flash_messages_helper.rb new file mode 100644 index 000000000..c24b3e3c4 --- /dev/null +++ b/test/support/flash_messages_helper.rb @@ -0,0 +1,13 @@ +module FlashMessagesHelper + def flash_error + find("[aria-label=\"flash error\"]")&.text + end + + def flash_alert + find("[aria-label=\"flash alert\"]")&.text + end + + def flash_notice + find("[aria-label=\"flash notice\"]")&.text + end +end diff --git a/test/support/mail_templates_helper.rb b/test/support/mail_templates_helper.rb new file mode 100644 index 000000000..67b0ffea0 --- /dev/null +++ b/test/support/mail_templates_helper.rb @@ -0,0 +1,11 @@ +module MailTemplatesHelper + def mail_templates(titles) + Array(titles).map do |title| + MailTemplate.create!(title: title) + end + end + + def mail_template(title) + MailTemplate.create!(title: title) + end +end diff --git a/test/support/sessions_helper.rb b/test/support/sessions_helper.rb new file mode 100644 index 000000000..a2faa58d0 --- /dev/null +++ b/test/support/sessions_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SessionsHelper + def login(owner) + session = create_session(owner) + visit "/sessions/#{session.token}" + end + + def create_session(owner, attributes = {}) + session = Session.new( + remote_addr: "127.0.0.1", + user_agent: "a browser user agent") + session.assign_attributes(attributes) + case owner + when Member + session.member_email = owner.emails_array.first + when Admin + session.admin_email = owner.email + end + session.save! + session + end + + def delete_session(owner) + owner.reload.sessions.each(&:delete) + end +end diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/system/members/sessions_test.rb b/test/system/members/sessions_test.rb new file mode 100644 index 000000000..6e29a2be9 --- /dev/null +++ b/test/system/members/sessions_test.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require "application_system_test_case" + +class Members::SessionsTest < ApplicationSystemTestCase + test "creates a new session from email" do + member = members(:john) + + visit "/" + assert_equal "/login", current_path + assert_text "Please log in to access your account." + + fill_in "session_email", with: " john@doe.com " + click_button "Send" + perform_enqueued_jobs + + session = member.sessions.last + + assert_equal "john@doe.com", session.email + assert_equal 1, SessionMailer.deliveries.size + + assert_equal "/login", current_path + assert_text "Thank you! An email has just been sent to you." + + open_email("john@doe.com") + current_email.click_link "Access my account" + + assert_equal "/activity_participations", current_path + assert_text "You are now logged in." + + delete_session(member) + visit "/" + + assert_equal "/login", current_path + assert_text "Please log in to access your account." + end + + test "does not accept blank email" do + visit "/" + assert_equal "/login", current_path + + fill_in "session_email", with: "" + click_button "Send" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "span.error", text: "can't be blank" + end + + test "does not accept invalid email" do + visit "/" + assert_equal "/login", current_path + + fill_in "session_email", with: "foo@bar" + click_button "Send" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "span.error", text: "is invalid" + end + + test "does not accept unknown email" do + visit "/" + assert_equal "/login", current_path + + fill_in "session_email", with: "unknown@member.com" + click_button "Send" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "span.error", text: "Unknown email" + end + + test "does not accept old session when not logged in" do + old_session = create_session(members(:john), created_at: 1.hour.ago) + + visit "/sessions/#{old_session.token}" + + assert_equal "/login", current_path + assert_text "Your login link is no longer valid. Please request a new one." + end + + test "handles old session when already logged in" do + member = members(:john) + login(member) + old_session = create_session(member, created_at: 1.hour.ago) + + visit "/sessions/#{old_session.token}" + + assert_equal "/activity_participations", current_path + assert_text "You are already logged in." + end + + test "logout session without email" do + member = members(:john) + login(member) + member.sessions.last.update!(email: nil) + + visit "/" + + assert_equal "/login", current_path + assert_text "Please log in to access your account." + end + + test "logout expired session" do + member = members(:john) + login(member) + member.sessions.last.update!(created_at: 1.year.ago) + + visit "/" + + assert_equal "/login", current_path + assert_text "Your session has expired, please log in again." + + visit "/" + + assert_equal "/login", current_path + assert_text "Please log in to access your account." + end + + test "update last usage column every hour when using the session" do + member = members(:john) + + travel_to Time.new(2018, 7, 6, 1) do + login(member) + + session = member.sessions.last + assert_equal Time.new(2018, 7, 6, 1), session.last_used_at + assert_equal "127.0.0.1", session.last_remote_addr + assert_equal "Other", session.last_user_agent.to_s + end + + travel_to Time.new(2018, 7, 6, 1, 59) do + visit "/" + assert_equal Time.new(2018, 7, 6, 1), member.sessions.last.last_used_at + end + + travel_to Time.new(2018, 7, 6, 2, 0, 1) do + visit "/" + assert_equal Time.new(2018, 7, 6, 2, 0, 1), member.sessions.last.last_used_at + end + end + + test "revoke session on logout" do + member = members(:john) + login(member) + session = member.sessions.last + + visit "/" + + assert_changes -> { session.reload.revoked_at }, from: nil do + click_button "Logout" + end + + visit "/" + + assert_equal "/login", current_path + assert_text "Please log in to access your account." + end +end diff --git a/test/system/sessions_test.rb b/test/system/sessions_test.rb new file mode 100644 index 000000000..19bacb6c7 --- /dev/null +++ b/test/system/sessions_test.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require "application_system_test_case" + +class SessionsTest < ApplicationSystemTestCase + test "creates a new session from email" do + admin = admins(:super) + + visit "/" + + assert_equal "/login", current_path + assert_equal "Please log in to access your account.", flash_alert + + fill_in "Email", with: " info@csa-admin.org " + click_button "Submit" + perform_enqueued_jobs + + session = admin.sessions.last + + assert_equal "info@csa-admin.org", session.email + assert_equal 1, SessionMailer.deliveries.size + + assert_equal "/login", current_path + assert_equal "Thank you! An email has just been sent to you.", flash_notice + + open_email("info@csa-admin.org") + current_email.click_link "Access my admin account" + + assert_equal "/", current_path + assert_equal "You are now logged in.", flash_notice + + delete_session(admin) + visit "/" + + assert_equal "/login", current_path + assert_equal "Please log in to access your account.", flash_alert + end + + test "does not accept blank email" do + visit "/" + assert_equal "/login", current_path + + fill_in "Email", with: "" + click_button "Submit" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "p.inline-errors", text: "can't be blank" + end + + test "does not accept invalid email" do + visit "/" + assert_equal "/login", current_path + + fill_in "Email", with: "@foo" + click_button "Submit" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "p.inline-errors", text: "is invalid" + end + + test "does not accept unknown email" do + visit "/" + assert_equal "/login", current_path + + fill_in "Email", with: "unknown@admin.com" + click_button "Submit" + perform_enqueued_jobs + + assert_equal 0, SessionMailer.deliveries.size + + assert_equal "/sessions", current_path + assert_selector "p.inline-errors", text: "Unknown email" + end + + test "does not accept old session when not logged in" do + old_session = create_session(admins(:super), created_at: 1.hour.ago) + + visit "/sessions/#{old_session.token}" + + assert_equal "/login", current_path + assert_equal "Your login link is no longer valid. Please request a new one.", flash_alert + end + + test "handles old session when already logged in" do + admin = admins(:super) + login(admin) + old_session = create_session(admin, created_at: 1.hour.ago) + + visit "/sessions/#{old_session.token}" + + assert_equal "/", current_path + assert_equal "You are already logged in.", flash_notice + end + + test "logout session without email" do + admin = admins(:super) + login(admin) + admin.sessions.last.update!(email: nil) + + visit "/" + + assert_equal "/login", current_path + assert_equal "Please log in to access your account.", flash_alert + end + + test "logout expired session" do + admin = admins(:super) + login(admin) + admin.sessions.last.update!(created_at: 1.year.ago) + + visit "/" + + assert_equal "/login", current_path + assert_equal "Your session has expired, please log in again.", flash_alert + + visit "/" + + assert_equal "/login", current_path + assert_equal "Please log in to access your account.", flash_alert + end + + test "update last usage column every hour when using the session" do + admin = admins(:super) + + travel_to Time.new(2018, 7, 6, 1) do + login(admin) + + session = admin.sessions.last + assert_equal Time.new(2018, 7, 6, 1), session.last_used_at + assert_equal "127.0.0.1", session.last_remote_addr + assert_equal "Other", session.last_user_agent.to_s + end + + travel_to Time.new(2018, 7, 6, 1, 59) do + visit "/" + assert_equal Time.new(2018, 7, 6, 1), admin.sessions.last.last_used_at + end + + travel_to Time.new(2018, 7, 6, 2, 0, 1) do + visit "/" + assert_equal Time.new(2018, 7, 6, 2, 0, 1), admin.sessions.last.last_used_at + end + end + + test "revoke session on logout" do + admin = admins(:super) + login(admin) + session = admin.sessions.last + + visit "/" + + assert_changes -> { session.reload.revoked_at }, from: nil do + click_link "Logout" + end + + visit "/" + + assert_equal "/login", current_path + assert_equal "Please log in to access your account.", flash_alert + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 000000000..7da4fd7e3 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +require "support/sessions_helper" +require "support/mail_templates_helper" + +module ActiveSupport + class TestCase + include ActiveJob::TestHelper + + include SessionsHelper + include MailTemplatesHelper + + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + setup do + Tenant.connect("acme") + end + + teardown do + Tenant.disconnect + end + end +end