blue_state_digital 0.6.0 → 0.7.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.
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