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 +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/README.md +17 -4
- data/lib/blue_state_digital.rb +2 -1
- data/lib/blue_state_digital/connection.rb +27 -3
- data/lib/blue_state_digital/constituent.rb +16 -6
- data/lib/blue_state_digital/error_middleware.rb +5 -2
- data/lib/blue_state_digital/event.rb +3 -2
- data/lib/blue_state_digital/signup_form.rb +93 -0
- data/lib/blue_state_digital/version.rb +1 -1
- data/spec/blue_state_digital/connection_spec.rb +50 -0
- data/spec/blue_state_digital/constituent_group_spec.rb +1 -1
- data/spec/blue_state_digital/constituent_spec.rb +8 -6
- data/spec/blue_state_digital/event_spec.rb +9 -8
- data/spec/blue_state_digital/signup_form_spec.rb +103 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4c617aba30c10bd08e560236ecebebdaf6a6698a9e55fbb5c17beb2284aedd5e
|
4
|
+
data.tar.gz: d1529a6282f4c39dabd7177999409f58a741573d400c2d3a4c4bc285c8b89630
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7f79e29f4c914a2e3fcccb809b866eae767ea97552131d739bcdb7f6981b88f9520574047165c3abcedad8d21ef0115609c2d3d74de88bc4519b28a790cc0ba
|
7
|
+
data.tar.gz: 11483f192126b7ea23da98263cb4ef074bde678dd43b8fc8bc05dfe5fbdd887a6feb850c6beed20ea5db72ad3a7f77a4f831ccfe35f8e5315236e2791d0e60c0
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.5.1
|
data/.travis.yml
CHANGED
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.
|
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
|
|
data/lib/blue_state_digital.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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/
|
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 =
|
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 "
|
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
|
-
|
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 =~
|
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,
|
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
|
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
|
@@ -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,
|
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/
|
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/
|
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
|
-
|
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(:
|
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
|
-
|
20
|
-
expect(
|
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
|
-
|
35
|
-
expect(
|
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.
|
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:
|
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.
|
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
|