cutting_edge 0.0.1 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -2
- data/Gemfile.lock +130 -0
- data/LICENSE +68 -81
- data/Procfile +1 -0
- data/README.md +272 -74
- data/Rakefile +0 -5
- data/bin/cutting_edge +60 -45
- data/config.rb +55 -0
- data/cutting_edge.gemspec +33 -4
- data/heroku.config.rb +20 -0
- data/lib/cutting_edge.rb +1 -1
- data/lib/cutting_edge/app.rb +95 -19
- data/lib/cutting_edge/langs.rb +7 -1
- data/lib/cutting_edge/langs/python.rb +5 -1
- data/lib/cutting_edge/langs/ruby.rb +5 -1
- data/lib/cutting_edge/langs/rust.rb +5 -1
- data/lib/cutting_edge/public/images/error.svg +24 -0
- data/lib/cutting_edge/public/images/languages/python.svg +1 -0
- data/lib/cutting_edge/public/images/languages/ruby.svg +1 -0
- data/lib/cutting_edge/public/images/languages/rust.svg +1 -0
- data/lib/cutting_edge/public/images/ok.svg +24 -0
- data/lib/cutting_edge/public/javascript/clipboard.min.js +7 -0
- data/lib/cutting_edge/public/javascript/cuttingedge.js +53 -0
- data/lib/cutting_edge/public/stylesheets/primer.css +22 -0
- data/lib/cutting_edge/repo.rb +124 -18
- data/lib/cutting_edge/templates/_footer.html.erb +3 -0
- data/lib/cutting_edge/templates/_header.html.erb +8 -0
- data/lib/cutting_edge/templates/_overview.html.erb +9 -0
- data/lib/cutting_edge/templates/badge.svg.erb +39 -0
- data/lib/cutting_edge/templates/index.html.erb +62 -0
- data/lib/cutting_edge/templates/info.html.erb +101 -0
- data/lib/cutting_edge/templates/mail.html.erb +163 -0
- data/lib/cutting_edge/workers/badge.rb +33 -11
- data/lib/cutting_edge/workers/dependency.rb +36 -16
- data/lib/cutting_edge/workers/helpers.rb +8 -0
- data/lib/cutting_edge/workers/mail.rb +38 -0
- data/projects.yml +25 -0
- data/spec/app_spec.rb +115 -0
- data/spec/badge_worker_spec.rb +77 -0
- data/spec/dependency_worker_spec.rb +132 -0
- data/spec/email_worker_spec.rb +43 -0
- data/spec/fixtures.rb +180 -0
- data/spec/fixtures/projects.yml +27 -0
- data/spec/langs/python_spec.rb +47 -5
- data/spec/langs/ruby_spec.rb +105 -0
- data/spec/langs/rust_spec.rb +31 -0
- data/spec/repo_spec.rb +52 -0
- data/spec/spec_helper.rb +9 -1
- metadata +43 -15
- data/lib/cutting_edge/badge.rb +0 -46
@@ -14,6 +14,14 @@ module WorkerHelpers
|
|
14
14
|
def get_from_store(identifier)
|
15
15
|
::CuttingEdge::App.store[identifier]
|
16
16
|
end
|
17
|
+
|
18
|
+
def badge_worker(identifier)
|
19
|
+
BadgeWorker.perform_async(identifier)
|
20
|
+
end
|
21
|
+
|
22
|
+
def mail_worker(identifier, to_address)
|
23
|
+
MailWorker.perform_async(identifier, to_address)
|
24
|
+
end
|
17
25
|
|
18
26
|
end
|
19
27
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path('../helpers.rb', __FILE__)
|
2
|
+
require 'erb'
|
3
|
+
require 'mail'
|
4
|
+
|
5
|
+
module CuttingEdge
|
6
|
+
MAIL_TEMPLATE = File.read(File.expand_path('../../templates/mail.html.erb', __FILE__)) unless defined?(MAIL_TEMPLATE)
|
7
|
+
end
|
8
|
+
|
9
|
+
class MailWorker < GenericWorker
|
10
|
+
|
11
|
+
def perform(identifier, to_addr)
|
12
|
+
log_info('Running Worker!')
|
13
|
+
dependencies = get_from_store(identifier)
|
14
|
+
unless to_addr && dependencies
|
15
|
+
log_info("Failed to execute email job for #{identifier}: #{dependencies ? dependencies : 'No dependencies found.'} #{'No e-mail address set.' if to_addr.nil?}")
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
|
19
|
+
Mail.deliver do
|
20
|
+
from "CuttingEdge <#{CuttingEdge::MAIL_FROM}>"
|
21
|
+
to to_addr
|
22
|
+
subject "Dependency Status Changed For #{identifier}"
|
23
|
+
|
24
|
+
text_part do
|
25
|
+
body "Dependency Status Update For #{identifier} By CuttingEdge"
|
26
|
+
end
|
27
|
+
|
28
|
+
html_part do
|
29
|
+
content_type 'text/html; charset=UTF-8'
|
30
|
+
body ERB.new(CuttingEdge::MAIL_TEMPLATE).result_with_hash(
|
31
|
+
project: identifier,
|
32
|
+
url: CuttingEdge::SERVER_URL,
|
33
|
+
specs: dependencies
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/projects.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Example projects.yml file for CuttingEdge
|
2
|
+
# For more information and options, see: https://github.com/repotag/cutting_edge
|
3
|
+
|
4
|
+
github:
|
5
|
+
repotag:
|
6
|
+
cutting_edge:
|
7
|
+
language: ruby
|
8
|
+
hide: mysecret
|
9
|
+
gollum:
|
10
|
+
gollum:
|
11
|
+
language: ruby
|
12
|
+
gollum-lib:
|
13
|
+
locations: [gemspec.rb]
|
14
|
+
dependency_types: [runtime, development]
|
15
|
+
singingwolfboy:
|
16
|
+
flask-dance:
|
17
|
+
language: python
|
18
|
+
email: false
|
19
|
+
rust-lang:
|
20
|
+
crates.io:
|
21
|
+
language: rust
|
22
|
+
gitlab:
|
23
|
+
cthowl01:
|
24
|
+
team-chess-ruby:
|
25
|
+
locations: [Gemfile]
|
data/spec/app_spec.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CuttingEdgeHelpers do
|
4
|
+
it 'fails to load invalid projects.yml' do
|
5
|
+
expect(load_repositories('/tmp/fail.yml')).to be_nil
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe CuttingEdge::App do
|
10
|
+
before(:all) {
|
11
|
+
CuttingEdge::App.set(:repositories, load_repositories(File.expand_path('../fixtures/projects.yml', __FILE__)))
|
12
|
+
CuttingEdge::App.set(:store, Moneta.new(:Memory))
|
13
|
+
}
|
14
|
+
let(:app) { CuttingEdge::App.new }
|
15
|
+
|
16
|
+
context 'landing page' do
|
17
|
+
let(:response) { get '/' }
|
18
|
+
it 'returns status 200 OK' do
|
19
|
+
expect(response.status).to eq 200
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'unknown project' do
|
24
|
+
let(:project) { 'github/doesnt/exist' }
|
25
|
+
['/info', '/info/json', '/svg', '/refresh'].each do |route|
|
26
|
+
it "returns status 404 not found for #{route}" do
|
27
|
+
response = get("/#{project}#{route}")
|
28
|
+
expect(response.status).to eq 404
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'known project' do
|
34
|
+
let(:project) { 'github/gollum/gollum' }
|
35
|
+
before {
|
36
|
+
add_to_store("svg-#{project}", 'test')
|
37
|
+
add_to_store(project, {'test' => true})
|
38
|
+
}
|
39
|
+
{'/info' => 'text/html;charset=utf-8', '/info/json' => 'application/json', '/svg' => 'image/svg+xml'}.each do |route, type|
|
40
|
+
it "returns status 200 OK for #{route} with mime #{type}" do
|
41
|
+
response = get("/#{project}#{route}")
|
42
|
+
expect(response.status).to eq 200
|
43
|
+
expect(response.content_type).to eq type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns status 500 for a known project json without data' do
|
48
|
+
response = get("/github/gollum/gollum-lib/info/json")
|
49
|
+
# the store contains nothing for gollum-lib
|
50
|
+
expect(response.status).to eq 500
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'svgs have cache-control: no-cache' do
|
54
|
+
response = get("/#{project}/svg")
|
55
|
+
expect(response.headers['Cache-Control']).to eq 'no-cache'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'hidden repos' do
|
60
|
+
let(:project) { 'gitlab/gitlab-org/gitlab-foss' }
|
61
|
+
let(:repo) { CuttingEdge::App.repositories[project] }
|
62
|
+
|
63
|
+
it 'does not list hidden repos on the landing page' do
|
64
|
+
response = get('/')
|
65
|
+
expect(response.body).to include('team-chess-ruby')
|
66
|
+
expect(response.body).not_to include('gitlab-foss')
|
67
|
+
end
|
68
|
+
|
69
|
+
before {
|
70
|
+
::CuttingEdge::SECRET_TOKEN = 'secret'
|
71
|
+
add_to_store(project, {'test' => true})
|
72
|
+
}
|
73
|
+
after {
|
74
|
+
CuttingEdge.send(:remove_const, :SECRET_TOKEN)
|
75
|
+
}
|
76
|
+
|
77
|
+
it 'returns a JSON encoded HTML partial if the token is correct' do
|
78
|
+
response = post('/hidden_repos', "{\"token\":\"secret\"}")
|
79
|
+
expect(response.body).to include('gitlab-foss')
|
80
|
+
expect(JSON.parse(response.body)['partial']).to include "<div class=\"Box\">"
|
81
|
+
expect(JSON.parse(response.body)['partial']).to include "gitlab/gitlab-org/gitlab-foss"
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'only gives access to repo information with the repo hidden token' do
|
85
|
+
['/info', '/info/json', '/svg'].each do |route|
|
86
|
+
url = File.join('/', project, route)
|
87
|
+
unauthorized_response = get(url)
|
88
|
+
expect(unauthorized_response.status).to eq 404
|
89
|
+
authorized_response = get(url, token: repo.hidden_token)
|
90
|
+
expect(authorized_response.status).to eq 200
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'refreshing' do
|
97
|
+
let(:project) { 'github/gollum/gollum' }
|
98
|
+
before {
|
99
|
+
::CuttingEdge::SECRET_TOKEN = 'secret'
|
100
|
+
}
|
101
|
+
after {
|
102
|
+
CuttingEdge.send(:remove_const, :SECRET_TOKEN)
|
103
|
+
}
|
104
|
+
|
105
|
+
it 'fails with wrong token' do
|
106
|
+
response = post "/#{project}/refresh", :token => 'fail'
|
107
|
+
expect(response.status).to eq 401
|
108
|
+
end
|
109
|
+
it 'succeeds with right token' do
|
110
|
+
expect(DependencyWorker).to receive(:perform_async).exactly(:once)
|
111
|
+
response = post "/#{project}/refresh", token: 'secret'
|
112
|
+
expect(response.status).to eq 200
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
standard_svg = <<EOF
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="20" role="img" aria-label="CuttingEdge Dependency Status">
|
3
|
+
<title>CuttingEdge Dependency Status</title>
|
4
|
+
<linearGradient id="s" x2="0" y2="100%">
|
5
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
6
|
+
<stop offset="1" stop-opacity=".1"/>
|
7
|
+
</linearGradient>
|
8
|
+
<clipPath id="r">
|
9
|
+
<rect width="100" height="20" rx="3" fill="#fff"/>
|
10
|
+
</clipPath>
|
11
|
+
<g clip-path="url(#r)">
|
12
|
+
<rect width="35" height="20" fill="#555"/>
|
13
|
+
|
14
|
+
<rect x="25" width="25" height="20" fill="#4c1"/>
|
15
|
+
|
16
|
+
<rect x="50" width="25" height="20" fill="#fe7d37"/>
|
17
|
+
|
18
|
+
<rect x="75" width="25" height="20" fill="#e05d44"/>
|
19
|
+
|
20
|
+
<rect width="100" height="20" fill="url(#s)"/>
|
21
|
+
</g>
|
22
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
|
23
|
+
|
24
|
+
<text aria-hidden="true" x="125" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">CE</text>
|
25
|
+
<text x="125" y="140" transform="scale(.1)" fill="#fff">CE</text>
|
26
|
+
|
27
|
+
|
28
|
+
<g>
|
29
|
+
<title>Ok: 3</title>
|
30
|
+
<text aria-hidden="true" x="370" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">3</text>
|
31
|
+
<text x="370" y="140" transform="scale(.1)" fill="#fff">3</text>
|
32
|
+
</g>
|
33
|
+
|
34
|
+
<g>
|
35
|
+
<title>Outdated Minor: 1</title>
|
36
|
+
<text aria-hidden="true" x="620" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">1</text>
|
37
|
+
<text x="620" y="140" transform="scale(.1)" fill="#fff">1</text>
|
38
|
+
</g>
|
39
|
+
|
40
|
+
<g>
|
41
|
+
<title>Outdated Major: 3</title>
|
42
|
+
<text aria-hidden="true" x="870" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">3</text>
|
43
|
+
<text x="870" y="140" transform="scale(.1)" fill="#fff">3</text>
|
44
|
+
</g>
|
45
|
+
|
46
|
+
</g>
|
47
|
+
</svg>
|
48
|
+
EOF
|
49
|
+
describe BadgeWorker do
|
50
|
+
let(:worker) { BadgeWorker.new }
|
51
|
+
let(:identifier) { 'github/gollum/gollum' }
|
52
|
+
|
53
|
+
let(:dependencies) {
|
54
|
+
mock_dependencies('gollum')
|
55
|
+
}
|
56
|
+
|
57
|
+
context 'performing' do
|
58
|
+
|
59
|
+
it 'generates an svg for outdated dependencies' do
|
60
|
+
expect(worker).to receive(:get_from_store).with(identifier).and_return(dependencies)
|
61
|
+
expect(worker).to receive(:add_to_store).with("svg-#{identifier}", standard_svg.chomp).and_return(true)
|
62
|
+
worker.perform(identifier)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'shows an error badge when there are no dependencies' do
|
66
|
+
expect(worker).to receive(:get_from_store).with(identifier).and_return(nil)
|
67
|
+
expect(worker).to receive(:add_to_store).with("svg-#{identifier}", CuttingEdge::BADGE_ERROR).and_return(true)
|
68
|
+
worker.perform(identifier)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'shows an ok badge when up to date' do
|
72
|
+
expect(worker).to receive(:get_from_store).with(identifier).and_return(mock_dependencies('ok'))
|
73
|
+
expect(worker).to receive(:add_to_store).with("svg-#{identifier}", CuttingEdge::BADGE_OK).and_return(true)
|
74
|
+
worker.perform(identifier)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
class MockResponse
|
2
|
+
def initialize(status, body)
|
3
|
+
@status = status
|
4
|
+
@body = body
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :status
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
@body
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe DependencyWorker do
|
15
|
+
let(:worker) { DependencyWorker.new }
|
16
|
+
let(:identifier) { 'github/gollum/gollum' }
|
17
|
+
let(:test_email) { 'cutting_edge@localhost' }
|
18
|
+
let(:lang) { 'ruby' }
|
19
|
+
let(:locations) {
|
20
|
+
{
|
21
|
+
'gollum.gemspec' => 'http://github.bla/gollum/gollum/gollum.gemspec',
|
22
|
+
'Gemfile' => 'http://github.bla/gollum/gollum/Gemfile'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
let(:dependency_types) { [:runtime] }
|
26
|
+
let(:old_dependencies) {
|
27
|
+
mock_dependencies('gollum')
|
28
|
+
}
|
29
|
+
let(:new_dependencies) {
|
30
|
+
mock_dependencies('gollum-updated')
|
31
|
+
}
|
32
|
+
|
33
|
+
context 'http fetching files' do
|
34
|
+
let(:response_ok) { MockResponse.new(200, 'body') }
|
35
|
+
let(:response_not_found) { MockResponse.new(404, 'Not found') }
|
36
|
+
let(:url) { locations.first.last }
|
37
|
+
before {
|
38
|
+
worker.instance_variable_set(:@provider, ::CuttingEdge::GitlabRepository)
|
39
|
+
}
|
40
|
+
|
41
|
+
it 'uses default headers' do
|
42
|
+
worker.instance_variable_set(:@provider, ::CuttingEdge::GithubRepository)
|
43
|
+
expect(HTTP).to receive(:headers).with({:accept => 'application/vnd.github.v3.raw'}).exactly(:once).and_call_original
|
44
|
+
expect_any_instance_of(HTTP::Client).to receive(:get).with(url).and_return(response_ok)
|
45
|
+
expect(worker.send(:http_get, url)).to eq 'body'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns body when http get is successful' do
|
49
|
+
expect_any_instance_of(HTTP::Client).to receive(:get).with(url).and_return(response_ok)
|
50
|
+
expect(worker.send(:http_get, url)).to eq 'body'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'uses an auth header when it is set for project' do
|
54
|
+
token = 'token'
|
55
|
+
header = "Bearer #{token}"
|
56
|
+
worker.instance_variable_set(:@auth_token, token)
|
57
|
+
expect(::CuttingEdge::GitlabRepository).to receive(:headers).with(token).exactly(:once).and_call_original
|
58
|
+
expect(HTTP).to receive(:headers).with(:authorization => 'Bearer token').exactly(:once).and_call_original
|
59
|
+
expect_any_instance_of(HTTP::Client).to receive(:get).with(url).and_return(response_ok)
|
60
|
+
expect(worker.send(:http_get, url)).to eq 'body'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns body when http get is unsuccessful' do
|
64
|
+
expect_any_instance_of(HTTP::Client).to receive(:get).with(url).and_return(response_not_found)
|
65
|
+
expect(worker.send(:http_get, url)).to be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'handles a timeout' do
|
69
|
+
expect_any_instance_of(HTTP::Client).to receive(:get).with(url).and_raise(HTTP::TimeoutError)
|
70
|
+
expect(worker).to receive(:log_info)
|
71
|
+
expect(worker.send(:http_get, url)).to be_nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'generates results for fetched requirements' do
|
76
|
+
worker.instance_variable_set(:@lang, RubyLang)
|
77
|
+
fetched = [RubyLang.unknown_dependency('unknown_requirement'), Gem::Version.new('1.0')]
|
78
|
+
result_no_requirement = {:no_requirement=>[{:latest=>"1.0", :name=>"unknown_requirement", :required=>"unknown", :type=>:runtime, :url=>nil}], :ok=>[], :outdated_major=>[], :outdated_minor=>[], :outdated_patch=>[], :unknown=>[]}
|
79
|
+
expect(worker.send(:get_results, [fetched], dependency_types)).to eq result_no_requirement
|
80
|
+
|
81
|
+
fetched = [Gem::Dependency.new('unknown_version', '1.0', :runtime), nil]
|
82
|
+
result_no_version = {:no_requirement=>[], :ok=>[], :outdated_major=>[], :outdated_minor=>[], :outdated_patch=>[], :unknown=>[{:latest=>"", :name=>"unknown_version", :required=>"= 1.0", :type=>:runtime, :url=>"https://rubygems.org/gems/unknown_version"}]}
|
83
|
+
expect(worker.send(:get_results, [fetched], dependency_types)).to eq result_no_version
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'performing' do
|
87
|
+
|
88
|
+
before(:each) {
|
89
|
+
expect(worker).to receive(:get_from_store).with(identifier).and_return(old_dependencies)
|
90
|
+
expect(worker).to receive(:http_get).and_return('fake').exactly(locations.length).times
|
91
|
+
}
|
92
|
+
|
93
|
+
context 'when the dependencies have changed' do
|
94
|
+
|
95
|
+
before(:each) {
|
96
|
+
locations.each_key do |loc|
|
97
|
+
expect(RubyLang).to receive(:parse_file).with(loc, 'fake').and_return(mock_fetched_requirements('gollum', loc, true))
|
98
|
+
end
|
99
|
+
expect(worker).to receive(:badge_worker).with(identifier).and_return(true)
|
100
|
+
expect(worker).to receive(:mail_worker).with(identifier, test_email).and_return(true)
|
101
|
+
}
|
102
|
+
|
103
|
+
it 'updates the store with newest dependencies' do
|
104
|
+
expect(worker).to receive(:add_to_store).with(identifier, new_dependencies).and_return(true)
|
105
|
+
expect(worker.instance_variable_get(:@nothing_changed)).to be_nil
|
106
|
+
worker.perform(identifier, lang, locations, dependency_types, test_email)
|
107
|
+
expect(worker.instance_variable_get(:@nothing_changed)).to be false
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when the dependencies have not changed' do
|
113
|
+
|
114
|
+
before(:each) {
|
115
|
+
locations.each_key do |loc|
|
116
|
+
expect(RubyLang).to receive(:parse_file).with(loc, 'fake').and_return(mock_fetched_requirements('gollum', loc, false))
|
117
|
+
end
|
118
|
+
expect(worker).not_to receive(:badge_worker)
|
119
|
+
expect(worker).not_to receive(:mail_worker)
|
120
|
+
}
|
121
|
+
|
122
|
+
it 'does not update the store' do
|
123
|
+
expect(worker).not_to receive(:add_to_store)
|
124
|
+
expect(worker.instance_variable_get(:@nothing_changed)).to be_nil
|
125
|
+
worker.perform(identifier, lang, locations, dependency_types, test_email)
|
126
|
+
expect(worker.instance_variable_get(:@nothing_changed)).to be true
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'mail'
|
2
|
+
Mail.defaults do
|
3
|
+
delivery_method :test
|
4
|
+
end
|
5
|
+
|
6
|
+
describe MailWorker do
|
7
|
+
let(:worker) { MailWorker.new }
|
8
|
+
let(:identifier) { 'github/repotag/cutting_edge' }
|
9
|
+
let(:test_email) { 'cutting_edge@localhost' }
|
10
|
+
let(:dependencies) {
|
11
|
+
mock_dependencies('gollum')
|
12
|
+
}
|
13
|
+
|
14
|
+
before(:each) {
|
15
|
+
expect(worker).to receive(:get_from_store).with(identifier).and_return(dependencies)
|
16
|
+
}
|
17
|
+
|
18
|
+
it 'returns nil when to address is nil ' do
|
19
|
+
expect(worker.perform(identifier, nil)).to eq nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sends an update mail' do
|
23
|
+
expect(Mail::TestMailer.deliveries).to be_empty
|
24
|
+
worker.perform(identifier, test_email)
|
25
|
+
expect(Mail::TestMailer.deliveries).to_not be_empty
|
26
|
+
|
27
|
+
mail = Mail::TestMailer.deliveries.first
|
28
|
+
expect(mail.from.first).to eq 'cutting_edge@localhost'
|
29
|
+
expect(mail.to.first).to eq test_email
|
30
|
+
expect(mail.subject).to eq "Dependency Status Changed For #{identifier}"
|
31
|
+
|
32
|
+
body = mail.body
|
33
|
+
expect(body.parts.length).to eq 2
|
34
|
+
|
35
|
+
html_body = body.parts.last.to_s
|
36
|
+
expect(html_body).to start_with('Content-Type: text/html')
|
37
|
+
expect(html_body).to include('This is <a href="http://localhost">CuttingEdge</a> informing you')
|
38
|
+
expect(html_body).to include("<a href=\"http://localhost/#{identifier}/info\">#{identifier}</a>")
|
39
|
+
expect(html_body).to include('In <b>gollum.gemspec</b>:')
|
40
|
+
expect(html_body).to include('<b>Outdated Major</b>:')
|
41
|
+
expect(html_body).to include('<li>rake ~> 12.3, >= 12.3.3 (latest: 13.0.1)</li>')
|
42
|
+
end
|
43
|
+
end
|