From d7591d46b9a3c8f512969baa33010bf482693cb3 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Mon, 28 Nov 2022 11:30:38 +0100 Subject: [PATCH] Add controller tests Co-authored-by: viehlieb Co-authored-by: Tobias Kneuker seperate expects refactor login user calls add more articles to test sorting with fix: fix test for rails upgrade --- .../application_controller_spec.rb | 12 + spec/controllers/articles_controller_spec.rb | 348 ++++++++++++++++++ .../controllers/concerns/auth_concern_spec.rb | 212 +++++++++++ .../finance/balancing_controller_spec.rb | 211 +++++++++++ .../finance/base_controller_spec.rb | 30 ++ spec/controllers/home_controller_spec.rb | 22 +- spec/controllers/login_controller_spec.rb | 67 ++++ spec/factories/invite.rb | 15 + spec/factories/order_article.rb | 8 + spec/fixtures/files/upload_test.csv | 3 + spec/fixtures/upload_test.csv | 3 + spec/models/supplier_spec.rb | 8 +- spec/support/spec_test_helper.rb | 9 +- 13 files changed, 932 insertions(+), 16 deletions(-) create mode 100644 spec/controllers/application_controller_spec.rb create mode 100644 spec/controllers/articles_controller_spec.rb create mode 100644 spec/controllers/concerns/auth_concern_spec.rb create mode 100644 spec/controllers/finance/balancing_controller_spec.rb create mode 100644 spec/controllers/finance/base_controller_spec.rb create mode 100644 spec/controllers/login_controller_spec.rb create mode 100644 spec/factories/invite.rb create mode 100644 spec/factories/order_article.rb create mode 100644 spec/fixtures/files/upload_test.csv create mode 100644 spec/fixtures/upload_test.csv diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb new file mode 100644 index 00000000..35db7574 --- /dev/null +++ b/spec/controllers/application_controller_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ApplicationController, type: :controller do + describe 'current' do + it 'returns current ApplicationController' do + described_class.new.send(:store_controller) + expect(described_class.current).to be_instance_of described_class + end + end +end diff --git a/spec/controllers/articles_controller_spec.rb b/spec/controllers/articles_controller_spec.rb new file mode 100644 index 00000000..dae89c70 --- /dev/null +++ b/spec/controllers/articles_controller_spec.rb @@ -0,0 +1,348 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ArticlesController, type: :controller do + let(:user) { create :user, :role_article_meta } + let(:article_category_a) { create :article_category, name: "AAAA" } + let(:article_category_b) { create :article_category, name: "BBBB" } + let(:article_category_c) { create :article_category, name: "CCCC" } + let(:supplier) { create :supplier} + let(:article_a) { create :article, name: 'AAAA', note: "ZZZZ", unit: '750 g', article_category: article_category_b, availability: false, supplier_id: supplier.id } + let(:article_b) { create :article, name: 'BBBB', note: "XXXX", unit: '500 g', article_category: article_category_a, availability: true, supplier_id: supplier.id } + let(:article_c) { create :article, name: 'CCCC', note: "YYYY", unit: '250 g', article_category: article_category_c, availability: true, supplier_id: supplier.id } + let(:article_no_supplier) { create :article, name: 'no_supplier', note: "no_supplier", unit: '100 g', article_category: article_category_b, availability: true } + + let(:order) { create :order } + let(:order2) { create :order } + + def get_with_supplier(action, params: {}, xhr: false, format: nil) + params['supplier_id'] = supplier.id + get_with_defaults(action, params: params, xhr: xhr, format: format) + end + + def post_with_supplier(action, params: {}, xhr: false, format: nil) + params['supplier_id'] = supplier.id + post_with_defaults(action, params: params, xhr: xhr, format: format) + end + + before { login user } + + describe 'GET index' do + before do + supplier + article_a + article_b + article_c + supplier.reload + end + it 'assigns sorting on articles' do + sortings = [ + ['name', [article_a, article_b, article_c]], + ['name_reverse', [article_c, article_b, article_a]], + ['note', [article_b, article_c, article_a]], + ['note_reverse', [article_a, article_c, article_b]], + ['unit', [article_c, article_b, article_a]], + ['unit_reverse', [article_a, article_b, article_c]], + ['article_category', [article_b, article_a, article_c]], + ['article_category_reverse', [article_c, article_a, article_b]], + ['availability', [article_a, article_b, article_c]], + ['availability_reverse', [article_b, article_c, article_a]] + ] + sortings.each do |sorting| + get_with_supplier :index, params: { sort: sorting[0] } + expect(response).to have_http_status(:success) + expect(assigns(:articles).to_a).to eq(sorting[1]) + end + end + + it 'triggers an article csv' do + get_with_supplier :index, format: :csv + expect(response.header['Content-Type']).to include('text/csv') + expect(response.body).to include(article_a.unit, article_b.unit) + end + end + + describe 'new' do + it 'renders form for a new article' do + get_with_supplier :new, xhr: true + expect(response).to have_http_status(:success) + end + end + + describe 'copy' do + it 'renders form with copy of an article' do + get_with_supplier :copy, params: { article_id: article_a.id }, xhr: true + expect(assigns(:article).attributes).to eq(article_a.dup.attributes) + expect(response).to have_http_status(:success) + end + end + + describe '#create' do + it 'creates a new article' do + valid_attributes = article_a.attributes.except('id') + valid_attributes['name'] = 'ABAB' + get_with_supplier :create, params: { article: valid_attributes }, xhr: true + expect(response).to have_http_status(:success) + end + + it 'fails to create a new article and renders #new' do + get_with_supplier :create, params: { article: { id: nil } }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/new') + end + end + + describe 'edit' do + it 'opens form to edit article attributes' do + get_with_supplier :edit, params: { id: article_a.id }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/new') + end + end + + describe '#edit all' do + it 'renders edit_all' do + get_with_supplier :edit_all, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/edit_all') + end + end + + describe '#update' do + it 'updates article attributes' do + get_with_supplier :update, params: { id: article_a.id, article: { unit: '300 g' } }, xhr: true + expect(assigns(:article).unit).to eq('300 g') + expect(response).to have_http_status(:success) + end + + it 'updates article with empty name attribute' do + get_with_supplier :update, params: { id: article_a.id, article: { name: nil } }, xhr: true + expect(response).to render_template('articles/new') + end + end + + describe '#update_all' do + it 'updates all articles' do + get_with_supplier :update_all, params: { articles: { "#{article_a.id}": attributes_for(:article), "#{article_b.id}": attributes_for(:article) } } + expect(response).to have_http_status(:redirect) + end + + it 'fails on updating all articles' do + get_with_supplier :update_all, params: { articles: { "#{article_a.id}": attributes_for(:article, name: 'ab') } } + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/edit_all') + end + end + + describe '#update_selected' do + let(:order_article) { create :order_article, order: order, article: article_no_supplier } + + before do + order_article + end + + it 'updates selected articles' do + get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id] } + expect(response).to have_http_status(:redirect) + end + + it 'destroys selected articles' do + get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'destroy' } + article_a.reload + article_b.reload + expect(article_a).to be_deleted + expect(article_b).to be_deleted + expect(response).to have_http_status(:redirect) + end + + it 'sets availability false on selected articles' do + get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'setNotAvailable' } + article_a.reload + article_b.reload + expect(article_a).not_to be_availability + expect(article_b).not_to be_availability + expect(response).to have_http_status(:redirect) + end + + it 'sets availability true on selected articles' do + get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'setAvailable' } + article_a.reload + article_b.reload + expect(article_a).to be_availability + expect(article_b).to be_availability + expect(response).to have_http_status(:redirect) + end + + it 'fails deletion if one article is in open order' do + get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_no_supplier.id], selected_action: 'destroy' } + article_a.reload + article_no_supplier.reload + expect(article_a).not_to be_deleted + expect(article_no_supplier).not_to be_deleted + expect(response).to have_http_status(:redirect) + end + end + + describe '#parse_upload' do + let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/upload_test.csv'), original_filename: 'upload_test.csv') } + + it 'updates particles from spreadsheet' do + post_with_supplier :parse_upload, params: { articles: { file: file, outlist_absent: '1', convert_units: '1' } } + expect(response).to have_http_status(:success) + end + + it 'missing file not updates particles from spreadsheet' do + post_with_supplier :parse_upload, params: { articles: { file: nil, outlist_absent: '1', convert_units: '1' } } + expect(response).to have_http_status(:redirect) + expect(flash[:alert]).to match(I18n.t('errors.general_msg', msg: "undefined method `original_filename' for \"\":String").to_s) + end + end + + describe '#sync' do + # TODO: double render error in controller + it 'throws double render error' do + expect do + post :sync, params: { foodcoop: FoodsoftConfig[:default_scope], supplier_id: supplier.id } + end.to raise_error(AbstractController::DoubleRenderError) + end + + xit 'updates particles from spreadsheet' do + post :sync, params: { foodcoop: FoodsoftConfig[:default_scope], supplier_id: supplier.id, articles: { '#{article_a.id}': attributes_for(:article), '#{article_b.id}': attributes_for(:article) } } + expect(response).to have_http_status(:redirect) + end + end + + describe '#destroy' do + let(:order_article) { create :order_article, order: order, article: article_no_supplier } + + before do + order_article + end + + it 'does not delete article if order open' do + get_with_supplier :destroy, params: { id: article_no_supplier.id }, xhr: true + expect(assigns(:article)).not_to be_deleted + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/destroy') + end + + it 'deletes article if order closed' do + get_with_supplier :destroy, params: { id: article_b.id }, xhr: true + expect(assigns(:article)).to be_deleted + expect(response).to have_http_status(:success) + expect(response).to render_template('articles/destroy') + end + end + + describe '#update_synchronized' do + let(:order_article) { create :order_article, order: order, article: article_no_supplier } + + before do + order_article + article_a + article_b + article_no_supplier + end + + it 'deletes articles' do + # TODO: double render error in controller + get_with_supplier :update_synchronized, params: { outlisted_articles: { article_a.id => article_a, article_b.id => article_b } } + article_a.reload + article_b.reload + expect(article_a).to be_deleted + expect(article_b).to be_deleted + expect(response).to have_http_status(:redirect) + end + + it 'updates articles' do + get_with_supplier :update_synchronized, params: { articles: { article_a.id => { name: 'NewNameA' }, article_b.id => { name: 'NewNameB' } } } + expect(assigns(:updated_articles).first.name).to eq 'NewNameA' + expect(response).to have_http_status(:redirect) + end + + it 'does not update articles if article with same name exists' do + get_with_supplier :update_synchronized, params: { articles: { article_a.id => { unit: '2000 g' }, article_b.id => { name: 'AAAA' } } } + error_array = [assigns(:updated_articles).first.errors.first, assigns(:updated_articles).last.errors.first] + expect(error_array).to include([:name, 'name is already taken']) + expect(response).to have_http_status(:success) + end + + it 'does update articles if article with same name was deleted before' do + get_with_supplier :update_synchronized, params: { + outlisted_articles: { article_a.id => article_a }, + articles: { + article_a.id => { name: 'NewName' }, + article_b.id => { name: 'AAAA' } + } + } + error_array = [assigns(:updated_articles).first.errors.first, assigns(:updated_articles).last.errors.first] + expect(error_array).not_to be_any + expect(response).to have_http_status(:redirect) + end + + it 'does not delete articles in open order' do + get_with_supplier :update_synchronized, params: { outlisted_articles: { article_no_supplier.id => article_no_supplier } } + article_no_supplier.reload + expect(article_no_supplier).not_to be_deleted + expect(response).to have_http_status(:success) + end + + it 'assigns updated article_pairs on error' do + get_with_supplier :update_synchronized, params: { + articles: { article_a.id => { name: 'EEEE' } }, + outlisted_articles: { article_no_supplier.id => article_no_supplier } + } + expect(assigns(:updated_article_pairs).first).to eq([article_a, { name: 'EEEE' }]) + article_no_supplier.reload + expect(article_no_supplier).not_to be_deleted + expect(response).to have_http_status(:success) + end + + it 'updates articles in open order' do + get_with_supplier :update_synchronized, params: { articles: { article_no_supplier.id => { name: 'EEEE' } } } + article_no_supplier.reload + expect(article_no_supplier.name).to eq 'EEEE' + expect(response).to have_http_status(:redirect) + end + end + + describe '#shared' do + let(:shared_supplier) { create :shared_supplier, shared_articles: [shared_article] } + let(:shared_article) { create :shared_article, name: 'shared' } + let(:article_s) { create :article, name: 'SSSS', note: 'AAAA', unit: '250 g', article_category: article_category_a, availability: false } + + let(:supplier_with_shared) { create :supplier, shared_supplier: shared_supplier } + + it 'renders view with articles' do + get_with_defaults :shared, params: { supplier_id: supplier_with_shared.id, name_cont_all_joined: 'shared' }, xhr: true + expect(assigns(:supplier).shared_supplier.shared_articles).to be_any + expect(assigns(:articles)).to be_any + expect(response).to have_http_status(:success) + end + end + + describe '#import' do + let(:shared_supplier) { create :shared_supplier, shared_articles: [shared_article] } + let(:shared_article) { create :shared_article, name: 'shared' } + + before do + shared_article + article_category_a + end + + it 'fills form with article details' do + get_with_supplier :import, params: { article_category_id: article_category_b.id, direct: 'true', shared_article_id: shared_article.id }, xhr: true + expect(assigns(:article)).not_to be_nil + expect(response).to have_http_status(:success) + expect(response).to render_template(:create) + end + + it 'does redirect to :new if param :direct not set' do + get_with_supplier :import, params: { article_category_id: article_category_b.id, shared_article_id: shared_article.id }, xhr: true + expect(assigns(:article)).not_to be_nil + expect(response).to have_http_status(:success) + expect(response).to render_template(:new) + end + end +end diff --git a/spec/controllers/concerns/auth_concern_spec.rb b/spec/controllers/concerns/auth_concern_spec.rb new file mode 100644 index 00000000..10bf8ec7 --- /dev/null +++ b/spec/controllers/concerns/auth_concern_spec.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require 'spec_helper' + +class DummyAuthController < ApplicationController; end + +describe 'Auth concern', type: :controller do + controller DummyAuthController do + # Defining a dummy action for an anynomous controller which inherits from the described class. + def authenticate_blank + authenticate + end + + def authenticate_unknown_group + authenticate('nooby') + end + + def authenticate_pickups + authenticate('pickups') + head :ok unless performed? + end + + def authenticate_finance_or_orders + authenticate('finance_or_orders') + head :ok unless performed? + end + + def try_authenticate_membership_or_admin + authenticate_membership_or_admin + end + + def try_authenticate_or_token + authenticate_or_token('xyz') + head :ok unless performed? + end + + def call_deny_access + deny_access + end + + def call_current_user + current_user + end + + def call_login_and_redirect_to_return_to + user = User.find(params[:user_id]) + login_and_redirect_to_return_to(user) + end + + def call_login + user = User.find(params[:user_id]) + login(user) + end + end + + # unit testing protected/private methods + describe 'protected/private methods' do + let(:user) { create :user } + let(:wrong_user) { create :user } + + describe '#current_user' do + before do + login user + routes.draw { get 'call_current_user' => 'dummy_auth#call_current_user' } + end + + describe 'with valid session' do + it 'returns current_user' do + get_with_defaults :call_current_user, params: { user_id: user.id }, format: JSON + expect(assigns(:current_user)).to eq user + end + end + + describe 'with invalid session' do + it 'not returns current_user' do + session[:user_id] = nil + get_with_defaults :call_current_user, params: { user_id: nil }, format: JSON + expect(assigns(:current_user)).to be_nil + end + end + end + + describe '#deny_access' do + it 'redirects to root_url' do + login user + routes.draw { get 'deny_access' => 'dummy_auth#call_deny_access' } + get_with_defaults :call_deny_access + expect(response).to redirect_to(root_url) + end + end + + describe '#login' do + before do + routes.draw { get 'call_login' => 'dummy_auth#call_login' } + end + + it 'sets user in session' do + login wrong_user + get_with_defaults :call_login, params: { user_id: user.id }, format: JSON + expect(session[:user_id]).to eq user.id + expect(session[:scope]).to eq FoodsoftConfig.scope + expect(session[:locale]).to eq user.locale + end + end + + describe '#login_and_redirect_to_return_to' do + it 'redirects to already set target' do + login user + session[:return_to] = my_profile_url + routes.draw { get 'call_login_and_redirect_to_return_to' => 'dummy_auth#call_login_and_redirect_to_return_to' } + get_with_defaults :call_login_and_redirect_to_return_to, params: { user_id: user.id } + expect(session[:return_to]).to be_nil + end + end + end + + describe 'authenticate' do + describe 'not logged in' do + it 'does not authenticate' do + routes.draw { get 'authenticate_blank' => 'dummy_auth#authenticate_blank' } + get_with_defaults :authenticate_blank + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(login_path) + expect(flash[:alert]).to match(I18n.t('application.controller.error_authn')) + end + end + + describe 'logged in' do + let(:user) { create :user } + let(:pickups_user) { create :user, :role_pickups } + let(:finance_user) { create :user, :role_finance } + let(:orders_user) { create :user, :role_orders } + + it 'does not authenticate with unknown group' do + login user + routes.draw { get 'authenticate_unknown_group' => 'dummy_auth#authenticate_unknown_group' } + get_with_defaults :authenticate_unknown_group + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to match(I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(I18n.t('application.controller.error_denied_sign_in'), login_path))) + end + + it 'does not authenticate with pickups group' do + login pickups_user + routes.draw { get 'authenticate_pickups' => 'dummy_auth#authenticate_pickups' } + get_with_defaults :authenticate_pickups + expect(response).to have_http_status(:success) + end + + it 'does not authenticate with finance group' do + login finance_user + routes.draw { get 'authenticate_finance_or_orders' => 'dummy_auth#authenticate_finance_or_orders' } + get_with_defaults :authenticate_finance_or_orders + expect(response).to have_http_status(:success) + end + + it 'does not authenticate with orders group' do + login orders_user + routes.draw { get 'authenticate_finance_or_orders' => 'dummy_auth#authenticate_finance_or_orders' } + get_with_defaults :authenticate_finance_or_orders + expect(response).to have_http_status(:success) + end + end + end + + describe 'authenticate_membership_or_admin' do + describe 'logged in' do + let(:pickups_user) { create :user, :role_pickups } + let(:workgroup) { create :workgroup } + + it 'redirects with not permitted group' do + group_id = workgroup.id + login pickups_user + routes.draw { get 'try_authenticate_membership_or_admin' => 'dummy_auth#try_authenticate_membership_or_admin' } + get_with_defaults :try_authenticate_membership_or_admin, params: { id: group_id } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to match(I18n.t('application.controller.error_members_only')) + end + end + end + + describe 'authenticate_or_token' do + describe 'logged in' do + let(:token_verifier) { TokenVerifier.new('xyz') } + let(:token_msg) { token_verifier.generate } + let(:user) { create :user } + + before { login user } + + it 'authenticates token' do + routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' } + get_with_defaults :try_authenticate_or_token, params: { token: token_msg } + expect(response).not_to have_http_status(:redirect) + end + + it 'redirects on faulty token' do + routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' } + get_with_defaults :try_authenticate_or_token, params: { token: 'abc' } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to match(I18n.t('application.controller.error_token')) + end + + it 'authenticates current user on empty token' do + routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' } + get_with_defaults :try_authenticate_or_token + expect(response).to have_http_status(:success) + end + end + end +end diff --git a/spec/controllers/finance/balancing_controller_spec.rb b/spec/controllers/finance/balancing_controller_spec.rb new file mode 100644 index 00000000..d62b9974 --- /dev/null +++ b/spec/controllers/finance/balancing_controller_spec.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Finance::BalancingController, type: :controller do + let(:user) { create :user, :role_finance, :role_orders, groups: [create(:ordergroup)] } + + before { login user } + + describe 'GET index' do + let(:order) { create :order } + + it 'renders index page' do + get_with_defaults :index + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/index') + end + end + + describe 'new balancing' do + let(:supplier) { create :supplier } + let(:article1) { create :article, name: 'AAAA', supplier: supplier, unit_quantity: 1 } + let(:article2) { create :article, name: 'AAAB', supplier: supplier, unit_quantity: 1 } + + let(:order) { create :order, supplier: supplier, article_ids: [article1.id, article2.id] } + + let(:go1) { create :group_order, order: order } + let(:go2) { create :group_order, order: order } + let(:oa1) { order.order_articles.find_by_article_id(article1.id) } + let(:oa2) { order.order_articles.find_by_article_id(article2.id) } + let(:oa3) { order2.order_articles.find_by_article_id(article2.id) } + let(:goa1) { create :group_order_article, group_order: go1, order_article: oa1 } + let(:goa2) { create :group_order_article, group_order: go1, order_article: oa2 } + + before do + goa1.update_quantities(3, 0) + goa2.update_quantities(1, 0) + oa1.update_results! + oa2.update_results! + end + + it 'renders new order page' do + get_with_defaults :new, params: { order_id: order.id } + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/new') + end + + it 'assigns sorting on articles' do + sortings = [ + ['name', [oa1, oa2]], + ['name_reverse', [oa2, oa1]], + ['order_number', [oa1, oa2]], + ['order_number_reverse', [oa1, oa2]] # just one order + ] + sortings.each do |sorting| + get_with_defaults :new, params: { order_id: order.id, sort: sorting[0] } + expect(response).to have_http_status(:success) + expect(assigns(:articles).to_a).to eq(sorting[1]) + end + end + end + + describe 'update summary' do + let(:order) { create(:order) } + + it 'shows the summary view' do + get_with_defaults :update_summary, params: { id: order.id }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/update_summary') + end + end + + describe 'new_on_order' do + let(:order) { create(:order) } + let(:order_article) { order.order_articles.first } + + it 'calls article update' do + get_with_defaults :new_on_order_article_update, params: { id: order.id, order_article_id: order_article.id }, xhr: true + expect(response).not_to render_template(layout: 'application') + expect(response).to render_template('finance/balancing/new_on_order_article_update') + end + + it 'calls article create' do + get_with_defaults :new_on_order_article_create, params: { id: order.id, order_article_id: order_article.id }, xhr: true + expect(response).not_to render_template(layout: 'application') + expect(response).to render_template('finance/balancing/new_on_order_article_create') + end + end + + describe 'edit_note' do + let(:order) { create(:order) } + + it 'updates order note' do + get_with_defaults :edit_note, params: { id: order.id, order: { note: 'Hello' } }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/edit_note') + end + end + + describe 'update_note' do + let(:order) { create(:order) } + + it 'updates order note' do + get_with_defaults :update_note, params: { id: order.id, order: { note: 'Hello' } }, xhr: true + expect(response).to have_http_status(:success) + end + + it 'redirects to edit note on failed update' do + get_with_defaults :update_note, params: { id: order.id, order: { article_ids: nil } }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/edit_note') + end + end + + describe 'transport' do + let(:order) { create(:order) } + + it 'calls the edit transport view' do + get_with_defaults :edit_transport, params: { id: order.id }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/edit_transport') + end + + it 'does redirect if order valid' do + get_with_defaults :update_transport, params: { id: order.id, order: { ends: Time.now } }, xhr: true + expect(response).to have_http_status(:redirect) + expect(assigns(:order).errors.count).to eq(0) + expect(response).to redirect_to(new_finance_order_path(order_id: order.id)) + end + + it 'does redirect if order invalid' do + get_with_defaults :update_transport, params: { id: order.id, order: { starts: Time.now + 2, ends: Time.now } }, xhr: true + expect(assigns(:order).errors.count).to eq(1) + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(new_finance_order_path(order_id: order.id)) + end + end + + describe 'confirm' do + let(:order) { create(:order) } + + it 'renders the confirm template' do + get_with_defaults :confirm, params: { id: order.id }, xhr: true + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/balancing/confirm') + end + end + + describe 'close and update account balances' do + let(:order) { create(:order) } + let(:order1) { create(:order, ends: Time.now) } + let(:fft) { create(:financial_transaction_type) } + + it 'does not close order if ends not set' do + get_with_defaults :close, params: { id: order.id, type: fft.id } + expect(assigns(:order)).not_to be_closed + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(new_finance_order_url(order_id: order.id)) + end + + it 'closes order' do + get_with_defaults :close, params: { id: order1.id, type: fft.id } + expect(assigns(:order)).to be_closed + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(finance_order_index_url) + end + end + + describe 'close direct' do + let(:order) { create(:order) } + + it 'does not close order if already closed' do + order.close_direct!(user) + get_with_defaults :close_direct, params: { id: order.id } + expect(assigns(:order)).to be_closed + end + + it 'closes order directly' do + get_with_defaults :close_direct, params: { id: order.id } + expect(assigns(:order)).to be_closed + end + end + + describe 'close all direct' do + let(:invoice) { create(:invoice) } + let(:invoice1) { create(:invoice) } + let(:order) { create(:order, state: 'finished', ends: Time.now + 2.hours, invoice: invoice) } + let(:order1) { create(:order, state: 'finished', ends: Time.now + 2.hours) } + + before do + order + order1 + end + + it 'does close orders' do + get_with_defaults :close_all_direct_with_invoice + order.reload + expect(order).to be_closed + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(finance_order_index_url) + end + + it 'does not close orders when invoice not set' do + get_with_defaults :close_all_direct_with_invoice + order1.reload + expect(order1).not_to be_closed + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(finance_order_index_url) + end + end +end diff --git a/spec/controllers/finance/base_controller_spec.rb b/spec/controllers/finance/base_controller_spec.rb new file mode 100644 index 00000000..388f3a17 --- /dev/null +++ b/spec/controllers/finance/base_controller_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Finance::BaseController, type: :controller do + let(:user) { create :user, :role_finance, :role_orders, :ordergroup } + + before { login user } + + describe 'GET index' do + let(:fin_trans) { create_list :financial_transaction, 3, user: user, ordergroup: user.ordergroup } + let(:orders) { create_list :order, 2, state: 'finished' } + let(:invoices) { create_list :invoice, 4 } + + before do + fin_trans + orders + invoices + end + + it 'renders index page' do + get_with_defaults :index + expect(response).to have_http_status(:success) + expect(response).to render_template('finance/index') + expect(assigns(:financial_transactions).size).to eq(fin_trans.size) + expect(assigns(:orders).size).to eq(orders.size) + expect(assigns(:unpaid_invoices).size).to eq(invoices.size) + end + end +end diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb index f3616cd4..be106282 100644 --- a/spec/controllers/home_controller_spec.rb +++ b/spec/controllers/home_controller_spec.rb @@ -131,24 +131,22 @@ describe HomeController, type: :controller do end describe 'assigns sortings' do - let(:fin_trans1) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'A', amount: 200, created_on: Time.now } - let(:fin_trans2) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'B', amount: 100, created_on: Time.now + 2.minutes } - let(:fin_trans3) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'C', amount: 50, created_on: Time.now + 1.minute } + let(:fin_trans1) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'A', amount: 100 } + let(:fin_trans2) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'B', amount: 200, created_on: Time.now + 1.minute } before do fin_trans1 fin_trans2 - fin_trans3 end it 'by criteria' do sortings = [ - ['date', [fin_trans1, fin_trans3, fin_trans2]], - ['note', [fin_trans1, fin_trans2, fin_trans3]], - ['amount', [fin_trans3, fin_trans2, fin_trans1]], - ['date_reverse', [fin_trans2, fin_trans3, fin_trans1]], - ['note_reverse', [fin_trans3, fin_trans2, fin_trans1]], - ['amount_reverse', [fin_trans1, fin_trans2, fin_trans3]] + ['date', [fin_trans1, fin_trans2]], + ['note', [fin_trans1, fin_trans2]], + ['amount', [fin_trans1, fin_trans2]], + ['date_reverse', [fin_trans2, fin_trans1]], + ['note_reverse', [fin_trans2, fin_trans1]], + ['amount_reverse', [fin_trans2, fin_trans1]] ] sortings.each do |sorting| get_with_defaults :ordergroup, params: { sort: sorting[0] } @@ -184,7 +182,7 @@ describe HomeController, type: :controller do get_with_defaults :cancel_membership, params: { group_id: fin_user.groups.first.id } expect(response).to have_http_status(:redirect) expect(response).to redirect_to(my_profile_path) - expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', group: membership.group.name)}/) + expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', :group => membership.group.name)}/) end it 'removes user membership' do @@ -192,7 +190,7 @@ describe HomeController, type: :controller do get_with_defaults :cancel_membership, params: { membership_id: membership.id } expect(response).to have_http_status(:redirect) expect(response).to redirect_to(my_profile_path) - expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', group: membership.group.name)}/) + expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', :group => membership.group.name)}/) end end end diff --git a/spec/controllers/login_controller_spec.rb b/spec/controllers/login_controller_spec.rb new file mode 100644 index 00000000..c824e429 --- /dev/null +++ b/spec/controllers/login_controller_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe LoginController, type: :controller do + let(:invite) { create :invite } + + describe 'GET accept_invitation' do + let(:expired_invite) { create :expired_invite } + + describe 'with valid token' do + it 'accepts invitation' do + get_with_defaults :accept_invitation, params: { token: invite.token } + expect(response).to have_http_status(:success) + expect(response).to render_template('login/accept_invitation') + end + end + + describe 'with invalid token' do + it 'redirects to login' do + get_with_defaults :accept_invitation, params: { token: invite.token + 'XX' } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(login_url) + expect(flash[:alert]).to match(I18n.t('login.controller.error_invite_invalid')) + end + end + + describe 'with timed out token' do + it 'redirects to login' do + get_with_defaults :accept_invitation, params: { token: expired_invite.token } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(login_url) + expect(flash[:alert]).to match(I18n.t('login.controller.error_invite_invalid')) + end + end + + describe 'without group' do + it 'redirects to login' do + invite.group.destroy + get_with_defaults :accept_invitation, params: { token: invite.token } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(login_url) + expect(flash[:alert]).to match(I18n.t('login.controller.error_group_invalid')) + end + end + end + + describe 'POST accept_invitation' do + describe 'with invalid parameters' do + it 'renders accept_invitation view' do + post_with_defaults :accept_invitation, params: { token: invite.token, user: invite.user.slice('first_name') } + expect(response).to have_http_status(:success) + expect(response).to render_template('login/accept_invitation') + expect(assigns(:user).errors.present?).to be true + end + end + + describe 'with valid parameters' do + it 'redirects to login' do + post_with_defaults :accept_invitation, params: { token: invite.token, user: invite.user.slice('first_name', 'password') } + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(login_url) + expect(flash[:notice]).to match(I18n.t('login.controller.accept_invitation.notice')) + end + end + end +end diff --git a/spec/factories/invite.rb b/spec/factories/invite.rb new file mode 100644 index 00000000..51d48840 --- /dev/null +++ b/spec/factories/invite.rb @@ -0,0 +1,15 @@ +require 'factory_bot' + +FactoryBot.define do + factory :invite do + user { create :user } + group { create :group } + email { Faker::Internet.email } + + factory :expired_invite do + after :create do |invite| + invite.update_column(:expires_at, Time.now.yesterday) + end + end + end +end diff --git a/spec/factories/order_article.rb b/spec/factories/order_article.rb new file mode 100644 index 00000000..99ca8701 --- /dev/null +++ b/spec/factories/order_article.rb @@ -0,0 +1,8 @@ +require 'factory_bot' + +FactoryBot.define do + factory :order_article do + order { create :order } + article { create :article } + end +end diff --git a/spec/fixtures/files/upload_test.csv b/spec/fixtures/files/upload_test.csv new file mode 100644 index 00000000..ac2f59b0 --- /dev/null +++ b/spec/fixtures/files/upload_test.csv @@ -0,0 +1,3 @@ +avail.;Order number;Name;Note;Manufacturer;Origin;Unit;Price (net);VAT;Deposit;Unit quantity;"";"";Category +"";;AAAA;AAAA;;;500 g;25.55;6.0;0.0;1;"";"";AAAA +"";;BBBB;BBBB;;;250 g;12.11;6.0;0.0;2;"";"";BBBB diff --git a/spec/fixtures/upload_test.csv b/spec/fixtures/upload_test.csv new file mode 100644 index 00000000..ac2f59b0 --- /dev/null +++ b/spec/fixtures/upload_test.csv @@ -0,0 +1,3 @@ +avail.;Order number;Name;Note;Manufacturer;Origin;Unit;Price (net);VAT;Deposit;Unit quantity;"";"";Category +"";;AAAA;AAAA;;;500 g;25.55;6.0;0.0;1;"";"";AAAA +"";;BBBB;BBBB;;;250 g;12.11;6.0;0.0;2;"";"";BBBB diff --git a/spec/models/supplier_spec.rb b/spec/models/supplier_spec.rb index 70ba6def..6bcc6e7b 100644 --- a/spec/models/supplier_spec.rb +++ b/spec/models/supplier_spec.rb @@ -19,9 +19,13 @@ describe Supplier do end it 'return correct tolerance' do - supplier = create :supplier, articles: create_list(:article, 1, unit_quantity: 1) + supplier = create :supplier + articles = create_list(:article, 1, unit_quantity: 1, supplier_id: supplier.id) + supplier.reload expect(supplier.has_tolerance?).to be false - supplier2 = create :supplier, articles: create_list(:article, 1, unit_quantity: 2) + supplier2 = create :supplier + articles = create_list(:article, 1, unit_quantity: 2, supplier_id: supplier2.id) + supplier.reload expect(supplier2.has_tolerance?).to be true end diff --git a/spec/support/spec_test_helper.rb b/spec/support/spec_test_helper.rb index f3737c15..58a1c0ef 100644 --- a/spec/support/spec_test_helper.rb +++ b/spec/support/spec_test_helper.rb @@ -2,7 +2,7 @@ module SpecTestHelper def login(user) - user = User.find_by_nick(user.nick) + user = User.where(:nick => user.nick).first if user.is_a?(Symbol) session[:user_id] = user.id session[:scope] = FoodsoftConfig[:default_scope] # Save scope in session to not allow switching between foodcoops with one account session[:locale] = user.locale @@ -16,8 +16,13 @@ module SpecTestHelper params['foodcoop'] = FoodsoftConfig[:default_scope] get action, params: params, xhr: xhr, format: format end + + def post_with_defaults(action, params: {}, xhr: false, format: nil) + params['foodcoop'] = FoodsoftConfig[:default_scope] + post action, params: params, xhr: xhr, format: format + end end RSpec.configure do |config| - config.include SpecTestHelper, type: :controller + config.include SpecTestHelper, :type => :controller end