metaforce 0.2.0.alpha → 0.3.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ script: bundle exec rspec spec
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # Metaforce
2
+ ![travis-ci](https://secure.travis-ci.org/ejholmes/metaforce.png)
3
+
2
4
  Metaforce is a Ruby gem for interacting with the [Salesforce Metadata API](http://www.salesforce.com/us/developer/docs/api_meta/index.htm).
3
5
  The goal of this project is to make the [Migration Tool](http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_deploying_ant.htm) obsolete, favoring Rake over Ant.
4
6
 
5
- Metaforce is in active development and is currently in alpha status. Don't use
6
- it to deploy code to production instances. You've been warned!
7
+ **Metaforce is in active development and is currently in alpha status. Don't use
8
+ it to deploy code to production instances. You've been warned!**
7
9
 
8
10
  ## Installation
9
11
  ```bash
10
- gem install metaforce -v '0.2.0.alpha'
12
+ gem install metaforce --pre
11
13
  ```
12
14
 
13
15
  ## Usage
@@ -22,7 +24,7 @@ client.describe
22
24
  client.list(:type => "CustomObject")
23
25
  # => [{ :created_by_id => "005U0000000EGpcIAG", :created_by_name => "Eric Holmes", ... }]
24
26
 
25
- deployment = client.deploy(File.expand_path("../src"))
27
+ deployment = client.deploy(File.dirname(__FILE__))
26
28
  # => #<Metaforce::Transaction:0x00000102779bf8 @id="04sU0000000WNWoIAO" @type=:deploy>
27
29
 
28
30
  deployment.done?
@@ -32,15 +34,61 @@ deployment.result(:wait_until_done => true)
32
34
  # => { :id => "04sU0000000WNWoIAO", :messages => [{ :changed => true ... :success => true }
33
35
  ```
34
36
 
37
+ ## DSL
38
+ Metaforce includes a lightweight DSL to make deployments and retrieve's easier.
39
+
40
+ ```ruby
41
+ require "metaforce/dsl"
42
+ include Metaforce::DSL::Metadata
43
+
44
+ login :username => 'username', :password => 'password', :security_token => 'security token' do
45
+
46
+ deploy File.dirname(__FILE__) do |result|
47
+ puts "Successful deployment!"
48
+ puts result
49
+ end
50
+
51
+ retrieve File.expand_path("../src/package.xml", __FILE__) |result, zip|
52
+ puts "Successful retrieve!"
53
+ puts result
54
+ File.open("retrieve.zip", "wb") do |file|
55
+ file.write(zip)
56
+ end
57
+ end
58
+
59
+ retrieve File.expand_path("../src/package.xml", __FILE__), :to => "directory"
60
+ end
61
+ ```
62
+
35
63
  ## Roadmap
36
64
  This gem is far from being feature complete. Here's a list of things that still
37
65
  need to be done.
38
66
 
39
- * Implement .retrieve for retrieving metadata.
67
+ * <del>Implement .retrieve for retrieving metadata.</del>
40
68
  * Implement CRUD based calls <http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_crud_based_calls_intro.htm>.
41
69
  * Implement some helper methods for diffing metadata.
70
+ * <del>Implement a DSL.</del>
42
71
  * And some other stuff that I haven't quite thought of yet...
43
72
 
73
+ ## Contributing
74
+ If you'd like to contribute code, please fork the repository and implement your
75
+ feature on a new branch, then send me a pull request with a detailed
76
+ description. Please provide applicable rspec specs.
77
+
78
+ ## Version History
79
+ **0.3.0.alpha** (January 29, 2012)
80
+
81
+ * Ability to retrieve metadata from an organization.
82
+ * Added a DSL.
83
+
84
+ **0.2.0.alpha** (January 28, 2012)
85
+
86
+ * Gem now supports interacting with the metadata api.
87
+
88
+ **0.1.0.alpha** (January 10, 2012)
89
+
90
+ * Ability to parse and modify package.xml files easily.
91
+
44
92
  ## License
45
93
  Copyright (C) 2012 Eric Holmes
46
94
 
@@ -7,14 +7,22 @@ require 'ostruct'
7
7
  module Metaforce
8
8
  module Metadata
9
9
  class Client
10
- DEPLOY_ZIP = 'deploy.zip'
11
- RETRIEVE_ZIP = 'retrieve.zip'
10
+ DEPLOY_ZIP = 'deploy.zip' # :nodoc:
11
+ RETRIEVE_ZIP = 'retrieve.zip' # :nodoc:
12
12
 
13
+ # Performs a login and sets the session_id and metadata_server_url.
14
+ # _options_ should be hash containing the :username, :password and
15
+ # :security_token keys.
16
+ #
17
+ # Metaforce::Metadata::Client.new :username => "username",
18
+ # :password => "password",
19
+ # :security_token => "security token"
13
20
  def initialize(options=nil)
14
21
  @session = Services::Client.new(options).session
15
22
  @client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/metadata.xml", __FILE__) do |wsdl|
16
23
  wsdl.endpoint = @session[:metadata_server_url]
17
24
  end
25
+ @client.http.auth.ssl.verify_mode = :none
18
26
  @header = {
19
27
  "ins0:SessionHeader" => {
20
28
  "ins0:sessionId" => @session[:session_id]
@@ -24,11 +32,10 @@ module Metaforce
24
32
 
25
33
  # Specify an array of component types to list
26
34
  #
27
- # example:
28
- # [
29
- # { :type => "ApexClass" },
30
- # { :type => "ApexComponent" }
31
- # ]
35
+ # [
36
+ # { :type => "ApexClass" },
37
+ # { :type => "ApexComponent" }
38
+ # ]
32
39
  def list(queries=[])
33
40
  unless queries.is_a?(Array)
34
41
  queries = [ queries ]
@@ -73,10 +80,12 @@ module Metaforce
73
80
  self.status(id)[:done]
74
81
  end
75
82
 
76
- # Deploys dir to the organisation
77
- def deploy(dir, options={})
78
- options = OpenStruct.new options
79
-
83
+ # Deploys _dir_ to the organisation
84
+ #
85
+ # See http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_deploy.htm#deploy_options
86
+ # for a list of _deploy_options_. Options should be convereted from
87
+ # camelCase to an :underscored_symbol.
88
+ def deploy(dir, deploy_options={})
80
89
  if dir.is_a?(String)
81
90
  filename = File.join(File.dirname(dir), DEPLOY_ZIP)
82
91
  zip_contents = create_deploy_file(filename, dir)
@@ -84,18 +93,51 @@ module Metaforce
84
93
  zip_contents = Base64.encode64(dir.read)
85
94
  end
86
95
 
87
- yield options if block_given?
88
-
89
96
  response = @client.request(:deploy) do |soap|
90
97
  soap.header = @header
91
98
  soap.body = {
92
99
  :zip_file => zip_contents,
93
- :deploy_options => options.marshal_dump
100
+ :deploy_options => deploy_options
94
101
  }
95
102
  end
96
103
  Transaction.deployment self, response[:deploy_response][:result][:id]
97
104
  end
98
105
 
106
+ # Performs a retrieve
107
+ #
108
+ # See http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_retrieve_request.htm
109
+ # for a list of _retrieve_request_ options. Options should be convereted from
110
+ # camelCase to an :underscored_symbol.
111
+ def retrieve(retrieve_request={})
112
+ response = @client.request(:retrieve) do |soap|
113
+ soap.header = @header
114
+ soap.body = {
115
+ :retrieve_request => retrieve_request
116
+ }
117
+ end
118
+ Transaction.retrieval self, response[:retrieve_response][:result][:id]
119
+ end
120
+
121
+ # Retrieves files specified in the manifest file (package.xml)
122
+ #
123
+ # Specificy any extra _retrieve_request_ options in _extra_.
124
+ def retrieve_unpackaged(manifest, extra=nil)
125
+ if manifest.is_a?(Metaforce::Manifest)
126
+ package = manifest.to_package
127
+ elsif manifest.is_a?(String)
128
+ package = Metaforce::Manifest.new(File.open(manifest).read).to_package
129
+ end
130
+ retrieve_request = {
131
+ :api_version => Metaforce.configuration.api_version,
132
+ :single_package => true,
133
+ :unpackaged => {
134
+ :types => package
135
+ }
136
+ }
137
+ retrieve_request.merge!(extra) if extra.is_a?(Hash)
138
+ retrieve(retrieve_request)
139
+ end
140
+
99
141
  private
100
142
 
101
143
  # Creates the deploy file, reads in the contents and returns the base64
@@ -3,8 +3,13 @@ require 'savon'
3
3
  module Metaforce
4
4
  module Services
5
5
  class Client
6
+ # Contains the session_id and metadata_server_url
6
7
  attr_reader :session
7
8
 
9
+ # Initializes a new instance of Client and logs in. _options_ should be a
10
+ # hash containing the username, password and security token. If options
11
+ # is nil, it will get the username, password and security token from the
12
+ # configuration.
8
13
  def initialize(options=nil)
9
14
  options = {
10
15
  :username => Metaforce.configuration.username,
@@ -14,11 +19,13 @@ module Metaforce
14
19
  @session = self.login(options[:username], options[:password], options[:security_token])
15
20
  end
16
21
 
22
+ # Performs a login and sets @session
17
23
  def login(username, password, security_token=nil)
18
24
  password = "#{password}#{security_token}" unless security_token.nil?
19
25
  client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/partner.xml", __FILE__) do |wsdl|
20
26
  wsdl.endpoint = wsdl.endpoint.to_s.sub(/login/, 'test') if Metaforce.configuration.test
21
27
  end
28
+ client.http.auth.ssl.verify_mode = :none
22
29
  response = client.request(:login) do
23
30
  soap.body = {
24
31
  :username => username,
@@ -1,3 +1,5 @@
1
+ require 'base64'
2
+
1
3
  module Metaforce
2
4
 
3
5
  # Convenience class for deployment/retrieval results
@@ -26,11 +28,34 @@ module Metaforce
26
28
  end
27
29
  alias :complete? :done?
28
30
  alias :completed? :done?
31
+
32
+ # Returns the zip file from a retrieval
33
+ def zip_file
34
+ raise "Request was not a retrieve." if @type != :retrieve
35
+ Base64.decode64(@result[:zip_file])
36
+ end
37
+
38
+ # Unzips the returned zip file to _destination_
39
+ def unzip(destination)
40
+ zip = zip_file
41
+ file = Tempfile.new("retrieve")
42
+ file.write(zip)
43
+ path = file.path
44
+ file.close
45
+
46
+ Zip::ZipFile.open(path) do |zip|
47
+ zip.each do |f|
48
+ path = File.join(destination, f.name)
49
+ FileUtils.mkdir_p(File.dirname(path))
50
+ zip.extract(f, path)
51
+ end
52
+ end
53
+ end
29
54
 
30
55
  # Returns the deploy or retrieve result
31
56
  def result(options={})
32
57
  self.wait_until_done if options[:wait_until_done]
33
- raise "Request is not complete! Be sure to call .done? first!" unless @done
58
+ raise "Request has not completed." unless @done
34
59
  @result = @client.status(@id, @type) if @result.nil?
35
60
  @result
36
61
  end
@@ -1,19 +1,35 @@
1
1
  module Metaforce
2
2
  class << self
3
+ # Returns the current Configuration
4
+ #
5
+ # Metaforce.configuration.username = "username"
6
+ # Metaforce.configuration.password = "password"
3
7
  def configuration
4
8
  @configuration ||= Configuration.new
5
9
  end
6
10
 
11
+ # Yields the Configuration
12
+ #
13
+ # Metaforce.configure do |config|
14
+ # config.username = "username"
15
+ # config.password = "password"
16
+ # end
7
17
  def configure
8
18
  yield configuration
9
19
  end
10
20
  end
11
21
 
12
22
  class Configuration
23
+ # The Salesforce API version to use. Defaults to 23.0
13
24
  attr_accessor :api_version
25
+ # The username to use during login.
14
26
  attr_accessor :username
27
+ # The password to use during login.
15
28
  attr_accessor :password
29
+ # The security token to use during login.
16
30
  attr_accessor :security_token
31
+ # Set this to true if you're authentication with a Sandbox instance.
32
+ # Defaults to false.
17
33
  attr_accessor :test
18
34
 
19
35
  def initialize
@@ -0,0 +1,42 @@
1
+ require 'tempfile'
2
+ require 'zip/zip'
3
+
4
+ module Metaforce
5
+ module DSL
6
+ module Metadata
7
+
8
+ # Logs in and creates a new instance of Metaforce::Metadata::Client
9
+ def login(options)
10
+ @client = Metaforce::Metadata::Client.new options
11
+ yield if block_given?
12
+ end
13
+
14
+ # Deploy the contents of _dir_ to the target organization
15
+ def deploy(dir, options={})
16
+ deployment = @client.deploy(dir, options)
17
+ result = deployment.result(:wait_until_done => true)
18
+ raise "Deploy failed." if !result[:success]
19
+ yield result if block_given?
20
+ end
21
+
22
+ # Retrieve the metadata specified in the manifest file. If manifest is a
23
+ # Hash, the underlying .retrieve method is used instead of
24
+ # .retrieve_unpackaged.
25
+ #
26
+ # If _options_ # contains a key _:to_, the resulting zip
27
+ # file that is retrieved is unzipped to the directory specified.
28
+ def retrieve(manifest, options={})
29
+ if manifest.is_a?(Hash)
30
+ retrieval = @client.retrieve(manifest)
31
+ else
32
+ retrieval = @client.retrieve_unpackaged(manifest)
33
+ end
34
+ result = retrieval.result(:wait_until_done => true)
35
+ zip_contents = retrieval.zip_file
36
+ retrieval.unzip(options[:to]) if options.has_key?(:to)
37
+ yield result, zip_contents if block_given?
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -4,16 +4,38 @@ module Metaforce
4
4
  class Manifest
5
5
  SFDC_API_VERSION = "23.0"
6
6
 
7
- # example format
8
- # {
9
- # :apex_class => [
10
- # "TestController",
11
- # "TestClass"
12
- # ],
13
- # :apex_component => [
14
- # "SiteLogin"
15
- # ]
16
- # }
7
+ # Initializes a new instance of a manifest (package.xml) file.
8
+ #
9
+ # It can either take a hash:
10
+ # {
11
+ # :apex_class => [
12
+ # "TestController",
13
+ # "TestClass"
14
+ # ],
15
+ # :apex_component => [
16
+ # "SiteLogin"
17
+ # ]
18
+ # }
19
+ #
20
+ # Or an xml string containing the contents of a packge.xml file:
21
+ # <?xml version="1.0"?>
22
+ # <Package xmlns="http://soap.sforce.com/2006/04/metadata">
23
+ # <types>
24
+ # <members>TestClass</members>
25
+ # <members>AnotherClass</members>
26
+ # <name>ApexClass</name>
27
+ # </types>
28
+ # <types>
29
+ # <members>Component</members>
30
+ # <name>ApexComponent</name>
31
+ # </types>
32
+ # <types>
33
+ # <members>Assets</members>
34
+ # <name>StaticResource</name>
35
+ # </types>
36
+ # <version>23.0</version>
37
+ # </Package>
38
+ #
17
39
  def initialize(components={})
18
40
  # Map component type => folder
19
41
  if components.is_a?(Hash)
@@ -25,6 +47,8 @@ module Metaforce
25
47
  end
26
48
 
27
49
  # Adds components to the package
50
+ #
51
+ # manifest.add :apex_class, 'SomeClass'
28
52
  def add(type, members=nil)
29
53
  unless members.nil?
30
54
  @components[type] = [] if @components[type].nil?
@@ -38,6 +62,8 @@ module Metaforce
38
62
  end
39
63
 
40
64
  # Removes components from the package
65
+ #
66
+ # manifest.remove :apex_class, 'SomeClass'
41
67
  def remove(type, members=nil)
42
68
  unless members.nil?
43
69
  members = [members] if members.is_a?(String)
@@ -53,6 +79,8 @@ module Metaforce
53
79
  end
54
80
 
55
81
  # Filters the components based on a list of files
82
+ #
83
+ # manifest.only(['classes/SomeClass'])
56
84
  def only(files)
57
85
  components = @components
58
86
  @components = {}
@@ -72,23 +100,41 @@ module Metaforce
72
100
  end
73
101
 
74
102
  # Returns the components name
75
- def component_name(key)
103
+ def component_name(key) # :nodoc:
76
104
  COMPONENT_TYPE_MAP[key][:name]
77
105
  end
78
106
 
79
107
  # Returns the components folder
80
- def component_folder(key)
108
+ def component_folder(key) # :nodoc:
81
109
  COMPONENT_TYPE_MAP[key][:folder]
82
110
  end
83
111
 
84
112
  # Returns a key for the component name
85
- def component_key(name)
113
+ def component_key(name) # :nodoc:
86
114
  COMPONENT_TYPE_MAP.each do |key, component|
87
115
  return key if component[:name] == name
88
116
  end
89
117
  end
90
118
 
91
119
  # Returns a string containing a package.xml file
120
+ #
121
+ # <?xml version="1.0"?>
122
+ # <Package xmlns="http://soap.sforce.com/2006/04/metadata">
123
+ # <types>
124
+ # <members>TestClass</members>
125
+ # <members>AnotherClass</members>
126
+ # <name>ApexClass</name>
127
+ # </types>
128
+ # <types>
129
+ # <members>Component</members>
130
+ # <name>ApexComponent</name>
131
+ # </types>
132
+ # <types>
133
+ # <members>Assets</members>
134
+ # <name>StaticResource</name>
135
+ # </types>
136
+ # <version>23.0</version>
137
+ # </Package>
92
138
  def to_xml
93
139
  xml_builder = Nokogiri::XML::Builder.new do |xml|
94
140
  xml.Package("xmlns" => "http://soap.sforce.com/2006/04/metadata") {
@@ -106,10 +152,22 @@ module Metaforce
106
152
  xml_builder.to_xml
107
153
  end
108
154
 
155
+ # Returns the underlying hash structure
156
+ #
157
+ # {
158
+ # :apex_class => [
159
+ # "TestController",
160
+ # "TestClass"
161
+ # ],
162
+ # :apex_component => [
163
+ # "SiteLogin"
164
+ # ]
165
+ # }
109
166
  def to_hash
110
167
  @components
111
168
  end
112
169
 
170
+ # Used internall my Metaforce::Metadata::Client
113
171
  def to_package
114
172
  components = []
115
173
  @components.each do |type, members|
@@ -1,3 +1,3 @@
1
1
  module Metaforce
2
- VERSION = "0.2.0.alpha"
2
+ VERSION = "0.3.0.alpha"
3
3
  end
data/metaforce.gemspec CHANGED
@@ -18,11 +18,12 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_dependency "nokogiri"
22
- s.add_dependency "savon"
23
- s.add_dependency "rubyzip"
21
+ s.add_dependency "nokogiri", "~> 1.5.0"
22
+ s.add_dependency "savon", "~> 0.9.7"
23
+ s.add_dependency "rubyzip", "~> 0.9.5"
24
24
 
25
+ s.add_development_dependency "rake"
25
26
  s.add_development_dependency "rspec"
26
27
  s.add_development_dependency "mocha"
27
- s.add_development_dependency "savon_spec"
28
+ s.add_development_dependency "savon_spec", "~> 0.1.6"
28
29
  end
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <soapenv:Body>
4
+ <checkRetrieveStatusResponse>
5
+ <result>
6
+ <fileProperties>
7
+ <createdById>005U0000000EGpcIAG</createdById>
8
+ <createdByName>Eric Holmes</createdByName>
9
+ <createdDate>2012-01-14T22:11:17.000Z</createdDate>
10
+ <fileName>classes/TestClass.cls</fileName>
11
+ <fullName>TestClass</fullName>
12
+ <id>01pU0000000pDrtIAE</id>
13
+ <lastModifiedById>005U0000000EGpcIAG</lastModifiedById>
14
+ <lastModifiedByName>Eric Holmes</lastModifiedByName>
15
+ <lastModifiedDate>2012-01-15T03:17:28.000Z</lastModifiedDate>
16
+ <manageableState>unmanaged</manageableState>
17
+ <type>ApexClass</type>
18
+ </fileProperties>
19
+ <fileProperties>
20
+ <createdById>005U0000000EGpcIAG</createdById>
21
+ <createdByName>Eric Holmes</createdByName>
22
+ <createdDate>2012-01-28T03:35:13.400Z</createdDate>
23
+ <fileName>package.xml</fileName>
24
+ <fullName>package.xml</fullName>
25
+ <id/>
26
+ <lastModifiedById>005U0000000EGpcIAG</lastModifiedById>
27
+ <lastModifiedByName>Eric Holmes</lastModifiedByName>
28
+ <lastModifiedDate>2012-01-28T03:35:13.400Z</lastModifiedDate>
29
+ <manageableState>unmanaged</manageableState>
30
+ <type>Package</type>
31
+ </fileProperties>
32
+ <id>04sU0000000WkdIIAS</id>
33
+ <zipFile>UEsDBBQACAAIAGYcPEAAAAAAAAAAAAAAAAAVAAAAY2xhc3Nlcy9UZXN0Q2xhc3MuY2xzKyhNyslMVkjOSSwuVghJLS5xBrOquWoBUEsHCKYgavMYAAAAGgAAAFBLAwQUAAgACABmHDxAAAAAAAAAAAAAAAAAHgAAAGNsYXNzZXMvVGVzdENsYXNzLmNscy1tZXRhLnhtbK2RTWsCMRCG7/srltzdibZUkWxECj321PY+xtGmbj7IRPHnN3SluwdBD87tffMM80DU6uy6+kSJbfCtmDZS1ORN2Fq/b8Xnx9tkIVa6UutI59cOmevCe27Fd85xCcABY8O7kAw1JjiYSfkC8hkcZdxiRqGruozCaL/6I3r21EgFo6InIpoD7ulSct/+vTj8Cen96DaU9FTBOI4g6wdoXqhRHiiPjrhcIo2dgiH1CnDV4ZFm8rYYcQ6J7pbjjPnIem2yPZWtS6wU/P+Yrn4BUEsHCKXszAvOAAAA5QEAAFBLAwQUAAgACABmHDxAAAAAAAAAAAAAAAAACwAAAHBhY2thZ2UueG1sTY7LCsIwEEX3/YqQvZlYRUTSFBFcu6gfENNRi82DTpD695Y+0FnNGQ53rip717I3dtQEX/C1kJyht6Fu/KPg1+q82vNSZ+pi7Ms8kA22p4I/U4oHAAomCrqHzqKwwUEu5Q7kFhwmU5tkuM7YMCp9ItK0j+zQ3YaXukJKp9YQKVhOP8kbh/oYsZ+Nkac8+AtUc3mdb4RUsFCmYO6ssy9QSwcIe4wRVJ4AAADlAAAAUEsBAhQAFAAIAAgAZhw8QKYgavMYAAAAGgAAABUAAAAAAAAAAAAAAAAAAAAAAGNsYXNzZXMvVGVzdENsYXNzLmNsc1BLAQIUABQACAAIAGYcPECl7MwLzgAAAOUBAAAeAAAAAAAAAAAAAAAAAFsAAABjbGFzc2VzL1Rlc3RDbGFzcy5jbHMtbWV0YS54bWxQSwECFAAUAAgACABmHDxAe4wRVJ4AAADlAAAACwAAAAAAAAAAAAAAAAB1AQAAcGFja2FnZS54bWxQSwUGAAAAAAMAAwDIAAAATAIAAAAA</zipFile>
34
+ </result>
35
+ </checkRetrieveStatusResponse>
36
+ </soapenv:Body>
37
+ </soapenv:Envelope>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <soapenv:Body>
4
+ <retrieveResponse>
5
+ <result>
6
+ <done>false</done>
7
+ <id>04sU0000000WkdIIAS</id>
8
+ <state>InProgress</state>
9
+ </result>
10
+ </retrieveResponse>
11
+ </soapenv:Body>
12
+ </soapenv:Envelope>
@@ -108,17 +108,6 @@ describe Metaforce::Metadata::Client do
108
108
 
109
109
  end
110
110
 
111
- # context "when given a file" do
112
-
113
- # it "deploys the file and returns the id of the result" do
114
- # path = Tempfile.new('zipfile').tap { |f| f.write('h'); f.close }.path
115
- # savon.expects(:deploy).with(:zip_file => "aA==\n", :deploy_options => {}).returns(:in_progress)
116
- # id = client.deploy(File.open(path))
117
- # id.should eq("04sU0000000WNWoIAO")
118
- # end
119
-
120
- # end
121
-
122
111
  it "allows deploy options to be configured via a hash" do
123
112
  savon.expects(:deploy).with(:zip_file => '', :deploy_options => { :run_all_tests => true }).returns(:in_progress)
124
113
  expect {
@@ -126,14 +115,61 @@ describe Metaforce::Metadata::Client do
126
115
  }.to_not raise_error
127
116
  end
128
117
 
129
- it "allows deploy options to be configured via a block" do
130
- savon.expects(:deploy).with(:zip_file => '', :deploy_options => { :run_all_tests => true }).returns(:in_progress)
131
- expect {
132
- client.deploy('') do |options|
133
- options.run_all_tests = true
118
+ end
119
+
120
+ describe ".retrieve" do
121
+
122
+ let(:manifest) do
123
+ Metaforce::Manifest.new(File.open(File.expand_path('../../../fixtures/sample/src/package.xml', __FILE__)).read)
124
+ end
125
+
126
+ describe ".retrieve_unpackaged" do
127
+
128
+ context "when given a manifest file" do
129
+ before(:each) do
130
+ savon.expects(:retrieve).with(:retrieve_request => { :api_version => Metaforce.configuration.api_version, :single_package => true, :unpackaged => { :types => manifest.to_package } }).returns(:in_progress)
131
+ savon.expects(:check_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:done)
132
+ savon.expects(:check_retrieve_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:success)
134
133
  end
135
- }.to_not raise_error
134
+
135
+ it "returns a valid retrieve result" do
136
+ retrieval = client.retrieve_unpackaged(manifest)
137
+ retrieval.done?
138
+ result = retrieval.result
139
+ result.should be_a(Hash)
140
+ end
141
+
142
+ end
143
+
144
+ context "when given the path to a manifest file" do
145
+ before(:each) do
146
+ savon.expects(:retrieve).with(:retrieve_request => { :api_version => Metaforce.configuration.api_version, :single_package => true, :unpackaged => { :types => manifest.to_package } }).returns(:in_progress)
147
+ savon.expects(:check_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:done)
148
+ savon.expects(:check_retrieve_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:success)
149
+ end
150
+
151
+ it "returns a valid retrieve result" do
152
+ retrieval = client.retrieve_unpackaged(File.expand_path('../../../fixtures/sample/src/package.xml', __FILE__))
153
+ retrieval.done?
154
+ result = retrieval.result
155
+ result.should be_a(Hash)
156
+ end
157
+
158
+ end
159
+
160
+ context "when given extra retrieve options" do
161
+ before(:each) do
162
+ savon.expects(:retrieve).with(:retrieve_request => { :api_version => Metaforce.configuration.api_version, :single_package => true, :unpackaged => { :types => manifest.to_package }, :extra => true }).returns(:in_progress)
163
+ end
164
+
165
+ it "merges the options" do
166
+ expect {
167
+ retrieval = client.retrieve_unpackaged(File.expand_path('../../../fixtures/sample/src/package.xml', __FILE__), { :extra => true })
168
+ }.to_not raise_error
169
+ end
170
+
171
+ end
136
172
  end
137
-
173
+
138
174
  end
139
175
  end
@@ -0,0 +1,93 @@
1
+ require "spec_helper"
2
+ require "metaforce/dsl"
3
+ include Metaforce::DSL::Metadata
4
+
5
+ describe Metaforce::DSL::Metadata do
6
+
7
+ let(:manifest) do
8
+ Metaforce::Manifest.new(File.open(File.expand_path('../../fixtures/sample/src/package.xml', __FILE__)).read)
9
+ end
10
+
11
+ let(:valid_credentials) do
12
+ { :username => 'valid', :password => 'password' }
13
+ end
14
+
15
+ describe ".login" do
16
+
17
+ before(:each) do
18
+ savon.expects(:login).with(valid_credentials).returns(:success)
19
+ end
20
+
21
+ it "logs the user in" do
22
+ expect { login valid_credentials }.to_not raise_error
23
+ end
24
+
25
+ end
26
+
27
+ describe ".deploy" do
28
+
29
+ before(:each) do
30
+ Metaforce::Metadata::Client.any_instance.stubs(:create_deploy_file).returns('')
31
+ savon.expects(:login).with(valid_credentials).returns(:success)
32
+ savon.expects(:deploy).with(:zip_file => '', :deploy_options => {}).returns(:in_progress)
33
+ savon.expects(:check_status).with(:ids => [ "04sU0000000WNWoIAO" ]).returns(:done)
34
+ savon.expects(:check_deploy_status).with(:ids => [ "04sU0000000WNWoIAO" ]).returns(:done)
35
+ end
36
+
37
+ context "when given a directory" do
38
+
39
+ it "deploys the directory" do
40
+ login valid_credentials
41
+ expect {
42
+ deploy File.expand_path('../../../fixtures/sample', __FILE__) do |result|
43
+ result.should be_a(Hash)
44
+ end
45
+ }.to_not raise_error
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ describe ".retrieve" do
53
+ before(:each) do
54
+ savon.expects(:login).with(valid_credentials).returns(:success)
55
+ savon.expects(:retrieve).with(:retrieve_request => { :api_version => Metaforce.configuration.api_version, :single_package => true, :unpackaged => { :types => manifest.to_package } }).returns(:in_progress)
56
+ savon.expects(:check_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:done)
57
+ savon.expects(:check_retrieve_status).with(:ids => ['04sU0000000WkdIIAS']).returns(:success)
58
+ end
59
+
60
+ context "when given a manifest file" do
61
+
62
+ it "retrieves the components" do
63
+ login valid_credentials
64
+ expect {
65
+ retrieve manifest do |result, zip|
66
+ result.should be_a(Hash)
67
+ zip.should be_a(String)
68
+ end
69
+ }.to_not raise_error
70
+ end
71
+
72
+ context "and :to is specified" do
73
+
74
+ before(:each) do
75
+ Metaforce::Transaction.any_instance.stubs(:unzip).returns('')
76
+ end
77
+
78
+ it "retrieves the components and unzips them to the directory" do
79
+ login valid_credentials
80
+ expect {
81
+ retrieve manifest, :to => "test_retrieve" do |result, zip|
82
+ result.should be_a(Hash)
83
+ zip.should be_a(String)
84
+ end
85
+ }.to_not raise_error
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metaforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.alpha
4
+ version: 0.3.0.alpha
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,44 +9,55 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-27 00:00:00.000000000 Z
12
+ date: 2012-01-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
16
- requirement: &2168826860 !ruby/object:Gem::Requirement
16
+ requirement: &3632840 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 1.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2168826860
24
+ version_requirements: *3632840
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: savon
27
- requirement: &2168825720 !ruby/object:Gem::Requirement
27
+ requirement: &3631640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
- - - ! '>='
30
+ - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: '0'
32
+ version: 0.9.7
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2168825720
35
+ version_requirements: *3631640
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rubyzip
38
- requirement: &2168824120 !ruby/object:Gem::Requirement
38
+ requirement: &3630690 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.9.5
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *3630690
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &3629920 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
42
53
  - !ruby/object:Gem::Version
43
54
  version: '0'
44
- type: :runtime
55
+ type: :development
45
56
  prerelease: false
46
- version_requirements: *2168824120
57
+ version_requirements: *3629920
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: rspec
49
- requirement: &2168822980 !ruby/object:Gem::Requirement
60
+ requirement: &3629280 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: '0'
55
66
  type: :development
56
67
  prerelease: false
57
- version_requirements: *2168822980
68
+ version_requirements: *3629280
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: mocha
60
- requirement: &2168821960 !ruby/object:Gem::Requirement
71
+ requirement: &3627890 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,18 +76,18 @@ dependencies:
65
76
  version: '0'
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *2168821960
79
+ version_requirements: *3627890
69
80
  - !ruby/object:Gem::Dependency
70
81
  name: savon_spec
71
- requirement: &2168820680 !ruby/object:Gem::Requirement
82
+ requirement: &3625600 !ruby/object:Gem::Requirement
72
83
  none: false
73
84
  requirements:
74
- - - ! '>='
85
+ - - ~>
75
86
  - !ruby/object:Gem::Version
76
- version: '0'
87
+ version: 0.1.6
77
88
  type: :development
78
89
  prerelease: false
79
- version_requirements: *2168820680
90
+ version_requirements: *3625600
80
91
  description: A Ruby gem for interacting with the Salesforce Metadata API
81
92
  email:
82
93
  - eric@ejholmes.net
@@ -85,7 +96,7 @@ extensions: []
85
96
  extra_rdoc_files: []
86
97
  files:
87
98
  - .gitignore
88
- - .rvmrc
99
+ - .travis.yml
89
100
  - Gemfile
90
101
  - Guardfile
91
102
  - README.md
@@ -96,6 +107,7 @@ files:
96
107
  - lib/metaforce/api/services.rb
97
108
  - lib/metaforce/api/transaction.rb
98
109
  - lib/metaforce/config.rb
110
+ - lib/metaforce/dsl.rb
99
111
  - lib/metaforce/manifest.rb
100
112
  - lib/metaforce/version.rb
101
113
  - metaforce.gemspec
@@ -103,6 +115,7 @@ files:
103
115
  - spec/fixtures/package.xml
104
116
  - spec/fixtures/requests/check_deploy_status/done.xml
105
117
  - spec/fixtures/requests/check_deploy_status/error.xml
118
+ - spec/fixtures/requests/check_retrieve_status/success.xml
106
119
  - spec/fixtures/requests/check_status/done.xml
107
120
  - spec/fixtures/requests/check_status/not_done.xml
108
121
  - spec/fixtures/requests/deploy/in_progress.xml
@@ -110,6 +123,7 @@ files:
110
123
  - spec/fixtures/requests/list_metadata/objects.xml
111
124
  - spec/fixtures/requests/login/failure.xml
112
125
  - spec/fixtures/requests/login/success.xml
126
+ - spec/fixtures/requests/retrieve/in_progress.xml
113
127
  - spec/fixtures/sample/src/classes/TestClass.cls
114
128
  - spec/fixtures/sample/src/classes/TestClass.cls-meta.xml
115
129
  - spec/fixtures/sample/src/package.xml
@@ -117,6 +131,7 @@ files:
117
131
  - spec/lib/api/services_spec.rb
118
132
  - spec/lib/api/transaction_spec.rb
119
133
  - spec/lib/config_spec.rb
134
+ - spec/lib/dsl_spec.rb
120
135
  - spec/lib/manifest_spec.rb
121
136
  - spec/spec_helper.rb
122
137
  - wsdl/23.0/metadata.xml
@@ -149,6 +164,7 @@ test_files:
149
164
  - spec/fixtures/package.xml
150
165
  - spec/fixtures/requests/check_deploy_status/done.xml
151
166
  - spec/fixtures/requests/check_deploy_status/error.xml
167
+ - spec/fixtures/requests/check_retrieve_status/success.xml
152
168
  - spec/fixtures/requests/check_status/done.xml
153
169
  - spec/fixtures/requests/check_status/not_done.xml
154
170
  - spec/fixtures/requests/deploy/in_progress.xml
@@ -156,6 +172,7 @@ test_files:
156
172
  - spec/fixtures/requests/list_metadata/objects.xml
157
173
  - spec/fixtures/requests/login/failure.xml
158
174
  - spec/fixtures/requests/login/success.xml
175
+ - spec/fixtures/requests/retrieve/in_progress.xml
159
176
  - spec/fixtures/sample/src/classes/TestClass.cls
160
177
  - spec/fixtures/sample/src/classes/TestClass.cls-meta.xml
161
178
  - spec/fixtures/sample/src/package.xml
@@ -163,5 +180,6 @@ test_files:
163
180
  - spec/lib/api/services_spec.rb
164
181
  - spec/lib/api/transaction_spec.rb
165
182
  - spec/lib/config_spec.rb
183
+ - spec/lib/dsl_spec.rb
166
184
  - spec/lib/manifest_spec.rb
167
185
  - spec/spec_helper.rb
data/.rvmrc DELETED
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
- # development environment upon cd'ing into the directory
5
-
6
- # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
- environment_id="ruby-1.9.2-p290@metaforce"
8
-
9
- #
10
- # Uncomment following line if you want options to be set only for given project.
11
- #
12
- # PROJECT_JRUBY_OPTS=( --1.9 )
13
-
14
- #
15
- # First we attempt to load the desired environment directly from the environment
16
- # file. This is very fast and efficient compared to running through the entire
17
- # CLI and selector. If you want feedback on which environment was used then
18
- # insert the word 'use' after --create as this triggers verbose mode.
19
- #
20
- if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
- && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
- then
23
- \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
-
25
- if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
- then
27
- . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
- fi
29
- else
30
- # If the environment file has not yet been created, use the RVM CLI to select.
31
- if ! rvm --create "$environment_id"
32
- then
33
- echo "Failed to create RVM environment '${environment_id}'."
34
- return 1
35
- fi
36
- fi
37
-
38
- #
39
- # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
- # it be automatically loaded. Uncomment the following and adjust the filename if
41
- # necessary.
42
- #
43
- # filename=".gems"
44
- # if [[ -s "$filename" ]]
45
- # then
46
- # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
47
- # fi
48
-
49
- # If you use bundler, this might be useful to you:
50
- # if command -v bundle && [[ -s Gemfile ]]
51
- # then
52
- # bundle install
53
- # fi
54
-
55
-