txgh-server 1.0.0.beta1
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 +7 -0
- data/lib/txgh-server/application.rb +141 -0
- data/lib/txgh-server/download_handler.rb +85 -0
- data/lib/txgh-server/github_request_auth.rb +28 -0
- data/lib/txgh-server/response.rb +15 -0
- data/lib/txgh-server/response_helpers.rb +26 -0
- data/lib/txgh-server/stream_response.rb +37 -0
- data/lib/txgh-server/tgz_stream_response.rb +39 -0
- data/lib/txgh-server/transifex_request_auth.rb +53 -0
- data/lib/txgh-server/triggers/handler.rb +50 -0
- data/lib/txgh-server/triggers/pull_handler.rb +18 -0
- data/lib/txgh-server/triggers/push_handler.rb +18 -0
- data/lib/txgh-server/triggers.rb +7 -0
- data/lib/txgh-server/version.rb +3 -0
- data/lib/txgh-server/webhooks/github/delete_handler.rb +37 -0
- data/lib/txgh-server/webhooks/github/handler.rb +20 -0
- data/lib/txgh-server/webhooks/github/ping_handler.rb +18 -0
- data/lib/txgh-server/webhooks/github/push_handler.rb +124 -0
- data/lib/txgh-server/webhooks/github/request_handler.rb +113 -0
- data/lib/txgh-server/webhooks/github.rb +11 -0
- data/lib/txgh-server/webhooks/transifex/hook_handler.rb +94 -0
- data/lib/txgh-server/webhooks/transifex/request_handler.rb +78 -0
- data/lib/txgh-server/webhooks/transifex.rb +8 -0
- data/lib/txgh-server/webhooks.rb +6 -0
- data/lib/txgh-server/zip_stream_response.rb +19 -0
- data/lib/txgh-server.rb +23 -0
- data/spec/application_spec.rb +347 -0
- data/spec/download_handler_spec.rb +91 -0
- data/spec/github_request_auth_spec.rb +39 -0
- data/spec/helpers/github_payload_builder.rb +141 -0
- data/spec/helpers/integration_setup.rb +47 -0
- data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
- data/spec/integration/cassettes/pull.yml +47 -0
- data/spec/integration/cassettes/push.yml +544 -0
- data/spec/integration/cassettes/transifex_hook_endpoint.yml +221 -0
- data/spec/integration/config/tx.config +10 -0
- data/spec/integration/hooks_spec.rb +159 -0
- data/spec/integration/payloads/github_postbody.json +161 -0
- data/spec/integration/payloads/github_postbody_l10n.json +136 -0
- data/spec/integration/payloads/github_postbody_release.json +136 -0
- data/spec/integration/triggers_spec.rb +45 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tgz_stream_response_spec.rb +59 -0
- data/spec/transifex_request_auth_spec.rb +39 -0
- data/spec/webhooks/github/delete_handler_spec.rb +38 -0
- data/spec/webhooks/github/ping_handler_spec.rb +16 -0
- data/spec/webhooks/github/push_handler_spec.rb +106 -0
- data/spec/webhooks/transifex/hook_handler_spec.rb +136 -0
- data/spec/zip_stream_response_spec.rb +58 -0
- data/txgh-server.gemspec +24 -0
- metadata +170 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rack/test'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
require 'helpers/github_payload_builder'
|
|
6
|
+
require 'helpers/standard_txgh_setup'
|
|
7
|
+
|
|
8
|
+
describe TxghServer::Application do
|
|
9
|
+
include Rack::Test::Methods
|
|
10
|
+
include StandardTxghSetup
|
|
11
|
+
|
|
12
|
+
def app
|
|
13
|
+
TxghServer::Application
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '/health_check' do
|
|
17
|
+
it 'indicates the server is running, returns a 200' do
|
|
18
|
+
get '/health_check'
|
|
19
|
+
expect(last_response).to be_ok
|
|
20
|
+
expect(JSON.parse(last_response.body)).to eq({})
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe '/config' do
|
|
25
|
+
it 'fetches and returns the config for the given project' do
|
|
26
|
+
get '/config', project_slug: project_name
|
|
27
|
+
config = JSON.parse(last_response.body)['data']
|
|
28
|
+
expect(config).to include('resources')
|
|
29
|
+
expect(config).to_not include('branch_slug')
|
|
30
|
+
expect(config['resources'].first).to eq(
|
|
31
|
+
'project_slug' => 'my_awesome_project',
|
|
32
|
+
'resource_slug' => 'my_resource',
|
|
33
|
+
'type' => 'YML',
|
|
34
|
+
'source_lang' => 'en',
|
|
35
|
+
'source_file' => 'sample.yml',
|
|
36
|
+
'translation_file' => 'translations/<lang>/sample.yml'
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'fetches and returns the config for the given project and branch' do
|
|
41
|
+
get '/config', project_slug: project_name, branch: branch
|
|
42
|
+
config = JSON.parse(last_response.body)['data']
|
|
43
|
+
expect(config).to include('resources')
|
|
44
|
+
expect(config['branch_slug']).to eq('heads_master')
|
|
45
|
+
expect(config['resources'].first).to eq(
|
|
46
|
+
'project_slug' => 'my_awesome_project',
|
|
47
|
+
'resource_slug' => 'my_resource',
|
|
48
|
+
'type' => 'YML',
|
|
49
|
+
'source_lang' => 'en',
|
|
50
|
+
'source_file' => 'sample.yml',
|
|
51
|
+
'translation_file' => 'translations/<lang>/sample.yml'
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "responds with not found when config can't be found" do
|
|
56
|
+
message = 'Red alert!'
|
|
57
|
+
|
|
58
|
+
expect(Txgh::Config::TxManager).to(
|
|
59
|
+
receive(:tx_config).and_raise(Txgh::ConfigNotFoundError, message)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
get '/config', project_slug: project_name
|
|
63
|
+
expect(last_response.status).to eq(404)
|
|
64
|
+
response = JSON.parse(last_response.body)
|
|
65
|
+
expect(response).to eq([
|
|
66
|
+
'error' => message
|
|
67
|
+
])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'responds with internal error when an unexpected error occurs' do
|
|
71
|
+
message = 'Red alert!'
|
|
72
|
+
|
|
73
|
+
expect(Txgh::Config::TxManager).to(
|
|
74
|
+
receive(:tx_config).and_raise(StandardError, message)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
get '/config', project_slug: project_name
|
|
78
|
+
expect(last_response.status).to eq(500)
|
|
79
|
+
response = JSON.parse(last_response.body)
|
|
80
|
+
expect(response).to eq([
|
|
81
|
+
'error' => message
|
|
82
|
+
])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe TxghServer::WebhookEndpoints do
|
|
88
|
+
include Rack::Test::Methods
|
|
89
|
+
include StandardTxghSetup
|
|
90
|
+
include TxghServer::ResponseHelpers
|
|
91
|
+
|
|
92
|
+
def app
|
|
93
|
+
TxghServer::WebhookEndpoints
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
let(:config) do
|
|
97
|
+
Txgh::Config::ConfigPair.new(project_config, repo_config)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
before(:each) do
|
|
101
|
+
allow(Txgh::Config::KeyManager).to(
|
|
102
|
+
receive(:config_from_project).with(project_name).and_return(config)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
allow(Txgh::Config::KeyManager).to(
|
|
106
|
+
receive(:config_from_repo).with(repo_name).and_return(config)
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe '/transifex' do
|
|
111
|
+
def sign_with(body)
|
|
112
|
+
header(
|
|
113
|
+
TxghServer::TransifexRequestAuth::TRANSIFEX_HEADER,
|
|
114
|
+
TxghServer::TransifexRequestAuth.header_value(
|
|
115
|
+
body, config.transifex_project.webhook_secret
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
let(:handler) { double(:handler) }
|
|
121
|
+
|
|
122
|
+
let(:params) do
|
|
123
|
+
{
|
|
124
|
+
'project' => project_name,
|
|
125
|
+
'resource' => resource_slug,
|
|
126
|
+
'language' => language,
|
|
127
|
+
'translated' => '100'
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
let(:payload) { URI.encode_www_form(params.to_a) }
|
|
132
|
+
|
|
133
|
+
before(:each) do
|
|
134
|
+
allow(TxghServer::Webhooks::Transifex::HookHandler).to(
|
|
135
|
+
receive(:new) do |options|
|
|
136
|
+
expect(options[:project].name).to eq(project_name)
|
|
137
|
+
expect(options[:repo].name).to eq(repo_name)
|
|
138
|
+
handler
|
|
139
|
+
end
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'creates a handler and executes it' do
|
|
144
|
+
expect(handler).to(
|
|
145
|
+
receive(:execute).and_return(respond_with(200, true))
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
payload = URI.encode_www_form(params.to_a)
|
|
149
|
+
sign_with payload
|
|
150
|
+
post '/transifex', payload
|
|
151
|
+
expect(last_response).to be_ok
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'returns unauthorized if not properly signed' do
|
|
155
|
+
post '/transifex', payload
|
|
156
|
+
expect(last_response.status).to eq(401)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'returns internal error on unexpected error' do
|
|
160
|
+
expect(Txgh::Config::KeyManager).to(
|
|
161
|
+
receive(:config_from_project).and_raise(StandardError)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
sign_with payload
|
|
165
|
+
post '/transifex', payload
|
|
166
|
+
expect(last_response.status).to eq(500)
|
|
167
|
+
expect(JSON.parse(last_response.body)).to eq([
|
|
168
|
+
'error' => 'Internal server error: StandardError'
|
|
169
|
+
])
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe '/github' do
|
|
174
|
+
def sign_with(body)
|
|
175
|
+
header(
|
|
176
|
+
TxghServer::GithubRequestAuth::GITHUB_HEADER,
|
|
177
|
+
TxghServer::GithubRequestAuth.header_value(
|
|
178
|
+
body, config.github_repo.webhook_secret
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
describe 'push event' do
|
|
184
|
+
let(:handler) { double(:handler) }
|
|
185
|
+
|
|
186
|
+
before(:each) do
|
|
187
|
+
allow(TxghServer::Webhooks::Github::PushHandler).to(
|
|
188
|
+
receive(:new) do |options|
|
|
189
|
+
expect(options[:project].name).to eq(project_name)
|
|
190
|
+
expect(options[:repo].name).to eq(repo_name)
|
|
191
|
+
handler
|
|
192
|
+
end
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'forwards the request to the github request handler' do
|
|
197
|
+
expect(handler).to(
|
|
198
|
+
receive(:execute).and_return(respond_with(200, true))
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
payload = GithubPayloadBuilder.push_payload(repo_name, ref)
|
|
202
|
+
|
|
203
|
+
sign_with payload.to_json
|
|
204
|
+
header 'X-GitHub-Event', 'push'
|
|
205
|
+
post '/github', payload.to_json
|
|
206
|
+
|
|
207
|
+
expect(last_response).to be_ok
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'returns unauthorized if not properly signed' do
|
|
211
|
+
payload = GithubPayloadBuilder.push_payload(repo_name, ref)
|
|
212
|
+
|
|
213
|
+
header 'X-GitHub-Event', 'push'
|
|
214
|
+
post '/github', payload.to_json
|
|
215
|
+
|
|
216
|
+
expect(last_response.status).to eq(401)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'returns invalid request if event unrecognized' do
|
|
220
|
+
header 'X-GitHub-Event', 'foobar'
|
|
221
|
+
post '/github'
|
|
222
|
+
|
|
223
|
+
expect(last_response.status).to eq(400)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'returns internal error on unexpected error' do
|
|
227
|
+
payload = GithubPayloadBuilder.push_payload(repo_name, ref)
|
|
228
|
+
|
|
229
|
+
expect(Txgh::Config::KeyManager).to(
|
|
230
|
+
receive(:config_from_repo).and_raise(StandardError)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
header 'X-GitHub-Event', 'push'
|
|
234
|
+
post '/github', payload.to_json
|
|
235
|
+
|
|
236
|
+
expect(last_response.status).to eq(500)
|
|
237
|
+
expect(JSON.parse(last_response.body)).to eq([
|
|
238
|
+
'error' => 'Internal server error: StandardError'
|
|
239
|
+
])
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
describe TxghServer::TriggerEndpoints do
|
|
246
|
+
include Rack::Test::Methods
|
|
247
|
+
include StandardTxghSetup
|
|
248
|
+
|
|
249
|
+
def app
|
|
250
|
+
TxghServer::TriggerEndpoints
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
let(:config) do
|
|
254
|
+
Txgh::Config::ConfigPair.new(project_config, repo_config)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
before(:each) do
|
|
258
|
+
allow(Txgh::Config::KeyManager).to(
|
|
259
|
+
receive(:config_from_project).with(project_name).and_return(config)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
allow(Txgh::Config::KeyManager).to(
|
|
263
|
+
receive(:config_from_repo).with(repo_name).and_return(config)
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
describe '/push' do
|
|
268
|
+
let(:params) do
|
|
269
|
+
{ project_slug: project_name, resource_slug: resource_slug, branch: branch }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'updates the expected resource' do
|
|
273
|
+
updater = double(:updater)
|
|
274
|
+
expect(Txgh::ResourceUpdater).to receive(:new).and_return(updater)
|
|
275
|
+
expect(Txgh::GithubApi).to receive(:new).and_return(github_api)
|
|
276
|
+
expect(github_api).to receive(:get_ref).and_return(object: { sha: 'abc123' })
|
|
277
|
+
|
|
278
|
+
expect(updater).to receive(:update_resource) do |resource, sha|
|
|
279
|
+
expected_branch = Txgh::Utils.absolute_branch(branch)
|
|
280
|
+
expect(resource.branch).to eq(expected_branch)
|
|
281
|
+
expect(resource.project_slug).to eq(project_name)
|
|
282
|
+
expect(resource.resource_slug).to(
|
|
283
|
+
eq("#{resource_slug}-#{Txgh::Utils.slugify(expected_branch)}")
|
|
284
|
+
)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
patch '/push', params
|
|
288
|
+
expect(last_response).to be_ok
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'returns internal error on unexpected error' do
|
|
292
|
+
expect(Txgh::Config::KeyManager).to(
|
|
293
|
+
receive(:config_from_project).and_raise(StandardError)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
patch '/push', params
|
|
297
|
+
|
|
298
|
+
expect(last_response.status).to eq(500)
|
|
299
|
+
expect(JSON.parse(last_response.body)).to eq([
|
|
300
|
+
{ 'error' => 'Internal server error: StandardError' }
|
|
301
|
+
])
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe '/pull' do
|
|
306
|
+
let(:params) do
|
|
307
|
+
{ project_slug: project_name, resource_slug: resource_slug, branch: branch }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'updates translations (in all locales) in the expected repo' do
|
|
311
|
+
committer = double(:committer)
|
|
312
|
+
languages = [{ 'language_code' => 'pt' }, { 'language_code' => 'ja' }]
|
|
313
|
+
project_config['languages'] = %w(pt ja)
|
|
314
|
+
expect(Txgh::ResourceCommitter).to receive(:new).and_return(committer)
|
|
315
|
+
expect(Txgh::TransifexApi).to receive(:new).and_return(transifex_api)
|
|
316
|
+
expect(transifex_api).to receive(:get_languages).and_return(languages)
|
|
317
|
+
|
|
318
|
+
languages.each do |language|
|
|
319
|
+
expect(committer).to receive(:commit_resource) do |resource, branch, lang|
|
|
320
|
+
expect(branch).to eq(branch)
|
|
321
|
+
expect(lang).to eq(language['language_code'])
|
|
322
|
+
expect(resource.branch).to eq(branch)
|
|
323
|
+
expect(resource.project_slug).to eq(project_name)
|
|
324
|
+
expect(resource.resource_slug).to(
|
|
325
|
+
eq("#{resource_slug}-#{Txgh::Utils.slugify(branch)}")
|
|
326
|
+
)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
patch '/pull', params
|
|
331
|
+
expect(last_response).to be_ok
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it 'returns internal error on unexpected error' do
|
|
335
|
+
expect(Txgh::Config::KeyManager).to(
|
|
336
|
+
receive(:config_from_project).and_raise(StandardError)
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
patch '/pull', params
|
|
340
|
+
|
|
341
|
+
expect(last_response.status).to eq(500)
|
|
342
|
+
expect(JSON.parse(last_response.body)).to eq([
|
|
343
|
+
{ 'error' => 'Internal server error: StandardError' }
|
|
344
|
+
])
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'helpers/standard_txgh_setup'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
include TxghServer
|
|
6
|
+
|
|
7
|
+
describe DownloadHandler do
|
|
8
|
+
include StandardTxghSetup
|
|
9
|
+
|
|
10
|
+
let(:format) { DownloadHandler::DEFAULT_FORMAT }
|
|
11
|
+
|
|
12
|
+
let(:params) do
|
|
13
|
+
{
|
|
14
|
+
'format' => format,
|
|
15
|
+
'project_slug' => project_name,
|
|
16
|
+
'branch' => ref
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context '.handle_request' do
|
|
21
|
+
let(:request) do
|
|
22
|
+
double(:request).tap do |dbl|
|
|
23
|
+
allow(dbl).to receive(:params).and_return(params)
|
|
24
|
+
allow(dbl).to receive(:env).and_return(env)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
let(:env) do
|
|
29
|
+
{ 'REQUEST_PATH' => "path/to/#{project_name}#{format}" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'responds with a streaming zip and has the project name as the attachment' do
|
|
33
|
+
response = DownloadHandler.handle_request(request)
|
|
34
|
+
expect(response).to be_streaming
|
|
35
|
+
expect(response).to be_a(ZipStreamResponse)
|
|
36
|
+
expect(response.attachment).to eq(project_name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'with a tgz format specified' do
|
|
40
|
+
let(:format) { '.tgz' }
|
|
41
|
+
|
|
42
|
+
it 'responds with a streaming tgz download' do
|
|
43
|
+
response = DownloadHandler.handle_request(request)
|
|
44
|
+
expect(response).to be_streaming
|
|
45
|
+
expect(response).to be_a(TgzStreamResponse)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when an error occurs' do
|
|
50
|
+
before(:each) do
|
|
51
|
+
expect(request).to receive(:params).and_raise(StandardError)
|
|
52
|
+
response = DownloadHandler.handle_request(request)
|
|
53
|
+
expect(response).to_not be_streaming
|
|
54
|
+
expect(response.status).to eq(500)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context '#execute' do
|
|
60
|
+
let(:handler) do
|
|
61
|
+
DownloadHandler.new(transifex_project, github_repo, params, logger)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'responds with a streaming zip download' do
|
|
65
|
+
expect(handler.execute).to be_a(ZipStreamResponse)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'responds with the project name as the attachment' do
|
|
69
|
+
response = handler.execute
|
|
70
|
+
expect(response.attachment).to eq(project_name)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'with a tgz format specified' do
|
|
74
|
+
let(:format) { '.tgz' }
|
|
75
|
+
|
|
76
|
+
it 'responds with a streaming tgz download' do
|
|
77
|
+
expect(handler.execute).to be_a(TgzStreamResponse)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'with a set of languages' do
|
|
82
|
+
let(:supported_languages) { %w(is fr) }
|
|
83
|
+
|
|
84
|
+
it "downloads translations for the project's supported languages" do
|
|
85
|
+
allow(transifex_api).to receive(:download)
|
|
86
|
+
files = handler.execute.enum.to_a.map(&:first)
|
|
87
|
+
expect(files).to eq(%w(translations/is/sample.yml translations/fr/sample.yml))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rack'
|
|
3
|
+
|
|
4
|
+
include TxghServer
|
|
5
|
+
|
|
6
|
+
describe GithubRequestAuth do
|
|
7
|
+
let(:secret) { 'abc123' }
|
|
8
|
+
let(:params) { '{"param1":"value1","param2":"value2","param3":123}' }
|
|
9
|
+
let(:valid_signature) { 'ea62c3f65c8e42f155d96a25b7ba6eb5d320630e' }
|
|
10
|
+
|
|
11
|
+
describe '.authentic_request?' do
|
|
12
|
+
it 'returns true if the request is signed correctly' do
|
|
13
|
+
request = Rack::Request.new(
|
|
14
|
+
GithubRequestAuth::RACK_HEADER => "sha1=#{valid_signature}",
|
|
15
|
+
'rack.input' => StringIO.new(params)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
authentic = GithubRequestAuth.authentic_request?(request, secret)
|
|
19
|
+
expect(authentic).to eq(true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns false if the request is not signed correctly' do
|
|
23
|
+
request = Rack::Request.new(
|
|
24
|
+
GithubRequestAuth::RACK_HEADER => 'incorrect',
|
|
25
|
+
'rack.input' => StringIO.new(params)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
authentic = GithubRequestAuth.authentic_request?(request, secret)
|
|
29
|
+
expect(authentic).to eq(false)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '.header' do
|
|
34
|
+
it 'calculates the signature and formats it as an http header' do
|
|
35
|
+
value = GithubRequestAuth.header_value(params, secret)
|
|
36
|
+
expect(value).to eq("sha1=#{valid_signature}")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
class GithubPayloadBuilder
|
|
4
|
+
class << self
|
|
5
|
+
def push_payload(*args)
|
|
6
|
+
GithubPushPayload.new(*args)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def delete_payload(*args)
|
|
10
|
+
GithubDeletePayload.new(*args)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class GithubPayload
|
|
16
|
+
def to_h
|
|
17
|
+
# convert symbolized keys to strings
|
|
18
|
+
JSON.parse(to_json)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_json
|
|
22
|
+
@result.to_json
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def digits
|
|
28
|
+
@@digits ||= ('a'..'f').to_a + ('0'..'9').to_a
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def generate_timestamp
|
|
32
|
+
Time.now.strftime('%Y-%m-%dT%H:%M:%S%:z')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generate_sha
|
|
36
|
+
blank_commit_id.gsub(/0/) { digits.sample }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def blank_commit_id
|
|
40
|
+
'0' * 40
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class GithubDeletePayload < GithubPayload
|
|
45
|
+
attr_reader :repo, :ref
|
|
46
|
+
|
|
47
|
+
def initialize(repo, ref)
|
|
48
|
+
@repo = repo
|
|
49
|
+
@ref = ref
|
|
50
|
+
|
|
51
|
+
@result = {
|
|
52
|
+
ref: "refs/#{ref}",
|
|
53
|
+
ref_type: 'branch',
|
|
54
|
+
pusher_type: 'user',
|
|
55
|
+
|
|
56
|
+
repository: {
|
|
57
|
+
name: repo.split('/').last,
|
|
58
|
+
full_name: repo,
|
|
59
|
+
owner: {
|
|
60
|
+
login: repo.split('/').first
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
sender: {
|
|
65
|
+
login: repo.split('/').first,
|
|
66
|
+
type: 'User'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class GithubPushPayload < GithubPayload
|
|
73
|
+
attr_reader :repo, :ref, :before, :after
|
|
74
|
+
|
|
75
|
+
DEFAULT_USER = {
|
|
76
|
+
name: 'Test User',
|
|
77
|
+
email: 'test@user.com',
|
|
78
|
+
username: 'testuser'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def initialize(repo, ref, before = nil, after = nil)
|
|
82
|
+
@repo = repo
|
|
83
|
+
@ref = ref
|
|
84
|
+
@before = before || blank_commit_id
|
|
85
|
+
@after = after || generate_sha
|
|
86
|
+
|
|
87
|
+
@result = {
|
|
88
|
+
ref: "refs/#{ref}",
|
|
89
|
+
before: @before,
|
|
90
|
+
after: @after,
|
|
91
|
+
created: true,
|
|
92
|
+
deleted: false,
|
|
93
|
+
forced: true,
|
|
94
|
+
base_ref: nil,
|
|
95
|
+
compare: "https://github.com/#{@repo}/commit/#{@after[0..12]}",
|
|
96
|
+
commits: [],
|
|
97
|
+
repository: {
|
|
98
|
+
name: repo.split('/').last,
|
|
99
|
+
full_name: repo,
|
|
100
|
+
owner: {
|
|
101
|
+
name: repo.split('/').first
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def add_commit(options = {})
|
|
108
|
+
id = if commits.empty? && !options.include?(:id)
|
|
109
|
+
after
|
|
110
|
+
else
|
|
111
|
+
options.fetch(:id) { generate_sha }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
commit_data = {
|
|
115
|
+
id: id,
|
|
116
|
+
distinct: options.fetch(:distinct, true),
|
|
117
|
+
message: options.fetch(:message, 'Default commit message'),
|
|
118
|
+
timestamp: options.fetch(:timestamp) { generate_timestamp },
|
|
119
|
+
url: "https://github.com/#{repo}/commit/#{id}",
|
|
120
|
+
author: options.fetch(:author, DEFAULT_USER),
|
|
121
|
+
committer: options.fetch(:committer, DEFAULT_USER),
|
|
122
|
+
added: options.fetch(:added, []),
|
|
123
|
+
removed: options.fetch(:removed, []),
|
|
124
|
+
modified: options.fetch(:modified, [])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if commit_data[:id] == after
|
|
128
|
+
@result[:head_commit] = commit_data
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
commits << commit_data
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def commits
|
|
135
|
+
@result[:commits]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def head_commit
|
|
139
|
+
@result[:head_commit]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module IntegrationSetup
|
|
2
|
+
extend RSpec::SharedContext
|
|
3
|
+
|
|
4
|
+
let(:base_config) do
|
|
5
|
+
{
|
|
6
|
+
'github' => {
|
|
7
|
+
'repos' => {
|
|
8
|
+
'txgh-bot/txgh-test-resources' => {
|
|
9
|
+
'api_username' => 'txgh-bot',
|
|
10
|
+
# github will auto-revoke a token if they notice it in one of your commits ;)
|
|
11
|
+
'api_token' => Base64.decode64('YjViYWY3Nzk5NTdkMzVlMmI0OGZmYjk4YThlY2M1ZDY0NzAwNWRhZA=='),
|
|
12
|
+
'push_source_to' => 'test-project-88',
|
|
13
|
+
'branch' => 'master'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
'transifex' => {
|
|
18
|
+
'projects' => {
|
|
19
|
+
'test-project-88' => {
|
|
20
|
+
'tx_config' => 'file://./config/tx.config',
|
|
21
|
+
'api_username' => 'txgh.bot',
|
|
22
|
+
'api_password' => '2aqFGW99fPRKWvXBPjbrxkdiR',
|
|
23
|
+
'push_translations_to' => 'txgh-bot/txgh-test-resources'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
before(:all) do
|
|
31
|
+
VCR.configure do |config|
|
|
32
|
+
config.filter_sensitive_data('<GITHUB_TOKEN>') do
|
|
33
|
+
base_config['github']['repos']['txgh-bot/txgh-test-resources']['api_token']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
config.filter_sensitive_data('<TRANSIFEX_PASSWORD>') do
|
|
37
|
+
base_config['transifex']['projects']['test-project-88']['api_password']
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
before(:each) do
|
|
43
|
+
allow(Txgh::Config::KeyManager).to(
|
|
44
|
+
receive(:base_config).and_return(base_config)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|