txbr 1.0.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.
@@ -0,0 +1,46 @@
1
+ $:.push(File.dirname(__FILE__))
2
+
3
+ require 'rspec'
4
+ require 'rack/test'
5
+ require 'pry-byebug'
6
+ require 'txbr'
7
+ require 'txgh'
8
+ require 'cgi'
9
+ require 'vcr'
10
+
11
+ require 'support/env_helpers'
12
+
13
+ FAKE_SESSION_ID = '123abc123abc123abc123abc123abc12'
14
+
15
+ VCR.configure do |config|
16
+ config.cassette_library_dir = 'spec/fixtures/cassettes'
17
+ config.hook_into :webmock
18
+
19
+ %w(BRAZE_EMAIL_ADDRESS BRAZE_PASSWORD).each do |var|
20
+ config.filter_sensitive_data(var) { ENV[var] || var }
21
+ config.filter_sensitive_data(var) { CGI.escape(ENV[var] || var) }
22
+ end
23
+
24
+ config.before_record do |interaction|
25
+ # Clear out the body for requests to the dashboard, etc. We need to keep
26
+ # the body for the auth request so mechanize can find the login form.
27
+ if %w(/auth).none? { |str| interaction.request.uri.include?(str) }
28
+ interaction.response.body.replace('')
29
+ interaction.response.headers['Content-Length'] = ['0']
30
+ end
31
+
32
+ # remove session ID from request cookie header
33
+ interaction.request.headers.fetch('Cookie', []).each do |header|
34
+ header.sub!(/_session_id=[a-z0-9]{32}/, "_session_id=#{FAKE_SESSION_ID}")
35
+ end
36
+
37
+ # remove session ID from response set cookie headers
38
+ interaction.response.headers.fetch('Set-Cookie', []).each do |header|
39
+ header.sub!(/_session_id=[a-z0-9]{32}/, "_session_id=#{FAKE_SESSION_ID}")
40
+ end
41
+ end
42
+ end
43
+
44
+ RSpec.configure do |config|
45
+ config.include(EnvHelpers)
46
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Txbr::StringsManifest do
4
+ let(:manifest) { described_class.new }
5
+
6
+ describe '#add' do
7
+ it 'adds the string with the given path' do
8
+ manifest.add(%w(foo bar), 'baz')
9
+ expect(manifest.to_h).to eq(
10
+ { 'foo' => { 'bar' => 'baz' } }
11
+ )
12
+ end
13
+
14
+ it 'nests correctly' do
15
+ manifest.add(%w(foo bar), 'baz')
16
+ manifest.add(%w(foo baz boo), 'bizz')
17
+ expect(manifest.to_h).to eq(
18
+ { 'foo' => { 'bar' => 'baz', 'baz' => { 'boo' => 'bizz' } } }
19
+ )
20
+ end
21
+ end
22
+
23
+ describe '#merge!' do
24
+ it 'merges the strings from another manifest into this one' do
25
+ other = described_class.new
26
+ other.add(%w(foo bar), 'baz')
27
+ manifest.add(%w(foo baz boo), 'bizz')
28
+ manifest.merge!(other)
29
+ expect(manifest.to_h).to eq(
30
+ { 'foo' => { 'bar' => 'baz', 'baz' => { 'boo' => 'bizz' } } }
31
+ )
32
+ end
33
+ end
34
+
35
+ describe '#each' do
36
+ it 'yields each path and corresponding value' do
37
+ manifest.add(%w(foo bar), 'baz')
38
+ manifest.add(%w(foo baz boo), 'bizz')
39
+
40
+ expect(manifest.each.to_a).to(
41
+ eq([[['foo', 'bar'], 'baz'], [['foo', 'baz', 'boo'], 'bizz']])
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ module EnvHelpers
2
+ def with_env(env)
3
+ # ENV can't be duped, so use select instead to make a copy
4
+ old_env = ENV.select { true }
5
+ env.each_pair { |k, v| ENV[k] = v }
6
+ yield
7
+ ensure
8
+ # reset back to old vars
9
+ env.each_pair { |k, _| ENV[k] = old_env[k] }
10
+ end
11
+ end
12
+
13
+ EnvHelpers.extend(EnvHelpers)
@@ -0,0 +1,14 @@
1
+ class FakeBrazeSession
2
+ attr_reader :api_url, :session_id, :was_reset
3
+ alias reset? was_reset
4
+
5
+ def initialize(api_url, session_id)
6
+ @api_url = api_url
7
+ @session_id = session_id
8
+ @was_reset = false
9
+ end
10
+
11
+ def reset!
12
+ @was_reset = true
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ class FakeEnv
2
+ attr_reader :url
3
+
4
+ def initialize(url)
5
+ @url = url
6
+ end
7
+ end
8
+
9
+ class FakeResponse
10
+ attr_reader :env, :status, :body, :headers
11
+
12
+ def initialize(url, status, body, headers)
13
+ @env = FakeEnv.new(url)
14
+ @status = status
15
+ @body = body
16
+ @headers = headers
17
+ end
18
+ end
19
+
20
+ class FakeConnection
21
+ class UnexpectedRequestError < StandardError; end
22
+
23
+ attr_reader :interactions
24
+
25
+ # of the form:
26
+ # [{
27
+ # request: { verb: 'get', url: 'foo.com', params: { ... }, body: '...' },
28
+ # response: { status: 200, body: '...', headers: { ... } }
29
+ # }]
30
+ def initialize(interactions)
31
+ @interactions = interactions
32
+ end
33
+
34
+ def get(url, params = {}, _headers = {})
35
+ idx, interaction = find_interaction('get', url)
36
+ raise UnexpectedRequestError, url unless interaction
37
+
38
+ if interaction_params = interaction[:request][:params]
39
+ raise UnexpectedRequestError, url unless params == interaction_params
40
+ end
41
+
42
+ interactions.delete_at(idx)
43
+ response_for(url, interaction)
44
+ end
45
+
46
+ def post(url, body = '', _headers = {})
47
+ idx, interaction = find_interaction('post', url)
48
+ raise UnexpectedRequestError, url unless interaction
49
+
50
+ if interaction_body = interaction[:request][:body]
51
+ raise UnexpectedRequestError, url unless body == interaction_body
52
+ end
53
+
54
+ interactions.delete_at(idx)
55
+ response_for(url, interaction)
56
+ end
57
+
58
+ private
59
+
60
+ def response_for(url ,interaction)
61
+ FakeResponse.new(
62
+ url,
63
+ interaction[:response][:status],
64
+ interaction[:response][:body],
65
+ interaction[:response][:headers]
66
+ )
67
+ end
68
+
69
+ def find_interaction(verb, url)
70
+ idx = interactions.find_index do |interaction|
71
+ request = interaction[:request]
72
+ request[:verb] == verb && request[:url] == url
73
+ end
74
+
75
+ return [nil, nil] unless idx
76
+ [idx, interactions[idx]]
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ require 'txgh'
2
+ require 'support/fake_braze_session'
3
+ require 'support/fake_connection'
4
+ require 'support/test_config'
5
+
6
+ shared_context 'standard setup' do
7
+ let(:session_id) { 'session_id' }
8
+ let(:app_group_id) { config[:braze_app_group_id] }
9
+ let(:braze_connection) { FakeConnection.new(braze_interactions) }
10
+ let(:transifex_connection) { FakeConnection.new(transifex_interactions) }
11
+ let(:braze_session) { FakeBrazeSession.new(config[:braze_api_url], session_id) }
12
+ let(:braze_api) { Txbr::BrazeSessionApi.new(braze_session, app_group_id, connection: braze_connection) }
13
+ let(:transifex_api) { Txgh::TransifexApi.create_from_connection(transifex_connection) }
14
+ let(:config) { TestConfig.config[:projects].first }
15
+ let(:project) { Txbr::Project.new(config.merge(braze_api: braze_api, transifex_api: transifex_api)) }
16
+
17
+ # override these
18
+ let(:braze_interactions) { [] }
19
+ let(:transifex_interactions) { [] }
20
+ end
@@ -0,0 +1,20 @@
1
+ class TestConfig
2
+ def self.config
3
+ {
4
+ transifex_api_username: 'transifex_username',
5
+ transifex_api_password: 'transifex_password',
6
+ projects: [{
7
+ handler_id: 'email-templates',
8
+ braze_api_key: 'braze_api_key',
9
+ braze_api_url: 'https://somewhere.braze.com',
10
+ strings_format: 'YML',
11
+ source_lang: 'en',
12
+
13
+ # @TODO: remove once Braze implements the endpoints we asked for
14
+ braze_email_address: 'braze@email.com',
15
+ braze_password: 'braze_password',
16
+ braze_app_group_id: '5551212'
17
+ }]
18
+ }
19
+ end
20
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'support/standard_setup'
3
+
4
+ describe Txbr::Uploader do
5
+ include_context 'standard setup'
6
+
7
+ let(:email_template_id) { 'abc123' }
8
+
9
+ let(:braze_interactions) do
10
+ [{
11
+ request: { verb: 'get', url: 'engagement/email_templates', start: 0, length: 1 },
12
+ response: { status: 200, body: { results: [{ id: email_template_id }] }.to_json }
13
+ }, {
14
+ request: { verb: 'get', url: "engagement/email_templates/#{email_template_id}" },
15
+ response: {
16
+ status: 200,
17
+ body: {
18
+ name: 'Super Slick Awesome',
19
+ template: template_html,
20
+ subject: '',
21
+ preheader: ''
22
+ }.to_json
23
+ }
24
+ }]
25
+ end
26
+
27
+ let(:transifex_interactions) do
28
+ # indicate the resource doesn't exist so the client will create it
29
+ [{
30
+ request: {
31
+ verb: 'get',
32
+ url: "#{Txgh::TransifexApi::API_ROOT}/project/my_project/resource/my_resource/"
33
+ },
34
+ response: { status: 404 }
35
+ }]
36
+ end
37
+
38
+ let(:template_html) do
39
+ <<~HTML
40
+ <html>
41
+ <head>
42
+ {% assign project_slug = "my_project" %}
43
+ {% assign resource_slug = "my_resource" %}
44
+ {% assign translation_enabled = true %}
45
+ {% connected_content http://my_strings_api.com/ :save strings %}
46
+ </head>
47
+ <body>
48
+ {{strings.header | default: 'Buy our stuff!'}}
49
+ {% if user.gets_discount? %}
50
+ {{strings.discount | default: 'You get a discount'}}
51
+ {% else %}
52
+ {{strings.no_discount | default: 'You get no discount'}}
53
+ {% endif %}
54
+ </body>
55
+ </html>
56
+ HTML
57
+ end
58
+
59
+ it 'uploads all resources' do
60
+ # unfortunately this can't be added to the transifex_interactions array
61
+ # because we need to check the upload contents, which is this funky
62
+ # UploadIO object from Faraday
63
+ create_url = "#{Txgh::TransifexApi::API_ROOT}/project/my_project/resources/"
64
+
65
+ expect(transifex_connection).to(
66
+ receive(:post)
67
+ .with(create_url, Hash) do |url, body|
68
+ expect(YAML.load(body[:content].io.string)).to eq(
69
+ {
70
+ 'en' => {
71
+ 'header'=>'Buy our stuff!',
72
+ 'discount'=>'You get a discount',
73
+ 'no_discount'=>'You get no discount'
74
+ }
75
+ }
76
+ )
77
+
78
+ FakeResponse.new(create_url, 200, '', {})
79
+ end
80
+ )
81
+
82
+ described_class.new(project).upload_all
83
+ end
84
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Txbr::Utils do
4
+ describe '.url_join' do
5
+ it 'joins urls' do
6
+ expect(described_class.url_join(*%w(http://foo.bar baz boo))).to(
7
+ eq('http://foo.bar/baz/boo')
8
+ )
9
+ end
10
+
11
+ it 'joins urls with leading slashes' do
12
+ expect(described_class.url_join(*%w(http://foo.bar/ baz /boo))).to(
13
+ eq('http://foo.bar/baz/boo')
14
+ )
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'txbr/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'txbr'
6
+ s.version = ::Txbr::VERSION
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'https://github.com/lumoslabs/txbr'
10
+
11
+ s.description = s.summary = 'A library for syncing translation resources between Braze and Transifex.'
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+ s.has_rdoc = true
15
+
16
+ s.add_dependency 'abroad', '~> 4.5'
17
+ s.add_dependency 'faraday', '~> 0.9'
18
+ s.add_dependency 'faraday_middleware', '~> 0.10'
19
+ s.add_dependency 'liquid', '~> 4.0'
20
+ s.add_dependency 'mechanize', '~> 2.7'
21
+ s.add_dependency 'sinatra', '~> 1.4'
22
+ s.add_dependency 'sinatra-contrib', '~> 1.4'
23
+ s.add_dependency 'txgh', '~> 6.6'
24
+
25
+ s.require_path = 'lib'
26
+ s.files = Dir['{lib,spec}/**/*', 'README.md', 'txbr.gemspec', 'LICENSE']
27
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: txbr
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: abroad
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.10'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: liquid
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mechanize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.7'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sinatra
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sinatra-contrib
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.4'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: txgh
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '6.6'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '6.6'
125
+ description: A library for syncing translation resources between Braze and Transifex.
126
+ email:
127
+ - camertron@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - LICENSE
133
+ - README.md
134
+ - lib/txbr.rb
135
+ - lib/txbr/application.rb
136
+ - lib/txbr/braze_api.rb
137
+ - lib/txbr/braze_session.rb
138
+ - lib/txbr/braze_session_api.rb
139
+ - lib/txbr/commands.rb
140
+ - lib/txbr/config.rb
141
+ - lib/txbr/email_template.rb
142
+ - lib/txbr/email_template_component.rb
143
+ - lib/txbr/email_template_handler.rb
144
+ - lib/txbr/project.rb
145
+ - lib/txbr/request_methods.rb
146
+ - lib/txbr/strings_manifest.rb
147
+ - lib/txbr/tasks.rb
148
+ - lib/txbr/uploader.rb
149
+ - lib/txbr/utils.rb
150
+ - lib/txbr/version.rb
151
+ - spec/application_spec.rb
152
+ - spec/braze_session_api_spec.rb
153
+ - spec/braze_session_spec.rb
154
+ - spec/config_spec.rb
155
+ - spec/email_template_spec.rb
156
+ - spec/fixtures/cassettes/braze_login.yml
157
+ - spec/spec_helper.rb
158
+ - spec/strings_manifest_spec.rb
159
+ - spec/support/env_helpers.rb
160
+ - spec/support/fake_braze_session.rb
161
+ - spec/support/fake_connection.rb
162
+ - spec/support/standard_setup.rb
163
+ - spec/support/test_config.rb
164
+ - spec/uploader_spec.rb
165
+ - spec/utils_spec.rb
166
+ - txbr.gemspec
167
+ homepage: https://github.com/lumoslabs/txbr
168
+ licenses: []
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubyforge_project:
186
+ rubygems_version: 2.7.6
187
+ signing_key:
188
+ specification_version: 4
189
+ summary: A library for syncing translation resources between Braze and Transifex.
190
+ test_files: []