Commit 7b754e5

mo khan <mo@mokhan.ca>
2021-07-01 17:31:23
chore: add ci job and fix linter errors
1 parent 1c62fd0
.github/workflows/ci.yml
@@ -0,0 +1,26 @@
+name: ci
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: '2.6'
+          bundler-cache: true
+      - run: ./bin/setup
+      - run: ./bin/test
+  style:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: '2.6'
+      - run: ./bin/setup
+      - run: ./bin/style
bin/console
@@ -1,8 +1,8 @@
 #!/usr/bin/env ruby
 # frozen_string_literal: true
 
-require 'bundler/setup'
-require 'minbox'
+require "bundler/setup"
+require "minbox"
 
 # You can add fixtures and/or initialization code here to make experimenting
 # with your gem easier. You can also use a different console, if you like.
@@ -11,5 +11,5 @@ require 'minbox'
 # require "pry"
 # Pry.start
 
-require 'irb'
+require "irb"
 IRB.start(__FILE__)
bin/lint → bin/style
File renamed without changes
exe/minbox
@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 # frozen_string_literal: true
 
-require 'minbox/cli'
+require "minbox/cli"
 
 Minbox::Cli::Application.start(ARGV)
lib/minbox/cli.rb
@@ -1,33 +1,33 @@
 # frozen_string_literal: true
 
-require 'mail'
-require 'net/smtp'
-require 'openssl'
-require 'thor'
+require "mail"
+require "net/smtp"
+require "openssl"
+require "thor"
 
-require 'minbox'
+require "minbox"
 
 module Minbox
   module Cli
     class Application < Thor
-      package_name 'minbox'
+      package_name "minbox"
 
-      method_option :from, type: :string, default: 'me@example.org'
-      method_option :to, type: :string, default: ['them@example.org']
+      method_option :from, type: :string, default: "me@example.org"
+      method_option :to, type: :string, default: ["them@example.org"]
       method_option :subject, type: :string, default: "#{Time.now} This is a test message."
       method_option :body, type: :string, default: "#{Time.now} This is a test message."
-      desc 'send <HOST> <PORT>', 'Send mail to SMTP server'
-      def send(host = 'localhost', port = 25)
+      desc "send <HOST> <PORT>", "Send mail to SMTP server"
+      def send(host = "localhost", port = 25)
         Net::SMTP.start(host, port) do |smtp|
           smtp.debug_output = Minbox.logger
           smtp.send_message(create_mail(options).to_s, options[:from], options[:to])
         end
       end
 
-      method_option :output, type: :array, default: ['stdout']
+      method_option :output, type: :array, default: ["stdout"]
       method_option :tls, type: :boolean, default: false
-      desc 'server <HOST> <PORT>', 'SMTP server'
-      def server(host = 'localhost', port = '25')
+      desc "server <HOST> <PORT>", "SMTP server"
+      def server(host = "localhost", port = "25")
         publisher = Publisher.from(options[:output])
         server = Server.new(host: host, port: port, tls: options[:tls])
         server.listen! do |mail|
@@ -35,7 +35,7 @@ module Minbox
         end
       end
 
-      desc 'version', 'Display the current version'
+      desc "version", "Display the current version"
       def version
         say Minbox::VERSION
       end
lib/minbox/client.rb
@@ -3,62 +3,62 @@
 module Minbox
   class Ehlo
     def run(client, line)
-      _ehlo, _client_domain = line.split(' ')
+      _ehlo, _client_domain = line.split(" ")
       client.write "250-#{client.server.host} offers a warm hug of welcome"
-      client.write '250-8BITMIME'
-      client.write '250-ENHANCEDSTATUSCODES'
+      client.write "250-8BITMIME"
+      client.write "250-ENHANCEDSTATUSCODES"
       # client.write "250 STARTTLS"
-      client.write '250-AUTH PLAIN LOGIN'
-      client.write '250 OK'
+      client.write "250-AUTH PLAIN LOGIN"
+      client.write "250 OK"
     end
   end
 
   class Helo
     def run(client, line)
-      _ehlo, _client_domain = line.split(' ')
+      _ehlo, _client_domain = line.split(" ")
       client.write "250 #{client.server.host}"
     end
   end
 
   class Noop
     def run(client, _line)
-      client.write '250 OK'
+      client.write "250 OK"
     end
   end
 
   class Quit
     def run(client, _line)
-      client.write '221 Bye'
+      client.write "221 Bye"
       client.close
     end
   end
 
   class Data
     def run(client, _line)
-      client.write '354 End data with <CR><LF>.<CR><LF>'
+      client.write "354 End data with <CR><LF>.<CR><LF>"
       body = []
       line = client.read
       until line.nil? || line.match(/^\.\r\n$/)
         body << line
         line = client.read
       end
-      client.write '250 OK'
+      client.write "250 OK"
       yield(Mail.new(body.join)) unless body.empty?
     end
   end
 
   class StartTls
     def run(client, _line)
-      client.write '220 Ready to start TLS'
+      client.write "220 Ready to start TLS"
       client.secure_socket!
     end
   end
 
   class AuthPlain
     def run(client, line)
-      data = line.gsub(/AUTH PLAIN ?/i, '')
-      if data.strip == ''
-        client.write '334'
+      data = line.gsub(/AUTH PLAIN ?/i, "")
+      if data.strip == ""
+        client.write "334"
         data = client.read
       end
       parts = Base64.decode64(data).split("\0")
@@ -70,12 +70,12 @@ module Minbox
 
   class AuthLogin
     def run(client, line)
-      username = line.gsub!(/AUTH LOGIN ?/i, '')
-      if username.strip == ''
-        client.write '334 VXNlcm5hbWU6'
+      username = line.gsub!(/AUTH LOGIN ?/i, "")
+      if username.strip == ""
+        client.write "334 VXNlcm5hbWU6"
         username = client.read
       end
-      client.write '334 UGFzc3dvcmQ6'
+      client.write "334 UGFzc3dvcmQ6"
       password = Base64.decode64(client.read)
       client.authenticate(username, password)
     end
@@ -84,7 +84,7 @@ module Minbox
   class Unsupported
     def run(client, line)
       client.logger.error(line)
-      client.write '502 Invalid/unsupported command'
+      client.write "502 Invalid/unsupported command"
     end
   end
 
@@ -152,9 +152,9 @@ module Minbox
 
     def authenticate(username, password)
       logger.debug("#{username}:#{password}")
-      return write '535 Authenticated failed - protocol error' unless username && password
+      return write "535 Authenticated failed - protocol error" unless username && password
 
-      write '235 2.7.0 Authentication successful'
+      write "235 2.7.0 Authentication successful"
     end
   end
 end
lib/minbox/inbox.rb
@@ -16,10 +16,8 @@ module Minbox
     end
 
     def emails(count: 0)
-      wait_until { |x| x.count >= count } if count > 0
-      with_lock do |emails|
-        emails.values
-      end
+      wait_until { |x| x.count >= count } if count.positive?
+      with_lock(&:values)
     end
 
     def wait_until(seconds: 10, wait: 0.1)
@@ -52,14 +50,14 @@ module Minbox
     end
 
     def each
-      @emails.each do |id, email|
+      @emails.each do |_id, email|
         yield email
       end
     end
 
     private
 
-    def changed(modified, added, removed)
+    def changed(_modified, added, removed)
       with_lock do |emails|
         added.each do |file|
           mail = Mail.read(file)
lib/minbox/publisher.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require 'redis'
+require "redis"
 
 module Minbox
   class LogPublisher
@@ -19,7 +19,7 @@ module Minbox
     end
 
     def publish(mail)
-      @redis.publish('minbox', mail.to_s)
+      @redis.publish("minbox", mail.to_s)
     end
   end
 
@@ -27,7 +27,7 @@ module Minbox
     attr_reader :dir
 
     def initialize(dir = Dir.pwd)
-      @dir = File.join(dir, 'tmp')
+      @dir = File.join(dir, "tmp")
       FileUtils.mkdir_p(@dir)
     end
 
lib/minbox/server.rb
@@ -2,10 +2,10 @@
 
 module Minbox
   class Server
-    SUBJECT = '/C=CA/ST=AB/L=Calgary/O=minbox/OU=development/CN=minbox'
+    SUBJECT = "/C=CA/ST=AB/L=Calgary/O=minbox/OU=development/CN=minbox"
     attr_reader :host, :logger, :key, :server
 
-    def initialize(host: 'localhost', port: 25, tls: false, logger: Minbox.logger, thread_pool: Concurrent::CachedThreadPool.new)
+    def initialize(host: "localhost", port: 25, tls: false, logger: Minbox.logger, thread_pool: Concurrent::CachedThreadPool.new)
       @host = host
       @logger = logger
       @tls = tls
@@ -21,7 +21,7 @@ module Minbox
 
     def listen!(&block)
       @server = upgrade(@server) if tls?
-      logger.debug('Server started!')
+      logger.debug("Server started!")
 
       loop do
         handle(server.accept, &block)
@@ -78,8 +78,8 @@ module Minbox
       extensions.subject_certificate =
         extensions.issuer_certificate = certificate
       [
-        ['subjectKeyIdentifier', 'hash', false],
-        ['keyUsage', 'keyEncipherment,digitalSignature', true],
+        ["subjectKeyIdentifier", "hash", false],
+        ["keyUsage", "keyEncipherment,digitalSignature", true],
       ].each do |x|
         certificate.add_extension(extensions.create_extension(x[0], x[1], x[2]))
       end
lib/minbox/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Minbox
-  VERSION = '0.1.6'
+  VERSION = "0.1.6"
 end
lib/minbox.rb
@@ -1,18 +1,18 @@
 # frozen_string_literal: true
 
-require 'base64'
-require 'concurrent'
-require 'hashie'
-require 'listen'
-require 'logger'
-require 'socket'
+require "base64"
+require "concurrent"
+require "hashie"
+require "listen"
+require "logger"
+require "socket"
 
-require 'minbox/client'
-require 'minbox/core'
-require 'minbox/inbox'
-require 'minbox/publisher'
-require 'minbox/server'
-require 'minbox/version'
+require "minbox/client"
+require "minbox/core"
+require "minbox/inbox"
+require "minbox/publisher"
+require "minbox/server"
+require "minbox/version"
 
 module Minbox
   class Error < StandardError; end
spec/minbox/inbox_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 RSpec.describe Minbox::Inbox do
-  subject! { described_class.instance(root_dir: 'tmp') }
+  subject! { described_class.instance(root_dir: "tmp") }
 
   before do
     IO.write("tmp/1.eml", Mail.new do
@@ -17,11 +17,11 @@ RSpec.describe Minbox::Inbox do
   end
 
   after do
-    FileUtils.rm(Dir.glob('tmp/*.eml'))
+    FileUtils.rm(Dir.glob("tmp/*.eml"))
   end
 
   describe "#empty!" do
-    before :example do
+    before do
       IO.write("tmp/3.eml", Mail.new do
         to Faker::Internet.email
         from Faker::Internet.email
@@ -34,20 +34,20 @@ RSpec.describe Minbox::Inbox do
   end
 
   describe "#emails" do
-    specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(['[ACTION] goodbye world', 'hello world']) }
+    specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(["[ACTION] goodbye world", "hello world"]) }
   end
 
   describe "#wait_until!" do
     context "when the condition is satisfied" do
       before { subject.wait_until! { |x| x.count == 2 } }
 
-      specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(['[ACTION] goodbye world', 'hello world']) }
+      specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(["[ACTION] goodbye world", "hello world"]) }
     end
 
     context "when the condition is not satisfied" do
       specify do
         expect do
-          subject.wait_until!(seconds: 0.1) { |inbox| false }
+          subject.wait_until!(seconds: 0.1) { |_inbox| false }
         end.to raise_error(/timeout/)
       end
     end
@@ -55,10 +55,10 @@ RSpec.describe Minbox::Inbox do
 
   describe "#open" do
     context "when opening an email by subject" do
-      specify { expect(subject.open(subject: '[ACTION] goodbye world').subject).to eql('[ACTION] goodbye world') }
-      specify { expect(subject.open(subject: /goodbye/).subject).to eql('[ACTION] goodbye world') }
-      specify { expect(subject.open(subject: /hello/).subject).to eql('hello world') }
-      specify { expect(subject.open(subject: /world/).subject).to eql('hello world') }
+      specify { expect(subject.open(subject: "[ACTION] goodbye world").subject).to eql("[ACTION] goodbye world") }
+      specify { expect(subject.open(subject: /goodbye/).subject).to eql("[ACTION] goodbye world") }
+      specify { expect(subject.open(subject: /hello/).subject).to eql("hello world") }
+      specify { expect(subject.open(subject: /world/).subject).to eql("hello world") }
     end
 
     context "when opening an email not in the inbox" do
spec/minbox/server_spec.rb
@@ -1,22 +1,22 @@
 # frozen_string_literal: true
 
 RSpec.describe Minbox::Server do
-  describe '#handle' do
-    let(:host) { 'localhost' }
+  describe "#handle" do
+    let(:host) { "localhost" }
     let(:port) { 8080 }
 
-    context 'when handling a simple client' do
+    context "when handling a simple client" do
       def create_mail(to: Faker::Internet.email, from: Faker::Internet.email)
         Mail.new do |x|
           x.from from
           x.to to
-          x.subject 'test message'
+          x.subject "test message"
           x.body "#{Time.now} This is a test message."
           yield x if block_given?
         end
       end
 
-      context 'when sending a single email' do
+      context "when sending a single email" do
         let(:result) do
           Net::SMTP.start(host, port) do |smtp|
             smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
@@ -27,7 +27,7 @@ RSpec.describe Minbox::Server do
         specify { expect(result.status.to_i).to be(250) }
       end
 
-      context 'when sending multiple emails' do
+      context "when sending multiple emails" do
         let(:n) { rand(10) }
         let(:result) do
           Net::SMTP.start(host, port) do |smtp|
@@ -40,9 +40,9 @@ RSpec.describe Minbox::Server do
         specify { expect(result).to eql(n) }
       end
 
-      context 'with plain authentication' do
+      context "with plain authentication" do
         let(:result) do
-          Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :plain) do |smtp|
+          Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :plain) do |smtp|
             smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
           end
         end
@@ -51,9 +51,9 @@ RSpec.describe Minbox::Server do
         specify { expect(result.status.to_i).to be(250) }
       end
 
-      context 'with login authentication' do
+      context "with login authentication" do
         let(:result) do
-          Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :login) do |smtp|
+          Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :login) do |smtp|
             smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
           end
         end
@@ -62,12 +62,12 @@ RSpec.describe Minbox::Server do
         specify { expect(result.status.to_i).to be(250) }
       end
 
-      context 'with attachment' do
+      context "with attachment" do
         let(:result) do
           mail = create_mail do |x|
             x.add_file __FILE__
           end
-          Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :login) do |smtp|
+          Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :login) do |smtp|
             smtp.debug_output = STDOUT
             smtp.send_message(mail.to_s, Faker::Internet.email, Faker::Internet.email)
           end
@@ -77,14 +77,14 @@ RSpec.describe Minbox::Server do
         specify { expect(result.status.to_i).to be(250) }
       end
 
-      context 'with html part' do
+      context "with html part" do
         let(:result) do
           mail = create_mail do |x|
             x.text_part do
-              body 'this is plain text'
+              body "this is plain text"
             end
             x.html_part do
-              body '<h1>this is html</h1>'
+              body "<h1>this is html</h1>"
             end
           end
           Net::SMTP.start(host, port) do |smtp|
@@ -96,20 +96,21 @@ RSpec.describe Minbox::Server do
         specify { expect(result.status.to_i).to be(250) }
       end
 
-      context 'when upgrading to tls' do
+      context "when upgrading to tls" do
         let(:result) do
           `(echo 'QUIT'; sleep 1) | openssl s_client -connect #{host}:#{port} -starttls smtp 2>&1`
         end
 
         specify { expect(result).to end_with("DONE\n") }
-        specify { expect(result).to include('250 OK') }
+        specify { expect(result).to include("250 OK") }
       end
 
-      context 'when sending multiple emails from multiple threads' do
+      context "when sending multiple emails from multiple threads" do
         let!(:email) { Faker::Internet.email }
         let!(:mail) { create_mail }
         let!(:mail_string) { mail.to_s }
 
+        # rubocop:disable RSpec/ExampleLength
         specify do
           threads = []
           10.times do |_n|
@@ -126,6 +127,7 @@ RSpec.describe Minbox::Server do
             threads.map(&:join)
           end
         end
+        # rubocop:enable RSpec/ExampleLength
       end
     end
   end
spec/spec_helper.rb
@@ -1,14 +1,14 @@
 # frozen_string_literal: true
 
-require 'bundler/setup'
-require 'minbox'
-require 'net/smtp'
-require 'mail'
-require 'faker'
+require "bundler/setup"
+require "minbox"
+require "net/smtp"
+require "mail"
+require "faker"
 
 RSpec.configure do |config|
   # Enable flags like --only-failures and --next-failure
-  config.example_status_persistence_file_path = '.rspec_status'
+  config.example_status_persistence_file_path = ".rspec_status"
 
   # Disable RSpec exposing methods globally on `Module` and `main`
   config.disable_monkey_patching!
.rubocop.yml
@@ -69,6 +69,9 @@ Metrics/LineLength:
   IgnoredPatterns:
     - '^#*'
 
+Naming/RescuedExceptionsVariableName:
+  PreferredName: error
+
 Style/Documentation:
   Enabled: false
 
@@ -76,7 +79,7 @@ Style/EachWithObject:
   Enabled: false
 
 Style/StringLiterals:
-  EnforcedStyle: 'single_quotes'
+  EnforcedStyle: 'double_quotes'
 
 Style/TrailingCommaInArrayLiteral:
   Enabled: false
Gemfile
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-source 'https://rubygems.org'
+source "https://rubygems.org"
 
 # Specify your gem's dependencies in minbox.gemspec
 gemspec
minbox.gemspec
@@ -1,29 +1,29 @@
 # frozen_string_literal: true
 
-lib = File.expand_path('lib', __dir__)
+lib = File.expand_path("lib", __dir__)
 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'minbox/version'
+require "minbox/version"
 
 Gem::Specification.new do |spec|
-  spec.name          = 'minbox'
+  spec.name          = "minbox"
   spec.version       = Minbox::VERSION
-  spec.authors       = ['mo khan']
-  spec.email         = ['mo@mokhan.ca']
+  spec.authors       = ["mo khan"]
+  spec.email         = ["mo@mokhan.ca"]
 
-  spec.summary       = 'A minimal smtp server.'
-  spec.description   = 'A minimal smtp server.'
-  spec.homepage      = 'https://www.mokhan.ca/'
-  spec.license       = 'MIT'
+  spec.summary       = "A minimal smtp server."
+  spec.description   = "A minimal smtp server."
+  spec.homepage      = "https://www.mokhan.ca/"
+  spec.license       = "MIT"
 
   # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
   # to allow pushing to a single host or delete this section to allow pushing to any host.
   if spec.respond_to?(:metadata)
-    spec.metadata['homepage_uri'] = spec.homepage
-    spec.metadata['source_code_uri'] = 'https://github.com/xlgmokha/minbox'
-    spec.metadata['changelog_uri'] = 'https://github.com/xlgmokha/minbox/blob/CHANGELOG.md'
+    spec.metadata["homepage_uri"] = spec.homepage
+    spec.metadata["source_code_uri"] = "https://github.com/xlgmokha/minbox"
+    spec.metadata["changelog_uri"] = "https://github.com/xlgmokha/minbox/blob/CHANGELOG.md"
   else
-    raise 'RubyGems 2.0 or newer is required to protect against ' \
-      'public gem pushes.'
+    raise "RubyGems 2.0 or newer is required to protect against " \
+      "public gem pushes."
   end
 
   # Specify which files should be added to the gem when it is released.
@@ -31,21 +31,21 @@ Gem::Specification.new do |spec|
   spec.files = Dir.chdir(File.expand_path(__dir__)) do
     `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
   end
-  spec.bindir        = 'exe'
+  spec.bindir        = "exe"
   spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
-  spec.require_paths = ['lib']
+  spec.require_paths = ["lib"]
 
-  spec.add_dependency 'concurrent-ruby', '~> 1.1'
-  spec.add_dependency 'hashie', '~> 3.6'
-  spec.add_dependency 'listen', '~> 3.1'
-  spec.add_dependency 'mail', '~> 2.7'
-  spec.add_dependency 'redis', '~> 4.1'
-  spec.add_dependency 'thor', '~> 0.20'
-  spec.add_development_dependency 'bundler', '~> 2.0'
-  spec.add_development_dependency 'bundler-audit', '~> 0.6'
-  spec.add_development_dependency 'faker', '~> 1.9'
-  spec.add_development_dependency 'rake', '~> 10.0'
-  spec.add_development_dependency 'rspec', '~> 3.0'
-  spec.add_development_dependency 'rubocop', '~> 0.52'
-  spec.add_development_dependency 'rubocop-rspec', '~> 1.22'
+  spec.add_dependency "concurrent-ruby", "~> 1.1"
+  spec.add_dependency "hashie", "~> 3.6"
+  spec.add_dependency "listen", "~> 3.1"
+  spec.add_dependency "mail", "~> 2.7"
+  spec.add_dependency "redis", "~> 4.1"
+  spec.add_dependency "thor", "~> 0.20"
+  spec.add_development_dependency "bundler", "~> 2.0"
+  spec.add_development_dependency "bundler-audit", "~> 0.6"
+  spec.add_development_dependency "faker", "~> 1.9"
+  spec.add_development_dependency "rake", "~> 10.0"
+  spec.add_development_dependency "rspec", "~> 3.0"
+  spec.add_development_dependency "rubocop", "~> 0.52"
+  spec.add_development_dependency "rubocop-rspec", "~> 1.22"
 end
Rakefile
@@ -1,13 +1,13 @@
 # frozen_string_literal: true
 
-require 'bundler/audit/task'
-require 'bundler/gem_tasks'
-require 'rspec/core/rake_task'
-require 'rubocop/rake_task'
+require "bundler/audit/task"
+require "bundler/gem_tasks"
+require "rspec/core/rake_task"
+require "rubocop/rake_task"
 
 RSpec::Core::RakeTask.new(:spec)
 RuboCop::RakeTask.new(:rubocop)
 Bundler::Audit::Task.new
 
-task lint: [:rubocop, 'bundle:audit']
+task lint: [:rubocop, "bundle:audit"]
 task default: :spec