diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4ce97a..e97e152 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,11 @@ on: branches: [ main ] pull_request: branches: [ main ] - jobs: rspec: runs-on: ubuntu-latest + env: + SLACK_TOKEN: "DUMMY" steps: - uses: actions/checkout@v3 - name: Set up Ruby diff --git a/Gemfile b/Gemfile index bacf771..cf63524 100644 --- a/Gemfile +++ b/Gemfile @@ -8,9 +8,12 @@ ruby File.read(".ruby-version").rstrip gem "activesupport" gem "config" +gem "dotenv" +gem "faraday" gem "mechanize" gem "rackup" gem "roda" +gem "rubyzip" gem "sidekiq" gem "slack-ruby-client" @@ -21,6 +24,8 @@ group :development do end group :development, :test do + gem "hashie" + gem "pry" gem "rspec", "~> 3.12" gem "rubocop" end diff --git a/Gemfile.lock b/Gemfile.lock index 58d1099..8a95666 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,6 +30,7 @@ GEM deep_merge (1.2.2) diff-lcs (1.5.1) domain_name (0.6.20240107) + dotenv (3.1.6) drb (2.2.1) faraday (2.10.0) faraday-net_http (>= 2.0, < 3.2) @@ -154,6 +155,7 @@ GEM ruby-progressbar (1.13.0) rubyntlm (0.6.5) base64 + rubyzip (2.3.2) securerandom (0.4.0) shellany (0.0.1) sidekiq (7.3.6) @@ -184,13 +186,18 @@ DEPENDENCIES activesupport byebug config + dotenv + faraday guard guard-rspec + hashie mechanize + pry rackup roda rspec (~> 3.12) rubocop + rubyzip sidekiq slack-ruby-client diff --git a/README.md b/README.md index e0ba592..3ab1427 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,18 @@ +# 対応入館証について + +`yokoso` はビル来客システムの連携させるツールですが、来客システムには以下の2つの仕様が確認されています。 + +- 2次元バーコードを発行するタイプ +- QRコードを発行するタイプ + +本repositoryの所有者であるSmartHRがオフィスをお借りしているビルでは、後者のQRコードを発行するタイプが使用されています。 +それに合わせて、mainブランチの仕様もQRコードを発行するタイプの来客システムに対応したものになっています。 +また、2次元バーコードを発行するタイプのシステムは利用できなくなったため、そちらの仕様については積極的に保守ができなくなりました。したがって、そちらの仕様については `2D-code` ブランチに切り離しております。 +ご利用される環境に合わせてブランチを選定、設定をお願いします。 + # 全体像 @@ -86,6 +98,8 @@ heroku local -e .env - webhook アドレスは `https://{{server_url}}/app/notification` - サービスは何でも良い - zapier なら [コチラ](https://zapier.com/app/editor/template/9205?selected_apis=ZapierMailAPI%2CWebHookAPI) + - 先述のQRコードを用いるタイプの来客システムの場合、2次元バーコードのタイプとは異なり入館IDからコードの生成を行えなくなっています(入館IDとQRコードを複合したデータが異なる) + - そのため、添付ファイルをWebhookに含める形でpostするよう設定してください。その時のKeyは `pdf` にしてください - 参考: - Webhook 用メールアドレスは後でサーバサイド環境変数へセットする diff --git a/app/controllers/slack_notification.rb b/app/controllers/slack_notification.rb index f1c7b0e..8d3ab2b 100644 --- a/app/controllers/slack_notification.rb +++ b/app/controllers/slack_notification.rb @@ -5,23 +5,24 @@ require_relative "../models/email" require_relative "../models/chat_message_sender" require_relative "../models/admission_code_message" +require_relative "../models/qr_code_pdf" + +require "securerandom" module SlackNotification def run(request) mail_body = request["body"] email = Email.new(mail_body) - # TODO: fix Ruby 3.1+ https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Security/YAMLLoadQ messages = open("./config/messages.yml", "r") { |f| YAML.unsafe_load(f) } res = AdmissionCodeMessage.new(email).post - barcode_url = "https://barcode.tec-it.com/barcode.ashx?data=#{email.recept_id}&code=Code128" text_guide_jap = messages["notification"]["text_guide_jap"] text_guide_eng = messages["notification"]["text_guide_eng"] - text_guide_jap.gsub!("RECEPT_ID", "#{email.recept_id}\n#{barcode_url}") - text_guide_eng.gsub!("RECEPT_ID", "#{email.recept_id}\n#{barcode_url}") + text_guide_jap.gsub!("RECEPT_ID", email.recept_id.to_s) + text_guide_eng.gsub!("RECEPT_ID", email.recept_id.to_s) day_of_the_week_eg2jp = { "Sun" => "日", @@ -36,19 +37,24 @@ def run(request) text_guide_jap.gsub!("RECEPT_DATE", recept_date_jap) text_guide_eng.gsub!("RECEPT_DATE", email.recept_date) - ChatMessageSender.new.post_public_message( - icon_emoji: ":office:", - channel: res.channel, - text: "#{text_guide_jap}\n#{text_guide_eng}", - attachments: [ - { - title: "バーコード/Barcode", - image_url: barcode_url - } - ], - thread_ts: res.ts + zapier_pdf_url = request["pdf"] + file_name_prefix = SecureRandom.alphanumeric(10) + qrcode = QrCodePdf.new(zapier_pdf_url, file_name_prefix) + qrcode.download + qrcode.unzip + + ChatMessageSender.new(direct_message_id: res["channel"]).post_admission_badge_message( + { + icon_emoji: ":office:", + channel: email.slack_id, + text: "#{text_guide_jap}\n#{text_guide_eng}", + as_user: true + }, + file_paths: qrcode.entry_qr_code_path ) + qrcode.cleanup + "" end diff --git a/app/models/admission_code_message.rb b/app/models/admission_code_message.rb index c5d9dbc..7803216 100644 --- a/app/models/admission_code_message.rb +++ b/app/models/admission_code_message.rb @@ -39,7 +39,8 @@ def api_post_body attachments: [ color: "good", fields: attachment_fields - ] } + ], + as_user: true } end def api_post_body_direct_message @@ -49,7 +50,8 @@ def api_post_body_direct_message attachments: [ color: "good", fields: attachment_fields - ] } + ], + as_user: true } end # @return [Array] attachment_field array diff --git a/app/models/chat_message_sender.rb b/app/models/chat_message_sender.rb index abd6baf..b83feef 100644 --- a/app/models/chat_message_sender.rb +++ b/app/models/chat_message_sender.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true # チャットツールへのメッセージ送信を扱うクラス + +require "faraday" class ChatMessageSender - def initialize + def initialize(direct_message_id: nil) @slack_api_client = Slack::Web::Client.new(token: ENV.fetch("SLACK_TOKEN")) + @direct_message_id = direct_message_id end # @param post_body [Hash] @@ -12,4 +15,57 @@ def initialize def post_public_message(post_body) @slack_api_client.chat_postMessage(post_body) end + + def post_admission_badge_message(post_body, file_paths: []) + if file_paths.empty? + post_body = { + icon_emoji: ":office:", + channel: @direct_message_id, + text: "入館証の発行に失敗しました", + as_user: true + } + @slack_api_client.chat_postMessage(post_body) + + raise "File Path is empty. Check Zapier Settings." + end + + files = upload_files(file_paths: file_paths) + posted_message_response = @slack_api_client.chat_postMessage(post_body) + return posted_message_response if file_paths.empty? + + @slack_api_client.files_completeUploadExternal( + files: files.to_json, + channel_id: @direct_message_id, + thread_ts: posted_message_response["ts"] + ) + end + + private + + def upload_files(file_paths:) + file_upload_metadata(file_paths: file_paths).map do |metadata| + conn = Faraday.new(url: metadata[:url]) do |f| + f.request :multipart + f.request :url_encoded + f.adapter Faraday.default_adapter + end + payload = { file: Faraday::UploadIO.new(metadata[:file_path], "application/pdf") } + conn.post do |req| + req.body = payload + end + + { id: metadata[:id], title: metadata[:title] } + end + end + + def file_upload_metadata(file_paths:) + file_paths.map do |file_path| + res = @slack_api_client.files_getUploadURLExternal( + filename: File.basename(file_path), + length: File.size(file_path) + ) + + { id: res.file_id, title: File.basename(file_path), url: res.upload_url, file_path: file_path } + end + end end diff --git a/app/models/email.rb b/app/models/email.rb index 71eebca..4692a0b 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -18,6 +18,6 @@ def recept_date end def recept_id - @params.match(/(?:ID:)(\d+)/)[1] + @params.match(/Guest ID\r\n(\d+)/)[1] end end diff --git a/app/models/qr_code_pdf.rb b/app/models/qr_code_pdf.rb new file mode 100644 index 0000000..6a2d8e0 --- /dev/null +++ b/app/models/qr_code_pdf.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "mechanize" +require "zip" +require "fileutils" + +class QrCodePdf + def initialize(pdf_url, prefix) + @pdf_url = pdf_url + @prefix = prefix + @agent = Mechanize.new + end + + def download + @agent.get(@pdf_url).save_as("#{@prefix}_qr_code.zip") + end + + def unzip + dist_path = "#{@prefix}_qr_code" + Dir.mkdir(dist_path) + Zip::File.open("#{@prefix}_qr_code.zip") do |zip_file| + zip_file.each do |entry| + entry.extract("#{dist_path}/#{entry.name}") + end + end + end + + def entry_qr_code_path + qr_code_path = "#{@prefix}_qr_code" + + Dir.glob("#{qr_code_path}/*pdf") + end + + def cleanup + File.delete("#{@prefix}_qr_code.zip") + FileUtils.rm_rf("#{@prefix}_qr_code") + end +end diff --git a/app/models/slack_message.rb b/app/models/slack_message.rb index 5667d21..3d84add 100644 --- a/app/models/slack_message.rb +++ b/app/models/slack_message.rb @@ -27,10 +27,13 @@ def self.post_received_message(modal_submission) # @see https://github.com/slack-ruby/slack-ruby-client/blob/master/lib/slack/web/api/endpoints/chat.rb # @private def received_message_post_body - { icon_emoji: MESSAGES["interactive"]["icon"], + { + icon_emoji: MESSAGES["interactive"]["icon"], channel: @modal_submission.slack_user_id, text:, - attachments: [attachment(fields: received_message_attachment_fields)] } + attachments: [attachment(fields: received_message_attachment_fields)], + as_user: true + } end def text diff --git a/app/workers/reception_worker.rb b/app/workers/reception_worker.rb index 4bd2e1f..60b032c 100644 --- a/app/workers/reception_worker.rb +++ b/app/workers/reception_worker.rb @@ -7,6 +7,8 @@ require_relative "../models/slack_message" require_relative "../../config/initializers/sidekiq" +require "dotenv" +Dotenv.load class ReceptionWorker include Sidekiq::Worker sidekiq_options queue: :default, retry: false @@ -41,7 +43,7 @@ def perform(slack_modal) agent.page.form.field_with(name: "mei[]").value = recept_visitor_name.gsub(/[[:punct:]]/, "") agent.page.form.field_with(name: "kana[]").value = "カナ" agent.page.form.field_with(name: "mail[]").value = ENV.fetch("MAIL_ADDRESS_WEBHOOK") - agent.page.form.field_with(name: "sinseiemail").value = ENV.fetch("MAIL_ADDRESS_HOST") + agent.page.form.field_with(name: "sinseiemail1").value = ENV.fetch("MAIL_ADDRESS_HOST") agent.page.form.field_with(name: "sinseitel").value = ENV.fetch("COMPANY_TEL") # regist diff --git a/config.ru b/config.ru index 2d912f0..455b117 100644 --- a/config.ru +++ b/config.ru @@ -5,6 +5,9 @@ require_relative "app/controllers/slack_slash_command" require_relative "app/controllers/slack_interactive_message" require_relative "app/controllers/slack_notification" +require "dotenv" +Dotenv.load + class App < Roda route do |r| r.post "app/dialog" do diff --git a/config/messages.yml b/config/messages.yml index 62e2b49..824e125 100644 --- a/config/messages.yml +++ b/config/messages.yml @@ -32,19 +32,13 @@ notification: 日比谷線・大江戸線「六本木駅」5番出口より徒歩5分 ◆ 入館ID - RECEPT_ID - (URLクリックでバーコード画像が表示されます) + RECEPT_ID ◆ ビル入館方法 - ①(1Fまたは4F)セキュリティゲート前の受付機にバーコードをかざしていただく、もしくは入館IDを入力して下さい。 - ②受付機より「入館証」が発券されます。 - (複数名でご来社の場合は人数分の発券をお願いいたします。同一バーコード/IDをご利用いただけます) - ③入館証のQRコードをゲートにかざし入館します。 - ④ゲート左手15F〜20F用のエレベータにて17Fへお越しください。 - ⑤SmartHR受付のiPadより担当者をお呼び出しください。 - - ◆ SmartHRオフィスの行き方 - https://shanaiho.smarthr.co.jp/n/na0b9906ec236 + ①添付のPDFのQRコードをゲートにかざし入館します。(複数名でご来社の場合でも、同一QRコードをご利用いただけます。) + ②15F〜20F用のエレベータにて17Fへお越しください。 + ③SmartHR受付のiPadより担当者をお呼び出しください。 + ※注意点 「入館証」はご退館時まで必要です。大切にお持ち下さい。 ``` @@ -56,11 +50,15 @@ notification:
Sumitomo Fudosan Roppongi Grand Tower 17F, 3-2-1 Roppongi, Minato-ku, Tokyo 106-6217 - - Please print out your admission tickets at the reception machine on the first or fourth floor, using the ID or barcode below. - You can print out as many tickets as you need with this ID/barcode. - These admission tickets are required to enter and exit the building gates, so please hold on to them. + RECEPT_ID - Take the elevator on your left to the 17th floor. + + + 1. Hold the QR code in the attached PDF over the gate to enter the building. The same QR code can be used even if multiple people are visiting the company. + 2. Take the elevator for floors 15-20 to the 17th floor. + 3. Call the person in charge using the iPad at the SmartHR reception desk. + + Note: You will need your admission pass until you leave the building. Please keep it safe. + ``` <<: *common diff --git a/spec/app/models/admission_code_message_spec.rb b/spec/app/models/admission_code_message_spec.rb index 8d71d81..5418871 100644 --- a/spec/app/models/admission_code_message_spec.rb +++ b/spec/app/models/admission_code_message_spec.rb @@ -8,26 +8,31 @@ describe AdmissionCodeMessage do let(:instance) { described_class.new(email) } let(:email) { Email.new(email_fixture) } + let!(:slack_id) { "U9999999999" } + let!(:reception_name) { "株式会社smarthr hoge piyo" } + let!(:invite_date) { "2024/12/18(Wed) 10:00" } + let!(:reception_id) { "12345" } let!(:email_fixture) do <<~EMAIL_BODY - To:【U9999999999】株式会社smarthr hoge piyo 様
-
- 平素は格別なご高配を賜り、厚く御礼申し上げます。株式会社SmartHRです。
- 以下のとおり入館申請を行いました。
- I have registered your admission application as below.
- =========================================================
-
- ■ ご来訪日時/Date
- 2022/03/31(Thu) 18:00
-
- ■ ビル/Building
- 住友不動産六本木グランドタワー/SUMITOMO FUDOSAN ROPPONGI GRAND TOWER
-
- ■ バーコード/Barcode
- 入館ID/Guest ID:12345678901
-
-   
-
+ To:【#{slack_id}】#{reception_name} 様
\r +
\r + 平素は格別なご高配を賜り、厚く御礼申し上げます。株式会社SmartHRです。
\r + 以下のとおり入館申請を行いました。
\r + I have registered your admission application as below.
\r +
\r + =========================================================
\r +
\r + ■ ご来訪日時/Date
\r + #{invite_date}
\r +
\r + ■ ビル/Building
\r + 住友不動産六本木グランドタワー/SUMITOMO FUDOSAN ROPPONGI GRAND TOWER
\r + 東京都港区六本木3-2-1
\r +
\r + ■ 入館ID/Guest ID
\r + #{reception_id}
\r +
\r +
\r EMAIL_BODY end @@ -81,6 +86,7 @@ end it "API post body が返ってくる" do expected = { + as_user: true, icon_emoji: ":office:", channel: "CH15TJXEX", text: "<@U9999999999> 入館受付が完了しました :tada:", @@ -99,10 +105,10 @@ value: "株式会社smarthr hoge piyo 様", short: true }, { title: "訪問日時", - value: "2022/03/31(Thu) 18:00", + value: invite_date, short: true }, { title: "入館ID", - value: "12345678901", + value: reception_id, short: true } ] expect(instance.send(:attachment_fields)).to eq expected diff --git a/spec/app/models/chat_message_sender_spec.rb b/spec/app/models/chat_message_sender_spec.rb new file mode 100644 index 0000000..5777fc6 --- /dev/null +++ b/spec/app/models/chat_message_sender_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe ChatMessageSender do + let(:slack_client_mock) { instance_double(Slack::Web::Client) } + before do + allow(Slack::Web::Client).to receive(:new).and_return(slack_client_mock) + end + describe "#post_public_message" do + let(:sender) { described_class.new } + let(:post_body) do + { + icon_emoji: ":smile:", + channel: "C1234567890", + text: "Hello", + as_user: true + } + end + + it "@slack_api_client.chat_postMessageにpost_bodyが渡される" do + allow(slack_client_mock).to receive(:chat_postMessage).with(post_body) + + sender.post_public_message(post_body) + expect(slack_client_mock).to have_received(:chat_postMessage).with(post_body) + end + end + + describe "#post_admission_badge_message" do + let(:sender) { described_class.new(direct_message_id: "U1234567890") } + context "file_pathsが空の場合" do + before do + allow(slack_client_mock).to receive(:chat_postMessage) + end + it "post_bodyに失敗メッセージがセットされてchat_postMessageが呼ばれる" do + expect { sender.post_admission_badge_message({}) }.to raise_error("File Path is empty. Check Zapier Settings.") + expect(slack_client_mock).to have_received(:chat_postMessage) + end + + it "raiseエラーが発生する" do + expect { sender.post_admission_badge_message({}) }.to raise_error("File Path is empty. Check Zapier Settings.") + end + end + + context "file_pathsが空でない場合" do + let(:post_body) do + { + icon_emoji: ":office:", + channel: "U1234567890", + text: "Hello", + as_user: true + } + end + let(:file_paths) { ["./tmp/qrcode.pdf"] } + let(:files) do + [ + { id: "F1234567890", title: "qrcode.pdf" } + ] + end + let(:posted_message_ts) { "1234567890" } + + let(:upload_url_external_response) do + Hashie::Mash.new({ + file_id: "F1234567890", + title: "qrcode.pdf", + url: "https://example.com/file_path", + file_path: "./tmp/qrcode.pdf" + }) + end + + let(:faraday_mock) { instance_double(Faraday::Connection) } + let(:faraday_upload_io_mock) { instance_double(Faraday::UploadIO) } + + let(:file_klass_mock) { class_double(File) } + before do + allow(Faraday).to receive(:new).and_return(faraday_mock) + allow(faraday_mock).to receive(:post) + + allow(Faraday::UploadIO).to receive(:new).and_return(faraday_upload_io_mock) + + allow(File).to receive(:size).with(file_paths[0]).and_return(100) + allow(File).to receive(:basename).with(file_paths[0]).and_return("qrcode.pdf") + + allow(slack_client_mock).to receive(:chat_postMessage).with(post_body).and_return( + Hashie::Mash.new({ ts: posted_message_ts }) + ) + allow(slack_client_mock).to receive(:files_completeUploadExternal) + allow(slack_client_mock).to receive(:files_getUploadURLExternal).and_return(upload_url_external_response) + end + + it "post_bodyにファイルが添付されてchat_postMessageが呼ばれる" do + sender.post_admission_badge_message(post_body, file_paths: file_paths) + expect(slack_client_mock).to have_received(:chat_postMessage).with(post_body) + end + + it "ファイルをfaradayでアップロードする" do + sender.post_admission_badge_message(post_body, file_paths: file_paths) + expect(faraday_mock).to have_received(:post) + end + + it "ファイルアップロードが完了させる" do + sender.post_admission_badge_message(post_body, file_paths: file_paths) + expect(slack_client_mock).to have_received(:files_completeUploadExternal).with( + files: files.to_json, + channel_id: "U1234567890", + thread_ts: posted_message_ts + ) + end + end + end +end diff --git a/spec/app/models/email_spec.rb b/spec/app/models/email_spec.rb index 9cbc8d9..73c09a2 100644 --- a/spec/app/models/email_spec.rb +++ b/spec/app/models/email_spec.rb @@ -4,41 +4,47 @@ require_relative "../../../app/models/email" describe Email do - context "正常系" do - email_html_body = <<~EMAIL_BODY - To:【U9999999999】株式会社smarthr hoge piyo 様
-
- 平素は格別なご高配を賜り、厚く御礼申し上げます。株式会社SmartHRです。
- 以下のとおり入館申請を行いました。
- I have registered your admission application as below.
- =========================================================
-
- ■ ご来訪日時/Date
- 2022/03/31(Thu) 18:00
-
- ■ ビル/Building
- 住友不動産六本木グランドタワー/SUMITOMO FUDOSAN ROPPONGI GRAND TOWER
-
- ■ バーコード/Barcode
- 入館ID/Guest ID:12345678901
-
-   
-
+ let!(:slack_id) { "U9999999999" } + let!(:reception_name) { "株式会社smarthr hoge piyo" } + let!(:invite_date) { "2024/12/18(Wed) 10:00" } + let!(:reception_id) { "12345" } + let!(:email_fixture) do + <<~EMAIL_BODY + To:【#{slack_id}】#{reception_name} 様
\r +
\r + 平素は格別なご高配を賜り、厚く御礼申し上げます。株式会社SmartHRです。
\r + 以下のとおり入館申請を行いました。
\r + I have registered your admission application as below.
\r +
\r + =========================================================
\r +
\r + ■ ご来訪日時/Date
\r + #{invite_date}
\r +
\r + ■ ビル/Building
\r + 住友不動産六本木グランドタワー/SUMITOMO FUDOSAN ROPPONGI GRAND TOWER
\r + 東京都港区六本木3-2-1
\r +
\r + ■ 入館ID/Guest ID
\r + #{reception_id}
\r +
\r +
\r EMAIL_BODY - - email = described_class.new(email_html_body) + end + context "正常系" do + let!(:email) { described_class.new(email_fixture) } it "slack_id" do - expect(email.slack_id).to eq "U9999999999" + expect(email.slack_id).to eq slack_id end it "recept_name" do - expect(email.recept_name).to eq "株式会社smarthr hoge piyo" + expect(email.recept_name).to eq reception_name end it "recept_date" do - expect(email.recept_date).to eq "2022/03/31(Thu) 18:00" + expect(email.recept_date).to eq invite_date end it "recept_id" do - expect(email.recept_id).to eq "12345678901" + expect(email.recept_id).to eq reception_id end end end diff --git a/spec/app/models/slack_message_spec.rb b/spec/app/models/slack_message_spec.rb index e1849b0..def6bdc 100644 --- a/spec/app/models/slack_message_spec.rb +++ b/spec/app/models/slack_message_spec.rb @@ -31,6 +31,7 @@ allow(ENV).to receive(:fetch).with("SEND_MODE").and_return("CHANNEL") expected = { + as_user: true, channel: "UCKTXCBRB", icon_emoji: ":office:", text: "以下の内容で受け付けました。受け付け完了までしばらくお待ちください :pray:", @@ -48,6 +49,7 @@ allow(ENV).to receive(:fetch).with("SEND_MODE").and_return("DM") expected = { + as_user: true, channel: "UCKTXCBRB", icon_emoji: ":office:", text: "以下の内容で受け付けました。受け付け完了までしばらくお待ちください :pray: \n受付が完了すると入館IDとバーコードがslackbotで届きます:mailbox_with_mail:", diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3496965..b6c9162 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,103 +1,17 @@ require "active_support" require "active_support/testing/time_helpers" -# This file was generated by the `rspec --init` command. Conventionally, all -# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. -# The generated `.rspec` file contains `--require spec_helper` which will cause -# this file to always be loaded, without a need to explicitly require it in any -# files. -# -# Given that it is always loaded, you are encouraged to keep this file as -# light-weight as possible. Requiring heavyweight dependencies from this file -# will add to the boot time of your test suite on EVERY test run, even for an -# individual file that may not need all of that loaded. Instead, consider making -# a separate helper file that requires the additional dependencies and performs -# the additional setup, and require it from the spec files that actually need -# it. -# -# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +Dir[File.expand_path("../../app/**/*.rb", __FILE__)].each { |file| require_relative file } + RSpec.configure do |config| - # rspec-expectations config goes here. You can use an alternate - # assertion/expectation library such as wrong or the stdlib/minitest - # assertions if you prefer. config.expect_with :rspec do |expectations| - # This option will default to `true` in RSpec 4. It makes the `description` - # and `failure_message` of custom matchers include text for helper methods - # defined using `chain`, e.g.: - # be_bigger_than(2).and_smaller_than(4).description - # # => "be bigger than 2 and smaller than 4" - # ...rather than: - # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end - # rspec-mocks config goes here. You can use an alternate test double - # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| - # Prevents you from mocking or stubbing a method that does not exist on - # a real object. This is generally recommended, and will default to - # `true` in RSpec 4. mocks.verify_partial_doubles = true end - # This option will default to `:apply_to_host_groups` in RSpec 4 (and will - # have no way to turn it off -- the option exists only for backwards - # compatibility in RSpec 3). It causes shared context metadata to be - # inherited by the metadata hash of host groups and examples, rather than - # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups - -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode - config.disable_monkey_patching! - - # This setting enables warnings. It's recommended, but in some cases may - # be too noisy due to issues in dependencies. - config.warnings = true - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end - config.include ActiveSupport::Testing::TimeHelpers end