metaforce 1.0.0a → 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.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ examples/tmp
6
6
  deploy.zip
7
7
  retrieve.zip
8
8
  test.rb
9
+ .metaforce.yml
data/README.md CHANGED
@@ -125,6 +125,40 @@ client.send_email(
125
125
  )
126
126
  ```
127
127
 
128
+ ## Command-line
129
+
130
+ Metaforce also comes with a handy command line utility that can deploy and retrieve
131
+ code from Salesforce. It also allows you to watch a directory and deploy when
132
+ anything changes.
133
+
134
+ When you deploy, it will also run all tests and provide you with a report,
135
+ similar to rspec.
136
+
137
+ ```bash
138
+ $ metaforce deploy ./src
139
+ Deploying: ./src
140
+ ```
141
+
142
+ ```bash
143
+ $ metaforce watch ./src
144
+ Watching: ./src
145
+ ```
146
+
147
+ ```bash
148
+ $ metaforce retrieve ./src
149
+ Retrieving: ./src/package.xml
150
+
151
+ $ metaforce retrieve ./src/package.xml ./other-location
152
+ Retrieving: ./src/package.xml
153
+ ```
154
+
155
+ ### .metaforce.yml
156
+
157
+ The metaforce command will pull it's configuration from a `.metaforce.yml`
158
+ file, if present. You can provide options for multiple environments, then use
159
+ the `-e` swtich on the command line to use an environment. See the
160
+ [examples](examples) directory for an example.
161
+
128
162
  ## Contributing
129
163
 
130
164
  1. Fork it
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'metaforce'
5
+
6
+ begin
7
+ require 'metaforce/cli'
8
+ Metaforce::CLI.start
9
+ rescue Interrupt => e
10
+ puts "\nQuitting..."
11
+ exit 1
12
+ rescue SystemExit => e
13
+ exit e.status
14
+ end
@@ -11,18 +11,12 @@ client = Metaforce.new :username => ENV['USER'],
11
11
  Metaforce::Job.disable_threading!
12
12
 
13
13
  # Test sending an email.
14
- print 'send email to: '
14
+ print 'send email to: '; email = STDIN.gets.chomp
15
15
  client.send_email(
16
- to_addresses: [STDIN.gets.chomp],
16
+ to_addresses: [email],
17
17
  subject: 'Test',
18
18
  plain_text_body: 'Test'
19
- )
20
-
21
- # Test deployment.
22
- client.deploy('../spec/fixtures/payload.zip')
23
- .on_complete { |job| puts "Deploy Completed: #{job.id}."}
24
- .on_error { |job| puts "Deploy Failed: #{job.id}."}
25
- .perform
19
+ ) if email
26
20
 
27
21
  # Test retrieve.
28
22
  manifest = Metaforce::Manifest.new(:custom_object => ['Account'])
@@ -32,6 +26,13 @@ client.retrieve_unpackaged(manifest)
32
26
  .extract_to('./tmp')
33
27
  .perform
34
28
 
29
+ # Test deployment.
30
+ client.deploy('./tmp')
31
+ .on_complete { |job| puts "Deploy Completed: #{job.id}. #{job.result}"}
32
+ .on_error { |job| puts "Deploy Failed: #{job.id}."}
33
+ .on_poll { |job| puts "Deploy: Polled status for #{job.id}."}
34
+ .perform
35
+
35
36
  # Test delete.
36
37
  client.delete(:apex_page, 'TestPage')
37
38
  .on_complete { |job| puts "Delete Completed: #{job.id}."}
@@ -0,0 +1,124 @@
1
+ require 'listen'
2
+ require 'thor'
3
+ require 'metaforce/reporters'
4
+ require 'metaforce/reporters'
5
+
6
+ Metaforce.configuration.log = false
7
+ Metaforce::Job.disable_threading!
8
+
9
+ module Metaforce
10
+ class CLI < Thor
11
+ CONFIG_FILE = '.metaforce.yml'
12
+
13
+ include Thor::Actions
14
+
15
+ class << self
16
+ def credential_options
17
+ method_option :username, :aliases => '-u', :desc => 'Username.'
18
+ method_option :password, :aliases => '-p', :desc => 'Password.'
19
+ method_option :security_token, :aliases => '-t', :desc => 'Security Token.'
20
+ method_option :environment, :aliases => '-e', :default => 'production', :desc => 'Environment to use from config file (if present).'
21
+ method_option :host, :aliases => '-h', :desc => 'Salesforce host to connect to.'
22
+ end
23
+
24
+ def deploy_options
25
+ method_option :deploy_options, :aliases => '-o', :type => :hash, :default => { :run_all_tests => true }, :desc => 'Deploy Options'
26
+ end
27
+
28
+ def retrieve_options
29
+ method_option :retrieve_options, :aliases => '-o', :type => :hash, :default => {}, :desc => 'Retrieve Options'
30
+ end
31
+ end
32
+
33
+ desc 'deploy <path>', 'Deploy <path> to the target organization.'
34
+
35
+ credential_options
36
+ deploy_options
37
+
38
+ def deploy(path)
39
+ say "Deploying: #{path} ", :cyan
40
+ say "#{options[:deploy_options].inspect}"
41
+ client(options).deploy(path, options[:deploy_options].symbolize_keys!)
42
+ .on_complete { |job| report job.result, :deploy }
43
+ .on_error(&error)
44
+ .on_poll(&polling)
45
+ .perform
46
+ end
47
+
48
+ desc 'retrieve <manifest> <path>', 'Retrieve the components specified in <manifest> (package.xml) to <path>.'
49
+
50
+ credential_options
51
+ retrieve_options
52
+
53
+ def retrieve(manifest, path=nil)
54
+ unless path
55
+ path = manifest
56
+ manifest = File.join(path, 'package.xml')
57
+ end
58
+ say "Retrieving: #{manifest} ", :cyan
59
+ say "#{options[:retrieve_options].inspect}"
60
+ client(options).retrieve_unpackaged(manifest, options[:retrieve_options].symbolize_keys!)
61
+ .extract_to(path)
62
+ .on_complete { |job| report(job.result, :retrieve) }
63
+ .on_complete { |job| say "Extracted: #{path}", :green }
64
+ .on_error(&error)
65
+ .on_poll(&polling)
66
+ .perform
67
+ end
68
+
69
+ desc 'watch <path>', 'Deploy <path> to the target organization whenever files are changed.'
70
+
71
+ credential_options
72
+ deploy_options
73
+
74
+ def watch(path)
75
+ say "Watching: #{path}"
76
+ @watching = true
77
+ Listen.to(path) { deploy path }
78
+ end
79
+
80
+ private
81
+
82
+ def report(results, type)
83
+ reporter = "Metaforce::Reporters::#{type.to_s.camelize}Reporter".constantize.new(results)
84
+ reporter.report
85
+ exit 1 if reporter.issues?
86
+ end
87
+
88
+ def exit(status)
89
+ super(status) if not watching?
90
+ end
91
+
92
+ def watching?
93
+ !!@watching
94
+ end
95
+
96
+ def error
97
+ proc { |job| say "Error: #{job.result.inspect}" }
98
+ end
99
+
100
+ def polling
101
+ proc { |job| say 'Polling ...', :cyan }
102
+ end
103
+
104
+ def client(options)
105
+ credentials = Thor::CoreExt::HashWithIndifferentAccess.new(config ? config[options[:environment]] : {})
106
+ credentials.merge!(options.slice(:username, :password, :security_token, :host))
107
+ credentials.tap do |credentials|
108
+ credentials[:username] ||= ask('username:')
109
+ credentials[:password] ||= ask('password:')
110
+ credentials[:security_token] ||= ask('security token:')
111
+ end
112
+ Metaforce.new credentials
113
+ end
114
+
115
+ def config
116
+ YAML.load(File.open(config_file)) if File.exists?(config_file)
117
+ end
118
+
119
+ def config_file
120
+ File.expand_path(CONFIG_FILE)
121
+ end
122
+
123
+ end
124
+ end
@@ -3,6 +3,9 @@ require 'base64'
3
3
 
4
4
  module Metaforce
5
5
  class Job
6
+ DELAY_START = 1
7
+ DELAY_MULTIPLIER = 2
8
+
6
9
  autoload :Deploy, 'metaforce/job/deploy'
7
10
  autoload :Retrieve, 'metaforce/job/retrieve'
8
11
  autoload :CRUD, 'metaforce/job/crud'
@@ -39,11 +42,18 @@ module Metaforce
39
42
  self
40
43
  end
41
44
 
42
- # Public: Register a block to be called when the job has completed.
45
+ # Public: Utility method to determine if .perform has been called yet.
46
+ #
47
+ # Returns true if @id is set, false otherwise.
48
+ def started?
49
+ !!@id
50
+ end
51
+
52
+ # Public: Register a block to be called when an event occurs.
43
53
  #
44
54
  # Yields the job.
45
55
  #
46
- # &block - Proc or Lambda to be run when the job completes.
56
+ # &block - Proc or Lambda to be run when the event is triggered.
47
57
  #
48
58
  # Examples
49
59
  #
@@ -51,28 +61,26 @@ module Metaforce
51
61
  # puts "Job ##{job.id} completed!"
52
62
  # end
53
63
  #
54
- # Returns self.
55
- def on_complete(&block)
56
- @_callbacks[:on_complete] << block
57
- self
58
- end
59
-
60
- # Public: Register a block to be called when if the job fails.
61
- #
62
- # Yields the job.
63
- #
64
- # &block - Proc or Lambda to be run when the job fails.
65
- #
66
- # Examples
67
- #
68
64
  # job.on_error do |job|
69
- # puts "Job ##{job.id} failed!"
65
+ # puts "Job failed!"
66
+ # end
67
+ #
68
+ # job.on_poll do |job|
69
+ # puts "Polled status for #{job.id}"
70
70
  # end
71
71
  #
72
72
  # Returns self.
73
- def on_error(&block)
74
- @_callbacks[:on_error] << block
75
- self
73
+ #
74
+ # Signature
75
+ #
76
+ # on_complete(&block)
77
+ # on_error(&block)
78
+ # on_poll(&block)
79
+ %w[complete error poll].each do |type|
80
+ define_method :"on_#{type}" do |&block|
81
+ @_callbacks[:"on_#{type}"] << block
82
+ self
83
+ end
76
84
  end
77
85
 
78
86
  # Public: Queries the job status from the API.
@@ -84,7 +92,7 @@ module Metaforce
84
92
  #
85
93
  # Returns the AsyncResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_asyncresult.htm).
86
94
  def status
87
- client.status(id)
95
+ @status ||= client.status(id)
88
96
  end
89
97
 
90
98
  # Public: Returns true if the job has completed.
@@ -106,9 +114,9 @@ module Metaforce
106
114
  # job.state
107
115
  # # => 'Completed'
108
116
  #
109
- # Returns the state if the job is done, false otherwise.
117
+ # Returns the state of the job.
110
118
  def state
111
- done? && status.state
119
+ status.state
112
120
  end
113
121
 
114
122
  # Public: Check if the job is in a given state.
@@ -140,7 +148,11 @@ module Metaforce
140
148
  def disable_threading!
141
149
  self.class_eval do
142
150
  def start_heart_beat
151
+ delay = DELAY_START
143
152
  loop do
153
+ @status = nil
154
+ wait (delay = delay * DELAY_MULTIPLIER)
155
+ trigger_poll_callbacks
144
156
  trigger_callbacks && break if completed? || error?
145
157
  end
146
158
  end
@@ -157,14 +169,26 @@ module Metaforce
157
169
  def start_heart_beat
158
170
  Thread.abort_on_exception = true
159
171
  @heart_beat ||= Thread.new do
160
- delay = 1
172
+ delay = DELAY_START
161
173
  loop do
162
- sleep (delay = delay * 2)
174
+ @status = nil
175
+ wait (delay = delay * DELAY_MULTIPLIER)
176
+ trigger_poll_callbacks
163
177
  trigger_callbacks && Thread.stop if completed? || error?
164
178
  end
165
179
  end
166
180
  end
167
181
 
182
+ def trigger_poll_callbacks
183
+ @_callbacks[:on_poll].each do |block|
184
+ block.call(self)
185
+ end
186
+ end
187
+
188
+ def wait(duration)
189
+ sleep duration
190
+ end
191
+
168
192
  def trigger_callbacks
169
193
  @_callbacks[callback_type].each do |block|
170
194
  block.call(self)
@@ -37,7 +37,7 @@ module Metaforce
37
37
  #
38
38
  # Returns the DeployResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_deployresult.htm).
39
39
  def result
40
- client.status(id, :deploy)
40
+ @result ||= client.status(id, :deploy)
41
41
  end
42
42
 
43
43
  # Public: Returns true if the deploy was successful.
@@ -52,7 +52,9 @@ module Metaforce
52
52
  result.success
53
53
  end
54
54
 
55
- # Public: Base64 encodes the contents of the zip file.
55
+ private
56
+
57
+ # Internal: Base64 encodes the contents of the zip file.
56
58
  #
57
59
  # Examples
58
60
  #
@@ -64,8 +66,6 @@ module Metaforce
64
66
  Base64.encode64(File.open(file, 'rb').read)
65
67
  end
66
68
 
67
- private
68
-
69
69
  # Internal: Returns the path to the zip file.
70
70
  def file
71
71
  File.file?(@path) ? @path : zip_file
@@ -37,7 +37,7 @@ module Metaforce
37
37
  #
38
38
  # Returns the RetrieveResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_retrieveresult.htm).
39
39
  def result
40
- client.status(id, :retrieve)
40
+ @result ||= client.status(id, :retrieve)
41
41
  end
42
42
 
43
43
  # Public: Decodes the content of the returned zip file.
@@ -63,7 +63,7 @@ module Metaforce
63
63
  #
64
64
  # Returns self.
65
65
  def extract_to(destination)
66
- return on_complete { |job| job.extract_to(destination) } unless @id
66
+ return on_complete { |job| job.extract_to(destination) } unless started?
67
67
  Zip::ZipFile.open(tmp_zip_file) do |zip|
68
68
  zip.each do |f|
69
69
  path = File.join(destination, f.name)
@@ -36,7 +36,7 @@ module Metaforce
36
36
  #
37
37
  # Examples
38
38
  #
39
- # client._update(:apex_page, 'OldPage', :full_name => 'NewPage')
39
+ # client._update(:apex_page, 'OldPage', :full_name => 'TestPage', :label => 'Test page', :content => '<apex:page>hello world</apex:page>')
40
40
  def _update(type, current_name, metadata={})
41
41
  type = type.to_s.camelize
42
42
  request :update do |soap|
@@ -70,7 +70,7 @@ module Metaforce
70
70
 
71
71
  # Internal: Prepare metadata by base64 encoding any content keys.
72
72
  def prepare(metadata)
73
- metadata = [metadata] unless metadata.is_a? Array
73
+ metadata = Array[metadata].compact.flatten
74
74
  metadata.each { |m| encode_content(m) }
75
75
  metadata
76
76
  end
@@ -97,7 +97,7 @@ module Metaforce
97
97
  package = if manifest.is_a?(Metaforce::Manifest)
98
98
  manifest
99
99
  elsif manifest.is_a?(String)
100
- Metaforce::Manifest.new(File.open(manifest).read)
100
+ Metaforce::Manifest.new(::File.open(manifest).read)
101
101
  end
102
102
  options = {
103
103
  :api_version => Metaforce.configuration.api_version,
@@ -0,0 +1,2 @@
1
+ require 'metaforce/reporters/deploy_reporter'
2
+ require 'metaforce/reporters/retrieve_reporter'
@@ -0,0 +1,56 @@
1
+ require 'thor/shell/color'
2
+
3
+ module Metaforce
4
+ module Reporters
5
+ class BaseReporter < Thor::Shell::Color
6
+ def initialize(results)
7
+ super()
8
+ @results = results
9
+ end
10
+
11
+ def report
12
+ end
13
+
14
+ def report_problems
15
+ return unless problems?
16
+ say
17
+ say "Problems:", :red
18
+ say
19
+ problems.each { |message| problem(message) }
20
+ end
21
+
22
+ def problem(message)
23
+ say "#{short_padding}#{message.file_name}:#{message.line_number}", :red
24
+ say "#{long_padding}#{message.problem}"
25
+ say
26
+ end
27
+
28
+ def short_padding
29
+ ' '
30
+ end
31
+
32
+ def long_padding
33
+ ' '
34
+ end
35
+
36
+ def problems?
37
+ problems.any?
38
+ end
39
+
40
+ def issues?
41
+ problems?
42
+ end
43
+
44
+ private
45
+
46
+ def messages
47
+ @messages ||= Array[@results.messages].compact.flatten
48
+ end
49
+
50
+ def problems
51
+ messages.select { |message| message.problem }
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,69 @@
1
+ require 'metaforce/reporters/base_reporter'
2
+
3
+ module Metaforce
4
+ module Reporters
5
+ class DeployReporter < BaseReporter
6
+
7
+ def report
8
+ report_problems
9
+ report_failed_tests
10
+ report_test_results if report_test_results?
11
+ end
12
+
13
+ def report_failed_tests
14
+ return unless failures?
15
+ say
16
+ say "Failures:", :red
17
+ say
18
+ failures.each { |failure| failed(failure) }
19
+ end
20
+
21
+ def report_test_results
22
+ say
23
+ say "Finished in #{total_time} seconds"
24
+ say "#{num_tests} tests, #{num_failures} failures", failures? ? :red : :green
25
+ end
26
+
27
+ def failed(failure)
28
+ say "#{short_padding}#{failure.stack_trace}:", :red
29
+ say "#{long_padding}#{failure.message}"
30
+ say
31
+ end
32
+
33
+ def failures?
34
+ num_failures > 0
35
+ end
36
+
37
+ def issues?
38
+ problems? || failures?
39
+ end
40
+
41
+ private
42
+
43
+ def report_test_results?
44
+ @results.success && problems.empty?
45
+ end
46
+
47
+ def test_results
48
+ @results.run_test_result
49
+ end
50
+
51
+ def failures
52
+ @failures ||= Array[test_results.failures].compact.flatten
53
+ end
54
+
55
+ def total_time
56
+ test_results.total_time
57
+ end
58
+
59
+ def num_tests
60
+ test_results.num_tests_run.to_i
61
+ end
62
+
63
+ def num_failures
64
+ test_results.num_failures.to_i
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,11 @@
1
+ require 'metaforce/reporters/base_reporter'
2
+
3
+ module Metaforce
4
+ module Reporters
5
+ class RetrieveReporter < BaseReporter
6
+ def report
7
+ report_problems
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Metaforce
2
- VERSION = '1.0.0a'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -24,7 +24,10 @@ EOL
24
24
  s.add_dependency 'savon', '~> 1.2.0'
25
25
  s.add_dependency 'rubyzip', '~> 0.9.9'
26
26
  s.add_dependency 'activesupport'
27
- s.add_dependency 'hashie'
27
+ s.add_dependency 'hashie', '~> 1.2.0'
28
+ s.add_dependency 'thor', '~> 0.16.0'
29
+ s.add_dependency 'listen', '~> 0.6.0'
30
+ s.add_dependency 'rb-fsevent', '~> 0.9.1'
28
31
 
29
32
  s.add_development_dependency 'rake'
30
33
  s.add_development_dependency 'rspec'
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'metaforce/cli'
3
+
4
+ describe Metaforce::CLI do
5
+ before do
6
+ Metaforce::Client.any_instance.stub(:deploy).and_return(double('deploy job').as_null_object)
7
+ Metaforce::Client.any_instance.stub(:retrieve).and_return(double('retrieve job').as_null_object)
8
+ subject.stub(:config).and_return(nil)
9
+ end
10
+
11
+ describe 'credentials' do
12
+ let(:output) { capture(:stdout) { subject.deploy('./path') } }
13
+
14
+ context 'when supplied credentials from the command line' do
15
+ let(:options) { indifferent_hash(:username => 'foo', :password => 'bar', :security_token => 'token', :deploy_options => {}) }
16
+
17
+ it 'uses supplied credentials from command line' do
18
+ subject.options = options
19
+ Metaforce.should_receive(:new).with(indifferent_hash(options).slice(:username, :password, :security_token)).and_call_original
20
+ output.should include('Deploying: ./path')
21
+ end
22
+ end
23
+
24
+ context 'when supplied credentials from config file' do
25
+ let(:options) { indifferent_hash(:environment => 'production', :deploy_options => {}) }
26
+ let(:config) { { 'username' => 'foo', 'password' => 'bar', 'security_token' => 'token' } }
27
+
28
+ it 'uses credentials from the config file' do
29
+ subject.options = options
30
+ subject.stub(:config).and_return('production' => config)
31
+ Metaforce.should_receive(:new).with(indifferent_hash(config)).and_call_original
32
+ output.should include('Deploying: ./path')
33
+ end
34
+ end
35
+ end
36
+
37
+ def indifferent_hash(hash)
38
+ Thor::CoreExt::HashWithIndifferentAccess.new(hash)
39
+ end
40
+ end
@@ -5,28 +5,29 @@ describe Metaforce::Job::Deploy do
5
5
  let(:path) { File.expand_path('../../../fixtures/payload.zip', __FILE__) }
6
6
  let(:job) { described_class.new client, path }
7
7
 
8
- describe '.payload' do
9
- subject { job.payload }
8
+ describe '.perform' do
9
+ subject { job.perform }
10
10
 
11
11
  context 'when the path is a file' do
12
- it { should be_a String }
12
+ before do
13
+ client.should_receive(:_deploy).with(/^UEsDBA.*/, {}).and_return(Hashie::Mash.new(id: '1234'))
14
+ client.should_receive(:status).any_number_of_times.and_return(Hashie::Mash.new(done: true, state: 'Completed'))
15
+ end
16
+
17
+ it { should eq job }
18
+ its(:id) { should eq '1234' }
13
19
  end
14
20
 
15
21
  context 'when the path is a directory' do
16
- let(:path) { File.expand_path('../../../fixtures', __FILE__) }
17
- it { should be_a String }
18
- end
19
- end
22
+ before do
23
+ client.should_receive(:_deploy).with(/.*1stwAAAJI.*/, {}).and_return(Hashie::Mash.new(id: '1234'))
24
+ client.should_receive(:status).any_number_of_times.and_return(Hashie::Mash.new(done: true, state: 'Completed'))
25
+ end
20
26
 
21
- describe '.perform' do
22
- before do
23
- client.should_receive(:_deploy).with(job.payload, {}).and_return(Hashie::Mash.new(id: '1234'))
24
- client.should_receive(:status).any_number_of_times.and_return(Hashie::Mash.new(done: true, state: 'Completed'))
27
+ let(:path) { File.expand_path('../../../fixtures', __FILE__) }
28
+ it { should eq job }
29
+ its(:id) { should eq '1234' }
25
30
  end
26
-
27
- subject { job.perform }
28
- it { should eq job }
29
- its(:id) { should eq '1234' }
30
31
  end
31
32
 
32
33
  describe '.result' do
@@ -11,6 +11,22 @@ describe Metaforce::Job do
11
11
  end
12
12
  end
13
13
 
14
+ describe '.started?' do
15
+ subject { job.started? }
16
+
17
+ context 'when .perform has been called and an @id has been set' do
18
+ before do
19
+ job.instance_variable_set(:@id, '1234')
20
+ end
21
+
22
+ it { should be_true }
23
+ end
24
+
25
+ context 'when .perform has not been called and no @id has been set' do
26
+ it { should be_false }
27
+ end
28
+ end
29
+
14
30
  describe '.on_complete' do
15
31
  it 'allows the user to register an on_complete callback' do
16
32
  client.should_receive(:status).any_number_of_times.and_return(Hashie::Mash.new(done: true, state: 'Completed'))
@@ -67,7 +83,7 @@ describe Metaforce::Job do
67
83
 
68
84
  context 'when done' do
69
85
  before do
70
- client.should_receive(:status).twice.and_return(Hashie::Mash.new(done: true, state: 'Completed'))
86
+ client.should_receive(:status).and_return(Hashie::Mash.new(done: true, state: 'Completed'))
71
87
  end
72
88
 
73
89
  it { should eq 'Completed' }
@@ -85,7 +101,7 @@ describe Metaforce::Job do
85
101
  %w[Queued InProgress Completed Error].each do |state|
86
102
  describe ".#{state.underscore}?" do
87
103
  before do
88
- client.should_receive(:status).twice.and_return(Hashie::Mash.new(done: true, state: state))
104
+ client.should_receive(:status).and_return(Hashie::Mash.new(done: true, state: state))
89
105
  end
90
106
 
91
107
  subject { job.send(:"#{state.underscore}?") }
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+ require 'metaforce/reporters/base_reporter'
3
+
4
+ describe Metaforce::Reporters::BaseReporter do
5
+ let(:results) { Hashie::Mash.new(success: true) }
6
+ let(:reporter) { described_class.new results }
7
+
8
+ describe '.problem' do
9
+ context 'when a line_number is present' do
10
+ let(:problem) { Hashie::Mash.new(file_name: 'path/file', line_number: '10', problem: 'Foobar') }
11
+
12
+ it 'prints the problem' do
13
+ reporter.should_receive(:say).with(' path/file:10', :red)
14
+ reporter.should_receive(:say).with(' Foobar')
15
+ reporter.should_receive(:say).with
16
+ reporter.problem(problem)
17
+ end
18
+ end
19
+
20
+ context 'when there is no line number present' do
21
+ let(:problem) { Hashie::Mash.new(file_name: 'path/file', problem: 'Foobar') }
22
+
23
+ it 'prints the problem' do
24
+ reporter.should_receive(:say).with(' path/file:', :red)
25
+ reporter.should_receive(:say).with(' Foobar')
26
+ reporter.should_receive(:say).with
27
+ reporter.problem(problem)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '.report_problems' do
33
+ context 'when there are no problems' do
34
+ it 'does not report any problems' do
35
+ reporter.should_receive(:say).never
36
+ reporter.should_receive(:problem).never
37
+ reporter.report_problems
38
+ end
39
+ end
40
+
41
+ context 'when there are problems' do
42
+ let(:results) { Hashie::Mash.new(success: true, messages: { problem: 'Problem', file_name: 'path/file', line_number: '10' }) }
43
+
44
+ it 'prints each problem' do
45
+ reporter.should_receive(:say)
46
+ reporter.should_receive(:say).with('Problems:', :red)
47
+ reporter.should_receive(:say)
48
+ reporter.should_receive(:problem)
49
+ reporter.report_problems
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '.problems?' do
55
+ subject { reporter.problems? }
56
+
57
+ context 'when there are no problems' do
58
+ it { should be_false }
59
+ end
60
+
61
+ context 'when there are problems' do
62
+ let(:results) { Hashie::Mash.new(success: true, messages: { problem: 'Problem', file_name: 'path/file', line_number: '10' }) }
63
+ it { should be_true }
64
+ end
65
+ end
66
+
67
+ describe '.issues?' do
68
+ subject { reporter.issues? }
69
+
70
+ context 'when there are no problems' do
71
+ it { should be_false }
72
+ end
73
+
74
+ context 'when there are problems' do
75
+ let(:results) { Hashie::Mash.new(success: true, messages: { problem: 'Problem', file_name: 'path/file', line_number: '10' }) }
76
+ it { should be_true }
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+ require 'metaforce/reporters/deploy_reporter'
3
+
4
+ describe Metaforce::Reporters::DeployReporter do
5
+ let(:results) { Hashie::Mash.new(success: true) }
6
+ let(:reporter) { described_class.new results }
7
+
8
+ describe '.failed' do
9
+ let(:failure) { Hashie::Mash.new(stack_trace: 'stack trace', message: 'message') }
10
+
11
+ it 'prints the failure' do
12
+ reporter.should_receive(:say).with(' stack trace:', :red)
13
+ reporter.should_receive(:say).with(' message')
14
+ reporter.should_receive(:say).with
15
+ reporter.failed(failure)
16
+ end
17
+ end
18
+
19
+ describe '.report' do
20
+ context 'when the deploy was successfull' do
21
+ it 'should report everything' do
22
+ reporter.should_receive(:report_problems)
23
+ reporter.should_receive(:report_failed_tests)
24
+ reporter.should_receive(:report_test_results)
25
+ reporter.report
26
+ end
27
+ end
28
+
29
+ context 'when the deploy was not successful' do
30
+ let(:results) { Hashie::Mash.new(success: false) }
31
+
32
+ it 'should report everything except test results' do
33
+ reporter.should_receive(:report_problems)
34
+ reporter.should_receive(:report_failed_tests)
35
+ reporter.should_receive(:report_test_results).never
36
+ reporter.report
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '.report_failed_tests' do
42
+ let(:results) { Hashie::Mash.new(success: true, run_test_result: { num_failures: num_failures, failures: failures }) }
43
+
44
+ context 'when there are no failed tests' do
45
+ let(:failures) { nil }
46
+ let(:num_failures) { '0' }
47
+
48
+ it 'does not report any failed tests' do
49
+ reporter.should_receive(:say).never
50
+ reporter.should_receive(:failed).never
51
+ reporter.report_failed_tests
52
+ end
53
+ end
54
+
55
+ context 'when there are failed tests' do
56
+ context 'passed as an object' do
57
+ let(:failures) { { stack_trace: 'stack trace', message: 'message' } }
58
+ let(:num_failures) { '1' }
59
+
60
+ it 'prints each failed tests' do
61
+ reporter.should_receive(:say)
62
+ reporter.should_receive(:say).with('Failures:', :red)
63
+ reporter.should_receive(:say)
64
+ reporter.should_receive(:failed)
65
+ reporter.report_failed_tests
66
+ end
67
+ end
68
+
69
+ context 'passed as an array' do
70
+ let(:failures) { [{ stack_trace: 'stack trace', message: 'message' }, { stack_trace: 'stack trace 2', message: 'message 2' }] }
71
+ let(:num_failures) { '2' }
72
+
73
+ it 'prints each failed tests' do
74
+ reporter.should_receive(:say)
75
+ reporter.should_receive(:say).with('Failures:', :red)
76
+ reporter.should_receive(:say)
77
+ reporter.should_receive(:failed).twice
78
+ reporter.report_failed_tests
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '.report_test_results' do
85
+ let(:results) { Hashie::Mash.new(success: true, run_test_result: { total_time: '20', num_tests_run: '10', num_failures: num_failures }) }
86
+
87
+ context 'when there are failures' do
88
+ let(:num_failures) { '5' }
89
+
90
+ it 'prints the test results in red' do
91
+ reporter.should_receive(:say)
92
+ reporter.should_receive(:say).with("Finished in 20 seconds")
93
+ reporter.should_receive(:say).with("10 tests, 5 failures", :red)
94
+ reporter.report_test_results
95
+ end
96
+ end
97
+
98
+ context 'when there are no failures' do
99
+ let(:num_failures) { '0' }
100
+
101
+ it 'prints the test results in red' do
102
+ reporter.should_receive(:say)
103
+ reporter.should_receive(:say).with("Finished in 20 seconds")
104
+ reporter.should_receive(:say).with("10 tests, 0 failures", :green)
105
+ reporter.report_test_results
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '.failures?' do
111
+ let(:results) { Hashie::Mash.new(success: true, run_test_result: { total_time: '20', num_tests_run: '10', num_failures: num_failures }) }
112
+ subject { reporter.failures? }
113
+
114
+ context 'when there are failures' do
115
+ let(:num_failures) { '5' }
116
+ it { should be_true }
117
+ end
118
+
119
+ context 'when there are no failures' do
120
+ let(:num_failures) { '0' }
121
+ it { should be_false }
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require 'metaforce/reporters/retrieve_reporter'
3
+
4
+ describe Metaforce::Reporters::RetrieveReporter do
5
+ let(:results) { Hashie::Mash.new(success: true) }
6
+ let(:reporter) { described_class.new results }
7
+
8
+ describe '.report' do
9
+ it 'reports the problems' do
10
+ reporter.should_receive(:report_problems)
11
+ reporter.report
12
+ end
13
+ end
14
+ end
@@ -1,14 +1,17 @@
1
1
  require 'bundler'
2
2
  Bundler.require :default, :development
3
3
  require 'pp'
4
+ require 'rspec/mocks'
4
5
 
5
6
  Dir['./spec/support/**/*.rb'].sort.each {|f| require f}
6
7
 
7
8
  RSpec.configure do |config|
9
+ config.mock_with :rspec
8
10
  config.include Savon::Spec::Macros
9
11
 
10
- config.before(:suite) do
12
+ config.before(:each) do
11
13
  Metaforce::Job.disable_threading!
14
+ Metaforce::Job.any_instance.stub(:wait)
12
15
  end
13
16
  end
14
17
 
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metaforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0a
5
- prerelease: 5
4
+ version: 1.0.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Eric J. Holmes
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-21 00:00:00.000000000 Z
13
+ date: 2012-12-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: savon
@@ -65,17 +65,65 @@ dependencies:
65
65
  requirement: !ruby/object:Gem::Requirement
66
66
  none: false
67
67
  requirements:
68
- - - ! '>='
68
+ - - ~>
69
69
  - !ruby/object:Gem::Version
70
- version: '0'
70
+ version: 1.2.0
71
71
  type: :runtime
72
72
  prerelease: false
73
73
  version_requirements: !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
- - - ! '>='
76
+ - - ~>
77
77
  - !ruby/object:Gem::Version
78
- version: '0'
78
+ version: 1.2.0
79
+ - !ruby/object:Gem::Dependency
80
+ name: thor
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ version: 0.16.0
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ version: 0.16.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: listen
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: 0.6.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 0.6.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rb-fsevent
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ version: 0.9.1
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ version: 0.9.1
79
127
  - !ruby/object:Gem::Dependency
80
128
  name: rake
81
129
  requirement: !ruby/object:Gem::Requirement
@@ -144,7 +192,8 @@ description: A Ruby gem for interacting with the Salesforce Metadata API
144
192
  email:
145
193
  - eric@ejholmes.net
146
194
  - Kevinp@madronasg.com
147
- executables: []
195
+ executables:
196
+ - metaforce
148
197
  extensions: []
149
198
  extra_rdoc_files: []
150
199
  files:
@@ -155,9 +204,12 @@ files:
155
204
  - LICENSE
156
205
  - README.md
157
206
  - Rakefile
207
+ - bin/metaforce
208
+ - examples/.metaforce.yml
158
209
  - examples/example.rb
159
210
  - lib/metaforce.rb
160
211
  - lib/metaforce/abstract_client.rb
212
+ - lib/metaforce/cli.rb
161
213
  - lib/metaforce/client.rb
162
214
  - lib/metaforce/config.rb
163
215
  - lib/metaforce/job.rb
@@ -169,6 +221,10 @@ files:
169
221
  - lib/metaforce/metadata/client.rb
170
222
  - lib/metaforce/metadata/client/crud.rb
171
223
  - lib/metaforce/metadata/client/file.rb
224
+ - lib/metaforce/reporters.rb
225
+ - lib/metaforce/reporters/base_reporter.rb
226
+ - lib/metaforce/reporters/deploy_reporter.rb
227
+ - lib/metaforce/reporters/retrieve_reporter.rb
172
228
  - lib/metaforce/services/client.rb
173
229
  - lib/metaforce/version.rb
174
230
  - metaforce.gemspec
@@ -192,6 +248,7 @@ files:
192
248
  - spec/fixtures/requests/retrieve/in_progress.xml
193
249
  - spec/fixtures/requests/send_email/success.xml
194
250
  - spec/fixtures/requests/update/in_progress.xml
251
+ - spec/lib/cli_spec.rb
195
252
  - spec/lib/client_spec.rb
196
253
  - spec/lib/config_spec.rb
197
254
  - spec/lib/job/deploy_spec.rb
@@ -201,6 +258,9 @@ files:
201
258
  - spec/lib/manifest_spec.rb
202
259
  - spec/lib/metadata/client_spec.rb
203
260
  - spec/lib/metaforce_spec.rb
261
+ - spec/lib/reporters/base_reporter_spec.rb
262
+ - spec/lib/reporters/deploy_reporter_spec.rb
263
+ - spec/lib/reporters/retrieve_reporter_spec.rb
204
264
  - spec/lib/services/client_spec.rb
205
265
  - spec/spec_helper.rb
206
266
  - spec/support/client.rb
@@ -223,15 +283,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
223
283
  - - ! '>='
224
284
  - !ruby/object:Gem::Version
225
285
  version: '0'
226
- segments:
227
- - 0
228
- hash: -4477921491118908816
229
286
  required_rubygems_version: !ruby/object:Gem::Requirement
230
287
  none: false
231
288
  requirements:
232
- - - ! '>'
289
+ - - ! '>='
233
290
  - !ruby/object:Gem::Version
234
- version: 1.3.1
291
+ version: '0'
235
292
  requirements: []
236
293
  rubyforge_project: metaforce
237
294
  rubygems_version: 1.8.23
@@ -259,6 +316,7 @@ test_files:
259
316
  - spec/fixtures/requests/retrieve/in_progress.xml
260
317
  - spec/fixtures/requests/send_email/success.xml
261
318
  - spec/fixtures/requests/update/in_progress.xml
319
+ - spec/lib/cli_spec.rb
262
320
  - spec/lib/client_spec.rb
263
321
  - spec/lib/config_spec.rb
264
322
  - spec/lib/job/deploy_spec.rb
@@ -268,6 +326,10 @@ test_files:
268
326
  - spec/lib/manifest_spec.rb
269
327
  - spec/lib/metadata/client_spec.rb
270
328
  - spec/lib/metaforce_spec.rb
329
+ - spec/lib/reporters/base_reporter_spec.rb
330
+ - spec/lib/reporters/deploy_reporter_spec.rb
331
+ - spec/lib/reporters/retrieve_reporter_spec.rb
271
332
  - spec/lib/services/client_spec.rb
272
333
  - spec/spec_helper.rb
273
334
  - spec/support/client.rb
335
+ has_rdoc: