MattHall-campaign_monitor 1.3.2.2

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.
@@ -0,0 +1,187 @@
1
+ #
2
+ # Copyright (c) 2006 Michael Koziarski
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in the
6
+ # Software without restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8
+ # Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18
+ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'xml/libxml'
23
+
24
+ class FasterXmlSimple
25
+ Version = '0.5.0'
26
+ class << self
27
+ # Take an string containing XML, and returns a hash representing that
28
+ # XML document. For example:
29
+ #
30
+ # FasterXmlSimple.xml_in("<root><something>1</something></root>")
31
+ # {"root"=>{"something"=>{"__content__"=>"1"}}}
32
+ #
33
+ # Faster XML Simple is designed to be a drop in replacement for the xml_in
34
+ # functionality of http://xml-simple.rubyforge.org
35
+ #
36
+ # The following options are supported:
37
+ #
38
+ # * <tt>contentkey</tt>: The key to use for the content of text elements,
39
+ # defaults to '\_\_content__'
40
+ # * <tt>forcearray</tt>: The list of elements which should always be returned
41
+ # as arrays. Under normal circumstances single element arrays are inlined.
42
+ # * <tt>suppressempty</tt>: The value to return for empty elements, pass +true+
43
+ # to remove empty elements entirely.
44
+ # * <tt>keeproot</tt>: By default the hash returned has a single key with the
45
+ # name of the root element. If the name of the root element isn't
46
+ # interesting to you, pass +false+.
47
+ # * <tt>forcecontent</tt>: By default a text element with no attributes, will
48
+ # be collapsed to just a string instead of a hash with a single key.
49
+ # Pass +true+ to prevent this.
50
+ #
51
+ #
52
+ def xml_in(string, options={})
53
+ new(string, options).out
54
+ end
55
+ end
56
+
57
+ def initialize(string, options) #:nodoc:
58
+ @doc = parse(string)
59
+ @options = default_options.merge options
60
+ end
61
+
62
+ def out #:nodoc:
63
+ if @options['keeproot']
64
+ {@doc.root.name => collapse(@doc.root)}
65
+ else
66
+ collapse(@doc.root)
67
+ end
68
+ end
69
+
70
+ private
71
+ def default_options
72
+ {'contentkey' => '__content__', 'forcearray' => [], 'keeproot'=>true}
73
+ end
74
+
75
+ def collapse(element)
76
+ result = hash_of_attributes(element)
77
+ if text_node? element
78
+ text = collapse_text(element)
79
+ result[content_key] = text if text =~ /\S/
80
+ elsif element.children?
81
+ element.inject(result) do |hash, child|
82
+ unless child.text?
83
+ child_result = collapse(child)
84
+ (hash[child.name] ||= []) << child_result
85
+ end
86
+ hash
87
+ end
88
+ end
89
+ if result.empty?
90
+ return empty_element
91
+ end
92
+ # Compact them to ensure it complies with the user's requests
93
+ inline_single_element_arrays(result)
94
+ remove_empty_elements(result) if suppress_empty?
95
+ if content_only?(result) && !force_content?
96
+ result[content_key]
97
+ else
98
+ result
99
+ end
100
+ end
101
+
102
+ def content_only?(result)
103
+ result.keys == [content_key]
104
+ end
105
+
106
+ def content_key
107
+ @options['contentkey']
108
+ end
109
+
110
+ def force_array?(key_name)
111
+ Array(@options['forcearray']).include?(key_name)
112
+ end
113
+
114
+ def inline_single_element_arrays(result)
115
+ result.each do |key, value|
116
+ if value.size == 1 && value.is_a?(Array) && !force_array?(key)
117
+ result[key] = value.first
118
+ end
119
+ end
120
+ end
121
+
122
+ def remove_empty_elements(result)
123
+ result.each do |key, value|
124
+ if value == empty_element
125
+ result.delete key
126
+ end
127
+ end
128
+ end
129
+
130
+ def suppress_empty?
131
+ @options['suppressempty'] == true
132
+ end
133
+
134
+ def empty_element
135
+ if !@options.has_key? 'suppressempty'
136
+ {}
137
+ else
138
+ @options['suppressempty']
139
+ end
140
+ end
141
+
142
+ # removes the content if it's nothing but blanks, prevents
143
+ # the hash being polluted with lots of content like "\n\t\t\t"
144
+ def suppress_empty_content(result)
145
+ result.delete content_key if result[content_key] !~ /\S/
146
+ end
147
+
148
+ def force_content?
149
+ @options['forcecontent']
150
+ end
151
+
152
+ # a text node is one with 1 or more child nodes which are
153
+ # text nodes, and no non-text children, there's no sensible
154
+ # way to support nodes which are text and markup like:
155
+ # <p>Something <b>Bold</b> </p>
156
+ def text_node?(element)
157
+ !element.text? && element.all? {|c| c.text?}
158
+ end
159
+
160
+ # takes a text node, and collapses it into a string
161
+ def collapse_text(element)
162
+ element.map {|c| c.content } * ''
163
+ end
164
+
165
+ def hash_of_attributes(element)
166
+ result = {}
167
+ element.each_attr do |attribute|
168
+ name = attribute.name
169
+ name = [attribute.ns, attribute.name].join(':') if attribute.ns?
170
+ result[name] = attribute.value
171
+ end
172
+ result
173
+ end
174
+
175
+ def parse(string)
176
+ if string == ''
177
+ string = ' '
178
+ end
179
+ XML::Parser.string(string).parse
180
+ end
181
+ end
182
+
183
+ class XmlSimple # :nodoc:
184
+ def self.xml_in(*args)
185
+ FasterXmlSimple.xml_in *args
186
+ end
187
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RegressionTest < FasterXSTest #:nodoc: all
4
+ def test_content_nil_regressions
5
+ expected = {"asdf"=>{"jklsemicolon"=>{}}}
6
+ assert_equal expected, FasterXmlSimple.xml_in("<asdf><jklsemicolon /></asdf>")
7
+ assert_equal expected, FasterXmlSimple.xml_in("<asdf><jklsemicolon /></asdf>", 'forcearray'=>['asdf'])
8
+ end
9
+
10
+ def test_s3_regression
11
+ str = File.read("test/fixtures/test-7.xml")
12
+ assert_nil FasterXmlSimple.xml_in(str)["AccessControlPolicy"]["AccessControlList"]["__content__"]
13
+ end
14
+
15
+ def test_xml_simple_transparency
16
+ assert_equal XmlSimple.xml_in("<asdf />"), FasterXmlSimple.xml_in("<asdf />")
17
+ end
18
+
19
+ def test_suppress_empty_variations
20
+ str = "<asdf><fdsa /></asdf>"
21
+
22
+ assert_equal Hash.new, FasterXmlSimple.xml_in(str)["asdf"]["fdsa"]
23
+ assert_nil FasterXmlSimple.xml_in(str, 'suppressempty'=>nil)["asdf"]["fdsa"]
24
+ assert_equal '', FasterXmlSimple.xml_in(str, 'suppressempty'=>'')["asdf"]["fdsa"]
25
+ assert !FasterXmlSimple.xml_in(str, 'suppressempty'=>true)["asdf"].has_key?("fdsa")
26
+ end
27
+
28
+ def test_empty_string_doesnt_crash
29
+ assert_raise(XML::Parser::ParseError) do
30
+ silence_stderr do
31
+ FasterXmlSimple.xml_in('')
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_keeproot_false
37
+ str = "<asdf><fdsa>1</fdsa></asdf>"
38
+ expected = {"fdsa"=>"1"}
39
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false)
40
+ end
41
+
42
+ def test_keeproot_false_with_force_content
43
+ str = "<asdf><fdsa>1</fdsa></asdf>"
44
+ expected = {"fdsa"=>{"__content__"=>"1"}}
45
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false, 'forcecontent'=>true)
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+
2
+ require 'test/unit'
3
+ require 'faster_xml_simple'
4
+
5
+ class FasterXSTest < Test::Unit::TestCase
6
+ def default_test
7
+ end
8
+
9
+ def silence_stderr
10
+ str = STDERR.dup
11
+ STDERR.reopen("/dev/null")
12
+ STDERR.sync=true
13
+ yield
14
+ ensure
15
+ STDERR.reopen(str)
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'yaml'
3
+
4
+ class XmlSimpleComparisonTest < FasterXSTest
5
+
6
+ # Define test methods
7
+
8
+ Dir["test/fixtures/test-*.xml"].each do |file_name|
9
+ xml_file_name = file_name
10
+ method_name = File.basename(file_name, ".xml").gsub('-', '_')
11
+ yml_file_name = file_name.gsub('xml', 'yml')
12
+ rails_yml_file_name = file_name.gsub('xml', 'rails.yml')
13
+ class_eval <<-EOV, __FILE__, __LINE__
14
+ def #{method_name}
15
+ assert_equal YAML.load(File.read('#{yml_file_name}')),
16
+ FasterXmlSimple.xml_in(File.read('#{xml_file_name}'), default_options )
17
+ end
18
+
19
+ def #{method_name}_rails
20
+ assert_equal YAML.load(File.read('#{rails_yml_file_name}')),
21
+ FasterXmlSimple.xml_in(File.read('#{xml_file_name}'), rails_options)
22
+ end
23
+ EOV
24
+ end
25
+
26
+ def default_options
27
+ {
28
+ 'keeproot' => true,
29
+ 'contentkey' => '__content__',
30
+ 'forcecontent' => true,
31
+ 'suppressempty' => nil,
32
+ 'forcearray' => ['something-else']
33
+ }
34
+ end
35
+
36
+ def rails_options
37
+ {
38
+ 'forcearray' => false,
39
+ 'forcecontent' => true,
40
+ 'keeproot' => true,
41
+ 'contentkey' => '__content__'
42
+ }
43
+ end
44
+
45
+
46
+ end
@@ -0,0 +1,90 @@
1
+ require 'rubygems'
2
+ require 'lib/campaign_monitor'
3
+ require 'test/unit'
4
+ require 'test/test_helper'
5
+
6
+ CLIENT_NAME = 'Spacely Space Sprockets'
7
+ LIST_NAME = 'List #1'
8
+
9
+ class CampaignMonitorTest < Test::Unit::TestCase
10
+
11
+ def setup
12
+ @cm = CampaignMonitor.new(ENV["API_KEY"])
13
+ # find an existing client
14
+ @client=find_test_client
15
+ assert_not_nil @client, "Please create a '#{CLIENT_NAME}' (company name) client so tests can run."
16
+ end
17
+
18
+
19
+ # def test_create_and_delete_client
20
+ # before=@cm.clients.size
21
+ # response = @cm.Client_Create(build_new_client)
22
+ # puts response.inspect
23
+ # assert_equal before+1, @cm.clients.size
24
+ # @client_id=response["__content__"]
25
+ # reponse = @cm.Client_Delete("ClientID" => @client_id)
26
+ # assert_equal before, @cm.clients.size
27
+ # end
28
+
29
+ def test_find_existing_client_by_name
30
+ clients = @cm.clients
31
+ assert clients.size > 0
32
+
33
+ assert clients.map {|c| c.name}.include?(CLIENT_NAME), "could not find client named: #{CLIENT_NAME}"
34
+ end
35
+
36
+ def test_invalid_key
37
+ @cm=CampaignMonitor.new("12345")
38
+ assert_raises (CampaignMonitor::InvalidAPIKey) { @cm.clients }
39
+ end
40
+
41
+ # we should not get confused here
42
+ def test_can_access_two_accounts_at_once
43
+ @cm=CampaignMonitor.new("12345")
44
+ @cm2=CampaignMonitor.new("abcdef")
45
+ @client=@cm.new_client
46
+ @client2=@cm.new_client
47
+ assert_equal "12345", @client.cm_client.api_key
48
+ assert_equal "abcdef", @client2.cm_client.api_key
49
+ end
50
+
51
+ def test_timezones
52
+ assert_equal 90, @cm.timezones.length
53
+ end
54
+
55
+ def test_countries
56
+ countries=@cm.countries
57
+ assert_equal 246, countries.length
58
+ assert countries.include?("United States of America")
59
+ end
60
+
61
+
62
+ # campaigns
63
+
64
+ # def test_campaigns
65
+ # client = find_test_client
66
+ # assert client.campaigns.size > 0, "should have one campaign"
67
+ # end
68
+
69
+
70
+ protected
71
+ def build_new_client(options={})
72
+ {"CompanyName" => "Spacely Space Sprockets", "ContactName" => "George Jetson",
73
+ "EmailAddress" => "george@sss.com", "Country" => "United States of America",
74
+ "TimeZone" => "(GMT-05:00) Indiana (East)"
75
+ }.merge(options)
76
+ end
77
+
78
+
79
+ def assert_success(result)
80
+ assert result.succeeded?, result.message
81
+ end
82
+
83
+ def find_test_client(clients=@cm.clients)
84
+ clients.detect { |c| c.name == CLIENT_NAME }
85
+ end
86
+
87
+ def find_test_list(lists=find_test_client.lists)
88
+ lists.detect { |l| l.name == LIST_NAME }
89
+ end
90
+ end
@@ -0,0 +1,110 @@
1
+ require 'rubygems'
2
+ require 'lib/campaign_monitor'
3
+ require 'test/unit'
4
+ require 'test/test_helper'
5
+
6
+ CLIENT_NAME = 'Spacely Space Sprockets'
7
+ CLIENT_CONTACT_NAME = 'George Jetson'
8
+ LIST_NAME = 'List #1'
9
+
10
+ class CampaignMonitorTest < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @cm = CampaignMonitor.new(ENV["API_KEY"])
14
+ # find an existing client and make sure we know it's values
15
+ @client=find_test_client(@cm.clients)
16
+ assert_not_nil @client, "Please create a '#{CLIENT_NAME}' (company name) client so tests can run."
17
+
18
+ @campaign=@client.campaigns.detect { |x| x["Subject"] == "Big Deal" }
19
+ assert_not_nil @campaign, "Please create a campaign named 'Big Deal' so tests can run."
20
+
21
+ # delete all existing lists
22
+ @client.lists.each { |l| l.Delete }
23
+ @list = @client.lists.build.defaults
24
+ end
25
+
26
+ def teardown
27
+ end
28
+
29
+ def test_finds_named_campaign
30
+ assert_equal 1, @campaign["TotalRecipients"]
31
+ assert_equal 1, @campaign.lists.size
32
+ assert_equal Hash.new, @campaign.lists.first["Title"]
33
+ end
34
+
35
+ def test_bracket_lookup_for_nonexistant
36
+ @campaign=CampaignMonitor::Campaign[12345]
37
+ assert_not_nil @campaign
38
+ assert_equal 12345, @campaign.id
39
+ assert_raises( CampaignMonitor::ApiError ) { @campaign.lists }
40
+ assert_raises( CampaignMonitor::ApiError ) { @campaign.GetSummary }
41
+ end
42
+
43
+ def test_bracket_lookup_for_existing
44
+ camp=CampaignMonitor::Campaign[@campaign.id]
45
+ assert_not_nil camp
46
+ camp.lists
47
+ end
48
+
49
+ def test_summary_interface
50
+ # old
51
+ assert_equal 1, @campaign.number_recipients
52
+ assert_equal 0, @campaign.number_opened
53
+ assert_equal 0, @campaign.number_clicks
54
+ assert_equal 0, @campaign.number_unsubscribed
55
+ assert_equal 0, @campaign.number_bounced
56
+ # new
57
+ assert_equal 1, @campaign.summary["Recipients"]
58
+ assert_equal 0, @campaign.summary["TotalOpened"]
59
+ assert_equal 0, @campaign.summary["Clicks"]
60
+ assert_equal 0, @campaign.summary["Unsubscribed"]
61
+ assert_equal 0, @campaign.summary["Bounced"]
62
+ end
63
+
64
+ def test_creating_a_campaign
65
+ return
66
+ @campaign=@client.new_campaign
67
+ # create two lists
68
+ @beef=@client.lists.build.defaults
69
+ @beef["Title"]="Beef"
70
+ @beef.Create
71
+ assert_success @beef.result
72
+ @chicken=@client.lists.build.defaults
73
+ @chicken["Title"]="Chicken"
74
+ @chicken.Create
75
+ assert_success @chicken.result
76
+
77
+ @campaign.lists << @beef
78
+ @campaign.lists << @chicken
79
+ @campaign["CampaignName"]="Noodles #{secure_digest(Time.now.to_s)}"
80
+ @campaign["CampaignSubject"]="Noodly #{secure_digest(Time.now.to_s)}"
81
+ @campaign["FromName"] = "George Bush"
82
+ @campaign["FromEmail"] = "george@aol.com"
83
+ @campaign["ReplyTo"] = "george@aol.com"
84
+ @campaign["HtmlUrl"] = "http://www.google.com/robots.txt"
85
+ @campaign["TextUrl"] = "http://www.google.com/robots.txt"
86
+ @campaign.Create
87
+ assert_success @campaign.result
88
+ assert_not_nil @campaign.id
89
+ assert_equal 32, @campaign.id.length
90
+ # test GetLists
91
+ @campaign.instance_variable_set("@lists",nil)
92
+ assert_equal 2, @campaign.lists.size
93
+ # test sending
94
+ @campaign.Send("ConfirmationEmail" => "george@aol.com", "SendDate" => "Immediately")
95
+ assert_success @campaign.result
96
+ end
97
+
98
+ def test_GetSummary
99
+ @campaign.GetSummary
100
+ assert @campaign.result.success?
101
+ end
102
+
103
+
104
+ protected
105
+
106
+ def find_test_client(clients)
107
+ clients.detect { |c| c.name == CLIENT_NAME }
108
+ end
109
+
110
+ end
@@ -0,0 +1,129 @@
1
+ require 'rubygems'
2
+ require 'lib/campaign_monitor'
3
+ require 'test/unit'
4
+ require 'test/test_helper'
5
+
6
+ CLIENT_NAME = 'Spacely Space Sprockets'
7
+ CLIENT_CONTACT_NAME = 'George Jetson'
8
+ LIST_NAME = 'List #1'
9
+
10
+ class CampaignMonitorTest < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @cm = CampaignMonitor.new(ENV["API_KEY"])
14
+ # find an existing client and make sure we know it's values
15
+ @client=find_test_client
16
+ assert_not_nil @client, "Please create a '#{CLIENT_NAME}' (company name) client so tests can run."
17
+ end
18
+
19
+
20
+ def teardown
21
+ # revert it back
22
+ @client["ContactName"]="George Jetson"
23
+ @client["EmailAddress"]="george@sss.com"
24
+
25
+ @client["Username"]=""
26
+ @client["Password"]=""
27
+ @client["AccessLevel"]=0
28
+
29
+ assert @client.update
30
+ assert_success @client.result
31
+ end
32
+
33
+ # def test_create_and_delete_client
34
+ # before=@cm.clients.size
35
+ # client=CampaignMonitor::Client.new(build_new_client)
36
+ # client.Create
37
+ # assert_success client.result
38
+ # assert_equal before+1, @cm.clients.size
39
+ # client.Delete
40
+ # assert_success client.result
41
+ # assert_equal before, @cm.clients.size
42
+ # end
43
+
44
+ def test_find_existing_client_by_name
45
+ clients = @cm.clients
46
+ assert clients.size > 0
47
+
48
+ assert clients.map {|c| c.name}.include?(CLIENT_NAME), "could not find client named: #{CLIENT_NAME}"
49
+ end
50
+
51
+ def test_client_attributes
52
+ assert_equal CLIENT_NAME, @client["CompanyName"]
53
+ assert_nil @client["ContactName"]
54
+ assert @client.GetDetail
55
+ assert_not_empty @client["ContactName"]
56
+ assert_not_empty @client["EmailAddress"]
57
+ assert_not_empty @client["Country"]
58
+ assert_not_empty @client["Timezone"]
59
+
60
+ assert_nil @client["Username"]
61
+ assert_nil @client["Password"]
62
+ assert_equal 0, @client["AccessLevel"]
63
+ end
64
+
65
+ def test_update_client_basics
66
+ # only update the name
67
+ @client["ContactName"]="Bob Watson"
68
+ assert @client.UpdateBasics
69
+ assert_success @client.result
70
+ client=@cm.clients.detect {|x| x.name==CLIENT_NAME}
71
+ client.GetDetail
72
+ assert_equal "Bob Watson", client["ContactName"]
73
+ # make sure e-mail has remained unchanged
74
+ assert_equal "george@sss.com", client["EmailAddress"]
75
+ end
76
+
77
+ def test_update_access_and_billing
78
+ @client["Username"]="login"
79
+ @client["Password"]="secret"
80
+ @client["AccessLevel"]=63
81
+ @client["BillingType"]="ClientPaysAtStandardRate"
82
+ @client["Currency"]="USD"
83
+ @client.UpdateAccessAndBilling
84
+ assert_success @client.result
85
+ # load it up again
86
+ client=@cm.clients.detect {|x| x.name==CLIENT_NAME}
87
+ client.GetDetail
88
+ assert_equal "login", client["Username"]
89
+ assert_equal "secret", client["Password"]
90
+ assert_equal "ClientPaysAtStandardRate", client["BillingType"]
91
+ assert_equal 63, client["AccessLevel"]
92
+ assert_equal "USD", client["Currency"]
93
+ end
94
+
95
+ def test_update_both
96
+ @client["ContactName"]="Bob Watson"
97
+ @client["Username"]="login"
98
+ @client["Password"]="secret"
99
+ @client["AccessLevel"]=63
100
+ @client["BillingType"]="ClientPaysAtStandardRate"
101
+ @client["Currency"]="USD"
102
+ @client.update
103
+ assert_success @client.result
104
+ assert_equal "login", @client["Username"]
105
+ assert_equal "Bob Watson", @client["ContactName"]
106
+ end
107
+
108
+
109
+ protected
110
+ def build_new_client(options={})
111
+ {"CompanyName" => "Lick More Enterprises", "ContactName" => "George Jetson",
112
+ "EmailAddress" => "george@jetson.com", "Country" => "United States of America",
113
+ "TimeZone" => "(GMT-05:00) Indiana (East)"
114
+ }.merge(options)
115
+ end
116
+
117
+
118
+ def assert_success(result)
119
+ assert result.succeeded?, result.message
120
+ end
121
+
122
+ def find_test_client(clients=@cm.clients)
123
+ clients.detect { |c| c.name == CLIENT_NAME }
124
+ end
125
+
126
+ def find_test_list(lists=find_test_client.lists)
127
+ lists.detect { |l| l.name == LIST_NAME }
128
+ end
129
+ end