blue_state_digital 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4e8917551b5e90a45a3c448cf6f58804e9175bb3
4
- data.tar.gz: b22e282e7cd262c56f50f51fe21cf64dd25cc51e
2
+ SHA256:
3
+ metadata.gz: 4c617aba30c10bd08e560236ecebebdaf6a6698a9e55fbb5c17beb2284aedd5e
4
+ data.tar.gz: d1529a6282f4c39dabd7177999409f58a741573d400c2d3a4c4bc285c8b89630
5
5
  SHA512:
6
- metadata.gz: ae5ab46979afce07ca7d8af7b8ce5d28e99765c403055f901502948deb9109ac9dbb2a2f4c68e6e38c4e3fcd6f7c8905f49d14ecac4d29f4e5eb308e7bf93edb
7
- data.tar.gz: 8667e65c8301f175e99ecf8ec7152849c8bb612e1c9ccd26773665b015bc7834e8cea566849b75dad4aa1c1d19d330dee761334d671bdc53c1cef93a63de2db3
6
+ metadata.gz: f7f79e29f4c914a2e3fcccb809b866eae767ea97552131d739bcdb7f6981b88f9520574047165c3abcedad8d21ef0115609c2d3d74de88bc4519b28a790cc0ba
7
+ data.tar.gz: 11483f192126b7ea23da98263cb4ef074bde678dd43b8fc8bc05dfe5fbdd887a6feb850c6beed20ea5db72ad3a7f77a4f831ccfe35f8e5315236e2791d0e60c0
@@ -1 +1 @@
1
- ruby-2.2.3
1
+ 2.5.1
@@ -1,3 +1,3 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.2.3"
3
+ - "2.3.4"
data/README.md CHANGED
@@ -11,25 +11,38 @@ gem blue_state_digital
11
11
  Configuration:
12
12
 
13
13
  ```ruby
14
- connection = BlueStateDigital::Connection.new(host:'foo.com' api_id: 'bar', api_secret: 'magic_secret')
14
+ connection = BlueStateDigital::Connection.new(host:'foo.com', api_id: 'bar', api_secret: 'magic_secret')
15
15
  cons = BlueStateDigital::Constituent.new({firstname: 'George', lastname: 'Washington', emails: [{ email: 'george@washington.com'}]}.merge({connection: connection}))
16
16
  cons.save
17
- cons.Id # created constituent ID
17
+ cons.id # created constituent ID
18
18
  ```
19
19
 
20
20
  Use the event machine adapter:
21
21
 
22
22
  ```ruby
23
- connection = BlueStateDigital::Connection.new(host:'foo.com' api_id: 'bar', api_secret: 'magic_secret', adapter: :em_synchrony)
23
+ connection = BlueStateDigital::Connection.new(host:'foo.com', api_id: 'bar', api_secret: 'magic_secret', adapter: :em_synchrony)
24
24
  ```
25
25
 
26
26
  ### Unsubscribes
27
27
 
28
28
  ```ruby
29
- connection = BlueStateDigital::Connection.new(host:'foo.com' api_id: 'bar', api_secret: 'magic_secret')
29
+ connection = BlueStateDigital::Connection.new(host:'foo.com', api_id: 'bar', api_secret: 'magic_secret')
30
30
  unsub = BlueStateDigital::EmailUnsubscribe.new({email: 'george@washington.com', reason: 'tea in the harbor'}.merge({connection: connection}))
31
31
  unsub.unsubscribe! # raises on error, returns true on success.
32
+ ```
33
+
34
+ ### Signup Forms
32
35
 
36
+ ```ruby
37
+ connection = BlueStateDigital::Connection.new(host:'foo.com', api_id: 'bar', api_secret: 'magic_secret')
38
+ signup_form = BlueStateDigital::SignupForm.clone(clone_from_id: 3, slug: 'foo', name: 'my new form', public_title: 'Sign Here For Puppies', connection: connection)
39
+ signup_form.set_cons_group(2345)
40
+ fields = signup_form.form_fields # returns a list of SignupFormField
41
+
42
+ # The keys of the field_data hash should match the labels of the signup form's fields
43
+ signup_form.process_signup(field_data: {'First Name' => 'George', 'Last Name' => 'Washington', 'Email Address' => 'george@example.com', 'A Custom Field' => 'some custom data'},
44
+ email_opt_in: true, source: 'foo', subsource: 'bar')
45
+ ```
33
46
 
34
47
  ### Dataset integration
35
48
 
@@ -25,8 +25,9 @@ require "blue_state_digital/dataset"
25
25
  require "blue_state_digital/dataset_map"
26
26
  require "blue_state_digital/error_middleware"
27
27
  require "blue_state_digital/email_unsubscribe"
28
+ require "blue_state_digital/signup_form"
28
29
 
29
30
 
30
31
  I18n.enforce_available_locales = false
31
32
 
32
- Faraday::Response.register_middleware :error_middleware => lambda { BlueStateDigital::ErrorMiddleware }
33
+ Faraday::Response.register_middleware :error_middleware => lambda { BlueStateDigital::ErrorMiddleware }
@@ -1,4 +1,6 @@
1
1
  module BlueStateDigital
2
+ class DeferredResultTimeout < StandardError ; end
3
+
2
4
  class Connection
3
5
  API_VERSION = 2
4
6
  API_BASE = '/page/api'
@@ -6,9 +8,13 @@ module BlueStateDigital
6
8
 
7
9
  attr_reader :constituents, :constituent_groups, :datasets, :dataset_maps
8
10
 
11
+ attr_accessor :instrumentation
12
+
9
13
  def initialize(params = {})
10
14
  @api_id = params[:api_id]
11
15
  @api_secret = params[:api_secret]
16
+ self.instrumentation = params[:instrumentation]
17
+
12
18
  @client = Faraday.new(:url => "https://#{params[:host]}/") do |faraday|
13
19
  faraday.request :url_encoded # form-encode POST params
14
20
  if defined?(Rails) && Rails.env.development?
@@ -26,7 +32,16 @@ module BlueStateDigital
26
32
 
27
33
  def perform_request_raw(call, params = {}, method = "GET", body = nil)
28
34
  path = API_BASE + call
29
- if method == "POST" || method == "PUT"
35
+
36
+ # instrumentation is a Proc that is called for each request that is performed.
37
+ if self.instrumentation
38
+ stats = {}
39
+ stats[:path] = path
40
+ stats[:api_id] = @api_id
41
+ self.instrumentation.call(stats)
42
+ end
43
+
44
+ resp = if method == "POST" || method == "PUT"
30
45
  @client.send(method.downcase.to_sym) do |req|
31
46
  content_type = params.delete(:content_type) || 'application/x-www-form-urlencoded'
32
47
  accept = params.delete(:accept) || 'text/xml'
@@ -39,6 +54,8 @@ module BlueStateDigital
39
54
  else
40
55
  @client.get(path, extended_params(path, params))
41
56
  end
57
+
58
+ resp
42
59
  end
43
60
 
44
61
  def perform_graph_request(call, params, method = 'POST')
@@ -106,11 +123,18 @@ module BlueStateDigital
106
123
  end
107
124
  end
108
125
 
109
- def wait_for_deferred_result(deferred_id)
126
+ def wait_for_deferred_result(deferred_id, timeout = 600)
110
127
  result = nil
128
+ time_waiting = 0
111
129
  while result.nil? || (result.respond_to?(:length) && result.length == 0)
112
130
  result = retrieve_results(deferred_id)
113
- sleep(2) if result.nil? || (result.respond_to?(:length) && result.length == 0)
131
+ if result.nil? || (result.respond_to?(:length) && result.length == 0)
132
+ time_waiting = time_waiting + 2
133
+ if time_waiting > timeout
134
+ raise BlueStateDigital::DeferredResultTimeout.new("exceeded timeout #{timeout} seconds waiting for #{deferred_id}")
135
+ end
136
+ sleep(2)
137
+ end
114
138
  end
115
139
  result
116
140
  end
@@ -11,14 +11,14 @@ module BlueStateDigital
11
11
  end
12
12
 
13
13
  def save
14
- xml = connection.perform_request '/cons/set_constituent_data', {}, "POST", self.to_xml
14
+ xml = connection.wait_for_deferred_result( connection.perform_request '/cons/upsert_constituent_data', {}, "POST", self.to_xml )
15
15
  doc = Nokogiri::XML(xml)
16
- record = doc.xpath('//cons').first
16
+ record = doc.xpath('//cons').first
17
17
  if record
18
18
  self.id = record[:id]
19
19
  self.is_new = record[:is_new]
20
20
  else
21
- raise "Set constituent data failed with message: #{xml}"
21
+ raise "upsert_constituent_data failed with message: #{xml}"
22
22
  end
23
23
  self
24
24
  end
@@ -96,17 +96,19 @@ module BlueStateDigital
96
96
 
97
97
  class Constituents < CollectionResource
98
98
  def get_constituents_by_email email, bundles= [ 'cons_group' ]
99
- get_constituents "email=#{email}", bundles
99
+ result = connection.perform_request('/cons/get_constituents_by_email', filter_parameters({emails: email, :bundles=> bundles.join(',')}), "GET")
100
+
101
+ from_response(result)
100
102
  end
101
103
 
102
104
  def get_constituents_by_id(cons_ids, bundles = ['cons_group'])
103
105
  cons_ids_concat = cons_ids.is_a?(Array) ? cons_ids.join(',') : cons_ids.to_s
104
106
 
105
- from_response(connection.perform_request('/cons/get_constituents_by_id', {:cons_ids => cons_ids_concat, :bundles=> bundles.join(',')}, "GET"))
107
+ from_response(connection.perform_request('/cons/get_constituents_by_id', filter_parameters({:cons_ids => cons_ids_concat, :bundles=> bundles.join(',')}), "GET"))
106
108
  end
107
109
 
108
110
  def get_constituents(filter, bundles = [ 'cons_group' ])
109
- result = connection.wait_for_deferred_result( connection.perform_request('/cons/get_constituents', {:filter => filter, :bundles=> bundles.join(',')}, "GET") )
111
+ result = connection.wait_for_deferred_result( connection.perform_request('/cons/get_constituents', filter_parameters({:filter => filter, :bundles=> bundles.join(',')}), "GET") )
110
112
 
111
113
  from_response(result)
112
114
  end
@@ -174,5 +176,13 @@ module BlueStateDigital
174
176
 
175
177
  cons
176
178
  end
179
+
180
+ private
181
+
182
+ def filter_parameters(params)
183
+ params.delete_if do |key, value|
184
+ value.blank?
185
+ end
186
+ end
177
187
  end
178
188
  end
@@ -2,6 +2,7 @@ module BlueStateDigital
2
2
  class Unauthorized < ::Faraday::Error::ClientError ; end
3
3
  class ResourceDoesNotExist < ::Faraday::Error::ClientError ; end
4
4
  class EmailNotFound < ::Faraday::Error::ClientError ; end
5
+ class XmlErrorResponse < ::Faraday::Error::ClientError ; end
5
6
 
6
7
  class ErrorMiddleware < ::Faraday::Response::RaiseError
7
8
  def on_complete(env)
@@ -11,7 +12,9 @@ module BlueStateDigital
11
12
  when 403
12
13
  raise BlueStateDigital::Unauthorized, response_values(env).to_s
13
14
  when 409
14
- if env.body =~ /does not exist/
15
+ if env.body =~ /<error>/
16
+ raise BlueStateDigital::XmlErrorResponse, env.body
17
+ elsif env.body =~ /does not exist/
15
18
  raise BlueStateDigital::ResourceDoesNotExist, response_values(env).to_s
16
19
  elsif env.body =~ /Email not found/
17
20
  raise BlueStateDigital::EmailNotFound, response_values(env).to_s
@@ -26,4 +29,4 @@ module BlueStateDigital
26
29
  end
27
30
  end
28
31
  end
29
- end
32
+ end
@@ -10,7 +10,8 @@ module BlueStateDigital
10
10
  end
11
11
  end
12
12
 
13
- FIELDS = [:event_id_obfuscated, :event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country, :venue_zip, :venue_city, :venue_state_cd, :start_date, :end_date]
13
+ FIELDS = [:event_id_obfuscated, :event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country,
14
+ :venue_zip, :venue_city, :venue_state_cd, :start_date, :end_date, :local_timezone]
14
15
  attr_accessor *FIELDS
15
16
 
16
17
  def save
@@ -37,7 +38,7 @@ module BlueStateDigital
37
38
  end
38
39
 
39
40
  duration_in_minutes = ((end_date - start_date) / 60).to_i
40
- day_attrs = { start_datetime_system: start_date.strftime('%Y-%m-%d %H:%M:%S %z'), duration: duration_in_minutes }
41
+ day_attrs = { start_datetime_system: start_date.strftime('%Y-%m-%d %H:%M:%S'), duration: duration_in_minutes }
41
42
  event_attrs[:days] = [ day_attrs ]
42
43
 
43
44
  event_attrs.to_json
@@ -0,0 +1,93 @@
1
+ module BlueStateDigital
2
+ class SignupFormField < ApiDataModel
3
+ FIELDS = [:id, :format, :label, :description, :is_required, :is_custom_field, :cons_field_id, :create_dt]
4
+ attr_accessor *FIELDS
5
+
6
+ # Takes a <signup_form_field> block already processed by Nokogiri
7
+ def self.from_xml(xml_record)
8
+ SignupFormField.new(id: xml_record[:id],
9
+ format: xml_record.xpath('format').text,
10
+ label: xml_record.xpath('label').text,
11
+ description: xml_record.xpath('description').text,
12
+ is_required: xml_record.xpath('is_required').text == '1',
13
+ is_custom_field: xml_record.xpath('is_custom_field').text == '1',
14
+ cons_field_id: xml_record.xpath('cons_field_id').text.to_i,
15
+ create_dt: xml_record.xpath('create_dt').text)
16
+ end
17
+ end
18
+
19
+ class SignupForm < ApiDataModel
20
+ FIELDS = [:id, :name, :slug, :public_title, :create_dt]
21
+ attr_accessor *FIELDS
22
+
23
+ def self.clone(clone_from_id:, slug:, name:, public_title:, connection:)
24
+ params = {signup_form_id: clone_from_id, title: public_title, signup_form_name: name, slug: slug}
25
+ xml_response = connection.perform_request '/signup/clone_form', {}, 'POST', params
26
+
27
+ doc = Nokogiri::XML(xml_response)
28
+ record = doc.xpath('//signup_form').first
29
+ if record
30
+ id = record.xpath('id').text.to_i
31
+ SignupForm.new(id: id, name: name, slug: slug, public_title: public_title, connection: connection)
32
+ else
33
+ raise "Cloning signup form failed with message: #{xml_response}"
34
+ end
35
+ end
36
+
37
+ # field_data is a hash mapping field labels to field values, like {'First Name' => 'Alice'}
38
+ def process_signup(field_data:, email_opt_in: false, source: nil, subsource: nil)
39
+ # Construct the XML to send
40
+ builder = Builder::XmlMarkup.new
41
+ builder.instruct! :xml, version: '1.0', encoding: 'utf-8'
42
+ xml_body = builder.api do |api|
43
+ api.signup_form(id: self.id) do |form|
44
+ form_fields.each do |field|
45
+ form.signup_form_field(field_data[field.label], id: field.id)
46
+ end
47
+ unless email_opt_in.nil?
48
+ form.email_opt_in(email_opt_in ? '1' : '0')
49
+ end
50
+ form.source(source) unless source.nil?
51
+ form.subsource(subsource) unless subsource.nil?
52
+ end
53
+ end
54
+
55
+ # Post it to the endpoint
56
+ begin
57
+ response = connection.perform_request_raw '/signup/process_signup', {}, 'POST', xml_body
58
+ if response.status >= 200 && response.status < 300
59
+ return true
60
+ else
61
+ raise "process signup failed with message: #{response.body}"
62
+ end
63
+ rescue BlueStateDigital::XmlErrorResponse => err
64
+ begin
65
+ errors = {}
66
+ error_xmls = Nokogiri::XML(err.message).xpath('//error')
67
+ error_xmls.each do |error_xml|
68
+ field = form_fields.select{|field| field.id.to_s == error_xml.xpath('signup_form_field_id').text}.first
69
+ errors[field.label] = error_xml.xpath('description').text
70
+ raise "process_signup failed with errors: #{errors}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def set_cons_group(cons_group_id)
77
+ connection.perform_request('/signup/set_cons_group', {signup_form_id: self.id}, 'POST', {cons_group_id: cons_group_id})
78
+ end
79
+
80
+ def form_fields
81
+ if @_form_fields.nil?
82
+ xml_response = connection.perform_request '/signup/list_form_fields', {signup_form_id: id}, 'GET', nil
83
+ doc = Nokogiri::XML(xml_response)
84
+
85
+ @_form_fields = []
86
+ doc.xpath('//signup_form_field').each do |form_field_record|
87
+ @_form_fields << SignupFormField.from_xml(form_field_record)
88
+ end
89
+ end
90
+ @_form_fields
91
+ end
92
+ end
93
+ end
@@ -1,3 +1,3 @@
1
1
  module BlueStateDigital
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -27,6 +27,46 @@ describe BlueStateDigital::Connection do
27
27
  end
28
28
  end
29
29
 
30
+ describe '#perform_request_raw' do
31
+ let(:api_call) { '/somemethod' }
32
+ let(:timestamp) { Time.now }
33
+ let(:api_ts) { timestamp.utc.to_i.to_s }
34
+ let(:api_mac) { connection.compute_hmac("/page/api#{api_call}", api_ts, { api_ver: '2', api_id: api_id, api_ts: api_ts }) }
35
+
36
+ describe 'instrumentation' do
37
+ before(:each) do
38
+ Timecop.freeze(timestamp) do
39
+ stub_url = "https://#{api_host}/page/api/somemethod?api_id=#{api_id}&api_mac=#{api_mac}&api_ts=#{api_ts}&api_ver=2"
40
+ stub_request(:post, stub_url).with do |request|
41
+ expect(request.body).to eq("a=b")
42
+ expect(request.headers['Accept']).to eq('text/xml')
43
+ expect(request.headers['Content-Type']).to eq('application/x-www-form-urlencoded')
44
+ true
45
+ end.to_return(body: "body")
46
+ end
47
+ end
48
+
49
+ it 'should not instrument anything if instrumentation is nil' do
50
+ expect(connection.instrumentation).to be_nil
51
+ connection.perform_request(api_call, params = {}, method = "POST", body = "a=b")
52
+ end
53
+
54
+ context 'with instrumentation' do
55
+ let(:instrumentation) do
56
+ Proc.new do |stats|
57
+ stats[:path]
58
+ end
59
+ end
60
+ let(:connection) { BlueStateDigital::Connection.new({host: api_host, api_id: api_id, api_secret: api_secret, instrumentation: instrumentation})}
61
+
62
+ it 'should call if set to proc' do
63
+ expect(instrumentation).to receive(:call).with({path: '/page/api/somemethod', :api_id=>"sfrazer"})
64
+ connection.perform_request(api_call, params = {}, method = "POST", body = "a=b")
65
+ end
66
+ end
67
+ end
68
+ end
69
+
30
70
  describe "#perform_request" do
31
71
  context 'POST' do
32
72
  it "should perform POST request" do
@@ -142,6 +182,16 @@ describe BlueStateDigital::Connection do
142
182
  expect(connection).to receive(:perform_request).and_return("foo")
143
183
  expect(connection.get_deferred_results("deferred_id")).to eq("foo")
144
184
  end
185
+
186
+ it 'should raise if timeout occurs' do
187
+ expect(connection).to receive(:perform_request).and_return(nil)
188
+ expect { connection.wait_for_deferred_result("deferred_id", 1) }.to raise_error(BlueStateDigital::DeferredResultTimeout)
189
+ end
190
+
191
+ it 'should not raise if successful' do
192
+ expect(connection).to receive(:perform_request).and_return("foo")
193
+ expect(connection.wait_for_deferred_result("deferred_id")).to eq("foo")
194
+ end
145
195
  end
146
196
 
147
197
  describe "#compute_hmac" do
@@ -226,7 +226,7 @@ xml_string
226
226
  expect(connection).to receive(:perform_request).with("/cons_group/#{method}", post_params, "POST").and_return("deferred_id")
227
227
  expect(connection).not_to receive(:perform_request).with('/get_deferred_results', {deferred_id: "deferred_id"}, "GET")
228
228
 
229
- connection.constituent_groups.send(method.to_sym, cons_group_id, cons_ids, {wait_for_result: false})
229
+ connection.constituent_groups.send(method.to_sym, cons_group_id, cons_ids, {wait_for_result: false})
230
230
  end
231
231
 
232
232
  it "should #{operation} a single constituent id to a group" do
@@ -254,8 +254,7 @@ describe BlueStateDigital::Constituent do
254
254
 
255
255
  describe ".get_constituents_by_email" do
256
256
  it "should make a filtered constituents query" do
257
- expect(connection).to receive(:perform_request).with('/cons/get_constituents', {:filter=>"email=george@washington.com", :bundles => 'cons_group'}, "GET").and_return("deferred_id")
258
- expect(connection).to receive(:perform_request).with('/get_deferred_results', {deferred_id: "deferred_id"}, "GET").and_return(@single_constituent)
257
+ expect(connection).to receive(:perform_request).with('/cons/get_constituents_by_email', {:emails=>"george@washington.com", :bundles => 'cons_group'}, "GET").and_return(@single_constituent)
259
258
  response = connection.constituents.get_constituents_by_email("george@washington.com").first
260
259
  expect(response.id).to eq("4382")
261
260
  expect(response.firstname).to eq('Bob')
@@ -263,8 +262,7 @@ describe BlueStateDigital::Constituent do
263
262
 
264
263
  it "should return constituents' details based on bundles" do
265
264
  bundles = 'cons_addr'
266
- expect(connection).to receive(:perform_request).with('/cons/get_constituents', {:filter=>"email=george@washington.com", :bundles => bundles}, "GET").and_return("deferred_id")
267
- expect(connection).to receive(:perform_request).with('/get_deferred_results', {deferred_id: "deferred_id"}, "GET").and_return(@constituent_with_addr)
265
+ expect(connection).to receive(:perform_request).with('/cons/get_constituents_by_email', {:emails=>"george@washington.com", :bundles => bundles}, "GET").and_return(@constituent_with_addr)
268
266
  response = connection.constituents.get_constituents_by_email("george@washington.com", ['cons_addr']).first
269
267
  response.addresses[0].addr1 == "aaa1"
270
268
  response.addresses[0].addr2 == "aaa2"
@@ -359,6 +357,7 @@ describe BlueStateDigital::Constituent do
359
357
  connection.constituents.delete_constituents_by_id(2)
360
358
  end
361
359
  end
360
+
362
361
  it "should set constituent data" do
363
362
  timestamp = Time.now.to_i
364
363
 
@@ -397,7 +396,10 @@ describe BlueStateDigital::Constituent do
397
396
  output << "</cons>"
398
397
  output << "</api>"
399
398
 
400
- expect(connection).to receive(:perform_request).with('/cons/set_constituent_data', {}, "POST", input) { output }
399
+ deferred_result_id = '123'
400
+
401
+ expect(connection).to receive(:wait_for_deferred_result).with(deferred_result_id).and_return(output)
402
+ expect(connection).to receive(:perform_request).with('/cons/upsert_constituent_data', {}, "POST", input) { deferred_result_id }
401
403
 
402
404
  cons_data = BlueStateDigital::Constituent.new(data)
403
405
  cons_data.save
@@ -419,4 +421,4 @@ describe BlueStateDigital::Constituent do
419
421
  expect(cons.to_xml).not_to be_nil
420
422
  end
421
423
  end
422
- end
424
+ end
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe BlueStateDigital::Event do
4
- let(:start_date) { Time.now }
4
+ let(:time_zone) { Time.find_zone('America/New_York') }
5
+ let(:start_date) { time_zone.now.change(usec: 0) }
5
6
  let(:end_date) { start_date + 1.hour }
6
- let(:event_attributes) { {event_type_id: '1', creator_cons_id: '2', name: 'event 1', description: 'my event', venue_name: 'home', venue_country: 'US', venue_zip: '10001', venue_city: 'New York', venue_state_cd: 'NY', start_date: start_date, end_date: end_date} }
7
+ let(:event_attributes) { {event_type_id: '1', creator_cons_id: '2', name: 'event 1', description: 'my event', venue_name: 'home', venue_country: 'US', venue_zip: '10001', venue_city: 'New York', venue_state_cd: 'NY', start_date: start_date, end_date: end_date, local_timezone: 'America/New_York'} }
7
8
 
8
9
  describe '#to_json' do
9
10
  it "should serialize event without event_id_obfuscated" do
@@ -12,12 +13,12 @@ describe BlueStateDigital::Event do
12
13
  event_json = JSON.parse(event.to_json)
13
14
 
14
15
  expect(event_json.keys).not_to include(:event_id_obfuscated)
15
- [:event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country, :venue_zip, :venue_city, :venue_state_cd].each do |direct_attribute|
16
+ [:event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country, :venue_zip, :venue_city, :venue_state_cd, :local_timezone].each do |direct_attribute|
16
17
  expect(event_json[direct_attribute.to_s]).to eq(event_attributes[direct_attribute])
17
18
  end
18
19
  expect(event_json['days'].count).to eq(1)
19
- start_date = event_json['days'][0]['start_datetime_system']
20
- expect(Time.parse(start_date)).to eq(start_date)
20
+ start_date_serialized = event_json['days'][0]['start_datetime_system']
21
+ expect(time_zone.parse(start_date_serialized)).to eq(start_date)
21
22
  expect(event_json['days'][0]['duration']).to eq(60)
22
23
  end
23
24
 
@@ -27,12 +28,12 @@ describe BlueStateDigital::Event do
27
28
 
28
29
  event_json = JSON.parse(event.to_json)
29
30
 
30
- [:event_id_obfuscated, :event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country, :venue_zip, :venue_city, :venue_state_cd].each do |direct_attribute|
31
+ [:event_id_obfuscated, :event_type_id, :creator_cons_id, :name, :description, :venue_name, :venue_country, :venue_zip, :venue_city, :venue_state_cd, :local_timezone].each do |direct_attribute|
31
32
  expect(event_json[direct_attribute.to_s]).to eq(event_attributes[direct_attribute])
32
33
  end
33
34
  expect(event_json['days'].count).to eq(1)
34
- start_date = event_json['days'][0]['start_datetime_system']
35
- expect(Time.parse(start_date)).to eq(start_date)
35
+ start_date_serialized = event_json['days'][0]['start_datetime_system']
36
+ expect(time_zone.parse(start_date_serialized)).to eq(start_date)
36
37
  expect(event_json['days'][0]['duration']).to eq(60)
37
38
  end
38
39
  end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe BlueStateDigital::SignupForm do
4
+ let(:connection) { BlueStateDigital::Connection.new({}) }
5
+
6
+ describe '.clone' do
7
+ it 'should create a new SignupForm' do
8
+ response = <<-EOF
9
+ <?xml version="1.0" encoding="utf-8"?>
10
+ <api>
11
+ <signup_form>
12
+ <id>3</id>
13
+ </signup_form>
14
+ </api>
15
+ EOF
16
+ expect(connection).to receive(:perform_request).with('/signup/clone_form', {}, 'POST',
17
+ {signup_form_id: 1,
18
+ title: 'Sign Up Here',
19
+ signup_form_name: 'Signup Form Foo',
20
+ slug: 'foo'}).and_return(response)
21
+
22
+ form = BlueStateDigital::SignupForm.clone(clone_from_id: 1, slug: 'foo', name: 'Signup Form Foo',
23
+ public_title: 'Sign Up Here', connection: connection)
24
+ expect(form.id).to eq(3)
25
+ expect(form.name).to eq('Signup Form Foo')
26
+ expect(form.slug).to eq('foo')
27
+ expect(form.public_title).to eq('Sign Up Here')
28
+ end
29
+ end
30
+
31
+ describe '#process_signup' do
32
+ let(:signup_form) { BlueStateDigital::SignupForm.new(id: 3, name: 'A Form', slug: 'asdf', public_title: 'The Best Form', connection: connection) }
33
+ let(:list_form_fields_response) { <<-EOF
34
+ <?xml version="1.0" encoding="utf-8"?>
35
+ <api>
36
+ <signup_form_field id="83">
37
+ <format>1</format>
38
+ <label>First Name</label>
39
+ <description>First Name</description>
40
+ <display_order>1</display_order>
41
+ <is_shown>1</is_shown>
42
+ <is_required>0</is_required>
43
+ <break_after>0</break_after>
44
+ <is_custom_field>0</is_custom_field>
45
+ <cons_field_id>0</cons_field_id>
46
+ <create_dt>2010-02-08 18:33:11</create_dt>
47
+ <extra_def></extra_def>
48
+ </signup_form_field>
49
+ <signup_form_field id="84">
50
+ <format>1</format>
51
+ <label>Last Name</label>
52
+ <description>Last Name</description>
53
+ <display_order>2</display_order>
54
+ <is_shown>1</is_shown>
55
+ <is_required>0</is_required>
56
+ <break_after>0</break_after>
57
+ <is_custom_field>0</is_custom_field>
58
+ <cons_field_id>0</cons_field_id>
59
+ <create_dt>2010-02-08 18:33:11</create_dt>
60
+ <extra_def></extra_def>
61
+ </signup_form_field>
62
+ </api>
63
+ EOF
64
+ }
65
+
66
+ before :each do
67
+ allow(connection).to receive(:perform_request).with('/signup/list_form_fields',
68
+ {signup_form_id: signup_form.id},
69
+ 'GET', nil).and_return(list_form_fields_response)
70
+ end
71
+
72
+ it 'should call process_signup' do
73
+ expected_body_readable = <<-EOF
74
+ <?xml version="1.0" encoding="utf-8"?>
75
+ <api>
76
+ <signup_form id="3">
77
+ <signup_form_field id="83">Susan</signup_form_field>
78
+ <signup_form_field id="84">Anthony</signup_form_field>
79
+ <email_opt_in>1</email_opt_in>
80
+ <source>foo</source>
81
+ </signup_form>
82
+ </api>
83
+ EOF
84
+ expected_body = expected_body_readable.squish.gsub('> <', '><')
85
+
86
+ expect(connection).to receive(:perform_request_raw).with('/signup/process_signup', {}, 'POST', expected_body).and_return(double(body: '', status: 200))
87
+
88
+ signup_data = {'First Name' => 'Susan', 'Middle Initial' => 'B', 'Last Name' => 'Anthony'}
89
+ signup_form.process_signup(field_data: signup_data, email_opt_in: true, source: 'foo')
90
+ end
91
+ end
92
+
93
+ describe '#set_cons_group' do
94
+ let(:signup_form) { BlueStateDigital::SignupForm.new(id: 3, name: 'A Form', slug: 'asdf', public_title: 'The Best Form', connection: connection) }
95
+ let(:cons_group_id) { 123 }
96
+
97
+ it 'should call set_cons_group' do
98
+ expect(connection).to receive(:perform_request).with('/signup/set_cons_group', {signup_form_id: signup_form.id}, 'POST', {cons_group_id: cons_group_id}).and_return('')
99
+
100
+ signup_form.set_cons_group(cons_group_id)
101
+ end
102
+ end
103
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blue_state_digital
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Woodhull
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-10-08 00:00:00.000000000 Z
12
+ date: 2018-07-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -271,6 +271,7 @@ files:
271
271
  - lib/blue_state_digital/event_rsvp.rb
272
272
  - lib/blue_state_digital/event_type.rb
273
273
  - lib/blue_state_digital/phone.rb
274
+ - lib/blue_state_digital/signup_form.rb
274
275
  - lib/blue_state_digital/version.rb
275
276
  - spec/blue_state_digital/address_spec.rb
276
277
  - spec/blue_state_digital/api_data_model_spec.rb
@@ -286,6 +287,7 @@ files:
286
287
  - spec/blue_state_digital/event_spec.rb
287
288
  - spec/blue_state_digital/event_type_spec.rb
288
289
  - spec/blue_state_digital/phone_spec.rb
290
+ - spec/blue_state_digital/signup_form_spec.rb
289
291
  - spec/fixtures/multiple_event_types.json
290
292
  - spec/fixtures/single_event_type.json
291
293
  - spec/spec_helper.rb
@@ -309,7 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
309
311
  version: '0'
310
312
  requirements: []
311
313
  rubyforge_project: blue_state_digital
312
- rubygems_version: 2.4.8
314
+ rubygems_version: 2.7.6
313
315
  signing_key:
314
316
  specification_version: 4
315
317
  summary: Simple wrapper for Blue State Digital.
@@ -328,6 +330,7 @@ test_files:
328
330
  - spec/blue_state_digital/event_spec.rb
329
331
  - spec/blue_state_digital/event_type_spec.rb
330
332
  - spec/blue_state_digital/phone_spec.rb
333
+ - spec/blue_state_digital/signup_form_spec.rb
331
334
  - spec/fixtures/multiple_event_types.json
332
335
  - spec/fixtures/single_event_type.json
333
336
  - spec/spec_helper.rb