cert_watch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +151 -0
- data/Rakefile +16 -0
- data/app/assets/javascripts/cert_watch/application.js +13 -0
- data/app/assets/stylesheets/cert_watch/application.css +15 -0
- data/app/controllers/cert_watch/application_controller.rb +5 -0
- data/app/jobs/cert_watch/install_certificate_job.rb +16 -0
- data/app/jobs/cert_watch/renew_certificate_job.rb +16 -0
- data/app/jobs/cert_watch/renew_expiring_certificates_job.rb +13 -0
- data/app/models/cert_watch/certificate.rb +42 -0
- data/app/views/layouts/cert_watch/application.html.erb +14 -0
- data/config/locales/de.yml +47 -0
- data/config/locales/en.yml +47 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20160711193700_create_certificates.rb +18 -0
- data/lib/cert_watch/CHANGELOG.md +5 -0
- data/lib/cert_watch/certbot_client.rb +32 -0
- data/lib/cert_watch/client.rb +7 -0
- data/lib/cert_watch/configuration.rb +37 -0
- data/lib/cert_watch/domain_owner.rb +23 -0
- data/lib/cert_watch/engine.rb +10 -0
- data/lib/cert_watch/error.rb +4 -0
- data/lib/cert_watch/install_error.rb +4 -0
- data/lib/cert_watch/installer.rb +7 -0
- data/lib/cert_watch/pem_directory_installer.rb +55 -0
- data/lib/cert_watch/renew_error.rb +4 -0
- data/lib/cert_watch/sanitize.rb +13 -0
- data/lib/cert_watch/shell.rb +20 -0
- data/lib/cert_watch/version.rb +3 -0
- data/lib/cert_watch/views/all.rb +3 -0
- data/lib/cert_watch/views/certificate_state.rb +42 -0
- data/lib/cert_watch.rb +32 -0
- data/spec/cert_watch/certbot_client_spec.rb +53 -0
- data/spec/cert_watch/domain_owner_spec.rb +62 -0
- data/spec/cert_watch/pem_directory_installer_spec.rb +75 -0
- data/spec/cert_watch/sanitize_spec.rb +19 -0
- data/spec/cert_watch/shell_spec.rb +19 -0
- data/spec/cert_watch/views/certificate_state_spec.rb +33 -0
- data/spec/examples.txt +40 -0
- data/spec/factories/certificates.rb +6 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +7 -0
- data/spec/internal/log/jobs/test/cert_watch.log +3793 -0
- data/spec/internal/log/test.log +24187 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/jobs/cert_watch/renew_expiring_certificates_job_spec.rb +43 -0
- data/spec/models/cert_watch/certificate_spec.rb +102 -0
- data/spec/rails_helper.rb +45 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/config/cert_watch.rb +7 -0
- data/spec/support/config/factory_girl.rb +11 -0
- data/spec/support/config/resque_logger.rb +16 -0
- data/spec/support/config/timecop.rb +21 -0
- data/spec/support/helpers/doubles.rb +28 -0
- data/spec/support/helpers/fixtures.rb +25 -0
- data/spec/support/helpers/inline_resque.rb +9 -0
- data/spec/support/helpers/view_component_example_group.rb +31 -0
- metadata +298 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
module CertWatch
|
2
|
+
module Sanitize
|
3
|
+
extend self
|
4
|
+
|
5
|
+
class ForbiddenCharacters < Error; end
|
6
|
+
|
7
|
+
def check_domain!(name)
|
8
|
+
if name =~ /[^0-9a-zA-Z.-]/
|
9
|
+
fail(ForbiddenCharacters, "Domain '#{name}' contains forbidden characters.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CertWatch
|
2
|
+
module Shell
|
3
|
+
extend self
|
4
|
+
|
5
|
+
class CommandFailed < Error; end
|
6
|
+
|
7
|
+
def sudo(command)
|
8
|
+
output, input = IO.pipe
|
9
|
+
prefix = !Rails.env.test? ? 'sudo ' : ''
|
10
|
+
full_command = [prefix, command].join
|
11
|
+
|
12
|
+
result = system(full_command, [:out, :err] => input)
|
13
|
+
input.close
|
14
|
+
|
15
|
+
unless result
|
16
|
+
fail(CommandFailed, "Command '#{full_command}' failed with output:\n\n#{output.read}\n")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'arbre'
|
2
|
+
require 'active_admin'
|
3
|
+
|
4
|
+
module CertWatch
|
5
|
+
module Views
|
6
|
+
class CertificateState < Arbre::Component
|
7
|
+
builder_method :cert_watch_certificate_state
|
8
|
+
|
9
|
+
STATE_MAPPING = {
|
10
|
+
'installed' => 'ok',
|
11
|
+
'installing' => 'warn',
|
12
|
+
'renewing' => 'warn',
|
13
|
+
'installing_failed' => 'error',
|
14
|
+
'renewing_failed' => 'error'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def build(certificate_or_domain, options = {})
|
18
|
+
state = get_state(certificate_or_domain)
|
19
|
+
format = options.fetch(:format, 'short')
|
20
|
+
|
21
|
+
add_class 'cert_watch_certificate_state'
|
22
|
+
|
23
|
+
status_tag(t(state, scope: "cert_watch.states.#{format}"),
|
24
|
+
[state, STATE_MAPPING[state]].compact.join(' '))
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def get_state(certificate_or_domain)
|
30
|
+
get_certificate(certificate_or_domain).try(:state) || 'not_found'
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_certificate(certificate_or_domain)
|
34
|
+
if certificate_or_domain.is_a?(String)
|
35
|
+
Certificate.find_by_domain(certificate_or_domain)
|
36
|
+
else
|
37
|
+
certificate_or_domain
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/cert_watch.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'cert_watch/engine'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
def self.config
|
5
|
+
fail('Call CertWatch.setup before accessing CertWatch.config') unless @config
|
6
|
+
@config
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.setup
|
10
|
+
@config = Configuration.new
|
11
|
+
yield @config if block_given?
|
12
|
+
|
13
|
+
self.client = CertbotClient.new(executable: config.certbot_executable,
|
14
|
+
port: config.certbot_port)
|
15
|
+
|
16
|
+
self.installer = PemDirectoryInstaller.new(pem_directory: config.pem_directory,
|
17
|
+
input_directory: config.certbot_output_directory,
|
18
|
+
reload_command: config.server_reload_command)
|
19
|
+
end
|
20
|
+
|
21
|
+
mattr_accessor :client
|
22
|
+
|
23
|
+
mattr_accessor :installer
|
24
|
+
|
25
|
+
def self.active_admin_load_path
|
26
|
+
Dir[CertWatch::Engine.root.join('admin')].first
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.domain_owner(options)
|
30
|
+
DomainOwner.define(options)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
RSpec.describe CertbotClient do
|
5
|
+
let(:shell) do
|
6
|
+
instance_double('Shell', sudo: nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:client) do
|
10
|
+
CertbotClient.new(executable: '/usr/bin/certbot',
|
11
|
+
port: 99,
|
12
|
+
shell: shell)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'invokes given executable' do
|
16
|
+
client.renew('some.example.com')
|
17
|
+
|
18
|
+
expect(shell).to have_received(:sudo).with(a_string_including('/usr/bin/certbot'))
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'passes given port' do
|
22
|
+
client.renew('some.example.com')
|
23
|
+
|
24
|
+
expect(shell).to have_received(:sudo).with(a_string_including('--http-01-port 99'))
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'passes domain' do
|
28
|
+
client.renew('some.example.com')
|
29
|
+
|
30
|
+
expect(shell).to have_received(:sudo).with(a_string_including('-d some.example.com'))
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'fails if domain contains forbidden characters' do
|
34
|
+
expect do
|
35
|
+
client.renew('some.example.com;" rm *')
|
36
|
+
end.to raise_error(Sanitize::ForbiddenCharacters)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'passes --renew-by-default flag' do
|
40
|
+
client.renew('some.example.com')
|
41
|
+
|
42
|
+
expect(shell).to have_received(:sudo).with(a_string_including('--renew-by-default'))
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'fails with RenewError if shell command fails' do
|
46
|
+
allow(shell).to receive(:sudo).and_raise(Shell::CommandFailed)
|
47
|
+
|
48
|
+
expect do
|
49
|
+
client.renew('some.example.com')
|
50
|
+
end.to raise_error(RenewError)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
require 'support/helpers/doubles'
|
4
|
+
|
5
|
+
module CertWatch
|
6
|
+
RSpec.describe DomainOwner do
|
7
|
+
let(:test_domain_owner_model) do
|
8
|
+
Class.new(ActiveRecord::Base) do
|
9
|
+
self.table_name = :test_domain_owner
|
10
|
+
include CertWatch.domain_owner(attribute: :cname)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'renews certificate on create' do
|
15
|
+
test_domain_owner_model.create!(cname: 'new.example.com')
|
16
|
+
|
17
|
+
certificate = Certificate.find_by_domain('new.example.com')
|
18
|
+
|
19
|
+
expect(certificate).to be_present
|
20
|
+
expect(certificate.state).to eq('renewing')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'renews certificate on update' do
|
24
|
+
domain_owner = test_domain_owner_model.create!(cname: 'old.example.com')
|
25
|
+
|
26
|
+
domain_owner.update!(cname: 'new.example.com')
|
27
|
+
certificate = Certificate.find_by_domain('new.example.com')
|
28
|
+
|
29
|
+
expect(certificate).to be_present
|
30
|
+
expect(certificate.state).to eq('renewing')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not renew certificate when cname is unchanged' do
|
34
|
+
domain_owner = test_domain_owner_model.create!(name: 'Old', cname: 'some.example.com')
|
35
|
+
certificate = Certificate.find_by_domain!('some.example.com')
|
36
|
+
certificate.update!(state: 'installed')
|
37
|
+
|
38
|
+
domain_owner.update!(name: 'New')
|
39
|
+
|
40
|
+
expect(certificate.reload.state).to eq('installed')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not create certificate when new cname is blank' do
|
44
|
+
domain_owner = test_domain_owner_model.create!(name: 'Old', cname: 'some.example.com')
|
45
|
+
certificate = Certificate.find_by_domain!('some.example.com')
|
46
|
+
certificate.update!(state: 'installed')
|
47
|
+
|
48
|
+
expect do
|
49
|
+
domain_owner.update!(cname: '')
|
50
|
+
end.not_to change { Certificate.count }
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'abandons old certificate on update' do
|
54
|
+
domain_owner = test_domain_owner_model.create!(cname: 'old.example.com')
|
55
|
+
|
56
|
+
domain_owner.update!(cname: 'new.example.com')
|
57
|
+
certificate = Certificate.find_by_domain('old.example.com')
|
58
|
+
|
59
|
+
expect(certificate.state).to eq('abandoned')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
RSpec.describe PemDirectoryInstaller, fixture_files: true do
|
5
|
+
before do
|
6
|
+
Fixtures.file('live/some.example.com/fullchain.pem', "FULL CHAIN\n")
|
7
|
+
Fixtures.file('live/some.example.com/privkey.pem', "PRIVATE KEY\n")
|
8
|
+
Fixtures.directory('ssl')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'concatenates full chain and private key files' do
|
12
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
13
|
+
pem_directory: 'ssl',
|
14
|
+
reload_command: 'touch reload.txt')
|
15
|
+
installer.install('some.example.com')
|
16
|
+
|
17
|
+
expect(File.read('ssl/some.example.com.pem')).to eq("FULL CHAIN\nPRIVATE KEY\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'invokes reload command' do
|
21
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
22
|
+
pem_directory: 'ssl',
|
23
|
+
reload_command: 'touch reload.txt')
|
24
|
+
installer.install('some.example.com')
|
25
|
+
|
26
|
+
expect(File.exist?('reload.txt')).to eq(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'fails with InstallError if reload command fails' do
|
30
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
31
|
+
pem_directory: 'ssl',
|
32
|
+
reload_command: './not_there')
|
33
|
+
expect do
|
34
|
+
installer.install('some.example.com')
|
35
|
+
end.to raise_error(InstallError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'fails with InstallError if input files not found' do
|
39
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
40
|
+
pem_directory: 'ssl')
|
41
|
+
expect do
|
42
|
+
installer.install('not-there.example.com')
|
43
|
+
end.to raise_error(InstallError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not create output file if input files not found' do
|
47
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
48
|
+
pem_directory: 'ssl')
|
49
|
+
|
50
|
+
begin
|
51
|
+
installer.install('not-there.example.com')
|
52
|
+
rescue InstallError
|
53
|
+
end
|
54
|
+
|
55
|
+
expect(File.exist?('ssl/not-there.example.com.pem')).to eq(false)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'fails with InstallError if output directory does not exist' do
|
59
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
60
|
+
pem_directory: 'not-there')
|
61
|
+
expect do
|
62
|
+
installer.install('some.example.com')
|
63
|
+
end.to raise_error(InstallError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'fails if domain contains forbidden characters' do
|
67
|
+
installer = PemDirectoryInstaller.new(input_directory: 'live',
|
68
|
+
pem_directory: 'ssl')
|
69
|
+
|
70
|
+
expect do
|
71
|
+
installer.install('some.*example ".com')
|
72
|
+
end.to raise_error(Sanitize::ForbiddenCharacters)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
RSpec.describe Sanitize do
|
5
|
+
describe '.check_domain!' do
|
6
|
+
it 'fails if string contains forbidden characters' do
|
7
|
+
expect do
|
8
|
+
Sanitize.check_domain!('some.12-; rm *"')
|
9
|
+
end.to raise_error(Sanitize::ForbiddenCharacters)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'handles multi line strings' do
|
13
|
+
expect do
|
14
|
+
Sanitize.check_domain!("valid\n rm *")
|
15
|
+
end.to raise_error(Sanitize::ForbiddenCharacters)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
RSpec.describe Shell, fixture_files: true do
|
5
|
+
describe '.sudo' do
|
6
|
+
it 'runs given command' do
|
7
|
+
Shell.sudo('echo "test" > foo')
|
8
|
+
|
9
|
+
expect(File.read('foo')).to eq("test\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'raises CommandFailed with output if command fails' do
|
13
|
+
expect do
|
14
|
+
Shell.sudo('LANG=en touch not/there')
|
15
|
+
end.to raise_error(Shell::CommandFailed, /cannot touch/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module CertWatch
|
4
|
+
module Views
|
5
|
+
RSpec.describe CertificateState, type: :view_component do
|
6
|
+
context 'with string argument' do
|
7
|
+
it 'renders status tag for matching certificate' do
|
8
|
+
create(:certificate, state: 'installed', domain: 'my.example.com')
|
9
|
+
|
10
|
+
render(:cert_watch_certificate_state, 'my.example.com')
|
11
|
+
|
12
|
+
expect(rendered).to have_selector('.status_tag.installed')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'renders status tag if certificate is not found' do
|
16
|
+
render(:cert_watch_certificate_state, 'not.there.com')
|
17
|
+
|
18
|
+
expect(rendered).to have_selector('.status_tag.not_found')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with certificate argument' do
|
23
|
+
it 'renders status tag for certificate' do
|
24
|
+
certificate = create(:certificate, state: 'installed', domain: 'my.example.com')
|
25
|
+
|
26
|
+
render(:cert_watch_certificate_state, certificate)
|
27
|
+
|
28
|
+
expect(rendered).to have_selector('.status_tag.installed')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/examples.txt
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
------------------------------------------------------------------- | ------ | --------------- |
|
3
|
+
./spec/cert_watch/certbot_client_spec.rb[1:1] | passed | 0.00682 seconds |
|
4
|
+
./spec/cert_watch/certbot_client_spec.rb[1:2] | passed | 0.00204 seconds |
|
5
|
+
./spec/cert_watch/certbot_client_spec.rb[1:3] | passed | 0.00203 seconds |
|
6
|
+
./spec/cert_watch/certbot_client_spec.rb[1:4] | passed | 0.00201 seconds |
|
7
|
+
./spec/cert_watch/certbot_client_spec.rb[1:5] | passed | 0.00223 seconds |
|
8
|
+
./spec/cert_watch/certbot_client_spec.rb[1:6] | passed | 0.0029 seconds |
|
9
|
+
./spec/cert_watch/domain_owner_spec.rb[1:1] | passed | 0.04831 seconds |
|
10
|
+
./spec/cert_watch/domain_owner_spec.rb[1:2] | passed | 0.07414 seconds |
|
11
|
+
./spec/cert_watch/domain_owner_spec.rb[1:3] | passed | 0.06987 seconds |
|
12
|
+
./spec/cert_watch/domain_owner_spec.rb[1:4] | passed | 0.08508 seconds |
|
13
|
+
./spec/cert_watch/domain_owner_spec.rb[1:5] | passed | 0.08109 seconds |
|
14
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:1] | passed | 0.03524 seconds |
|
15
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:2] | passed | 0.05051 seconds |
|
16
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:3] | passed | 0.03625 seconds |
|
17
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:4] | passed | 0.01778 seconds |
|
18
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:5] | passed | 0.02905 seconds |
|
19
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:6] | passed | 0.02549 seconds |
|
20
|
+
./spec/cert_watch/pem_directory_installer_spec.rb[1:7] | passed | 0.00437 seconds |
|
21
|
+
./spec/cert_watch/sanitize_spec.rb[1:1:1] | passed | 0.00192 seconds |
|
22
|
+
./spec/cert_watch/sanitize_spec.rb[1:1:2] | passed | 0.00219 seconds |
|
23
|
+
./spec/cert_watch/shell_spec.rb[1:1:1] | passed | 0.01215 seconds |
|
24
|
+
./spec/cert_watch/shell_spec.rb[1:1:2] | passed | 0.01372 seconds |
|
25
|
+
./spec/cert_watch/views/certificate_state_spec.rb[1:1:1] | passed | 0.0096 seconds |
|
26
|
+
./spec/cert_watch/views/certificate_state_spec.rb[1:1:2] | passed | 0.00646 seconds |
|
27
|
+
./spec/cert_watch/views/certificate_state_spec.rb[1:2:1] | passed | 0.03833 seconds |
|
28
|
+
./spec/jobs/cert_watch/renew_expiring_certificates_job_spec.rb[1:1] | passed | 0.01287 seconds |
|
29
|
+
./spec/jobs/cert_watch/renew_expiring_certificates_job_spec.rb[1:2] | passed | 0.0086 seconds |
|
30
|
+
./spec/jobs/cert_watch/renew_expiring_certificates_job_spec.rb[1:3] | passed | 0.0154 seconds |
|
31
|
+
./spec/jobs/cert_watch/renew_expiring_certificates_job_spec.rb[1:4] | passed | 0.01655 seconds |
|
32
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:1] | passed | 0.01814 seconds |
|
33
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:2] | passed | 0.03732 seconds |
|
34
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:3] | passed | 0.01962 seconds |
|
35
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:4] | passed | 0.02037 seconds |
|
36
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:5] | passed | 0.01938 seconds |
|
37
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:6:1] | passed | 0.01556 seconds |
|
38
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:6:2] | passed | 0.01882 seconds |
|
39
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:7:1] | passed | 0.02004 seconds |
|
40
|
+
./spec/models/cert_watch/certificate_spec.rb[1:1:7:2] | passed | 0.02153 seconds |
|
Binary file
|