deepl-rb 3.9.0 → 3.10.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 +4 -4
- data/.gitlab-ci.yml +70 -52
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +429 -18
- data/Gemfile +4 -0
- data/README.md +981 -223
- data/Rakefile +18 -5
- data/VERSION +1 -1
- data/deepl-rb.gemspec +6 -6
- data/lib/deepl/admin_client.rb +183 -0
- data/lib/deepl/api.rb +30 -6
- data/lib/deepl/app_info.rb +20 -0
- data/lib/deepl/client.rb +969 -0
- data/lib/deepl/configuration.rb +56 -10
- data/lib/deepl/constants/admin_usage_group_by.rb +13 -0
- data/lib/deepl/constants/custom_tag_usage_aggregate_by.rb +13 -0
- data/lib/deepl/constants/document_status_code.rb +17 -0
- data/lib/deepl/constants/language_include.rb +17 -0
- data/lib/deepl/constants/language_resource_name.rb +21 -0
- data/lib/deepl/constants/tag_handling_version.rb +14 -0
- data/lib/deepl/deprecation.rb +42 -0
- data/lib/deepl/document_api.rb +41 -22
- data/lib/deepl/exceptions/bad_request.rb +1 -3
- data/lib/deepl/exceptions/connection_error.rb +37 -0
- data/lib/deepl/exceptions/document_not_ready_error.rb +17 -0
- data/lib/deepl/exceptions/document_translation_error.rb +17 -1
- data/lib/deepl/exceptions/error.rb +13 -1
- data/lib/deepl/exceptions/not_found.rb +8 -3
- data/lib/deepl/exceptions/not_supported.rb +6 -0
- data/lib/deepl/exceptions/request_error.rb +19 -2
- data/lib/deepl/exceptions/server_error.rb +7 -1
- data/lib/deepl/exceptions/validation_error.rb +14 -0
- data/lib/deepl/requests/admin/base.rb +94 -0
- data/lib/deepl/requests/admin/create_developer_key.rb +38 -0
- data/lib/deepl/requests/admin/custom_tag_usage_analytics.rb +38 -0
- data/lib/deepl/requests/admin/list_developer_keys.rb +28 -0
- data/lib/deepl/requests/admin/update_developer_key.rb +35 -0
- data/lib/deepl/requests/admin/usage_analytics.rb +36 -0
- data/lib/deepl/requests/base.rb +229 -59
- data/lib/deepl/requests/correct.rb +57 -0
- data/lib/deepl/requests/document/download.rb +11 -3
- data/lib/deepl/requests/document/get_status.rb +2 -2
- data/lib/deepl/requests/document/upload.rb +43 -10
- data/lib/deepl/requests/glossary/create.rb +6 -2
- data/lib/deepl/requests/glossary/language_pairs.rb +20 -4
- data/lib/deepl/requests/language_resources.rb +41 -0
- data/lib/deepl/requests/languages.rb +28 -4
- data/lib/deepl/requests/languages_for_resource.rb +54 -0
- data/lib/deepl/requests/multilingual_glossary/create.rb +37 -0
- data/lib/deepl/requests/multilingual_glossary/destroy.rb +36 -0
- data/lib/deepl/requests/multilingual_glossary/destroy_dictionary.rb +51 -0
- data/lib/deepl/requests/multilingual_glossary/entries.rb +66 -0
- data/lib/deepl/requests/multilingual_glossary/find.rb +35 -0
- data/lib/deepl/requests/multilingual_glossary/helpers.rb +63 -0
- data/lib/deepl/requests/multilingual_glossary/list.rb +39 -0
- data/lib/deepl/requests/multilingual_glossary/replace_dictionary.rb +41 -0
- data/lib/deepl/requests/multilingual_glossary/update.rb +40 -0
- data/lib/deepl/requests/rephrase.rb +5 -9
- data/lib/deepl/requests/spoken_terms/create.rb +37 -0
- data/lib/deepl/requests/spoken_terms/destroy.rb +36 -0
- data/lib/deepl/requests/spoken_terms/destroy_term_list.rb +41 -0
- data/lib/deepl/requests/spoken_terms/entries.rb +46 -0
- data/lib/deepl/requests/spoken_terms/find.rb +35 -0
- data/lib/deepl/requests/spoken_terms/helpers.rb +67 -0
- data/lib/deepl/requests/spoken_terms/list.rb +35 -0
- data/lib/deepl/requests/spoken_terms/replace_term_list.rb +41 -0
- data/lib/deepl/requests/spoken_terms/update.rb +40 -0
- data/lib/deepl/requests/style_profile/base.rb +83 -0
- data/lib/deepl/requests/style_profile/create.rb +35 -0
- data/lib/deepl/requests/style_profile/destroy.rb +34 -0
- data/lib/deepl/requests/style_profile/find.rb +33 -0
- data/lib/deepl/requests/style_profile/list.rb +43 -0
- data/lib/deepl/requests/style_profile/update.rb +35 -0
- data/lib/deepl/requests/style_rule/create.rb +1 -1
- data/lib/deepl/requests/style_rule/create_custom_instruction.rb +4 -2
- data/lib/deepl/requests/style_rule/destroy.rb +4 -2
- data/lib/deepl/requests/style_rule/destroy_custom_instruction.rb +5 -3
- data/lib/deepl/requests/style_rule/find.rb +4 -2
- data/lib/deepl/requests/style_rule/find_custom_instruction.rb +5 -3
- data/lib/deepl/requests/style_rule/helpers.rb +35 -0
- data/lib/deepl/requests/style_rule/list.rb +1 -1
- data/lib/deepl/requests/style_rule/update.rb +4 -2
- data/lib/deepl/requests/style_rule/update_configured_rules.rb +4 -2
- data/lib/deepl/requests/style_rule/update_custom_instruction.rb +5 -3
- data/lib/deepl/requests/translate.rb +52 -20
- data/lib/deepl/requests/translation_memory/base.rb +1 -4
- data/lib/deepl/requests/translation_memory/create_export.rb +1 -1
- data/lib/deepl/requests/translation_memory/create_import.rb +1 -1
- data/lib/deepl/requests/translation_memory/destroy.rb +1 -1
- data/lib/deepl/requests/translation_memory/download_export.rb +4 -0
- data/lib/deepl/requests/translation_memory/find.rb +1 -1
- data/lib/deepl/requests/translation_memory/find_job.rb +1 -1
- data/lib/deepl/requests/translation_memory/list.rb +2 -1
- data/lib/deepl/requests/translation_memory/segments.rb +3 -2
- data/lib/deepl/requests/translation_memory/storage_base.rb +40 -8
- data/lib/deepl/requests/translation_memory/upload_file.rb +1 -1
- data/lib/deepl/requests/usage.rb +28 -1
- data/lib/deepl/requests/write_improvements.rb +35 -0
- data/lib/deepl/resources/admin_usage_report.rb +73 -0
- data/lib/deepl/resources/custom_tag_usage_report.rb +59 -0
- data/lib/deepl/resources/developer_key.rb +57 -0
- data/lib/deepl/resources/document_handle.rb +24 -25
- data/lib/deepl/resources/document_translation_status.rb +23 -4
- data/lib/deepl/resources/language.rb +10 -2
- data/lib/deepl/resources/language_resource.rb +57 -0
- data/lib/deepl/resources/language_support.rb +56 -0
- data/lib/deepl/resources/multilingual_glossary.rb +112 -0
- data/lib/deepl/resources/spoken_terms.rb +61 -0
- data/lib/deepl/resources/style_profile.rb +44 -0
- data/lib/deepl/resources/style_rule.rb +3 -0
- data/lib/deepl/resources/text.rb +18 -2
- data/lib/deepl/resources/translation_memory.rb +8 -2
- data/lib/deepl/resources/translation_memory_import.rb +1 -1
- data/lib/deepl/resources/translation_memory_job.rb +23 -3
- data/lib/deepl/resources/translation_memory_segments.rb +8 -6
- data/lib/deepl/resources/usage.rb +93 -4
- data/lib/deepl/style_rule_api.rb +11 -0
- data/lib/deepl/translation_memory_api.rb +104 -24
- data/lib/deepl/utils/client_builder.rb +54 -0
- data/lib/deepl/utils/document_poller.rb +70 -0
- data/lib/deepl/utils/exception_builder.rb +3 -2
- data/lib/deepl/utils/http_client_builder.rb +79 -0
- data/lib/deepl/utils/number_parser.rb +34 -0
- data/lib/deepl/utils/term_validator.rb +33 -0
- data/lib/deepl/utils/time_parser.rb +14 -0
- data/lib/deepl.rb +166 -41
- data/lib/http_client_options.rb +21 -1
- data/lib/version.rb +1 -1
- data/spec/fixtures.rb +33 -0
- data/spec/offline/admin_client_spec.rb +551 -0
- data/spec/offline/client_spec.rb +273 -0
- data/spec/{api → offline}/configuration_spec.rb +45 -3
- data/spec/{constants → offline}/constants_spec.rb +23 -1
- data/spec/offline/correct_text_spec.rb +50 -0
- data/spec/offline/deprecation_spec.rb +32 -0
- data/spec/offline/errors_spec.rb +92 -0
- data/spec/offline/glossaries_spec.rb +98 -0
- data/spec/offline/http_client_spec.rb +319 -0
- data/spec/offline/languages_spec.rb +155 -0
- data/spec/offline/module_methods_spec.rb +13 -0
- data/spec/offline/multilingual_glossaries_spec.rb +358 -0
- data/spec/offline/offline_helper.rb +71 -0
- data/spec/offline/rephrase_text_spec.rb +73 -0
- data/spec/offline/spoken_terms_spec.rb +215 -0
- data/spec/offline/style_profiles_spec.rb +266 -0
- data/spec/offline/style_rules_spec.rb +318 -0
- data/spec/offline/translate_document_spec.rb +606 -0
- data/spec/offline/translate_text_spec.rb +286 -0
- data/spec/offline/translation_memories_client_spec.rb +256 -0
- data/spec/offline/translation_memories_spec.rb +463 -0
- data/spec/offline/usage_spec.rb +151 -0
- data/spec/real_api/admin_client_spec.rb +77 -0
- data/spec/real_api/correct_text_spec.rb +20 -0
- data/spec/real_api/glossaries_spec.rb +28 -0
- data/spec/real_api/languages_spec.rb +27 -0
- data/spec/real_api/multilingual_glossaries_spec.rb +53 -0
- data/spec/real_api/proxy_spec.rb +41 -0
- data/spec/real_api/real_api_helper.rb +65 -0
- data/spec/real_api/rephrase_text_spec.rb +16 -0
- data/spec/real_api/spoken_terms_spec.rb +56 -0
- data/spec/real_api/style_profiles_spec.rb +46 -0
- data/spec/real_api/style_rules_spec.rb +46 -0
- data/spec/real_api/translate_document_spec.rb +18 -0
- data/spec/real_api/translate_text_spec.rb +16 -0
- data/spec/real_api/translation_memories_spec.rb +56 -0
- data/spec/real_api/usage_spec.rb +20 -0
- data/spec/spec_helper.rb +1 -50
- metadata +116 -76
- data/.circleci/config.yml +0 -27
- data/.github/workflows/add_issues_to_kanban.yml +0 -16
- data/spec/api/api_spec.rb +0 -20
- data/spec/api/deepl_spec.rb +0 -262
- data/spec/integration_tests/document_api_spec.rb +0 -211
- data/spec/integration_tests/document_error_paths_spec.rb +0 -33
- data/spec/integration_tests/glossary_api_spec.rb +0 -114
- data/spec/integration_tests/glossary_error_paths_spec.rb +0 -107
- data/spec/integration_tests/integration_test_utils.rb +0 -178
- data/spec/integration_tests/languages_api_spec.rb +0 -54
- data/spec/integration_tests/languages_error_paths_spec.rb +0 -25
- data/spec/integration_tests/rephrase_api_spec.rb +0 -90
- data/spec/integration_tests/rephrase_error_paths_spec.rb +0 -53
- data/spec/integration_tests/smoke_test_spec.rb +0 -24
- data/spec/integration_tests/style_rule_api_spec.rb +0 -117
- data/spec/integration_tests/style_rule_error_paths_spec.rb +0 -45
- data/spec/integration_tests/translate_api_spec.rb +0 -117
- data/spec/integration_tests/translate_error_paths_spec.rb +0 -48
- data/spec/integration_tests/translation_memory_api_spec.rb +0 -244
- data/spec/integration_tests/translation_memory_error_paths_spec.rb +0 -87
- data/spec/integration_tests/usage_api_spec.rb +0 -29
- data/spec/integration_tests/usage_error_paths_spec.rb +0 -18
- data/spec/requests/document/upload_spec.rb +0 -130
- data/spec/requests/extra_body_parameters_types_spec.rb +0 -82
- data/spec/requests/glossary/create_spec.rb +0 -44
- data/spec/requests/glossary/destroy_spec.rb +0 -27
- data/spec/requests/glossary/entries_spec.rb +0 -27
- data/spec/requests/glossary/find_spec.rb +0 -28
- data/spec/requests/glossary/language_pairs_spec.rb +0 -27
- data/spec/requests/glossary/list_spec.rb +0 -27
- data/spec/requests/languages_spec.rb +0 -27
- data/spec/requests/rephrase_spec.rb +0 -46
- data/spec/requests/style_rule/create_custom_instruction_spec.rb +0 -30
- data/spec/requests/style_rule/create_spec.rb +0 -29
- data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +0 -28
- data/spec/requests/style_rule/destroy_spec.rb +0 -27
- data/spec/requests/style_rule/find_custom_instruction_spec.rb +0 -29
- data/spec/requests/style_rule/find_spec.rb +0 -28
- data/spec/requests/style_rule/list_spec.rb +0 -27
- data/spec/requests/style_rule/update_configured_rules_spec.rb +0 -31
- data/spec/requests/style_rule/update_custom_instruction_spec.rb +0 -32
- data/spec/requests/style_rule/update_spec.rb +0 -29
- data/spec/requests/translate_spec.rb +0 -338
- data/spec/requests/translation_memory/create_export_spec.rb +0 -37
- data/spec/requests/translation_memory/create_import_spec.rb +0 -61
- data/spec/requests/translation_memory/destroy_spec.rb +0 -36
- data/spec/requests/translation_memory/download_export_spec.rb +0 -54
- data/spec/requests/translation_memory/find_job_spec.rb +0 -37
- data/spec/requests/translation_memory/find_spec.rb +0 -36
- data/spec/requests/translation_memory/list_spec.rb +0 -27
- data/spec/requests/translation_memory/segments_spec.rb +0 -58
- data/spec/requests/translation_memory/upload_file_spec.rb +0 -55
- data/spec/requests/usage_spec.rb +0 -27
- data/spec/resources/custom_instruction_spec.rb +0 -32
- data/spec/resources/glossary_spec.rb +0 -38
- data/spec/resources/language_pair_spec.rb +0 -23
- data/spec/resources/language_spec.rb +0 -45
- data/spec/resources/style_rule_spec.rb +0 -68
- data/spec/resources/text_spec.rb +0 -23
- data/spec/resources/translation_memory_export_spec.rb +0 -38
- data/spec/resources/translation_memory_import_spec.rb +0 -30
- data/spec/resources/translation_memory_job_spec.rb +0 -109
- data/spec/resources/translation_memory_segments_spec.rb +0 -78
- data/spec/resources/translation_memory_spec.rb +0 -52
- data/spec/resources/usage_spec.rb +0 -35
- data/spec/support/live_mock_server.rb +0 -12
- data/spec/support/managed_glossary.rb +0 -65
- data/spec/support/managed_style_rule.rb +0 -17
- data/spec/support/managed_translation_memory.rb +0 -55
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Copyright 2026 DeepL SE (https://www.deepl.com)
|
|
2
|
+
# Use of this source code is governed by an MIT
|
|
3
|
+
# license that can be found in the LICENSE file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Client do
|
|
9
|
+
# The operations are added to DeepL::Client separately; until then the client's connection is
|
|
10
|
+
# exercised with the usage request.
|
|
11
|
+
def usage(client)
|
|
12
|
+
DeepL::Requests::Usage.new(client.send(:api)).request
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def stub_usage(base = OfflineHelper::HOST, headers: {})
|
|
16
|
+
stub = stub_request(:get, "#{base}/v2/usage")
|
|
17
|
+
stub = stub.with(headers: headers) unless headers.empty?
|
|
18
|
+
stub.to_return(usage_reply)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#initialize' do
|
|
22
|
+
it 'sends the trimmed auth key to the Pro server' do
|
|
23
|
+
stub_usage(headers: { 'Authorization' => 'DeepL-Auth-Key abc' })
|
|
24
|
+
|
|
25
|
+
usage(described_class.new(" abc \n"))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'sends a Free key to the Free server' do
|
|
29
|
+
stub_usage('https://api-free.deepl.com',
|
|
30
|
+
headers: { 'Authorization' => 'DeepL-Auth-Key a:fx' })
|
|
31
|
+
|
|
32
|
+
usage(described_class.new('a:fx'))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'keeps the path prefix of the server URL' do
|
|
36
|
+
stub_usage('http://localhost:3000/proxy')
|
|
37
|
+
|
|
38
|
+
usage(described_class.new('abc', server_url: 'http://localhost:3000/proxy'))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'reads no environment variables' do
|
|
42
|
+
env = ENV.to_h.slice('DEEPL_AUTH_KEY', 'DEEPL_SERVER_URL')
|
|
43
|
+
ENV['DEEPL_SERVER_URL'] = 'http://environment.example.test'
|
|
44
|
+
stub_usage(headers: { 'Authorization' => 'DeepL-Auth-Key abc' })
|
|
45
|
+
|
|
46
|
+
usage(described_class.new('abc'))
|
|
47
|
+
ensure
|
|
48
|
+
ENV.delete('DEEPL_SERVER_URL')
|
|
49
|
+
ENV.update(env)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'rejects an empty or blank auth key' do
|
|
53
|
+
['', " \t", nil].each do |key|
|
|
54
|
+
expect { described_class.new(key) }.to raise_error(ArgumentError, /auth_key/)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'does not show the auth key in inspect' do
|
|
59
|
+
expect(described_class.new('secret-key').inspect).not_to include('secret-key')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'does not expose its connection or configuration' do
|
|
63
|
+
client = described_class.new('abc')
|
|
64
|
+
|
|
65
|
+
expect(client).not_to respond_to(:api)
|
|
66
|
+
expect(client).not_to respond_to(:configuration)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe 'headers' do
|
|
71
|
+
it 'lets the headers replace the Authorization header' do
|
|
72
|
+
stub_usage(headers: { 'Authorization' => 'DeepL-Auth-Key other', 'X-Custom' => 'v' })
|
|
73
|
+
|
|
74
|
+
usage(described_class.new('abc', headers: { 'X-Custom' => 'v',
|
|
75
|
+
'authorization' => 'DeepL-Auth-Key other' }))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'replaces the User-Agent with the one in headers' do
|
|
79
|
+
stub_usage(headers: { 'User-Agent' => 'my-agent/1' })
|
|
80
|
+
|
|
81
|
+
usage(described_class.new('abc', headers: { 'user-agent' => 'my-agent/1' }))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'adds the app info to the User-Agent' do
|
|
85
|
+
stub_usage(headers: { 'User-Agent' => %r{\Adeepl-ruby/[^ ]+ my-app/1\.2\z} })
|
|
86
|
+
|
|
87
|
+
usage(described_class.new('abc', send_platform_info: false,
|
|
88
|
+
app_info: { app_name: 'my-app', app_version: '1.2' }))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'never mixes the options of two clients' do
|
|
92
|
+
stub_usage(headers: { 'Authorization' => 'DeepL-Auth-Key one', 'X-Client' => '1' })
|
|
93
|
+
stub_usage('http://localhost:3000',
|
|
94
|
+
headers: { 'Authorization' => 'DeepL-Auth-Key two', 'X-Client' => '2' })
|
|
95
|
+
|
|
96
|
+
usage(described_class.new('one', headers: { 'X-Client' => '1' }))
|
|
97
|
+
usage(described_class.new('two', server_url: 'http://localhost:3000',
|
|
98
|
+
headers: { 'X-Client' => '2' }))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe 'max_retries' do
|
|
103
|
+
it 'retries as often as configured' do
|
|
104
|
+
stub = stub_request(:get, url('/v2/usage')).to_return(reply({}, 429), usage_reply)
|
|
105
|
+
|
|
106
|
+
usage(described_class.new('abc', max_retries: 1))
|
|
107
|
+
expect(stub).to have_been_requested.twice
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'does not retry with max_retries 0' do
|
|
111
|
+
stub = stub_request(:get, url('/v2/usage')).to_return(reply({}, 503))
|
|
112
|
+
|
|
113
|
+
client = described_class.new('abc', max_retries: 0)
|
|
114
|
+
expect { usage(client) }.to raise_error(DeepL::Exceptions::ServerError)
|
|
115
|
+
expect(stub).to have_been_requested.once
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe 'timeout' do
|
|
120
|
+
it 'applies the timeout to each attempt' do
|
|
121
|
+
stub_usage
|
|
122
|
+
client = described_class.new('abc', timeout: 3)
|
|
123
|
+
|
|
124
|
+
usage(client)
|
|
125
|
+
|
|
126
|
+
http = client.send(:api).http_client
|
|
127
|
+
expect([http.open_timeout, http.read_timeout, http.write_timeout]).to eq([3.0, 3.0, 3.0])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'defaults to 10 seconds and keeps the timeouts of the HTTP client options' do
|
|
131
|
+
stub_usage
|
|
132
|
+
options = DeepL::HTTPClientOptions.new({}, nil, read_timeout: 7)
|
|
133
|
+
client = described_class.new('abc', http_client_options: options)
|
|
134
|
+
|
|
135
|
+
usage(client)
|
|
136
|
+
|
|
137
|
+
http = client.send(:api).http_client
|
|
138
|
+
expect([http.open_timeout, http.read_timeout, http.write_timeout]).to eq([10.0, 7, 10.0])
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe 'proxy' do
|
|
143
|
+
def http_client(**options)
|
|
144
|
+
described_class.new('abc', **options).send(:api).http_client
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'accepts a proxy URL with credentials' do
|
|
148
|
+
expect(http_client(proxy: 'http://us%40r:p%3Ass@proxy.example.test:3128'))
|
|
149
|
+
.to have_attributes(proxy_address: 'proxy.example.test', proxy_port: 3128,
|
|
150
|
+
proxy_user: 'us@r', proxy_pass: 'p:ss')
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'combines the proxy URL with the other HTTP client options' do
|
|
154
|
+
options = DeepL::HTTPClientOptions.new({}, nil, enable_ssl_verification: false)
|
|
155
|
+
|
|
156
|
+
expect(http_client(proxy: 'http://proxy.example.test:3128', http_client_options: options))
|
|
157
|
+
.to have_attributes(proxy_address: 'proxy.example.test',
|
|
158
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'rejects a proxy in both proxy: and http_client_options:' do
|
|
162
|
+
options = DeepL::HTTPClientOptions.new('http://other.example.test:1')
|
|
163
|
+
|
|
164
|
+
expect { http_client(proxy: 'http://proxy.example.test:3128', http_client_options: options) }
|
|
165
|
+
.to raise_error(ArgumentError, /proxy/)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'does not show the proxy credentials in inspect' do
|
|
169
|
+
client = described_class.new('abc', proxy: 'http://u:secret@proxy.test:3128')
|
|
170
|
+
|
|
171
|
+
expect(client.inspect).not_to include('secret')
|
|
172
|
+
expect(client.send(:api).inspect).not_to include('secret')
|
|
173
|
+
expect(client.send(:api).http_client_options.inspect).to include('proxy.test:3128')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'does not show the proxy credentials in the error for an invalid proxy URL' do
|
|
177
|
+
['u:secret@proxy.test:3128', 'http://u:secret@proxy test:3128'].each do |proxy|
|
|
178
|
+
expect { described_class.new('abc', proxy: proxy) }
|
|
179
|
+
.to raise_error(ArgumentError) { |e| expect(e.full_message).not_to include('secret') }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe '.free_account_auth_key?' do
|
|
185
|
+
it 'detects Free keys' do
|
|
186
|
+
expect(%w[a:fx a].map { |key| described_class.free_account_auth_key?(key) })
|
|
187
|
+
.to eq([true, false])
|
|
188
|
+
expect(described_class.free_account_auth_key?(nil)).to be(false)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe '#translate_text' do
|
|
193
|
+
let(:client) { described_class.new(OfflineHelper::AUTH_KEY) }
|
|
194
|
+
|
|
195
|
+
def stub_translate(body, translations)
|
|
196
|
+
stub_request(:post, url('/v2/translate'))
|
|
197
|
+
.with(body: body, headers: json_headers)
|
|
198
|
+
.to_return(reply(translations: translations))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'sends the text, the options and show_billed_characters and returns one result' do
|
|
202
|
+
stub_translate({ text: ['Hello'], target_lang: 'DE', show_billed_characters: true,
|
|
203
|
+
formality: 'less', glossary_id: 'g-1', split_sentences: '0' },
|
|
204
|
+
[{ text: 'Hallo', billed_characters: 5 }])
|
|
205
|
+
|
|
206
|
+
result = client.translate_text('Hello', nil, 'DE', formality: 'less', glossary: 'g-1',
|
|
207
|
+
split_sentences: false, context: nil)
|
|
208
|
+
expect([result.class, result.text, result.billed_characters])
|
|
209
|
+
.to eq([DeepL::Resources::Text, 'Hallo', 5])
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'returns an array for a one-element array' do
|
|
213
|
+
stub_translate({ text: ['Hello'], source_lang: 'EN', target_lang: 'DE',
|
|
214
|
+
show_billed_characters: true }, [{ text: 'Hallo' }])
|
|
215
|
+
|
|
216
|
+
expect(client.translate_text(['Hello'], 'EN', 'DE').map(&:text)).to eq(['Hallo'])
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe '#rephrase_text' do
|
|
221
|
+
let(:client) { described_class.new(OfflineHelper::AUTH_KEY) }
|
|
222
|
+
|
|
223
|
+
def stub_rephrase(body, improvements)
|
|
224
|
+
stub_request(:post, url('/v2/write/rephrase'))
|
|
225
|
+
.with(body: body, headers: json_headers)
|
|
226
|
+
.to_return(reply(improvements: improvements))
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'sends the writing style and tone keywords, no nil options, and returns one result' do
|
|
230
|
+
stub_rephrase({ text: ['Hello'], target_lang: 'en-GB', writing_style: 'business',
|
|
231
|
+
tone: 'friendly' },
|
|
232
|
+
[{ text: 'Hi', detected_source_language: 'en', target_language: 'en-GB' }])
|
|
233
|
+
|
|
234
|
+
result = client.rephrase_text('Hello', 'en-GB', writing_style: 'business',
|
|
235
|
+
tone: 'friendly', context: nil)
|
|
236
|
+
expect([result.class, result.text, result.target_language])
|
|
237
|
+
.to eq([DeepL::Resources::Text, 'Hi', 'en-GB'])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'applies extra_body_parameters last, replacing string or symbol keyed fields' do
|
|
241
|
+
stub_rephrase({ text: ['Hello'], tone: 'confident', beta: true }, [{ text: 'Hi' }])
|
|
242
|
+
|
|
243
|
+
client.rephrase_text('Hello', tone: 'friendly',
|
|
244
|
+
extra_body_parameters: { 'tone' => 'confident', beta: true })
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it 'returns an array for a one-element array' do
|
|
248
|
+
stub_rephrase({ text: ['Hello'] }, [{ text: 'Hi' }])
|
|
249
|
+
|
|
250
|
+
expect(client.rephrase_text(['Hello']).map(&:text)).to eq(['Hi'])
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
describe '#correct_text' do
|
|
255
|
+
let(:client) { described_class.new(OfflineHelper::AUTH_KEY) }
|
|
256
|
+
|
|
257
|
+
it 'sends the text, the target language and the extra body parameters' do
|
|
258
|
+
stub_request(:post, url('/v2/write/correct'))
|
|
259
|
+
.with(body: { text: ['Helo'], target_lang: 'en-US', beta: 1 }, headers: json_headers)
|
|
260
|
+
.to_return(reply(improvements: [{ text: 'Hello', target_language: 'en-US' }]))
|
|
261
|
+
|
|
262
|
+
result = client.correct_text('Helo', 'en-US', extra_body_parameters: { beta: 1 })
|
|
263
|
+
expect([result.text, result.target_language]).to eq(%w[Hello en-US])
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'returns an array for a one-element array' do
|
|
267
|
+
stub_request(:post, url('/v2/write/correct'))
|
|
268
|
+
.to_return(reply(improvements: [{ text: 'Hello' }]))
|
|
269
|
+
|
|
270
|
+
expect(client.correct_text(['Helo']).map(&:text)).to eq(['Hello'])
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# frozen_string_literal: true
|
|
5
5
|
|
|
6
6
|
require 'logger'
|
|
7
|
-
|
|
7
|
+
require_relative 'offline_helper'
|
|
8
8
|
|
|
9
9
|
describe DeepL::Configuration do
|
|
10
10
|
subject(:config) { described_class.new(attributes) }
|
|
@@ -25,6 +25,7 @@ describe DeepL::Configuration do
|
|
|
25
25
|
expect(config.host).to eq('https://api.deepl.com')
|
|
26
26
|
expect(config.max_doc_status_queries).to be_nil
|
|
27
27
|
expect(config.version).to eq('v2')
|
|
28
|
+
expect(config.timeout).to eq(60.0)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
it 'sends platform information' do
|
|
@@ -48,6 +49,23 @@ describe DeepL::Configuration do
|
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
context 'when the environment provides the key and the server URL' do
|
|
53
|
+
before do
|
|
54
|
+
ENV['DEEPL_AUTH_KEY'] = 'env-key'
|
|
55
|
+
ENV['DEEPL_SERVER_URL'] = 'https://api.example.test'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'uses the environment values' do
|
|
59
|
+
expect(config.auth_key).to eq('env-key')
|
|
60
|
+
expect(config.host).to eq('https://api.example.test')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'prefers the given attributes' do
|
|
64
|
+
config = described_class.new(auth_key: 'given', host: 'https://given.example.test')
|
|
65
|
+
expect([config.auth_key, config.host]).to eq(%w[given https://given.example.test])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
51
69
|
context 'when using a free key' do
|
|
52
70
|
let(:attributes) { { auth_key: '123e4567-e89b-12d3-a456-426614174000:fx' } }
|
|
53
71
|
|
|
@@ -56,6 +74,22 @@ describe DeepL::Configuration do
|
|
|
56
74
|
end
|
|
57
75
|
end
|
|
58
76
|
|
|
77
|
+
context 'when the free key is set after the configuration was created' do
|
|
78
|
+
it 'uses the free API URL' do
|
|
79
|
+
config.auth_key = 'abc:fx'
|
|
80
|
+
|
|
81
|
+
expect(config.host).to eq('https://api-free.deepl.com')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context 'when the free key has surrounding whitespace' do
|
|
86
|
+
let(:attributes) { { auth_key: 'abc:fx ' } }
|
|
87
|
+
|
|
88
|
+
it 'uses the free API URL' do
|
|
89
|
+
expect(config.host).to eq('https://api-free.deepl.com')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
59
93
|
context 'when using a pro key' do
|
|
60
94
|
let(:attributes) { { auth_key: '123e4567-e89b-12d3-a456-426614174000' } }
|
|
61
95
|
|
|
@@ -103,7 +137,7 @@ describe DeepL::Configuration do
|
|
|
103
137
|
describe '#validate!' do
|
|
104
138
|
let(:auth_message) { 'auth_key not provided' }
|
|
105
139
|
|
|
106
|
-
context 'when
|
|
140
|
+
context 'when the auth key is empty' do
|
|
107
141
|
let(:attributes) { { auth_key: '' } }
|
|
108
142
|
|
|
109
143
|
it 'raises an error' do
|
|
@@ -111,7 +145,15 @@ describe DeepL::Configuration do
|
|
|
111
145
|
end
|
|
112
146
|
end
|
|
113
147
|
|
|
114
|
-
context 'when
|
|
148
|
+
context 'when the auth key is blank' do
|
|
149
|
+
let(:attributes) { { auth_key: " \n" } }
|
|
150
|
+
|
|
151
|
+
it 'raises an error' do
|
|
152
|
+
expect { config.validate! }.to raise_error(DeepL::Exceptions::Error, auth_message)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'when the auth key is set' do
|
|
115
157
|
let(:attributes) { { auth_key: 'not-empty' } }
|
|
116
158
|
|
|
117
159
|
it 'does not raise an error' do
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# license that can be found in the LICENSE.md file.
|
|
4
4
|
# frozen_string_literal: true
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
7
|
|
|
8
8
|
describe 'DeepL::Constants' do
|
|
9
9
|
describe DeepL::Constants::Tone do
|
|
@@ -89,6 +89,28 @@ describe 'DeepL::Constants' do
|
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
describe DeepL::Constants::TagHandlingVersion do
|
|
93
|
+
it 'includes v1 and v2' do
|
|
94
|
+
expect([described_class::V1, described_class::V2]).to eq(%w[v1 v2])
|
|
95
|
+
expect(described_class.options).to contain_exactly('v1', 'v2')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe DeepL::Constants::LanguageResourceName do
|
|
100
|
+
it 'includes the resources of the languages endpoint' do
|
|
101
|
+
expect(described_class.options).to contain_exactly(
|
|
102
|
+
'translate_text', 'translate_document', 'glossary', 'voice', 'write', 'style_rules',
|
|
103
|
+
'translation_memory'
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe DeepL::Constants::LanguageInclude do
|
|
109
|
+
it 'includes beta and external' do
|
|
110
|
+
expect([described_class::BETA, described_class::EXTERNAL]).to eq(%w[beta external])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
92
114
|
describe DeepL::Constants::SplitSentences do
|
|
93
115
|
subject(:split_sentences) { described_class }
|
|
94
116
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright 2026 DeepL SE (https://www.deepl.com)
|
|
2
|
+
# Use of this source code is governed by an MIT
|
|
3
|
+
# license that can be found in the LICENSE file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
|
+
|
|
8
|
+
describe 'DeepL::Client#correct_text' do
|
|
9
|
+
let(:client) { DeepL::Client.new(OfflineHelper::AUTH_KEY) }
|
|
10
|
+
|
|
11
|
+
def stub_correct(body = nil)
|
|
12
|
+
request = stub_request(:post, url('/v2/write/correct'))
|
|
13
|
+
request = request.with(body: body, headers: json_headers) unless body.nil?
|
|
14
|
+
request
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'sends only the text when no target language is given' do
|
|
18
|
+
stub_correct(text: ['Helo']).to_return(reply(improvements: [{ text: 'Hello' }]))
|
|
19
|
+
client.correct_text('Helo')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'sends several texts' do
|
|
23
|
+
stub_correct(text: %w[a b], target_lang: 'en-US')
|
|
24
|
+
.to_return(reply(improvements: [{ text: 'a' }, { text: 'b' }]))
|
|
25
|
+
|
|
26
|
+
client.correct_text(%w[a b], 'en-US')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns a text for a string' do
|
|
30
|
+
stub_correct.to_return(reply(improvements: [{ text: 'Hello' }]))
|
|
31
|
+
|
|
32
|
+
expect(client.correct_text('Helo')).to be_a(DeepL::Resources::Text)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
[['Helo', []], [%w[a b], [{ text: 'x' }]]].each do |text, improvements|
|
|
36
|
+
it "raises DeepL::Exceptions::Error for #{improvements.size} improvements of #{text.inspect}" do
|
|
37
|
+
stub_correct.to_return(reply(improvements: improvements))
|
|
38
|
+
|
|
39
|
+
expect { client.correct_text(text) }
|
|
40
|
+
.to raise_error(an_instance_of(DeepL::Exceptions::Error), /expected \d improvements/)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
[[''], [[]], [['a', '']]].each do |args|
|
|
45
|
+
it "raises ArgumentError for #{args.first.inspect} without sending a request" do
|
|
46
|
+
expect { client.correct_text(*args) }.to raise_error(ArgumentError, /non-empty/)
|
|
47
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright 2026 DeepL SE (https://www.deepl.com)
|
|
2
|
+
# Use of this source code is governed by an MIT
|
|
3
|
+
# license that can be found in the LICENSE file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Deprecation do
|
|
9
|
+
around do |example|
|
|
10
|
+
enabled = Warning[:deprecated]
|
|
11
|
+
example.run
|
|
12
|
+
ensure
|
|
13
|
+
Warning[:deprecated] = enabled
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "prints nothing while Ruby's deprecation warnings are off" do
|
|
17
|
+
Warning[:deprecated] = false
|
|
18
|
+
|
|
19
|
+
expect { described_class.warn_once('spec-off', 'm') }.not_to output.to_stderr
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'warns in the deprecated category when they are on' do
|
|
23
|
+
Warning[:deprecated] = true
|
|
24
|
+
|
|
25
|
+
if RUBY_VERSION >= '3.0'
|
|
26
|
+
expect(Warning).to receive(:warn).with(/m/, category: :deprecated)
|
|
27
|
+
described_class.warn_once('spec-on', 'm')
|
|
28
|
+
else
|
|
29
|
+
expect { described_class.warn_once('spec-on', 'm') }.to output(/m/).to_stderr
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright 2026 DeepL SE (https://www.deepl.com)
|
|
2
|
+
# Use of this source code is governed by an MIT
|
|
3
|
+
# license that can be found in the LICENSE file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
|
+
|
|
8
|
+
describe 'DeepL error mapping' do
|
|
9
|
+
{
|
|
10
|
+
401 => [DeepL::Exceptions::AuthorizationFailed, /Authorization failed/],
|
|
11
|
+
403 => [DeepL::Exceptions::AuthorizationFailed, /Authorization failed/],
|
|
12
|
+
456 => [DeepL::Exceptions::QuotaExceeded, /Quota exceeded/],
|
|
13
|
+
413 => [DeepL::Exceptions::RequestEntityTooLarge,
|
|
14
|
+
/request size has reached the supported limit/],
|
|
15
|
+
418 => [DeepL::Exceptions::RequestError, 'Request failed with HTTP status 418: m']
|
|
16
|
+
}.each do |status, (error_class, message)|
|
|
17
|
+
it "raises #{error_class.name.split('::').last} for status #{status} without retrying" do
|
|
18
|
+
stub = stub_request(:get, url('/v2/usage')).to_return(status: status, body: '{"message":"m"}')
|
|
19
|
+
|
|
20
|
+
expect { DeepL.usage }.to raise_error(error_class, message)
|
|
21
|
+
expect(stub).to have_been_requested.once
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
400 => DeepL::Exceptions::BadRequest,
|
|
27
|
+
404 => DeepL::Exceptions::NotFound
|
|
28
|
+
}.each do |status, error_class|
|
|
29
|
+
it "raises #{error_class.name.split('::').last} with the API message for status #{status}" do
|
|
30
|
+
stub = stub_request(:get,
|
|
31
|
+
url('/v2/usage')).to_return(reply({ message: 'Value for x invalid' },
|
|
32
|
+
status))
|
|
33
|
+
|
|
34
|
+
expect { DeepL.usage }.to raise_error(error_class, 'Value for x invalid')
|
|
35
|
+
expect(stub).to have_been_requested.once
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "raises #{error_class.name.split('::').last} with a plain-text body as the message" do
|
|
39
|
+
stub_request(:get, url('/v2/usage')).to_return(status: status, body: 'plain text')
|
|
40
|
+
|
|
41
|
+
expect { DeepL.usage }.to raise_error(error_class, 'plain text')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'retries a 503 reply and raises ServerError with its status' do
|
|
46
|
+
stub = stub_request(:get, url('/v2/usage')).to_return(status: 503, body: '')
|
|
47
|
+
|
|
48
|
+
expect { DeepL.usage }
|
|
49
|
+
.to raise_error(DeepL::Exceptions::ServerError) { |e| expect(e.http_status_code).to eq(503) }
|
|
50
|
+
expect(stub).to have_been_requested.times(3)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'keeps the status code and the failed request on the raised error' do
|
|
54
|
+
stub_request(:get, url('/v2/usage')).to_return(status: 418, body: '')
|
|
55
|
+
|
|
56
|
+
expect { DeepL.usage }.to(raise_error(DeepL::Exceptions::RequestError) do |error|
|
|
57
|
+
expect([error.http_status_code, error.request.path, error.message])
|
|
58
|
+
.to eq([418, '/v2/usage', 'Request failed with HTTP status 418.'])
|
|
59
|
+
end)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'uses the raw body as the message of a 400 reply with a JSON array' do
|
|
63
|
+
stub_request(:get, url('/v2/usage')).to_return(status: 400, body: '["x"]')
|
|
64
|
+
|
|
65
|
+
expect { DeepL.usage }.to raise_error(DeepL::Exceptions::BadRequest, '["x"]')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
['not json', '[]', '{"translations":[1]}'].each do |body|
|
|
69
|
+
it "raises DeepL::Exceptions::Error for the malformed response #{body}" do
|
|
70
|
+
stub_request(:post, url('/v2/translate')).to_return(status: 200, body: body)
|
|
71
|
+
|
|
72
|
+
expect { DeepL.translate('a', nil, 'DE') }
|
|
73
|
+
.to raise_error(an_instance_of(DeepL::Exceptions::Error), /Unexpected response/)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'keeps the response on the raised error' do
|
|
78
|
+
stub_request(:get, url('/v2/usage')).to_return(status: 456, body: 'quota')
|
|
79
|
+
|
|
80
|
+
expect { DeepL.usage }.to(raise_error { |error| expect(error.response.body).to eq('quota') })
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'raises the errors of every endpoint family the same way' do
|
|
84
|
+
stub_request(:post, %r{/v2/(translate|write/rephrase)}).to_return(status: 403)
|
|
85
|
+
stub_request(:delete, url('/v2/glossaries/g-1')).to_return(status: 404,
|
|
86
|
+
body: '{"message":"gone"}')
|
|
87
|
+
|
|
88
|
+
expect { DeepL.translate('a', nil, 'DE') }.to raise_error(DeepL::Exceptions::AuthorizationFailed)
|
|
89
|
+
expect { DeepL.rephrase('a') }.to raise_error(DeepL::Exceptions::AuthorizationFailed)
|
|
90
|
+
expect { DeepL.glossaries.destroy('g-1') }.to raise_error(DeepL::Exceptions::NotFound, 'gone')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright 2026 DeepL SE (https://www.deepl.com)
|
|
2
|
+
# Use of this source code is governed by an MIT
|
|
3
|
+
# license that can be found in the LICENSE file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require_relative 'offline_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::GlossaryApi do
|
|
9
|
+
let(:glossaries) { DeepL.glossaries }
|
|
10
|
+
let(:glossary_json) do
|
|
11
|
+
{ glossary_id: 'g-1', name: 'terms', ready: true, source_lang: 'en', target_lang: 'de',
|
|
12
|
+
creation_time: '2026-01-02T03:04:05.678Z', entry_count: 2 }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#create' do
|
|
16
|
+
it 'sends the entries of an array as TSV' do
|
|
17
|
+
stub_request(:post, url('/v2/glossaries')).with(
|
|
18
|
+
body: { name: 'terms', source_lang: 'EN', target_lang: 'DE',
|
|
19
|
+
entries: "Apple\tApfel\nBanana\tBanane\n", entries_format: 'tsv' },
|
|
20
|
+
headers: json_headers
|
|
21
|
+
).to_return(reply(glossary_json, 201))
|
|
22
|
+
|
|
23
|
+
glossaries.create('terms', 'EN', 'DE', [%w[Apple Apfel], %w[Banana Banane]])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'sends a string of entries and the entries format as given' do
|
|
27
|
+
stub_request(:post, url('/v2/glossaries')).with(
|
|
28
|
+
body: { name: 'terms', source_lang: 'EN', target_lang: 'DE', entries: "Apple,Apfel\n",
|
|
29
|
+
entries_format: 'csv' }
|
|
30
|
+
).to_return(reply(glossary_json, 201))
|
|
31
|
+
|
|
32
|
+
glossaries.create('terms', 'EN', 'DE', "Apple,Apfel\n", entries_format: 'csv')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'strips surrounding whitespace from the terms of an array' do
|
|
36
|
+
stub_request(:post, url('/v2/glossaries'))
|
|
37
|
+
.with(body: hash_including(entries: "Apple\tApfel\n"))
|
|
38
|
+
.to_return(reply(glossary_json, 201))
|
|
39
|
+
|
|
40
|
+
glossaries.create('terms', 'EN', 'DE', [[' Apple ', "Apfel\n"]])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'returns a glossary' do
|
|
44
|
+
stub_request(:post, url('/v2/glossaries')).to_return(reply(glossary_json, 201))
|
|
45
|
+
|
|
46
|
+
expect(glossaries.create('terms', 'EN', 'DE', [%w[a b]])).to be_a(DeepL::Resources::Glossary)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#create with invalid terms' do
|
|
51
|
+
[[%W[Apple\tPie Apfel]], [%W[Apple Apfel\nKuchen]], [['Apple', ' ']]].each do |entries|
|
|
52
|
+
it "raises ValidationError for #{entries.inspect} and sends no request" do
|
|
53
|
+
expect { glossaries.create('terms', 'EN', 'DE', entries) }
|
|
54
|
+
.to raise_error(DeepL::Exceptions::ValidationError)
|
|
55
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '#find' do
|
|
61
|
+
it 'sends a GET without a body' do
|
|
62
|
+
stub_request(:get, url('/v2/glossaries/g-1')).with(headers: auth_headers) { bodyless?(_1) }
|
|
63
|
+
.to_return(reply(glossary_json))
|
|
64
|
+
|
|
65
|
+
glossaries.find('g-1')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#list' do
|
|
70
|
+
it 'returns one glossary per entry of the reply' do
|
|
71
|
+
second = glossary_json.merge(glossary_id: 'g-2')
|
|
72
|
+
stub_request(:get, url('/v2/glossaries'))
|
|
73
|
+
.with(headers: auth_headers) { bodyless?(_1) }
|
|
74
|
+
.to_return(reply(glossaries: [glossary_json, second]))
|
|
75
|
+
|
|
76
|
+
expect(glossaries.list.size).to eq(2)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#entries' do
|
|
81
|
+
it 'parses the TSV reply into pairs' do
|
|
82
|
+
stub_request(:get, url('/v2/glossaries/g-1/entries'))
|
|
83
|
+
.with(headers: auth_headers) { bodyless?(_1) }
|
|
84
|
+
.to_return(status: 200, body: "Apple\tApfel\nBanana\tBanane")
|
|
85
|
+
|
|
86
|
+
expect(glossaries.entries('g-1')).to eq([%w[Apple Apfel], %w[Banana Banane]])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#destroy' do
|
|
91
|
+
it 'sends a DELETE without a body and returns the ID' do
|
|
92
|
+
stub_request(:delete, url('/v2/glossaries/g-1')).with(headers: auth_headers) { bodyless?(_1) }
|
|
93
|
+
.to_return(status: 204)
|
|
94
|
+
|
|
95
|
+
expect(glossaries.destroy('g-1')).to eq('g-1')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|