{"id":280779,"date":"2016-11-11T09:45:04","date_gmt":"2016-11-11T06:45:04","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=280779"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=280779","title":{"rendered":"\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 Redmine"},"content":{"rendered":"<p>\u0417\u0430 \u043f\u0440\u043e\u0448\u0435\u0434\u0448\u0438\u0439 \u0433\u043e\u0434 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0447\u0435\u0442\u044b\u0440\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0434\u043b\u044f \u0441\u0432\u044f\u0437\u043a\u0438 <a href=\"http:\/\/www.redmine.org\/\">Redmine<\/a> + <a href=\"https:\/\/telegram.org\/\">Telegram<\/a> (<a href=\"https:\/\/github.com\/centosadmin\/redmine_2fa\">\u0440\u0430\u0437<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_intouch\">\u0434\u0432\u0430<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_chat_telegram\">\u0442\u0440\u0438<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_telegram_common\">\u0447\u0435\u0442\u044b\u0440\u0435<\/a>).<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0442\u0438\u0445\u043e\u043d\u0435\u0447\u043a\u0443 \u0432\u044b\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u0432\u043e\u0438 Best Practices \u0432 \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0438 \u043d\u0438\u0445. \u0412 \u044d\u0442\u043e\u0439 \u0437\u0430\u043c\u0435\u0442\u043a\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u043c \u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 <a href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a>.<br \/>  <a href=\"https:\/\/habrahabr.ru\/company\/centosadmin\/blog\/314954\/\"><\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\"  src=\"https:\/\/habrastorage.org\/files\/5f8\/d48\/070\/5f8d4807070d4b9cb5548db046691219.png\"\/><\/div>\n<p><\/a><br \/>  \u041f\u043e\u0447\u0435\u043c\u0443 \u0440\u0435\u0448\u0438\u043b\u0438 \u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c? \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0434\u043b\u044f <a href=\"http:\/\/www.redmine.org\/\">Redmine<\/a> \u2014 \u0442\u043e\u0442 \u0435\u0449\u0451 \u043a\u0432\u0435\u0441\u0442.<\/p>\n<p>  <a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"vnachale\">\u0412\u043d\u0430\u0447\u0430\u043b\u0435&#8230;<\/h2>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 <a href=\"http:\/\/rspec.info\/\">rspec<\/a> \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0437\u0430\u0445\u043e\u0434\u0430 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c. \u0421\u043e \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0438 \u0442\u0440\u0435\u0442\u044c\u0435\u0433\u043e \u0442\u043e\u0436\u0435.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0441 \u0445\u043e\u0440\u043e\u0448\u0438\u043c \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0442\u0430\u043a\u0436\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438. \u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u043b\u0438\u0431\u043e \u0441\u043e\u0432\u0441\u0435\u043c \u0431\u0435\u0437 \u0442\u0435\u0441\u0442\u043e\u0432, \u043b\u0438\u0431\u043e \u0441\u0430\u043c\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u044b \u0442\u0430\u043a\u0438\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435, \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043d\u0438\u0445 \u043f\u043e\u043c\u0435\u0449\u0430\u043b\u0438\u0441\u044c \u0432 \u043e\u0434\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435. \u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u0438 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u0432\u044c\u044e\u0445. \u0410 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0443.<\/p>\n<p>  <\/p>\n<p>\u041b\u0443\u0447\u0448\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u043e\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u0442\u0435\u0441\u0442\u044b \u0441\u0430\u043c\u043e\u0433\u043e Redmine. \u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u0435 rails default \u0442\u0435\u0441\u0442\u044b \u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e \u043c\u0430\u043b\u043e\u043c\u0430\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f (<a href=\"https:\/\/github.com\/redmine\/redmine\/blob\/master\/test\/unit\/issue_test.rb\">\u043f\u0440\u0443\u0444<\/a>).<\/p>\n<p>  <\/p>\n<p>\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0430 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u043a\u043b\u0430\u0441\u0441\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u044f\u043c\u043e\u0435 \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u0442\u0430\u043a\u043e\u043c\u0443 \u043f\u043e\u0434\u0445\u043e\u0434\u0443 \u0437\u0430\u0432\u0435\u043b\u043e \u043d\u0430\u0441 \u043e\u0434\u043d\u0430\u0436\u0434\u044b \u0432 \u0442\u0443\u043f\u0438\u043a.<\/p>\n<p>  <\/p>\n<p>\u041e\u0447\u0435\u043d\u044c \u0432\u044b\u0440\u0443\u0447\u0438\u043b\u0430 \u043a\u043d\u0438\u0433\u0430 <a href=\"https:\/\/pragprog.com\/book\/nrtest\/rails-test-prescriptions\">Rails Test Prescription<\/a> \u0438\u0437\u0434\u0430\u043d\u0438\u044f 2011 \u0433\u043e\u0434\u0430. \u0418\u0437 \u043d\u0435\u0451 \u043c\u044b \u0443\u0437\u043d\u0430\u043b\u0438 \u043f\u0440\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u043f\u0438\u0441\u0430\u0442\u044c Rails default style \u0442\u0435\u0441\u0442\u044b, \u0430 \u0442\u0430\u0436\u0435 \u043f\u0440\u043e \u0433\u0435\u043c <a href=\"https:\/\/github.com\/thoughtbot\/shoulda\">shoulda<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438 \u0441\u0442\u0430\u043b \u043d\u0430\u0448\u0438\u043c \u0441\u043f\u0430\u0441\u0435\u043d\u0438\u0435\u043c.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0437 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 <a href=\"https:\/\/github.com\/centosadmin\/redmine_2fa\">redmine_2fa<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b <a href=\"https:\/\/habrahabr.ru\/company\/centosadmin\/blog\/312656\/\">\u0430\u043d\u043e\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043b\u0438<\/a> \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u043c \u043c\u0435\u0441\u044f\u0446\u0435. \u0410 \u0442\u0430\u043a\u0436\u0435 \u0438\u0437 \u043d\u043e\u0432\u043e\u0438\u0441\u043f\u0435\u0447\u0451\u043d\u043d\u043e\u0433\u043e <a href=\"https:\/\/github.com\/centosadmin\/redmine_telegram_common\">redmine_telegram_common<\/a>, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0442\u043e\u0436\u0435 \u043e\u0434\u043d\u0430\u0436\u0434\u044b \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u043c.<\/p>\n<p>  <\/p>\n<h1 id=\"kak-zapuskat-testy\">\u041a\u0430\u043a \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b?<\/h1>\n<p>  <\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u0438 \u043a\u043e\u0440\u043d\u044f Redmine:<\/p>\n<p>  <\/p>\n<pre><code>bundle exec rake redmine:plugins:test NAME=redmine_2fa<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 Redmine 3.3.1 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441\u044b\u043f\u0435\u0442\u0441\u044f \u043c\u043d\u043e\u0433\u043e ruby warning-\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u044b\u0434\u0430\u0447\u0443 \u043f\u043e\u0447\u0438\u0449\u0435, \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 <code>test_helper.rb<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">$VERBOSE = nil # for hide ruby warnings<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0432\u043e\u0438\u0445 \u0444\u0438\u043a\u0441\u0442\u0443\u0440 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445, \u0432 \u0442\u043e\u0442 \u0436\u0435 \u0444\u0430\u0439\u043b \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '\/fixtures\/', [:auth_sources])<\/code><\/pre>\n<p>  <\/p>\n<p>\u0413\u0434\u0435 \u0432\u043c\u0435\u0441\u0442\u043e <code>:auth_sources<\/code> \u0431\u0443\u0434\u0443\u0442 \u0442\u0435 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043a \u0442\u0435\u0441\u0442\u0430\u043c.<\/p>\n<p>  <\/p>\n<h2 id=\"gemfile\">Gemfile<\/h2>\n<p>  <\/p>\n<pre><code class=\"ruby\">group :test do   gem 'shoulda' end<\/code><\/pre>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/thoughtbot\/shoulda\">shoulda<\/a> \u2014 must have gem \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u2014 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<h2 id=\"testirovanie-patchey-dlya-kontrollerov\">\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0442\u0447\u0435\u0439 \u0434\u043b\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432<\/h2>\n<p>  <\/p>\n<pre><code class=\"ruby\">class AccountControllerPatchTest &lt; ActionController::TestCase   # \u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u0442\u0435\u0441\u0442\u0430 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u043a\u043e\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c   tests AccountController    # \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b   fixtures :users, :email_addresses, :roles, :auth_sources    # \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u0449\u0438\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442   setup do     @user = User.find(2) # jsmith     Redmine2FA.stubs(:active_protocols).returns(Redmine2FA::AVAILABLE_PROTOCOLS)   end    # \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0447\u0430\u0441\u0442\u043d\u044b\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c   context 'user without 2fa' do     context 'with valid login data' do     setup { post :login, username: 'jsmith', password: 'jsmith', back_url: 'http:\/\/test.host\/' }      context 'prepare' do       should set_session[:otp_user_id].to(2)       should set_session[:otp_back_url].to('http:\/\/test.host\/')       should 'set user instance variable' do         assert_equal @user, assigns(:user)       end     end   end end<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"stub-y--i-mock-i\">Stub-\u044b \u0438 mock-\u0438<\/h2>\n<p>  <\/p>\n<p>\u041d\u043e\u0432\u043e\u0433\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0435 \u0441\u0442\u0430\u043b\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0433\u0435\u043c <a href=\"https:\/\/github.com\/freerange\/mocha\">mocha<\/a> \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 Redmine.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">User.any_instance.stubs(:mobile_phone).returns('7894561230') User.any_instance.expects(:authenticate_otp).returns(true)<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"testirovanie-marshrutov\">\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/h2>\n<p>  <\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0435 <code>integration\/routing<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0412 Redmine \u0435\u0441\u0442\u044c \u0441\u0432\u043e\u0438 \u0445\u0435\u043b\u043f\u0435\u0440\u044b \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">should_route 'POST \/redmine_2fa\/bot\/init' =&gt; 'otp_bot#create'<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u043e \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432, \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0445\u0435\u043b\u043f\u0435\u0440 \u0438\u0437 \u0440\u0435\u043b\u044c\u0441:<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">assert_routing({ method: 'post', path: '\/redmine_2fa\/bot\/token:token\/update' },                controller: 'otp_bot_webhook', action: 'update', token: 'token:token')<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"unit-testy\">Unit-\u0442\u0435\u0441\u0442\u044b<\/h2>\n<p>  <\/p>\n<p>\u0418\u0445 \u043d\u0435\u0442 \u0441\u043c\u044b\u0441\u043b\u0430 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043e\u0431\u043e\u0439\u0434\u0451\u043c\u0441\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"ruby\">context 'confirmation' do   setup do     User.any_instance.stubs(:mobile_phone).returns('79243216547')   end    should 'confirm mobile phone with valid code' do     User.any_instance.expects(:authenticate_otp).returns(true)     @user.confirm_mobile_phone('valid')     @user.reload     assert @user.mobile_phone_confirmed?   end    should 'return errors with invalid code' do     User.any_instance.expects(:authenticate_otp).returns(false)     @user.confirm_mobile_phone('invalid')     @user.reload     assert !@user.mobile_phone_confirmed?     assert @user.errors.present?   end end<\/code><\/pre>\n<p>  <\/p>\n<h1 id=\"teper-pro-ci\">\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e CI<\/h1>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u044b \u043f\u043e\u043c\u043d\u0438\u0442\u0435 \u0438\u0437 \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0442\u0430\u0442\u044c\u0438, \u0442\u0435\u0441\u0442\u044b \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u0438\u0437 \u043a\u043e\u0440\u043d\u044f Redmine. \u0410 \u043a\u043e\u0434 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0441\u0430\u043c\u043e\u0433\u043e Redmine \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0443\u0436\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 Redmine, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0435\u0433\u043e \u0431\u0430\u0437\u044b \u0438 \u0443\u0436\u0435 \u043f\u043e\u0442\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>  <\/p>\n<h2 id=\"primer-nastroyki\">\u041f\u0440\u0438\u043c\u0435\u0440 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438<\/h2>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">.travis.yml<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code>language: ruby rvm:   - 2.3.0  addons:   postgresql: &quot;9.4&quot;  env:   - REDMINE_VER=3.3-stable   - REDMINE_VER=master  install: &quot;echo skip bundle install&quot;  before_script:   - psql -c 'create database travis_ci_test;' -U postgres  script:   - export TESTSPACE=`pwd`\/testspace   - export NAME_OF_PLUGIN=redmine_telegram_common   - export PATH_TO_PLUGIN=`pwd`   - export PATH_TO_REDMINE=$TESTSPACE\/redmine   - mkdir $TESTSPACE   - cp test\/support\/* $TESTSPACE\/   - bash -x .\/travis.sh<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0412 <code>install<\/code> \u0441\u0442\u043e\u0438\u0442<\/p>\n<p>  <\/p>\n<pre><code>install: &quot;echo skip bundle install&quot;<\/code><\/pre>\n<p>  <\/p>\n<p>\u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0435<br \/>  <code>bash -x .\/travis.sh<\/code><\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">travis.sh<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code>#\/bin\/bash  set -e  if [[ ! &quot;$TESTSPACE&quot; = \/* ]] ||    [[ ! &quot;$PATH_TO_REDMINE&quot; = \/* ]] ||    [[ ! &quot;$REDMINE_VER&quot; = * ]] ||    [[ ! &quot;$NAME_OF_PLUGIN&quot; = * ]] ||    [[ ! &quot;$PATH_TO_PLUGIN&quot; = \/* ]]; then   echo &quot;You should set&quot;\\        &quot; TESTSPACE, PATH_TO_REDMINE, REDMINE_VER&quot;\\        &quot; NAME_OF_PLUGIN, PATH_TO_PLUGIN&quot;\\        &quot; environment variables&quot;   echo &quot;You set:&quot;\\        &quot;$TESTSPACE&quot;\\        &quot;$PATH_TO_REDMINE&quot;\\        &quot;$REDMINE_VER&quot;\\        &quot;$NAME_OF_PLUGIN&quot;\\        &quot;$PATH_TO_PLUGIN&quot;   exit 1; fi  export RAILS_ENV=test  export REDMINE_GIT_REPO=git:\/\/github.com\/redmine\/redmine.git export REDMINE_GIT_TAG=$REDMINE_VER export BUNDLE_GEMFILE=$PATH_TO_REDMINE\/Gemfile  # checkout redmine git clone $REDMINE_GIT_REPO $PATH_TO_REDMINE cd $PATH_TO_REDMINE if [ ! &quot;$REDMINE_GIT_TAG&quot; = &quot;master&quot; ]; then   git checkout -b $REDMINE_GIT_TAG origin\/$REDMINE_GIT_TAG fi  mv $TESTSPACE\/database.yml.travis config\/database.yml mv $TESTSPACE\/additional_environment.rb config\/  # create a link to the backlogs plugin ln -sf $PATH_TO_PLUGIN plugins\/$NAME_OF_PLUGIN  # install gems bundle install  # run redmine database migrations bundle exec rake db:migrate  # run plugin database migrations bundle exec rake redmine:plugins:migrate  bundle exec rake db:structure:dump  # run tests bundle exec rake redmine:plugins:test NAME=$NAME_OF_PLUGIN<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u044d\u0442\u0438\u043c \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c, \u0442\u043e \u043a\u043e\u043d\u0444\u0438\u0433 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u044b \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u0444\u0430\u0439\u043b\u0435 <code>test\/support\/database.yml.travis<\/code><\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">database.yml.travis<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code>test:   adapter: postgresql   encoding: unicode   pool: 5   database: travis_ci_test   user: postgres<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<h1 id=\"rezyume\">\u0420\u0435\u0437\u044e\u043c\u0435<\/h1>\n<p>  <\/p>\n<p>\u041a\u043e\u0440\u043e\u0442\u0435\u043d\u044c\u043a\u043e \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u043f\u0443\u043d\u043a\u0442\u0430\u043c \u043f\u0440\u043e\u0448\u043b\u0438\u0441\u044c. \u041e \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0438 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u044f\u0445 \u2014 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445. \u0411\u043e\u043b\u044c\u0448\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u2014 \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0430\u0445 <a href=\"https:\/\/github.com\/centosadmin\/redmine_2fa\">redmine_2fa<\/a> \u0438 <a href=\"https:\/\/github.com\/centosadmin\/redmine_telegram_common\">redmine_telegram_common<\/a>.<\/p>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habrahabr.ru\/post\/314954\/\"> https:\/\/habrahabr.ru\/post\/314954\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0417\u0430 \u043f\u0440\u043e\u0448\u0435\u0434\u0448\u0438\u0439 \u0433\u043e\u0434 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0447\u0435\u0442\u044b\u0440\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0434\u043b\u044f \u0441\u0432\u044f\u0437\u043a\u0438 <a href=\"http:\/\/www.redmine.org\/\">Redmine<\/a> + <a href=\"https:\/\/telegram.org\/\">Telegram<\/a> (<a href=\"https:\/\/github.com\/centosadmin\/redmine_2fa\">\u0440\u0430\u0437<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_intouch\">\u0434\u0432\u0430<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_chat_telegram\">\u0442\u0440\u0438<\/a>, <a href=\"https:\/\/github.com\/centosadmin\/redmine_telegram_common\">\u0447\u0435\u0442\u044b\u0440\u0435<\/a>).<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0442\u0438\u0445\u043e\u043d\u0435\u0447\u043a\u0443 \u0432\u044b\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u0432\u043e\u0438 Best Practices \u0432 \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0438 \u043d\u0438\u0445. \u0412 \u044d\u0442\u043e\u0439 \u0437\u0430\u043c\u0435\u0442\u043a\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u043c \u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 <a href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a>.<br \/>  <a href=\"https:\/\/habrahabr.ru\/company\/centosadmin\/blog\/314954\/\"><\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\"  src=\"https:\/\/habrastorage.org\/files\/5f8\/d48\/070\/5f8d4807070d4b9cb5548db046691219.png\"\/><\/div>\n<p><\/a><br \/>  \u041f\u043e\u0447\u0435\u043c\u0443 \u0440\u0435\u0448\u0438\u043b\u0438 \u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c? \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0434\u043b\u044f <a href=\"http:\/\/www.redmine.org\/\">Redmine<\/a> \u2014 \u0442\u043e\u0442 \u0435\u0449\u0451 \u043a\u0432\u0435\u0441\u0442.<\/p>\n<p>  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-280779","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/280779","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=280779"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/280779\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=280779"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=280779"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=280779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}