txbr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []