txgh 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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +64 -0
  4. data/lib/ext/zipline/output_stream.rb +62 -0
  5. data/lib/txgh.rb +53 -0
  6. data/lib/txgh/app.rb +135 -0
  7. data/lib/txgh/category_support.rb +31 -0
  8. data/lib/txgh/config.rb +11 -0
  9. data/lib/txgh/config/config_pair.rb +36 -0
  10. data/lib/txgh/config/key_manager.rb +54 -0
  11. data/lib/txgh/config/provider_instance.rb +20 -0
  12. data/lib/txgh/config/provider_support.rb +26 -0
  13. data/lib/txgh/config/providers.rb +9 -0
  14. data/lib/txgh/config/providers/file_provider.rb +19 -0
  15. data/lib/txgh/config/providers/git_provider.rb +58 -0
  16. data/lib/txgh/config/providers/raw_provider.rb +19 -0
  17. data/lib/txgh/config/tx_config.rb +77 -0
  18. data/lib/txgh/config/tx_manager.rb +15 -0
  19. data/lib/txgh/diff_calculator.rb +90 -0
  20. data/lib/txgh/empty_resource_contents.rb +43 -0
  21. data/lib/txgh/errors.rb +9 -0
  22. data/lib/txgh/github_api.rb +83 -0
  23. data/lib/txgh/github_repo.rb +88 -0
  24. data/lib/txgh/github_request_auth.rb +28 -0
  25. data/lib/txgh/handlers.rb +12 -0
  26. data/lib/txgh/handlers/download_handler.rb +84 -0
  27. data/lib/txgh/handlers/github.rb +10 -0
  28. data/lib/txgh/handlers/github/delete_handler.rb +65 -0
  29. data/lib/txgh/handlers/github/handler.rb +20 -0
  30. data/lib/txgh/handlers/github/push_handler.rb +108 -0
  31. data/lib/txgh/handlers/github/request_handler.rb +106 -0
  32. data/lib/txgh/handlers/response.rb +17 -0
  33. data/lib/txgh/handlers/stream_response.rb +39 -0
  34. data/lib/txgh/handlers/tgz_stream_response.rb +41 -0
  35. data/lib/txgh/handlers/transifex.rb +8 -0
  36. data/lib/txgh/handlers/transifex/hook_handler.rb +77 -0
  37. data/lib/txgh/handlers/transifex/request_handler.rb +78 -0
  38. data/lib/txgh/handlers/triggers.rb +9 -0
  39. data/lib/txgh/handlers/triggers/handler.rb +66 -0
  40. data/lib/txgh/handlers/triggers/pull_handler.rb +29 -0
  41. data/lib/txgh/handlers/triggers/push_handler.rb +21 -0
  42. data/lib/txgh/handlers/zip_stream_response.rb +21 -0
  43. data/lib/txgh/merge_calculator.rb +74 -0
  44. data/lib/txgh/parse_config.rb +24 -0
  45. data/lib/txgh/resource_committer.rb +39 -0
  46. data/lib/txgh/resource_contents.rb +118 -0
  47. data/lib/txgh/resource_downloader.rb +141 -0
  48. data/lib/txgh/resource_updater.rb +104 -0
  49. data/lib/txgh/response_helpers.rb +30 -0
  50. data/lib/txgh/transifex_api.rb +165 -0
  51. data/lib/txgh/transifex_project.rb +37 -0
  52. data/lib/txgh/transifex_request_auth.rb +53 -0
  53. data/lib/txgh/tx_branch_resource.rb +59 -0
  54. data/lib/txgh/tx_logger.rb +12 -0
  55. data/lib/txgh/tx_resource.rb +66 -0
  56. data/lib/txgh/utils.rb +44 -0
  57. data/lib/txgh/version.rb +3 -0
  58. data/spec/app_spec.rb +346 -0
  59. data/spec/category_support_spec.rb +43 -0
  60. data/spec/config/config_pair_spec.rb +47 -0
  61. data/spec/config/key_manager_spec.rb +48 -0
  62. data/spec/config/provider_instance_spec.rb +30 -0
  63. data/spec/config/provider_support_spec.rb +55 -0
  64. data/spec/config/tx_config_spec.rb +49 -0
  65. data/spec/config/tx_manager_spec.rb +57 -0
  66. data/spec/diff_calculator_spec.rb +90 -0
  67. data/spec/github_api_spec.rb +148 -0
  68. data/spec/github_repo_spec.rb +178 -0
  69. data/spec/github_request_auth_spec.rb +39 -0
  70. data/spec/handlers/download_handler_spec.rb +81 -0
  71. data/spec/handlers/github/delete_handler_spec.rb +71 -0
  72. data/spec/handlers/github/push_handler_spec.rb +76 -0
  73. data/spec/handlers/tgz_stream_response_spec.rb +59 -0
  74. data/spec/handlers/transifex/hook_handler_spec.rb +115 -0
  75. data/spec/handlers/zip_stream_response_spec.rb +58 -0
  76. data/spec/helpers/github_payload_builder.rb +141 -0
  77. data/spec/helpers/integration_setup.rb +47 -0
  78. data/spec/helpers/nil_logger.rb +10 -0
  79. data/spec/helpers/standard_txgh_setup.rb +92 -0
  80. data/spec/helpers/test_provider.rb +12 -0
  81. data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
  82. data/spec/integration/cassettes/pull.yml +47 -0
  83. data/spec/integration/cassettes/push.yml +544 -0
  84. data/spec/integration/cassettes/transifex_hook_endpoint.yml +560 -0
  85. data/spec/integration/config/tx.config +10 -0
  86. data/spec/integration/hooks_spec.rb +158 -0
  87. data/spec/integration/payloads/github_postbody.json +161 -0
  88. data/spec/integration/payloads/github_postbody_l10n.json +136 -0
  89. data/spec/integration/payloads/github_postbody_release.json +136 -0
  90. data/spec/integration/triggers_spec.rb +45 -0
  91. data/spec/merge_calculator_spec.rb +112 -0
  92. data/spec/parse_config_spec.rb +52 -0
  93. data/spec/resource_committer_spec.rb +42 -0
  94. data/spec/resource_contents_spec.rb +212 -0
  95. data/spec/resource_downloader_spec.rb +205 -0
  96. data/spec/resource_updater_spec.rb +147 -0
  97. data/spec/spec_helper.rb +32 -0
  98. data/spec/transifex_api_spec.rb +345 -0
  99. data/spec/transifex_project_spec.rb +45 -0
  100. data/spec/transifex_request_auth_spec.rb +39 -0
  101. data/spec/tx_branch_resource_spec.rb +99 -0
  102. data/spec/tx_resource_spec.rb +47 -0
  103. data/spec/utils_spec.rb +58 -0
  104. data/txgh.gemspec +29 -0
  105. metadata +296 -0
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ include Txgh
4
+
5
+ describe CategorySupport do
6
+ describe '.deserialize_categories' do
7
+ it 'converts an array of categories into a hash' do
8
+ categories = %w(captain:janeway commander:chakotay)
9
+ result = CategorySupport.deserialize_categories(categories)
10
+ expect(result).to eq('captain' => 'janeway', 'commander' => 'chakotay')
11
+ end
12
+
13
+ it 'converts an array of space-separated categories' do
14
+ categories = ['captain:janeway commander:chakotay']
15
+ result = CategorySupport.deserialize_categories(categories)
16
+ expect(result).to eq('captain' => 'janeway', 'commander' => 'chakotay')
17
+ end
18
+ end
19
+
20
+ describe '.serialize_categories' do
21
+ it 'converts a hash of categories into an array' do
22
+ categories = { 'captain' => 'janeway', 'commander' => 'chakotay' }
23
+ result = CategorySupport.serialize_categories(categories)
24
+ expect(result.sort).to eq(['captain:janeway', 'commander:chakotay'])
25
+ end
26
+ end
27
+
28
+ describe '.escape_category' do
29
+ it 'replaces spaces in category values' do
30
+ expect(CategorySupport.escape_category('Katherine Janeway')).to(
31
+ eq('Katherine_Janeway')
32
+ )
33
+ end
34
+ end
35
+
36
+ describe '.join_categories' do
37
+ it 'joins an array of categories by spaces' do
38
+ expect(CategorySupport.join_categories(%w(foo:bar baz:boo))).to(
39
+ eq('foo:bar baz:boo')
40
+ )
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'helpers/standard_txgh_setup'
3
+
4
+ include Txgh::Config
5
+
6
+ describe ConfigPair do
7
+ include StandardTxghSetup
8
+
9
+ let(:config) do
10
+ ConfigPair.new(project_config, repo_config)
11
+ end
12
+
13
+ describe '#github_repo' do
14
+ it 'instantiates a github repo with the right config' do
15
+ repo = config.github_repo
16
+ expect(repo).to be_a(GithubRepo)
17
+ expect(repo.name).to eq(repo_name)
18
+ expect(repo.branch).to eq(branch)
19
+ end
20
+ end
21
+
22
+ describe '#transifex_project' do
23
+ it 'instantiates a transifex project with the right config' do
24
+ project = config.transifex_project
25
+ expect(project).to be_a(TransifexProject)
26
+ expect(project.name).to eq(project_name)
27
+ expect(tx_config.resources.first.resource_slug).to eq(resource_slug)
28
+ end
29
+ end
30
+
31
+ describe '#transifex_api' do
32
+ it 'instantiates an API instance' do
33
+ api = config.transifex_api
34
+ expect(api).to be_a(TransifexApi)
35
+ expect(api.connection.headers).to include('Authorization')
36
+ end
37
+ end
38
+
39
+ describe '#github_api' do
40
+ it 'instantiates an API instance' do
41
+ api = config.github_api
42
+ expect(api).to be_a(GithubApi)
43
+ expect(api.client.login).to eq(repo_config['api_username'])
44
+ expect(api.client.access_token).to eq(repo_config['api_token'])
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'helpers/standard_txgh_setup'
3
+
4
+ include Txgh
5
+ include Txgh::Config
6
+
7
+ describe KeyManager do
8
+ include StandardTxghSetup
9
+
10
+ describe '.config_from_project' do
11
+ it 'creates a config object' do
12
+ config = KeyManager.config_from_project(project_name)
13
+ expect(config).to be_a(Txgh::Config)
14
+ end
15
+
16
+ it 'creates a config object that contains both project and repo configs' do
17
+ config = KeyManager.config_from_project(project_name)
18
+ expect(config.project_config).to eq(project_config)
19
+ expect(config.repo_config).to eq(repo_config)
20
+ end
21
+ end
22
+
23
+ describe '.config_from_repo' do
24
+ it 'creates a config object' do
25
+ config = KeyManager.config_from_repo(repo_name)
26
+ expect(config).to be_a(Txgh::Config)
27
+ end
28
+
29
+ it 'creates a config object that contains both project and repo configs' do
30
+ config = KeyManager.config_from_repo(repo_name)
31
+ expect(config.project_config).to eq(project_config)
32
+ expect(config.repo_config).to eq(repo_config)
33
+ end
34
+ end
35
+
36
+ describe '.config_from' do
37
+ it 'creates a config object' do
38
+ config = KeyManager.config_from(project_name, repo_name)
39
+ expect(config).to be_a(Txgh::Config)
40
+ end
41
+
42
+ it 'creates a config object that contains both project and repo configs' do
43
+ config = KeyManager.config_from(project_name, repo_name)
44
+ expect(config.project_config).to eq(project_config)
45
+ expect(config.repo_config).to eq(repo_config)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'helpers/test_provider'
3
+
4
+ include Txgh
5
+ include Txgh::Config
6
+
7
+ describe ProviderInstance do
8
+ let(:provider) { TestProvider }
9
+ let(:payload) { :fake_payload }
10
+ let(:parser) { :fake_parser }
11
+ let(:options) { :fake_options }
12
+ let(:instance) { ProviderInstance.new(provider, parser) }
13
+
14
+ describe '#supports?' do
15
+ it 'returns true if the scheme matches' do
16
+ expect(instance.supports?('test')).to eq(true)
17
+ end
18
+
19
+ it "returns false if the scheme doesn't match" do
20
+ expect(instance.supports?('foo')).to eq(false)
21
+ end
22
+ end
23
+
24
+ describe '#load' do
25
+ it "calls the provider's load method passing the parser and options" do
26
+ expect(provider).to receive(:load).with(payload, parser, options)
27
+ instance.load(payload, options)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'helpers/test_provider'
3
+
4
+ include Txgh
5
+ include Txgh::Config
6
+
7
+ describe ProviderSupport do
8
+ let(:klass) do
9
+ Class.new { extend ProviderSupport }
10
+ end
11
+
12
+ let(:provider) { TestProvider }
13
+
14
+ describe '#register_provider' do
15
+ it 'adds an instance to the provider list' do
16
+ expect { klass.register_provider(:fake_provider, :fake_parser) }.to(
17
+ change { klass.providers.size }.by(1)
18
+ )
19
+
20
+ instance = klass.providers.first
21
+ expect(instance.provider).to eq(:fake_provider)
22
+ expect(instance.parser).to eq(:fake_parser)
23
+ end
24
+ end
25
+
26
+ describe '#provider_for' do
27
+ it 'returns nil if no provider can be found' do
28
+ expect(klass.provider_for('foo')).to be_nil
29
+ end
30
+ end
31
+
32
+ describe '#split_uri' do
33
+ it 'separates the scheme and payload' do
34
+ expect(klass.split_uri('foo://bar')).to eq(%w(foo bar))
35
+ end
36
+
37
+ it 'determines that the scheme is nil if not given' do
38
+ expect(klass.split_uri('bar')).to eq([nil, 'bar'])
39
+ end
40
+ end
41
+
42
+ context 'with a registered provider' do
43
+ before(:each) do
44
+ klass.register_provider(provider, :fake_parser)
45
+ end
46
+
47
+ describe '#provider_for' do
48
+ it 'finds the first provider that matches the given scheme' do
49
+ instance = klass.provider_for('test')
50
+ expect(instance.provider).to eq(provider)
51
+ expect(instance.parser).to eq(:fake_parser)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'helpers/standard_txgh_setup'
3
+
4
+ include Txgh::Config
5
+
6
+ describe TxConfig do
7
+ include StandardTxghSetup
8
+
9
+ describe '.load' do
10
+ it 'parses the config correctly' do
11
+ config_str = """
12
+ [main]
13
+ host = https://www.transifex.com
14
+ lang_map = pt-BR:pt, ko-KR:ko
15
+
16
+ [my_proj.my_resource]
17
+ file_filter = translations/<lang>/sample.po
18
+ source_file = sample.po
19
+ source_lang = en
20
+ type = PO
21
+ """
22
+
23
+ config = TxConfig.load(config_str)
24
+ expect(config.lang_map).to eq('pt-BR' => 'pt', 'ko-KR' => 'ko')
25
+ expect(config.resources.size).to eq(1)
26
+
27
+ resource = config.resources.first
28
+ expect(resource.project_slug).to eq('my_proj')
29
+ expect(resource.resource_slug).to eq('my_resource')
30
+ expect(resource.source_file).to eq('sample.po')
31
+ expect(resource.source_lang).to eq('en')
32
+ expect(resource.translation_file).to eq('translations/<lang>/sample.po')
33
+ expect(resource.type).to eq('PO')
34
+ end
35
+ end
36
+
37
+ describe '#resource' do
38
+ it 'finds the resource by slug' do
39
+ resource = tx_config.resource(resource_slug)
40
+ expect(resource).to be_a(TxResource)
41
+ expect(resource.resource_slug).to eq(resource_slug)
42
+ end
43
+
44
+ it 'returns nil if there is no resource with the given slug' do
45
+ resource = tx_config.resource('foobarbaz')
46
+ expect(resource).to be_nil
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'helpers/standard_txgh_setup'
3
+
4
+ include Txgh
5
+ include Txgh::Config
6
+
7
+ describe TxManager do
8
+ include StandardTxghSetup
9
+
10
+ describe '.tx_config' do
11
+ let(:config) { KeyManager.config_from(project_name, repo_name) }
12
+ let(:project) { config.transifex_project }
13
+ let(:repo) { config.github_repo }
14
+
15
+ it 'loads tx config from the given file' do
16
+ path = 'file://path/to/tx_config'
17
+ project_config.merge!('tx_config' => path)
18
+ expect(TxConfig).to receive(:load_file).with('path/to/tx_config').and_return(:tx_config)
19
+ config = TxManager.tx_config(project, repo)
20
+ expect(config).to eq(:tx_config)
21
+ end
22
+
23
+ context 'with git-based config' do
24
+ before(:each) do
25
+ project_config.merge!('tx_config' => 'git://./tx.config')
26
+ end
27
+
28
+ it 'raises an error if asked to load config from a git repository and no ref is given' do
29
+ expect { TxManager.tx_config(project, repo) }.to raise_error(TxghError)
30
+ end
31
+
32
+ it "raises an error if the git repo doesn't contain the requested config file" do
33
+ expect(repo.api).to receive(:download).and_raise(Octokit::NotFound)
34
+ expect { TxManager.tx_config(project, repo, 'my_branch') }.to(
35
+ raise_error(ConfigNotFoundError)
36
+ )
37
+ end
38
+
39
+ it 'loads tx config from a git repository' do
40
+ expect(repo.api).to(
41
+ receive(:download)
42
+ .with(repo.name, './tx.config', 'my_branch')
43
+ .and_return("[main]\nlang_map = ko:ko_KR")
44
+ )
45
+
46
+ config = TxManager.tx_config(project, repo, 'my_branch')
47
+ expect(config.lang_map).to eq({ 'ko' => 'ko_KR' })
48
+ end
49
+ end
50
+
51
+ it 'loads raw tx config' do
52
+ project_config.merge!('tx_config' => "raw://[main]\nlang_map = ko:ko_KR")
53
+ config = TxManager.tx_config(project, repo)
54
+ expect(config.lang_map).to eq({ 'ko' => 'ko_KR' })
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ include Txgh
4
+
5
+ describe DiffCalculator do
6
+ def phrase(key, string)
7
+ { 'key' => key, 'string' => string }
8
+ end
9
+
10
+ describe '.compare' do
11
+ let(:diff) do
12
+ DiffCalculator.compare(head_phrases, diff_point_phrases)
13
+ end
14
+
15
+ context 'with phrases added to HEAD' do
16
+ let(:head_phrases) do
17
+ diff_point_phrases + [
18
+ phrase('TheNextGeneration', 'Jean Luc Picard')
19
+ ]
20
+ end
21
+
22
+ let(:diff_point_phrases) do
23
+ [
24
+ phrase('Voyager', 'Kathryn Janeway'),
25
+ phrase('DeepSpaceNine', 'Benjamin Sisko'),
26
+ ]
27
+ end
28
+
29
+ it 'includes the new string' do
30
+ expect(diff[:added].size).to eq(1)
31
+ expect(diff[:modified].size).to eq(0)
32
+ phrase = diff[:added].first
33
+ expect(phrase['key']).to eq('TheNextGeneration')
34
+ expect(phrase['string']).to eq('Jean Luc Picard')
35
+ end
36
+ end
37
+
38
+ context 'with phrases removed from HEAD' do
39
+ let(:head_phrases) do
40
+ []
41
+ end
42
+
43
+ let(:diff_point_phrases) do
44
+ [phrase('Voyager', 'Kathryn Janeway')]
45
+ end
46
+
47
+ it 'does not include the new string if string has been removed' do
48
+ expect(diff[:added].size).to eq(0)
49
+ expect(diff[:modified].size).to eq(0)
50
+ end
51
+ end
52
+
53
+ context 'with phrases modified in HEAD' do
54
+ let(:head_phrases) do
55
+ [phrase('TheNextGeneration', 'Jean Luc Picard (rocks)')]
56
+ end
57
+
58
+ let(:diff_point_phrases) do
59
+ [phrase('TheNextGeneration', 'Jean Luc Picard')]
60
+ end
61
+
62
+ it 'includes the modified string' do
63
+ expect(diff[:added].size).to eq(0)
64
+ expect(diff[:modified].size).to eq(1)
65
+ phrase = diff[:modified].first
66
+ expect(phrase['key']).to eq('TheNextGeneration')
67
+ expect(phrase['string']).to eq('Jean Luc Picard (rocks)')
68
+ end
69
+ end
70
+
71
+ context 'with no phrases modified, added, or removed' do
72
+ let(:head_phrases) do
73
+ [
74
+ phrase('TheNextGeneration', 'Jean Luc Picard'),
75
+ phrase('Voyager', 'Kathryn Janeway'),
76
+ phrase('DeepSpaceNine', 'Benjamin Sisko')
77
+ ]
78
+ end
79
+
80
+ let(:diff_point_phrases) do
81
+ head_phrases
82
+ end
83
+
84
+ it 'does not include any phrases' do
85
+ expect(diff[:added].size).to eq(0)
86
+ expect(diff[:modified].size).to eq(0)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ include Txgh
4
+
5
+ describe GithubApi do
6
+ let(:client) { double(:client) }
7
+ let(:api) { GithubApi.create_from_client(client) }
8
+ let(:repo) { 'my_org/my_repo' }
9
+ let(:branch) { 'master' }
10
+ let(:sha) { 'abc123' }
11
+
12
+ describe '#tree' do
13
+ it 'retrieves a git tree using the client' do
14
+ expect(client).to receive(:tree).with(repo, sha, recursive: 1)
15
+ api.tree(repo, sha)
16
+ end
17
+ end
18
+
19
+ describe '#blob' do
20
+ it 'retrieves a git blob using the client' do
21
+ expect(client).to receive(:blob).with(repo, sha)
22
+ api.blob(repo, sha)
23
+ end
24
+ end
25
+
26
+ describe '#create_ref' do
27
+ it 'creates the given ref using the client' do
28
+ expect(client).to receive(:create_ref).with(repo, branch, sha)
29
+ api.create_ref(repo, branch, sha)
30
+ end
31
+
32
+ it 'returns false on client error' do
33
+ expect(client).to receive(:create_ref).and_raise(StandardError)
34
+ expect(api.create_ref(repo, branch, sha)).to eq(false)
35
+ end
36
+ end
37
+
38
+ describe '#commit' do
39
+ let(:path) { 'path/to/translations' }
40
+ let(:other_path) { 'other/path/to/translations' }
41
+
42
+ before(:each) do
43
+ allow(client).to receive(:create_blob).with(repo, :new_content).and_return(:blob_sha)
44
+ allow(client).to receive(:ref).with(repo, branch).and_return(object: { sha: :branch_sha })
45
+ allow(client).to receive(:commit).with(repo, :branch_sha).and_return(commit: { tree: { sha: :base_tree_sha } })
46
+ allow(client).to receive(:create_tree).and_return(sha: :new_tree_sha)
47
+ end
48
+
49
+ it 'creates a new commit and updates the branch' do
50
+ expect(client).to(
51
+ receive(:create_commit)
52
+ .with(repo, "Updating translations for #{path}", :new_tree_sha, :branch_sha)
53
+ .and_return(sha: :new_commit_sha)
54
+ )
55
+
56
+ expect(client).to receive(:update_ref).with(repo, branch, :new_commit_sha, false)
57
+ api.commit(repo, branch, { path => :new_content }, true)
58
+ end
59
+
60
+ it 'updates multiple files at a time' do
61
+ allow(client).to receive(:create_blob).with(repo, :other_content).and_return(:blob_sha_2)
62
+
63
+ expect(client).to(
64
+ receive(:create_commit)
65
+ .with(repo, "Updating translations for #{path}, #{other_path}", :new_tree_sha, :branch_sha)
66
+ .and_return(sha: :new_commit_sha)
67
+ )
68
+
69
+ expect(client).to receive(:update_ref).with(repo, branch, :new_commit_sha, false)
70
+ content_map = { path => :new_content, other_path => :other_content }
71
+ api.commit(repo, branch, content_map, true)
72
+ end
73
+
74
+ context 'with an empty commit' do
75
+ before(:each) do
76
+ allow(client).to(
77
+ receive(:compare)
78
+ .with(repo, :branch_sha, :new_commit_sha)
79
+ .and_return(files: [])
80
+ )
81
+
82
+ expect(client).to(
83
+ receive(:create_commit)
84
+ .with(repo, "Updating translations for #{path}", :new_tree_sha, :branch_sha)
85
+ .and_return(sha: :new_commit_sha)
86
+ )
87
+ end
88
+
89
+ it 'does not allow empty commits by default' do
90
+ expect(client).to_not receive(:update_ref)
91
+ api.commit(repo, branch, { path => :new_content })
92
+ end
93
+ end
94
+
95
+ context 'with a non-empty commit' do
96
+ before(:each) do
97
+ allow(client).to(
98
+ receive(:compare)
99
+ .with(repo, :branch_sha, :new_commit_sha)
100
+ .and_return(files: %w(abc def))
101
+ )
102
+
103
+ expect(client).to(
104
+ receive(:create_commit)
105
+ .with(repo, "Updating translations for #{path}", :new_tree_sha, :branch_sha)
106
+ .and_return(sha: :new_commit_sha)
107
+ )
108
+ end
109
+
110
+ it 'updates the ref as expected' do
111
+ expect(client).to receive(:update_ref).with(repo, branch, :new_commit_sha, false)
112
+ api.commit(repo, branch, { path => :new_content })
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#get_commit' do
118
+ it 'retrieves the given commit using the client' do
119
+ expect(client).to receive(:commit).with(repo, sha)
120
+ api.get_commit(repo, sha)
121
+ end
122
+ end
123
+
124
+ describe '#get_ref' do
125
+ it 'retrieves the given ref (i.e. branch) using the client' do
126
+ expect(client).to receive(:ref).with(repo, sha)
127
+ api.get_ref(repo, sha)
128
+ end
129
+ end
130
+
131
+ describe '#download' do
132
+ it 'downloads the file from the given branch' do
133
+ path = 'path/to/file.xyz'
134
+
135
+ expect(client).to receive(:ref).with(repo, branch).and_return(object: { sha: :branch_sha })
136
+ expect(client).to receive(:commit).with(repo, :branch_sha).and_return(commit: { tree: { sha: :base_tree_sha } })
137
+ expect(client).to receive(:tree).with(repo, :base_tree_sha, recursive: 1).and_return(
138
+ tree: [{ path: path, sha: :blob_sha }]
139
+ )
140
+
141
+ expect(client).to receive(:blob).with(repo, :blob_sha).and_return(
142
+ { 'content' => :blob, 'encoding' => 'utf-8' }
143
+ )
144
+
145
+ expect(api.download(repo, path, branch)).to eq(:blob)
146
+ end
147
+ end
148
+ end