1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-29 10:24:20 +01:00

Merge branch 'dev' for release 4.5.4

This commit is contained in:
Sylvain 2020-07-29 15:17:18 +02:00
commit 27046492d0
33 changed files with 1111 additions and 36 deletions

View File

@ -1,5 +1,18 @@
# Changelog Fab-manager
## v4.5.4 2020 July 29
- Display an asterisk on the phone input field, in the admin creation form, if the phone is configured as required
- Keep the history of footprints data for verification purposes
- Enhanced rake task to create fixtures for test cases
- Automated tests for exports
- Fix a bug: unable to export reservations
- Fix a bug: unable to export subscriptions
- Fix a bug: unable to receive mails in development
- Fix a security issue: updated json to 2.3.1 to fix [CVE-2020-10663](https://nvd.nist.gov/vuln/detail/CVE-2020-10663)
- [TODO DEPLOY] `rails db:migrate`
- [TODO DEPLOY] `rails fablab:maintenance:save_footprint_data`
## v4.5.3 2020 July 21
- Documentation of the easy upgrade procedure

View File

@ -24,8 +24,7 @@ gem 'jquery-rails'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
gem 'jbuilder_cache_multi'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc # TODO, remove unused ?
gem "json", ">= 2.3.0"
gem 'forgery'
gem 'responders', '~> 2.0'
@ -61,6 +60,7 @@ group :test do
gem 'pdf-reader'
gem 'vcr', '3.0.1'
gem 'webmock'
gem 'rubyXL'
end
group :production, :staging do

View File

@ -206,7 +206,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
json (2.3.1)
jwt (2.2.1)
kaminari (1.2.1)
activesupport (>= 4.1.0)
@ -344,7 +344,6 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
rb-readline (0.5.5)
rdoc (4.3.0)
recurrence (1.3.0)
activesupport
i18n
@ -366,6 +365,9 @@ GEM
ruby-rc4 (0.1.5)
ruby-vips (2.0.17)
ffi (~> 1.9)
rubyXL (3.4.14)
nokogiri (>= 1.10.8)
rubyzip (>= 1.3.0)
rubyzip (1.3.0)
safe_yaml (1.0.5)
sass (3.4.25)
@ -377,9 +379,6 @@ GEM
tilt (>= 1.1, < 3)
sassc (2.2.1)
ffi (~> 1.9)
sdoc (0.4.2)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
seed_dump (3.3.1)
activerecord (>= 4)
activesupport (>= 4)
@ -490,6 +489,7 @@ DEPENDENCIES
jbuilder (~> 2.5)
jbuilder_cache_multi
jquery-rails
json (>= 2.3.0)
kaminari
listen (~> 3.0.5)
message_format
@ -518,9 +518,9 @@ DEPENDENCIES
responders (~> 2.0)
rolify
rubocop (~> 0.61.1)
rubyXL
rubyzip (>= 1.3.0)
sass-rails (~> 5.0, >= 5.0.6)
sdoc (~> 0.4.0)
seed_dump
sha3
sidekiq (>= 6.0.7)

View File

@ -1070,7 +1070,8 @@ Application.Controllers.controller('ImportMembersResultController', ['$scope', '
/**
* Controller used in the admin's creation page (admin view)
*/
Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', function ($state, $scope, Admin, growl, _t) {
Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'Admin', 'growl', '_t', 'phoneRequiredPromise',
function ($state, $scope, Admin, growl, _t, phoneRequiredPromise) {
// default admin profile
let getGender;
$scope.admin = {
@ -1090,6 +1091,9 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
}
};
// is the phone number required in _admin_form?
$scope.phoneRequired = (phoneRequiredPromise.setting.value === 'true');
/**
* Shows the birth day datepicker
* @param $event {Object} jQuery event object

View File

@ -947,6 +947,9 @@ angular.module('application.router', ['ui.router'])
templateUrl: '<%= asset_path "admin/admins/new.html" %>',
controller: 'NewAdminController'
}
},
resolve: {
phoneRequiredPromise: ['Setting', function (Setting) { return Setting.get({ name: 'phone_required' }).$promise; }]
}
})
.state('app.admin.managers_new', {

View File

@ -110,12 +110,13 @@
<div class="form-group" ng-class="{'has-error': adminForm['admin[profile_attributes][phone]'].$dirty && adminForm['admin[profile_attributes][phone]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-phone"></i> </span>
<span class="input-group-addon"><i class="fa fa-phone"></i> <span class="exponent" ng-show="phoneRequired"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input ng-model="admin.profile_attributes.phone"
type="text"
name="admin[profile_attributes][phone]"
class="form-control" id="user_phone"
placeholder="{{ 'app.admin.admins_new.phone_number' | translate }}">
placeholder="{{ 'app.admin.admins_new.phone_number' | translate }}"
ng-required="phoneRequired">
</div>
</div>
</div>

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
# When a footprint is generated, the associated data is kept to allow further verifications
class FootprintDebug < ApplicationRecord
end

View File

@ -12,12 +12,21 @@ class HistoryValue < ApplicationRecord
def chain_record
self.footprint = compute_footprint
save!
FootprintDebug.create!(
footprint: footprint,
data: FootprintService.footprint_data(HistoryValue, self, 'created_at'),
klass: HistoryValue.name
)
end
def check_footprint
footprint == compute_footprint
end
def debug_footprint
FootprintService.debug_footprint(HistoryValue, self)
end
def user
invoicing_profile.user
end

View File

@ -174,12 +174,21 @@ class Invoice < ApplicationRecord
def chain_record
self.footprint = compute_footprint
save!
FootprintDebug.create!(
footprint: footprint,
data: FootprintService.footprint_data(Invoice, self),
klass: Invoice.name
)
end
def check_footprint
invoice_items.map(&:check_footprint).all? && footprint == compute_footprint
end
def debug_footprint
FootprintService.debug_footprint(Invoice, self)
end
def set_wallet_transaction(amount, transaction_id)
raise InvalidFootprintError unless check_footprint

View File

@ -15,12 +15,21 @@ class InvoiceItem < ApplicationRecord
def chain_record
self.footprint = compute_footprint
save!
FootprintDebug.create!(
footprint: footprint,
data: FootprintService.footprint_data(InvoiceItem, self),
klass: InvoiceItem.name
)
end
def check_footprint
footprint == compute_footprint
end
def debug_footprint
FootprintService.debug_footprint(InvoiceItem, self)
end
def amount_after_coupon
# deduct coupon discount
coupon_service = CouponService.new

View File

@ -3,19 +3,47 @@
# Provides helper methods to compute footprints
class FootprintService
# Compute the footprint
# @param class_name Invoice|InvoiceItem|HistoryValue
# @param klass Invoice|InvoiceItem|HistoryValue
# @param item an instance of the provided class
# @param sort the items in database by the provided criterion, to find the previous one
def self.compute_footprint(klass, item, sort_on = 'id')
Checksum.text(FootprintService.footprint_data(klass, item, sort_on))
end
# Return the original data string used to compute the footprint
# @param klass Invoice|InvoiceItem|HistoryValue
# @param item an instance of the provided class
# @param sort the items in database by the provided criterion, to find the previous one
def self.footprint_data(klass, item, sort_on = 'id')
raise TypeError unless item.is_a? klass
previous = klass.where("#{sort_on} < ?", item[sort_on])
.order("#{sort_on} DESC")
.limit(1)
.order("#{sort_on} DESC")
.limit(1)
columns = klass.columns.map(&:name)
.delete_if { |c| %w[footprint updated_at].include? c }
columns = FootprintService.footprint_columns(klass)
Checksum.text("#{columns.map { |c| item[c] }.join}#{previous.first ? previous.first.footprint : ''}")
"#{columns.map { |c| item[c] }.join}#{previous.first ? previous.first.footprint : ''}"
end
# Return an ordered array of the columns used in the footprint computation
# @param klass Invoice|InvoiceItem|HistoryValue
def self.footprint_columns(klass)
klass.columns.map(&:name).delete_if { |c| %w[footprint updated_at].include? c }
end
# Logs a debugging message to help finding why a footprint is invalid
# @param klass Invoice|InvoiceItem|HistoryValue
# @param item an instance of the provided class
def self.debug_footprint(klass, item)
columns = FootprintService.footprint_columns(klass)
current = FootprintService.footprint_data(klass, item)
saved = FootprintDebug.find_by(footprint: item.footprint, klass: klass)
puts "Debug footprint for #{klass} [ id: #{item.id} ]"
puts '-----------------------------------------'
puts "columns: [ #{columns.join(', ')} ]"
puts "current footprint: #{current}"
puts " saved footprint: #{saved&.data}"
puts '-----------------------------------------'
end
end

View File

@ -15,14 +15,14 @@ wb.add_worksheet(name: t('export_reservations.reservations')) do |sheet|
# data rows
@reservations.each do |resrv|
data = [
resrv.user.id,
resrv.user.profile.full_name,
resrv.user.email,
resrv.user&.id,
resrv.user&.profile&.full_name || t('export_reservations.deleted_user'),
resrv.user&.email,
resrv.created_at.to_date,
resrv.reservable_type,
(resrv.reservable.nil? ? '' : resrv.reservable.name),
(resrv.reservable_type == 'Event') ? resrv.total_booked_seats: resrv.slots.count,
(resrv.invoice.paid_with_stripe?) ? t('export_reservations.local_payment') : t('export_reservations.online_payment')
(resrv.invoice&.paid_with_stripe?) ? t('export_reservations.online_payment') : t('export_reservations.local_payment')
]
styles = [nil, nil, nil, date, nil, nil, nil, nil]
types = [:integer, :string, :string, :date, :string, :string, :integer, :string]

View File

@ -15,9 +15,9 @@ wb.add_worksheet(name: t('export_subscriptions.subscriptions')) do |sheet|
# data rows
@subscriptions.each do |sub|
data = [
sub.user.id,
sub.user.profile.full_name,
sub.user.email,
sub.user&.id,
sub.user&.profile&.full_name || t('export_subscriptions.deleted_user'),
sub.user&.email,
sub.plan.human_readable_name(group: true),
t("duration.#{sub.plan.interval}", count: sub.plan.interval_count),
sub.created_at.to_date,

View File

@ -188,6 +188,7 @@ en:
payment_method: "Payment method"
local_payment: "Payment at the reception"
online_payment: "Online payment"
deleted_user: "Deleted user"
#subscriptions list export to EXCEL format
export_subscriptions:
subscriptions: "Subscriptions"
@ -202,6 +203,7 @@ en:
payment_method: "Payment method"
local_payment: "Payment at the reception"
online_payment: "Online payment"
deleted_user: "Deleted user"
#reservation slots export, by type, to EXCEL format
export_availabilities:
machines: "Machines"

View File

@ -188,6 +188,7 @@ es:
payment_method: "Método de pago"
local_payment: "Pago en recepción"
online_payment: "Pago online"
deleted_user: "Usuario eliminado"
#subscriptions list export to EXCEL format
export_subscriptions:
subscriptions: "Suscripciones"
@ -202,6 +203,7 @@ es:
payment_method: "Método de pago"
local_payment: "Pago en recepción"
online_payment: "Pago en línea"
deleted_user: "Usario eliminado"
#reservation slots export, by type, to EXCEL format
export_availabilities:
machines: "Máquinas"

View File

@ -188,6 +188,7 @@ fr:
payment_method: "Méthode de paiement"
local_payment: "Paiement à l'accueil"
online_payment: "Paiement en ligne"
deleted_user: "Utilisateur supprimé"
#subscriptions list export to EXCEL format
export_subscriptions:
subscriptions: "Abonnements"
@ -202,6 +203,7 @@ fr:
payment_method: "Méthode de paiement"
local_payment: "Paiement à l'accueil"
online_payment: "Paiement en ligne"
deleted_user: "Utilisateur supprimé"
#reservation slots export, by type, to EXCEL format
export_availabilities:
machines: "Machines"

View File

@ -188,6 +188,7 @@ pt:
payment_method: "Método de pagamento"
local_payment: "Pagamento na recepção"
online_payment: "Pagamento online"
deleted_user: "Usuário deletado"
#subscriptions list export to EXCEL format
export_subscriptions:
subscriptions: "Assinaturas"
@ -202,6 +203,7 @@ pt:
payment_method: "Método de pagamento"
local_payment: "Pagamento na recepção"
online_payment: "Pagamento online"
deleted_user: "Usuário deletado"
#reservation slots export, by type, to EXCEL format
export_availabilities:
machines: "Máquinas"

View File

@ -188,6 +188,7 @@ zu:
payment_method: "crwdns3501:0crwdne3501:0"
local_payment: "crwdns3503:0crwdne3503:0"
online_payment: "crwdns3505:0crwdne3505:0"
deleted_user: "crwdns20886:0crwdne20886:0"
#subscriptions list export to EXCEL format
export_subscriptions:
subscriptions: "crwdns3507:0crwdne3507:0"
@ -202,6 +203,7 @@ zu:
payment_method: "crwdns3525:0crwdne3525:0"
local_payment: "crwdns3527:0crwdne3527:0"
online_payment: "crwdns3529:0crwdne3529:0"
deleted_user: "crwdns20888:0crwdne20888:0"
#reservation slots export, by type, to EXCEL format
export_availabilities:
machines: "crwdns3531:0crwdne3531:0"

View File

@ -14,6 +14,8 @@ development:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
default_host: <%= ENV["DEFAULT_HOST"] %>
default_protocol: <%= ENV["DEFAULT_PROTOCOL"] %>
smtp_address: <%= ENV["SMTP_ADDRESS"] %>
smtp_port: <%= ENV["SMTP_PORT"] %>
time_zone: <%= ENV["TIME_ZONE"] %>
week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %>
d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> # .dump is needed as the value may start by a '%', see https://github.com/tenderlove/psych/issues/75

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
# This table saves the original data used to create footprints, this allows
# to debug invalid footprints
class CreateFootprintDebugs < ActiveRecord::Migration[5.2]
def change
create_table :footprint_debugs do |t|
t.string :footprint
t.string :data
t.string :klass
t.timestamps
end
end
end

View File

@ -763,6 +763,39 @@ CREATE SEQUENCE public.exports_id_seq
ALTER SEQUENCE public.exports_id_seq OWNED BY public.exports.id;
--
-- Name: footprint_debugs; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.footprint_debugs (
id bigint NOT NULL,
footprint character varying,
data character varying,
klass character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: footprint_debugs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.footprint_debugs_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: footprint_debugs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.footprint_debugs_id_seq OWNED BY public.footprint_debugs.id;
--
-- Name: friendly_id_slugs; Type: TABLE; Schema: public; Owner: -
--
@ -3020,6 +3053,13 @@ ALTER TABLE ONLY public.events_event_themes ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY public.exports ALTER COLUMN id SET DEFAULT nextval('public.exports_id_seq'::regclass);
--
-- Name: footprint_debugs id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.footprint_debugs ALTER COLUMN id SET DEFAULT nextval('public.footprint_debugs_id_seq'::regclass);
--
-- Name: friendly_id_slugs id; Type: DEFAULT; Schema: public; Owner: -
--
@ -3607,6 +3647,14 @@ ALTER TABLE ONLY public.exports
ADD CONSTRAINT exports_pkey PRIMARY KEY (id);
--
-- Name: footprint_debugs footprint_debugs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.footprint_debugs
ADD CONSTRAINT footprint_debugs_pkey PRIMARY KEY (id);
--
-- Name: friendly_id_slugs friendly_id_slugs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -5598,6 +5646,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200622135401'),
('20200623134900'),
('20200623141305'),
('20200629123011');
('20200629123011'),
('20200721162939');

30
lib/tasks/db.rake Normal file
View File

@ -0,0 +1,30 @@
# frozen_string_literal: false
namespace :db do
desc 'Convert development DB to Rails test fixtures'
task :to_fixtures, [:table] => :environment do |_task, args|
TABLES_TO_SKIP = %w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze
begin
ActiveRecord::Base.establish_connection
ActiveRecord::Base.connection.tables.each do |table_name|
next if TABLES_TO_SKIP.include?(table_name)
next if args.table && args.table != table_name
conter = '000'
file_path = "#{Rails.root}/test/fixtures/test/#{table_name}.yml"
File.open(file_path, 'w') do |file|
rows = ActiveRecord::Base.connection.select_all("SELECT * FROM #{table_name}")
data = rows.each_with_object({}) do |record, hash|
suffix = record['id'].blank? ? conter.succ! : record['id']
hash["#{table_name.singularize}_#{suffix}"] = record
end
puts "Writing table '#{table_name}' to '#{file_path}'"
file.write(data.to_yaml)
end
end
ensure
ActiveRecord::Base.connection&.close
end
end
end

View File

@ -35,15 +35,6 @@ namespace :fablab do
end
end
desc 'generate fixtures from db'
task generate_fixtures: :environment do
Rails.application.eager_load!
ActiveRecord::Base.descendants.reject { |c| [ActiveRecord::SchemaMigration, PartnerPlan].include? c }.each do |ar_base|
p "========== #{ar_base} =============="
ar_base.dump_fixtures
end
end
desc 'generate current code checksum'
task checksum: :environment do
require 'checksum'
@ -91,5 +82,18 @@ namespace :fablab do
Sidekiq::Queue.new('default').clear
Sidekiq::DeadSet.new.clear
end
desc 'save the footprint original data'
task save_footprint_data: :environment do
[Invoice, InvoiceItem, HistoryValue].each do |klass|
klass.all.each do |item|
FootprintDebug.create!(
footprint: item.footprint,
data: FootprintService.footprint_data(klass, item, klass == 'HistoryValue' ? 'created_at' : 'id'),
klass: klass
)
end
end
end
end
end

View File

@ -1,6 +1,6 @@
{
"name": "fab-manager",
"version": "4.5.3",
"version": "4.5.4",
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
"keywords": [
"fablab",

10
test/fixtures/README.md vendored Normal file
View File

@ -0,0 +1,10 @@
# Test fixtures
Fixtures are test data.
Every time a new test is run, the database is filled with these data.
You can create fixtures manually or using the following task, to dump your current table/database to the YAML fixture files:
```bash
rails db:to_fixtures[table]
```
The parameter `table` is optional. If not specified, the whole database will be dumped.

645
test/fixtures/footprint_debugs.yml vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -19,3 +19,10 @@ trainings_availability_3:
availability_id: 8
created_at: 2016-04-04 15:26:49.574308000 Z
updated_at: 2016-04-04 15:26:49.574308000 Z
trainings_availability_4:
id: 4
training_id: 3
availability_id: 12
created_at: 2020-07-22 10:09:41.841162000 Z
updated_at: 2020-07-22 10:09:41.841162000 Z

View File

@ -30,6 +30,7 @@ user_2:
uid:
auth_token:
merged_at:
is_allow_newsletter: true
user_4:
id: 4
@ -62,6 +63,7 @@ user_4:
uid:
auth_token:
merged_at:
is_allow_newsletter: false
user_6:
id: 6
@ -94,6 +96,7 @@ user_6:
uid:
auth_token:
merged_at:
is_allow_newsletter: true
user_5:
id: 5
@ -126,6 +129,7 @@ user_5:
uid:
auth_token:
merged_at:
is_allow_newsletter: true
user_3:
id: 3
@ -158,6 +162,7 @@ user_3:
uid:
auth_token:
merged_at:
is_allow_newsletter: false
user_1:
id: 1
@ -190,6 +195,7 @@ user_1:
uid:
auth_token:
merged_at:
is_allow_newsletter: true
user_7:
@ -223,3 +229,4 @@ user_7:
uid:
auth_token:
merged_at:
is_allow_newsletter: false

View File

@ -8,7 +8,7 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest
login_as(admin, scope: :user)
end
test 'creation modification reservation and re-modification scenario' do
test 'export accounting period to ACD software' do
# First, we create a new export
post '/api/accounting/export',
params: {

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'test_helper'
require 'rubyXL'
module Exports; end
class Exports::AvailabilitiesExportTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'export availabilities to Excel' do
# First, we create a new export
get '/api/availabilities/export_index.xlsx'
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime[:json], response.content_type
# Check the export was created correctly
res = json_response(response.body)
e = Export.where(id: res[:export_id]).first
assert_not_nil e, 'Export was not created in database'
# Run the worker
worker = AvailabilitiesExportWorker.new
worker.perform(e.id)
# notification
assert_not_empty Notification.where(attached_object: e)
# resulting XLSX file
assert FileTest.exist?(e.file), 'XLSX file was not generated'
workbook = RubyXL::Parser.parse(e.file)
# test worksheets
assert_not_nil workbook[I18n.t('export_availabilities.machines')]
assert_not_nil workbook[I18n.t('export_availabilities.trainings')]
assert_not_nil workbook[I18n.t('export_availabilities.events')]
if Setting.get('spaces_module')
assert_not_nil workbook[I18n.t('export_availabilities.spaces')]
else
assert_nil workbook[I18n.t('export_availabilities.spaces')]
end
# test data
availability = Availability.find(13)
machines = workbook[I18n.t('export_availabilities.machines')]
assert_equal availability.start_at.to_date, machines.sheet_data[1][0].value.to_date
assert_equal I18n.l(availability.start_at, format: '%A').capitalize, machines.sheet_data[1][1].value
assert_match(/^#{availability.start_at.strftime('%H:%M')} - /, machines.sheet_data[1][2].value)
assert_includes availability.machines.map(&:name), machines.sheet_data[1][3].value
# Clean XLSX file
require 'fileutils'
FileUtils.rm(e.file)
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'test_helper'
require 'rubyXL'
module Exports; end
class Exports::MembersExportTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'export members to Excel' do
# First, we create a new export
get '/api/members/export_members.xlsx'
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime[:json], response.content_type
# Check the export was created correctly
res = json_response(response.body)
e = Export.where(id: res[:export_id]).first
assert_not_nil e, 'Export was not created in database'
# Run the worker
worker = UsersExportWorker.new
worker.perform(e.id)
# notification
assert_not_empty Notification.where(attached_object: e)
# resulting XLSX file
assert FileTest.exist?(e.file), 'XLSX file was not generated'
workbook = RubyXL::Parser.parse(e.file)
# test worksheet
assert_not_nil workbook[I18n.t('export_members.members')]
# test data
member = User.members.first
wb = workbook[I18n.t('export_members.members')]
assert_equal member.id, wb.sheet_data[1][0].value
assert_equal (member.is_allow_newsletter ? 1 : nil), wb.sheet_data[1][4].value
# Clean XLSX file
require 'fileutils'
FileUtils.rm(e.file)
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'test_helper'
require 'rubyXL'
module Exports; end
class Exports::ReservationsExportTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'export reservations to Excel' do
# First, we create a new export
get '/api/members/export_reservations.xlsx'
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime[:json], response.content_type
# Check the export was created correctly
res = json_response(response.body)
e = Export.where(id: res[:export_id]).first
assert_not_nil e, 'Export was not created in database'
# Run the worker
worker = UsersExportWorker.new
worker.perform(e.id)
# notification
assert_not_empty Notification.where(attached_object: e)
# resulting XLSX file
assert FileTest.exist?(e.file), 'XLSX file was not generated'
workbook = RubyXL::Parser.parse(e.file)
# test worksheet
assert_not_nil workbook[I18n.t('export_reservations.reservations')]
# test data
reservation = Reservation.find(1)
wb = workbook[I18n.t('export_reservations.reservations')]
assert_equal reservation.user.id, wb.sheet_data[1][0].value
assert_equal reservation.created_at.to_date, wb.sheet_data[1][3].value.to_date
assert_equal reservation.reservable_type, wb.sheet_data[1][4].value
# Clean XLSX file
require 'fileutils'
FileUtils.rm(e.file)
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'test_helper'
require 'rubyXL'
module Exports; end
class Exports::SubscriptionsExportTest < ActionDispatch::IntegrationTest
setup do
admin = User.with_role(:admin).first
login_as(admin, scope: :user)
end
test 'export subscriptions to Excel' do
# First, we create a new export
get '/api/members/export_subscriptions.xlsx'
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime[:json], response.content_type
# Check the export was created correctly
res = json_response(response.body)
e = Export.where(id: res[:export_id]).first
assert_not_nil e, 'Export was not created in database'
# Run the worker
worker = UsersExportWorker.new
worker.perform(e.id)
# notification
assert_not_empty Notification.where(attached_object: e)
# resulting XLSX file
assert FileTest.exist?(e.file), 'XLSX file was not generated'
workbook = RubyXL::Parser.parse(e.file)
# test worksheet
assert_not_nil workbook[I18n.t('export_subscriptions.subscriptions')]
# test data
subscription = Subscription.find(1)
wb = workbook[I18n.t('export_subscriptions.subscriptions')]
assert_equal subscription.user.id, wb.sheet_data[1][0].value
assert_equal subscription.plan.human_readable_name(group: true), wb.sheet_data[1][3].value
assert_equal subscription.created_at.to_date, wb.sheet_data[1][5].value.to_date
# Clean XLSX file
require 'fileutils'
FileUtils.rm(e.file)
end
end