gemnasium 3.2.1
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.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +52 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +157 -0
- data/Rakefile +6 -0
- data/bin/gemnasium +43 -0
- data/features/gemnasium.feature +58 -0
- data/features/gemnasium_create.feature +13 -0
- data/features/gemnasium_install.feature +119 -0
- data/features/step_definitions/gemnasium_steps.rb +52 -0
- data/features/support/env.rb +7 -0
- data/gemnasium.gemspec +24 -0
- data/lib/gemnasium.rb +328 -0
- data/lib/gemnasium/configuration.rb +110 -0
- data/lib/gemnasium/connection.rb +42 -0
- data/lib/gemnasium/dependency_files.rb +55 -0
- data/lib/gemnasium/errors.rb +16 -0
- data/lib/gemnasium/options.rb +107 -0
- data/lib/gemnasium/version.rb +3 -0
- data/lib/templates/gemnasium.rake +24 -0
- data/lib/templates/gemnasium.yml +10 -0
- data/lib/templates/post-commit +7 -0
- data/spec/gemnasium/configuration_spec.rb +195 -0
- data/spec/gemnasium/connection_spec.rb +47 -0
- data/spec/gemnasium/dependency_files_spec.rb +80 -0
- data/spec/gemnasium/options_spec.rb +135 -0
- data/spec/gemnasium_spec.rb +520 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/gemnasium_helper.rb +86 -0
- metadata +177 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Gemnasium::Connection do
|
4
|
+
before do
|
5
|
+
stub_config
|
6
|
+
stub_requests
|
7
|
+
end
|
8
|
+
let(:connection) { Gemnasium::Connection.new }
|
9
|
+
|
10
|
+
describe 'initialize' do
|
11
|
+
it 'initializes a Net::HTTP object' do
|
12
|
+
expect(connection.instance_variable_get('@connection')).to be_kind_of(Net::HTTP)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'get' do
|
17
|
+
before { connection.get('/test_path') }
|
18
|
+
|
19
|
+
it 'issues a GET request' do
|
20
|
+
expect(WebMock).to have_requested(:get, api_url('/test_path'))
|
21
|
+
.with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'post' do
|
26
|
+
before { connection.post('/test_path', { foo: 'bar' }.to_json) }
|
27
|
+
|
28
|
+
it 'issues a POST request' do
|
29
|
+
expect(WebMock).to have_requested(:post, api_url('/test_path'))
|
30
|
+
.with(:body => {"foo"=>"bar"}, :headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'api_path_for' do
|
35
|
+
context 'base API path' do
|
36
|
+
it{ expect(connection.api_path_for('base')).to eql "/api/#{Gemnasium.config.api_version}" }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'projects API path' do
|
40
|
+
it{ expect(connection.api_path_for('projects')).to eql "/api/#{Gemnasium.config.api_version}/projects" }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'dependency files API path' do
|
44
|
+
it{ expect(connection.api_path_for('dependency_files')).to eql "/api/#{Gemnasium.config.api_version}/projects/#{Gemnasium.config.project_slug}/dependency_files" }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Gemnasium::DependencyFiles do
|
4
|
+
let(:project_path) { File.expand_path("#{File.dirname(__FILE__)}../../../")}
|
5
|
+
|
6
|
+
describe 'get_sha1s_hash' do
|
7
|
+
context 'with a non matching regexp' do
|
8
|
+
it 'returns an empty Hash' do
|
9
|
+
sha1s_hash = Gemnasium::DependencyFiles.get_sha1s_hash("#{project_path}/tmp")
|
10
|
+
|
11
|
+
expect(sha1s_hash).to be_kind_of Hash
|
12
|
+
expect(sha1s_hash).to be_empty
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with a mathing regexp' do
|
17
|
+
before { allow(Gemnasium).to receive_message_chain(:config, :ignored_paths).and_return([]) }
|
18
|
+
it 'returns a Hash of matching files and their git calculated hash' do
|
19
|
+
sha1s_hash = Gemnasium::DependencyFiles.get_sha1s_hash(project_path)
|
20
|
+
|
21
|
+
expect(sha1s_hash).to include({ 'gemnasium.gemspec' => git_hash('gemnasium.gemspec') })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with a dependency file in a subdirectory' do
|
26
|
+
let(:subdir_file_path) { 'tmp/ignored.gemspec' }
|
27
|
+
|
28
|
+
before do
|
29
|
+
FileUtils.touch(subdir_file_path)
|
30
|
+
allow(Gemnasium).to receive_message_chain(:config, :ignored_paths).and_return([])
|
31
|
+
end
|
32
|
+
after { File.delete(subdir_file_path) }
|
33
|
+
|
34
|
+
it 'returns a Hash of the subdirectory dependency file' do
|
35
|
+
sha1s_hash = Gemnasium::DependencyFiles.get_sha1s_hash(project_path)
|
36
|
+
|
37
|
+
expect(sha1s_hash).to have_key(subdir_file_path)
|
38
|
+
if File.exists?('Gemfile.lock')
|
39
|
+
expect(sha1s_hash).to have_key('Gemfile.lock')
|
40
|
+
else
|
41
|
+
pending "Warning: 'Gemfile.lock' not found, skipping test."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'which is ignored' do
|
46
|
+
before { allow(Gemnasium).to receive_message_chain(:config, :ignored_paths).and_return([/^tmp/, /[^\/]+\.lock/]) }
|
47
|
+
|
48
|
+
it 'returns a Hash of matching files without ignored ones' do
|
49
|
+
sha1s_hash = Gemnasium::DependencyFiles.get_sha1s_hash(project_path)
|
50
|
+
|
51
|
+
expect(sha1s_hash).to_not have_key(subdir_file_path)
|
52
|
+
expect(sha1s_hash).to_not have_key('Gemfile.lock')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'get_content_to_upload' do
|
59
|
+
context 'with no files' do
|
60
|
+
it 'returns an empty Hash' do
|
61
|
+
content_to_upload = Gemnasium::DependencyFiles.get_content_to_upload(project_path, [])
|
62
|
+
|
63
|
+
expect(content_to_upload).to be_kind_of Array
|
64
|
+
expect(content_to_upload).to be_empty
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with a mathing regexp' do
|
69
|
+
it 'returns a Hash of matching files and their git calculated hash' do
|
70
|
+
content_to_upload = Gemnasium::DependencyFiles.get_content_to_upload(project_path, ['gemnasium.gemspec'])
|
71
|
+
|
72
|
+
expect(content_to_upload).to eql([{ filename: 'gemnasium.gemspec', sha: git_hash('gemnasium.gemspec'), content: File.open('gemnasium.gemspec') {|io| io.read} }])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def git_hash(path)
|
79
|
+
%x( git hash-object #{path} ).strip
|
80
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Gemnasium::Options do
|
4
|
+
describe 'parse' do
|
5
|
+
context 'without options' do
|
6
|
+
it 'does not parse any option' do
|
7
|
+
options, parser = Gemnasium::Options.parse []
|
8
|
+
expect(options).to be_empty
|
9
|
+
expect(parser).to be_kind_of OptionParser
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'with version options' do
|
14
|
+
it 'understands short version' do
|
15
|
+
options, parser = Gemnasium::Options.parse ['-v']
|
16
|
+
expect(options).to eql({ show_version: true })
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'understands long version' do
|
20
|
+
options, parser = Gemnasium::Options.parse ['--version']
|
21
|
+
expect(options).to eql({ show_version: true })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with help options' do
|
26
|
+
it 'understands short version' do
|
27
|
+
options, parser = Gemnasium::Options.parse ['-h']
|
28
|
+
expect(options).to eql({ show_help: true })
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'understands long version' do
|
32
|
+
options, parser = Gemnasium::Options.parse ['--help']
|
33
|
+
expect(options).to eql({ show_help: true })
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with multiple options' do
|
38
|
+
it 'understands concatenated options' do
|
39
|
+
options, parser = Gemnasium::Options.parse ['-hv']
|
40
|
+
expect(options).to eql({ show_help: true, show_version: true })
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'understands separated options' do
|
44
|
+
options, parser = Gemnasium::Options.parse ['-v', '--help']
|
45
|
+
expect(options).to eql({ show_help: true, show_version: true })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with unsupported option' do
|
50
|
+
it 'raises an error' do
|
51
|
+
expect { Gemnasium::Options.parse ['--foo'] }.to raise_error OptionParser::ParseError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with unsupported subcommand' do
|
56
|
+
it 'raises an error' do
|
57
|
+
expect { Gemnasium::Options.parse ['hack'] }.to raise_error OptionParser::ParseError
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with unsupported option for a valid subcommand' do
|
62
|
+
it 'raises an error' do
|
63
|
+
expect { Gemnasium::Options.parse ['install', '--foo'] }.to raise_error OptionParser::ParseError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with valid subcommand' do
|
68
|
+
context '`create`' do
|
69
|
+
context 'with no options' do
|
70
|
+
it 'correctly set the options' do
|
71
|
+
options, parser = Gemnasium::Options.parse ['create']
|
72
|
+
expect(options).to eql({ command: 'create' })
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context '`install`' do
|
78
|
+
context 'with no options' do
|
79
|
+
it 'correctly set the options' do
|
80
|
+
options, parser = Gemnasium::Options.parse ['install']
|
81
|
+
expect(options).to eql({ command: 'install' })
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with rake option' do
|
86
|
+
it 'correctly set the options' do
|
87
|
+
options, parser = Gemnasium::Options.parse ['install', '--rake']
|
88
|
+
expect(options).to eql({ command: 'install', install_rake_task: true })
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context '`push`' do
|
94
|
+
context 'with no options' do
|
95
|
+
it 'correctly set the options' do
|
96
|
+
options, parser = Gemnasium::Options.parse ['push']
|
97
|
+
expect(options).to eql({ command: 'push' })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'with ignore branch options' do
|
102
|
+
it 'correctly set the options' do
|
103
|
+
options, parser = Gemnasium::Options.parse ['push', '--ignore-branch']
|
104
|
+
expect(options).to eql({ command: 'push', ignore_branch: true })
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with silence branch failure options' do
|
109
|
+
it 'correctly set the options' do
|
110
|
+
options, parser = Gemnasium::Options.parse ['push', '--silence-branch']
|
111
|
+
expect(options).to eql({ command: 'push', silence_branch: true })
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context '`migrate`' do
|
117
|
+
context 'with no options' do
|
118
|
+
it 'correctly set the options' do
|
119
|
+
options, parser = Gemnasium::Options.parse ['migrate']
|
120
|
+
expect(options).to eql({ command: 'migrate' })
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context '`resolve`' do
|
126
|
+
context 'with no options' do
|
127
|
+
it 'correctly set the options' do
|
128
|
+
options, parser = Gemnasium::Options.parse ['resolve']
|
129
|
+
expect(options).to eql({ command: 'resolve' })
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,520 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'an installed file' do
|
4
|
+
it 'creates the configuration file' do
|
5
|
+
expect(File.exists? target_path).to be_truthy
|
6
|
+
# Test that the files are identical comparing their MD5 hashes
|
7
|
+
template_file_md5 = Digest::MD5.hexdigest(File.read(template_path))
|
8
|
+
new_file_md5 = Digest::MD5.hexdigest(File.read(target_path))
|
9
|
+
expect(new_file_md5).to eql template_file_md5
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'informs the user that the file has been created' do
|
13
|
+
expect(output).to include "File created in #{target_path}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples_for 'a command that requires a compatible config file' do
|
18
|
+
context 'with an old configuration file' do
|
19
|
+
before { stub_config(needs_to_migrate?: true) }
|
20
|
+
|
21
|
+
it 'quits the program' do
|
22
|
+
expect{ Gemnasium.push({ project_path: project_path }) }.to raise_error { |e|
|
23
|
+
expect(e).to be_kind_of SystemExit
|
24
|
+
expect(error_output).to include 'Your configuration file is not compatible with this version. Please run the `migrate` command first.'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe Gemnasium do
|
31
|
+
let(:output) { [] }
|
32
|
+
let(:error_output) { [] }
|
33
|
+
let(:project_path) { File.expand_path("#{File.dirname(__FILE__)}../../tmp") }
|
34
|
+
before do
|
35
|
+
allow(Gemnasium).to receive(:notify) { |arg| output << arg }
|
36
|
+
allow(Gemnasium).to receive(:quit_because_of) { |arg| error_output << arg && abort }
|
37
|
+
stub_config
|
38
|
+
stub_requests
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'push' do
|
42
|
+
|
43
|
+
it_should_behave_like 'a command that requires a compatible config file'
|
44
|
+
|
45
|
+
context 'on a non tracked branch' do
|
46
|
+
before { allow(Gemnasium).to receive(:current_branch).and_return('non_project_branch') }
|
47
|
+
|
48
|
+
context 'with supported dependency files for gemnasium project not up-to-date' do
|
49
|
+
let(:sha1_hash) {{ 'new_gemspec.gemspec' => 'gemspec_sha1_hash', 'modified_lockfile.lock' => 'lockfile_sha1_hash', 'Gemfile_unchanged.lock' => 'gemfile_sha1_hash' }}
|
50
|
+
let(:hash_to_upload) {[{ filename: 'new_gemspec.gemspec', sha: 'gemspec_sha1_hash', content: 'stubbed gemspec content' },
|
51
|
+
{ filename: 'modified_lockfile.lock', sha: 'lockfile_sha1_hash', content: 'stubbed lockfile content' }]}
|
52
|
+
|
53
|
+
before do
|
54
|
+
allow(Gemnasium::DependencyFiles).to receive(:get_sha1s_hash).and_return(sha1_hash)
|
55
|
+
allow(Gemnasium::DependencyFiles).to receive(:get_content_to_upload).and_return(hash_to_upload)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'quit the program with an error' do
|
59
|
+
expect{ Gemnasium.push({ project_path: project_path }) }.to raise_error { |e|
|
60
|
+
expect(e).to be_kind_of SystemExit
|
61
|
+
expect(error_output).to include 'Not on tracked branch (master)'
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when :silence_branch is true" do
|
66
|
+
it "should not raise an error" do
|
67
|
+
expect{ Gemnasium.push({ project_path: project_path, silence_branch: true }) }.to_not raise_error
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should not contact Gemnasium' do
|
71
|
+
Gemnasium.push({ project_path: project_path, silence_branch: true })
|
72
|
+
expect(WebMock).to_not have_requested(:post, api_url('/api/v3/projects/existing-slug/dependency_files/compare'))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when :ignore_branch is true" do
|
77
|
+
it "should not raise an error" do
|
78
|
+
expect{ Gemnasium.push({ project_path: project_path, ignore_branch: true }) }.to_not raise_error
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should still contact Gemnasium' do
|
82
|
+
Gemnasium.push({ project_path: project_path, ignore_branch: true })
|
83
|
+
expect(WebMock).to have_requested(:post, api_url('/api/v3/projects/existing-slug/dependency_files/compare'))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'on the tracked branch' do
|
90
|
+
before { allow(Gemnasium).to receive(:current_branch).and_return('master') }
|
91
|
+
|
92
|
+
context 'with no project slug' do
|
93
|
+
before do
|
94
|
+
stub_config({ project_slug: nil })
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'quit the program with an error' do
|
98
|
+
expect{ Gemnasium.push({ project_path: project_path }) }.to raise_error { |e|
|
99
|
+
expect(e).to be_kind_of SystemExit
|
100
|
+
expect(error_output).to include 'Project slug not defined. Please create a new project or "resolve" the name of an existing project.'
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with no supported dependency files found' do
|
106
|
+
before { allow(Gemnasium::DependencyFiles).to receive(:get_sha1s_hash).and_return([]) }
|
107
|
+
|
108
|
+
it 'quit the program with an error' do
|
109
|
+
expect{ Gemnasium.push({ project_path: project_path }) }.to raise_error { |e|
|
110
|
+
expect(e).to be_kind_of SystemExit
|
111
|
+
expect(error_output).to include "No supported dependency files detected."
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with supported dependency files found' do
|
117
|
+
let(:sha1_hash) {{ 'new_gemspec.gemspec' => 'gemspec_sha1_hash', 'modified_lockfile.lock' => 'lockfile_sha1_hash', 'Gemfile_unchanged.lock' => 'gemfile_sha1_hash' }}
|
118
|
+
before { allow(Gemnasium::DependencyFiles).to receive(:get_sha1s_hash).and_return(sha1_hash) }
|
119
|
+
|
120
|
+
context 'for a gemnasium project already up-to-date' do
|
121
|
+
before do
|
122
|
+
stub_config({ project_slug: 'up_to_date_project' })
|
123
|
+
Gemnasium.push({ project_path: project_path })
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'quit the program with an error' do
|
127
|
+
expect(output).to include "The project's dependency files are up-to-date."
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'for gemnasium project not up-to-date' do
|
132
|
+
let(:hash_to_upload) {[{ filename: 'new_gemspec.gemspec', sha: 'gemspec_sha1_hash', content: 'stubbed gemspec content' },
|
133
|
+
{ filename: 'modified_lockfile.lock', sha: 'lockfile_sha1_hash', content: 'stubbed lockfile content' }]}
|
134
|
+
before do
|
135
|
+
allow(Gemnasium::DependencyFiles).to receive(:get_content_to_upload).and_return(hash_to_upload)
|
136
|
+
Gemnasium.push({ project_path: project_path })
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'informs the user that it found supported dependency files' do
|
140
|
+
expect(output).to include "#{sha1_hash.keys.count} supported dependency file(s) found: #{sha1_hash.keys.join(', ')}"
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'makes a request to Gemnasium to get updated files to upload' do
|
144
|
+
expect(WebMock).to have_requested(:post, api_url('/api/v3/projects/existing-slug/dependency_files/compare'))
|
145
|
+
.with(:body => sha1_hash.to_json,
|
146
|
+
:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'})
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'informs the user that Gemnasium has deleted an old dependency file' do
|
150
|
+
expect(output).to include "1 deleted file(s): old_dependency_file"
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'displays the list of files that are being uploaded' do
|
154
|
+
expect(output).to include "2 file(s) to upload: new_gemspec.gemspec, modified_lockfile.lock"
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'makes a request to Gemnasium to upload needed files' do
|
158
|
+
expect(WebMock).to have_requested(:post, api_url('/api/v3/projects/existing-slug/dependency_files/upload'))
|
159
|
+
.with(:body => hash_to_upload.to_json,
|
160
|
+
:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'})
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'informs the user of added file' do
|
164
|
+
expect(output).to include 'Added dependency files: ["new_gemspec.gemspec"]'
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'informs the user of updated file' do
|
168
|
+
expect(output).to include 'Updated dependency files: ["modified_lockfile.lockfile"]'
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'informs the user of unchanged file' do
|
172
|
+
expect(output).to_not include 'Unchanged dependency files: []'
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'informs the user of unsupported file' do
|
176
|
+
expect(output).to_not include 'Unsupported dependency files: []'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'create_project' do
|
184
|
+
|
185
|
+
it_should_behave_like 'a command that requires a compatible config file'
|
186
|
+
|
187
|
+
context 'with a project slug' do
|
188
|
+
before { stub_config({ project_slug: 'existing-slug' }) }
|
189
|
+
|
190
|
+
it 'quit the program with an error' do
|
191
|
+
expect{ Gemnasium.create_project({ project_path: project_path }) }.to raise_error { |e|
|
192
|
+
expect(e).to be_kind_of SystemExit
|
193
|
+
expect(error_output).to include "You already have a project slug refering to an existing project. Please remove this project slug from your configuration file to create a new project."
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'with no project slug' do
|
199
|
+
before { stub_config({ project_slug: nil }) }
|
200
|
+
|
201
|
+
it 'issues the correct request' do
|
202
|
+
Gemnasium.create_project({ project_path: project_path })
|
203
|
+
|
204
|
+
expect(WebMock).to have_requested(:post, api_url("/api/v3/projects"))
|
205
|
+
.with(:body => {name: "gemnasium-gem", branch: "master"},
|
206
|
+
:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'})
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'displays a confirmation message' do
|
210
|
+
Gemnasium.create_project({ project_path: project_path })
|
211
|
+
|
212
|
+
expect(output).to include 'Project `gemnasium-gem` successfully created.'
|
213
|
+
expect(output).to include 'Project slug is `new-slug`.'
|
214
|
+
expect(output).to include 'Remaining private slots: 9001'
|
215
|
+
expect(output).to include 'Your configuration file has been updated.'
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'updates the configuration file' do
|
219
|
+
expect(Gemnasium.config).to receive(:store_value!)
|
220
|
+
.with(:project_slug, 'new-slug', "This unique project slug has been set by `gemnasium create`.")
|
221
|
+
|
222
|
+
Gemnasium.create_project({ project_path: project_path })
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'with a read-only config file' do
|
227
|
+
before { stub_config({ project_slug: nil, writable?: false }) }
|
228
|
+
before { Gemnasium.create_project({ project_path: project_path }) }
|
229
|
+
|
230
|
+
it 'displays a confirmation message' do
|
231
|
+
expect(output).to include 'Project `gemnasium-gem` successfully created.'
|
232
|
+
expect(output).to include 'Project slug is `new-slug`.'
|
233
|
+
expect(output).to include 'Remaining private slots: 9001'
|
234
|
+
expect(output).to include 'Configuration file cannot be updated. Please edit the file and update the project slug manually.'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe 'migrate' do
|
240
|
+
|
241
|
+
context 'when the config file needs a migration' do
|
242
|
+
before { stub_config({ needs_to_migrate?: true }) }
|
243
|
+
|
244
|
+
it 'notifies the user' do
|
245
|
+
Gemnasium.migrate({ project_path: project_path })
|
246
|
+
expect(output).to include "Your configuration has been updated."
|
247
|
+
expect(output).to include "Run `gemnasium resolve` if your config is related to an existing project."
|
248
|
+
expect(output).to include "Run `gemnasium create` if you want to create a new project on Gemnasium."
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'updates the config file' do
|
252
|
+
expect(Gemnasium.config).to receive(:migrate!)
|
253
|
+
Gemnasium.migrate({ project_path: project_path })
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'when the config file is already up-to-date' do
|
258
|
+
before do
|
259
|
+
stub_config({ needs_to_migrate?: false })
|
260
|
+
Gemnasium.migrate({ project_path: project_path })
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'notifies the user' do
|
264
|
+
expect(output).to include "Your configuration file is already up-to-date."
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe 'resolve_project' do
|
270
|
+
|
271
|
+
it_should_behave_like 'a command that requires a compatible config file'
|
272
|
+
|
273
|
+
context 'with a project slug' do
|
274
|
+
before { stub_config({ project_slug: 'existing-slug' }) }
|
275
|
+
|
276
|
+
it 'quit the program with an error' do
|
277
|
+
expect{ Gemnasium.resolve_project({ project_path: project_path }) }.to raise_error { |e|
|
278
|
+
expect(e).to be_kind_of SystemExit
|
279
|
+
expect(error_output).to include "You already have a project slug refering to an existing project. Please remove this project slug from your configuration file."
|
280
|
+
}
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'with no project slug' do
|
285
|
+
context 'with no candidate on the server' do
|
286
|
+
before { stub_config({ project_slug: nil, project_name: 'no-candidate' }) }
|
287
|
+
|
288
|
+
it 'quit the program with an error' do
|
289
|
+
expect { Gemnasium.resolve_project({ project_path: project_path }) }.to raise_error { |e|
|
290
|
+
expect(e).to be_kind_of SystemExit
|
291
|
+
expect(error_output).to include "You have no off-line project matching name `no-candidate` and branch `master`."
|
292
|
+
}
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'with one candidate on the server' do
|
297
|
+
before { stub_config({ project_slug: nil, project_name: 'one-candidate' }) }
|
298
|
+
|
299
|
+
it 'displays a confirmation message' do
|
300
|
+
Gemnasium.resolve_project({ project_path: project_path })
|
301
|
+
|
302
|
+
expect(output).to include 'Project slug is `one-candidate-slug`.'
|
303
|
+
expect(output).to include 'Your configuration file has been updated.'
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'updates the configuration file' do
|
307
|
+
expect(Gemnasium.config).to receive(:store_value!)
|
308
|
+
.with(:project_slug, 'one-candidate-slug',
|
309
|
+
"This unique project slug has been set by `gemnasium resolve`.")
|
310
|
+
|
311
|
+
Gemnasium.resolve_project({ project_path: project_path })
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'with a read-only config file' do
|
315
|
+
before { stub_config({ project_slug: nil, project_name: 'one-candidate', writable?: false }) }
|
316
|
+
before { Gemnasium.resolve_project({ project_path: project_path }) }
|
317
|
+
|
318
|
+
it 'displays a confirmation message' do
|
319
|
+
expect(output).to include 'Project slug is `one-candidate-slug`.'
|
320
|
+
expect(output).to include 'Configuration file cannot be updated. Please edit the file and update the project slug manually.'
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'with many candidates on the server' do
|
326
|
+
before { stub_config({ project_slug: nil, project_name: 'many-candidates' }) }
|
327
|
+
|
328
|
+
it 'quit the program with an error' do
|
329
|
+
expect{ Gemnasium.resolve_project({ project_path: project_path }) }.to raise_error { |e|
|
330
|
+
expect(e).to be_kind_of SystemExit
|
331
|
+
expect(error_output).to include "You have more than one off-line project matching name `many-candidates` and branch `master`."
|
332
|
+
}
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe 'install' do
|
339
|
+
after { FileUtils.rm_r "#{project_path}/config" }
|
340
|
+
|
341
|
+
context 'if config file already exists' do
|
342
|
+
before do
|
343
|
+
FileUtils.mkdir_p "#{project_path}/config"
|
344
|
+
FileUtils.touch("#{project_path}/config/gemnasium.yml")
|
345
|
+
|
346
|
+
Gemnasium.install({ project_path: project_path })
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'informs the user that the file already exists' do
|
350
|
+
expect(output).to include "The file #{project_path}/config/gemnasium.yml already exists"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context 'if config file does not exist' do
|
355
|
+
context 'neither do the config folder' do
|
356
|
+
before do
|
357
|
+
FileUtils.touch "#{project_path}/.gitignore"
|
358
|
+
Gemnasium.install({ project_path: project_path })
|
359
|
+
end
|
360
|
+
after { FileUtils.rm "#{project_path}/.gitignore" }
|
361
|
+
|
362
|
+
it 'creates the config folder' do
|
363
|
+
expect(File.exists? "#{project_path}/config").to be_truthy
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'informs the user that the folder has been created' do
|
367
|
+
expect(output).to include "Creating config directory"
|
368
|
+
end
|
369
|
+
|
370
|
+
it_should_behave_like 'an installed file' do
|
371
|
+
let(:template_path) { File.expand_path("#{File.dirname(__FILE__)}../../lib/templates/gemnasium.yml") }
|
372
|
+
let(:target_path) { "#{project_path}/config/gemnasium.yml" }
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'adds the config file to the .gitignore' do
|
376
|
+
expect(output).to include "Configuration file added to your project's .gitignore."
|
377
|
+
expect(File.open("#{project_path}/.gitignore").read()).to include "# Gemnasium gem configuration file\nconfig/gemnasium.yml"
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'asks the user to fill the config file' do
|
381
|
+
expect(output).to include 'Please fill configuration file with accurate values.'
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
context 'with an already existing config folder' do
|
386
|
+
before do
|
387
|
+
FileUtils.mkdir_p "#{project_path}/config"
|
388
|
+
Gemnasium.install({ project_path: project_path })
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'does not inform the user that the config folder has been created' do
|
392
|
+
expect(output).to_not include "Creating config directory"
|
393
|
+
end
|
394
|
+
|
395
|
+
it_should_behave_like 'an installed file' do
|
396
|
+
let(:template_path) { File.expand_path("#{File.dirname(__FILE__)}../../lib/templates/gemnasium.yml") }
|
397
|
+
let(:target_path) { "#{project_path}/config/gemnasium.yml" }
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'asks the user to fill the config file' do
|
401
|
+
expect(output).to include 'Please fill configuration file with accurate values.'
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
context 'with git option' do
|
407
|
+
context 'for a non git repo' do
|
408
|
+
before { Gemnasium.install({ project_path: project_path, install_git_hook: true }) }
|
409
|
+
|
410
|
+
it 'informs the user that the target project is not a git repository' do
|
411
|
+
expect(output).to include "#{project_path} is not a git repository. Try to run `git init`."
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'does not install the hook' do
|
415
|
+
expect(File.exists? "#{project_path}/.git/hooks/post-commit").to eql false
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
context 'for a git repo' do
|
420
|
+
before { FileUtils.mkdir_p "#{project_path}/.git/hooks" }
|
421
|
+
after { FileUtils.rm_r "#{project_path}/.git" }
|
422
|
+
|
423
|
+
context 'if the hook already exists' do
|
424
|
+
before do
|
425
|
+
FileUtils.touch("#{project_path}/.git/hooks/post-commit")
|
426
|
+
Gemnasium.install({ project_path: project_path, install_git_hook: true })
|
427
|
+
end
|
428
|
+
|
429
|
+
it 'informs the user that a post-commit hook already exists.' do
|
430
|
+
expect(output).to include "The file #{project_path}/.git/hooks/post-commit already exists"
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
context 'if the hook does not exist' do
|
435
|
+
before { Gemnasium.install({ project_path: project_path, install_git_hook: true }) }
|
436
|
+
|
437
|
+
it_should_behave_like 'an installed file' do
|
438
|
+
let(:template_path) { File.expand_path("#{File.dirname(__FILE__)}../../lib/templates/post-commit") }
|
439
|
+
let(:target_path) { "#{project_path}/.git/hooks/post-commit" }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
context 'with rake option' do
|
446
|
+
context 'for a repo without Rakefile' do
|
447
|
+
before { Gemnasium.install({ project_path: project_path, install_rake_task: true }) }
|
448
|
+
|
449
|
+
it 'informs the user that the target repo does not contain Rakefile' do
|
450
|
+
expect(output).to include "Rakefile not found."
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'does not install the task' do
|
454
|
+
expect(File.exists? "#{project_path}/lib/tasks/gemnasium.rake").to eql false
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context 'for a project with Rakefile' do
|
459
|
+
before { FileUtils.touch("#{project_path}/Rakefile") }
|
460
|
+
after do
|
461
|
+
FileUtils.rm "#{project_path}/Rakefile"
|
462
|
+
FileUtils.rm_r "#{project_path}/lib"
|
463
|
+
end
|
464
|
+
|
465
|
+
context 'without /lib/tasks foler' do
|
466
|
+
before { Gemnasium.install({ project_path: project_path, install_rake_task: true }) }
|
467
|
+
|
468
|
+
it 'creates the /lib/tasks folder' do
|
469
|
+
expect(File.exists? "#{project_path}/lib/tasks").to be_truthy
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'informs the user that the folder has been created' do
|
473
|
+
expect(output).to include "Creating lib/tasks directory"
|
474
|
+
end
|
475
|
+
|
476
|
+
it_should_behave_like 'an installed file' do
|
477
|
+
let(:template_path) { File.expand_path("#{File.dirname(__FILE__)}../../lib/templates/gemnasium.rake") }
|
478
|
+
let(:target_path) { "#{project_path}/lib/tasks/gemnasium.rake" }
|
479
|
+
end
|
480
|
+
|
481
|
+
it 'informs the user on how to use the rake tasks' do
|
482
|
+
expect(output).to include 'Usage:'
|
483
|
+
expect(output).to include "\trake gemnasium:push \t\t- to push your dependency files"
|
484
|
+
expect(output).to include "\trake gemnasium:create \t\t- to create your project on Gemnasium"
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
context 'with existing /lib/tasks foler' do
|
489
|
+
before { FileUtils.mkdir_p "#{project_path}/lib/tasks" }
|
490
|
+
|
491
|
+
context 'if the task file already exists' do
|
492
|
+
before do
|
493
|
+
FileUtils.touch("#{project_path}/lib/tasks/gemnasium.rake")
|
494
|
+
Gemnasium.install({ project_path: project_path, install_rake_task: true })
|
495
|
+
end
|
496
|
+
|
497
|
+
it 'informs the user that the rake task already exists.' do
|
498
|
+
expect(output).to include "The file #{project_path}/lib/tasks/gemnasium.rake already exists"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
context 'if the task file does not exist' do
|
503
|
+
before { Gemnasium.install({ project_path: project_path, install_rake_task: true }) }
|
504
|
+
|
505
|
+
it_should_behave_like 'an installed file' do
|
506
|
+
let(:template_path) { File.expand_path("#{File.dirname(__FILE__)}../../lib/templates/gemnasium.rake") }
|
507
|
+
let(:target_path) { "#{project_path}/lib/tasks/gemnasium.rake" }
|
508
|
+
end
|
509
|
+
|
510
|
+
it 'informs the user on how to use the rake tasks' do
|
511
|
+
expect(output).to include 'Usage:'
|
512
|
+
expect(output).to include "\trake gemnasium:push \t\t- to push your dependency files"
|
513
|
+
expect(output).to include "\trake gemnasium:create \t\t- to create your project on Gemnasium"
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|