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,606 @@
|
|
|
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 'open3'
|
|
7
|
+
require_relative 'offline_helper'
|
|
8
|
+
|
|
9
|
+
describe DeepL::DocumentApi do
|
|
10
|
+
let(:documents) { DeepL.document }
|
|
11
|
+
let(:handle) { DeepL::Resources::DocumentHandle.new('doc-1', 'key-1') }
|
|
12
|
+
let(:upload_reply) { reply(document_id: 'doc-1', document_key: 'key-1') }
|
|
13
|
+
let(:document_key_body) { { document_key: 'key-1' } }
|
|
14
|
+
|
|
15
|
+
include_context 'with a temporary directory'
|
|
16
|
+
|
|
17
|
+
def stub_upload
|
|
18
|
+
stub_request(:post, url('/v2/document')).with(
|
|
19
|
+
headers: { 'Authorization' => json_headers['Authorization'],
|
|
20
|
+
'Content-Type' => %r{\Amultipart/form-data} }
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Net::HTTP writes multipart parts straight to the socket, so WebMock sees no request body.
|
|
25
|
+
# The form fields are read from the argument of `set_form` instead.
|
|
26
|
+
def capture_forms
|
|
27
|
+
forms = []
|
|
28
|
+
allow(Net::HTTP::Post).to receive(:new).and_wrap_original do |original, *args|
|
|
29
|
+
original.call(*args).tap do |request|
|
|
30
|
+
allow(request).to receive(:set_form).and_wrap_original do |set_form, form, *rest|
|
|
31
|
+
forms << form
|
|
32
|
+
set_form.call(form, *rest)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
forms
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Uploads a text document with the given options and returns the form that was sent.
|
|
40
|
+
def sent_form(filename = nil, **options)
|
|
41
|
+
input, = create_text_document(temp_dir)
|
|
42
|
+
stub_upload.to_return(upload_reply)
|
|
43
|
+
forms = capture_forms
|
|
44
|
+
documents.upload(input, 'EN', 'DE', filename, **options)
|
|
45
|
+
forms.first
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def resource_options
|
|
49
|
+
resources = DeepL::Resources
|
|
50
|
+
memory = resources::TranslationMemory.new({ 'translation_memory_id' => 'tm-obj' }, nil, nil)
|
|
51
|
+
glossary = resources::Glossary.new({ 'glossary_id' => 'g-obj' }, nil, nil)
|
|
52
|
+
multilingual = resources::MultilingualGlossary.new({ 'glossary_id' => 'g-ml', 'name' => 'n' },
|
|
53
|
+
nil, nil)
|
|
54
|
+
{ style_rule: resources::StyleRule.new({ 'style_id' => 's-obj' }, nil, nil),
|
|
55
|
+
style_profile: resources::StyleProfile.new({ 'style_profile_id' => 'sp-obj' }, nil, nil),
|
|
56
|
+
translation_memory: memory, glossary_ids: [glossary, multilingual, 'g-str'] }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def form_fields(form)
|
|
60
|
+
form.reject { |name, _| name == 'file' }.to_h
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def stub_status(*statuses)
|
|
64
|
+
stub_request(:post, url('/v2/document/doc-1')).with(body: document_key_body,
|
|
65
|
+
headers: json_headers)
|
|
66
|
+
.to_return(*statuses)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def status_reply(status, extra = {})
|
|
70
|
+
reply({ document_id: 'doc-1', status: status }.merge(extra))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#upload' do
|
|
74
|
+
it 'raises Errno::ENOENT for a missing input file' do
|
|
75
|
+
expect { documents.upload(File.join(temp_dir, 'missing.txt'), nil, 'DE') }
|
|
76
|
+
.to raise_error(Errno::ENOENT)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'sends a multipart POST with the credentials and the file, languages and filename' do
|
|
80
|
+
input, = create_text_document(temp_dir)
|
|
81
|
+
|
|
82
|
+
form = sent_form
|
|
83
|
+
|
|
84
|
+
expect(form.assoc('file').last.path).to eq(input)
|
|
85
|
+
expect(form_fields(form)).to eq('source_lang' => 'EN', 'target_lang' => 'DE',
|
|
86
|
+
'filename' => 'example_document.txt')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'sends no source_lang when the source language is nil' do
|
|
90
|
+
input, = create_text_document(temp_dir)
|
|
91
|
+
stub_upload.to_return(upload_reply)
|
|
92
|
+
forms = capture_forms
|
|
93
|
+
|
|
94
|
+
documents.upload(input, nil, 'DE')
|
|
95
|
+
|
|
96
|
+
expect(forms.first.assoc('file').last.path).to eq(input)
|
|
97
|
+
expect(form_fields(forms.first)).to eq('target_lang' => 'DE',
|
|
98
|
+
'filename' => 'example_document.txt')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'sends every option as its own field, serializing IDs, the threshold and glossary_ids' do
|
|
102
|
+
form = sent_form('name.txt', formality: 'more', output_format: 'txt',
|
|
103
|
+
glossary_ids: %w[g-1 g-2], style_rule: 's-1',
|
|
104
|
+
style_profile: 'sp-1', translation_memory: 'tm-1',
|
|
105
|
+
translation_memory_threshold: 80)
|
|
106
|
+
|
|
107
|
+
expect(form_fields(form)).to eq(
|
|
108
|
+
'source_lang' => 'EN', 'target_lang' => 'DE', 'filename' => 'name.txt',
|
|
109
|
+
'formality' => 'more', 'output_format' => 'txt', 'glossary_ids' => 'g-1,g-2',
|
|
110
|
+
'style_id' => 's-1', 'style_profile_id' => 'sp-1', 'translation_memory_id' => 'tm-1',
|
|
111
|
+
'translation_memory_threshold' => '80'
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'sends enable_watermark as true or false, combined with output_format' do
|
|
116
|
+
expect(form_fields(sent_form(enable_watermark: true, output_format: 'pdf')))
|
|
117
|
+
.to include('enable_watermark' => 'true', 'output_format' => 'pdf')
|
|
118
|
+
expect(form_fields(sent_form(enable_watermark: false)))
|
|
119
|
+
.to include('enable_watermark' => 'false')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'does not send enable_watermark when it is not set' do
|
|
123
|
+
expect(form_fields(sent_form)).not_to have_key('enable_watermark')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'does not send style_profile_id when it is not set' do
|
|
127
|
+
expect(form_fields(sent_form(style_rule: 's-1'))).not_to have_key('style_profile_id')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'does not send style_profile_id when it is nil' do
|
|
131
|
+
expect(form_fields(sent_form(style_profile: nil))).not_to have_key('style_profile_id')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'does not send style_id when style_rule is nil' do
|
|
135
|
+
expect(form_fields(sent_form(style_rule: nil))).not_to have_key('style_id')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'takes the IDs of style rule, style profile, translation memory and glossary objects' do
|
|
139
|
+
form = sent_form(**resource_options)
|
|
140
|
+
|
|
141
|
+
expect(form_fields(form)).to include(
|
|
142
|
+
'style_id' => 's-obj', 'style_profile_id' => 'sp-obj', 'translation_memory_id' => 'tm-obj',
|
|
143
|
+
'glossary_ids' => 'g-obj,g-ml,g-str'
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'does not send translation_memory_id or the threshold when they are nil' do
|
|
148
|
+
expect(form_fields(sent_form(translation_memory: nil, translation_memory_threshold: nil)))
|
|
149
|
+
.to eq('source_lang' => 'EN', 'target_lang' => 'DE', 'filename' => 'example_document.txt')
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'sends the glossary option, an ID or a glossary object, as glossary_id' do
|
|
153
|
+
glossary = DeepL::Resources::MultilingualGlossary.new({ 'glossary_id' => 'g-ml',
|
|
154
|
+
'name' => 'n' }, nil, nil)
|
|
155
|
+
expect(form_fields(sent_form(glossary: 'g-1'))).to include('glossary_id' => 'g-1')
|
|
156
|
+
expect(form_fields(sent_form(glossary: glossary))).to include('glossary_id' => 'g-ml')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'treats glossary_id: nil as unset next to glossary or glossary_ids' do
|
|
160
|
+
expect(form_fields(sent_form(glossary: 'g-1', glossary_id: nil)))
|
|
161
|
+
.to include('glossary_id' => 'g-1')
|
|
162
|
+
expect(form_fields(sent_form(glossary_ids: %w[g-1 g-2], glossary_id: nil)))
|
|
163
|
+
.to include('glossary_ids' => 'g-1,g-2').and(satisfy { |f| !f.key?('glossary_id') })
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'lets extra_body_parameters add a field and override an existing one' do
|
|
167
|
+
form = sent_form(formality: 'more', extra_body_parameters: { formality: 'less', beta: true })
|
|
168
|
+
|
|
169
|
+
expect(form.count { |name, _| name == 'formality' }).to eq(1)
|
|
170
|
+
expect(form_fields(form)).to include('formality' => 'less', 'beta' => 'true')
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'raises ArgumentError for invalid glossary_ids without sending a request' do
|
|
174
|
+
input, = create_text_document(temp_dir)
|
|
175
|
+
|
|
176
|
+
expect { documents.upload(input, nil, 'DE', nil, glossary_ids: ['g-1']) }
|
|
177
|
+
.to raise_error(ArgumentError, /requires `source_lang`/)
|
|
178
|
+
expect { documents.upload(input, 'EN', 'DE', nil, glossary_ids: ['g-1'], glossary_id: 'g') }
|
|
179
|
+
.to raise_error(ArgumentError, /cannot be used together/)
|
|
180
|
+
expect { documents.upload(input, 'EN', 'DE', nil, glossary_ids: %w[1 2 3 4 5 6]) }
|
|
181
|
+
.to raise_error(ArgumentError, /maximum of 5/)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'retries after a 429 reply' do
|
|
185
|
+
input, = create_text_document(temp_dir)
|
|
186
|
+
stub = stub_upload.to_return(reply({}, 429), upload_reply)
|
|
187
|
+
|
|
188
|
+
documents.upload(input, 'EN', 'DE')
|
|
189
|
+
|
|
190
|
+
expect(stub).to have_been_requested.twice
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe '#get_status' do
|
|
195
|
+
it 'sends the document key and derives that a translating document is not finished' do
|
|
196
|
+
stub_status(status_reply('translating', seconds_remaining: 3, billed_characters: 11,
|
|
197
|
+
error_message: 'none'))
|
|
198
|
+
|
|
199
|
+
status = documents.get_status(handle)
|
|
200
|
+
|
|
201
|
+
expect(status.finished?).to be(false)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'parses done as done and error as failed' do
|
|
205
|
+
stub_status(status_reply('done'), status_reply('error'))
|
|
206
|
+
|
|
207
|
+
statuses = Array.new(2) { documents.get_status(handle) }
|
|
208
|
+
|
|
209
|
+
expect(statuses.map { |s| [s.done?, s.error?, s.finished?] })
|
|
210
|
+
.to eq([[true, false, true], [false, true, true]])
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'derives ok? as not failed, also while queued or translating' do
|
|
214
|
+
codes = DeepL::Constants::DocumentStatusCode
|
|
215
|
+
stub_status(*[codes::QUEUED, codes::TRANSLATING, codes::DONE, codes::ERROR].map do |code|
|
|
216
|
+
status_reply(code)
|
|
217
|
+
end)
|
|
218
|
+
|
|
219
|
+
statuses = Array.new(4) { documents.get_status(handle) }
|
|
220
|
+
|
|
221
|
+
expect(statuses.map { |s| [s.status, s.ok?] }).to eq(
|
|
222
|
+
[['queued', true], ['translating', true], ['done', true], ['error', false]]
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'URL-encodes the document ID in the path' do
|
|
227
|
+
stub_request(:post, url('/v2/document/a%20b%2Fc')).to_return(status_reply('done'))
|
|
228
|
+
|
|
229
|
+
documents.get_status(DeepL::Resources::DocumentHandle.new('a b/c', 'key-1', nil, nil))
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
describe '#download' do
|
|
234
|
+
it 'writes the response body to the output file byte for byte' do
|
|
235
|
+
output = File.join(temp_dir, 'result.docx')
|
|
236
|
+
stub_request(:post, url('/v2/document/doc-1/result'))
|
|
237
|
+
.with(body: document_key_body, headers: json_headers)
|
|
238
|
+
.to_return(status: 200, body: Fixtures::BINARY_CONTENT)
|
|
239
|
+
|
|
240
|
+
documents.download(handle, output)
|
|
241
|
+
|
|
242
|
+
expect(File.binread(output)).to eq(Fixtures::BINARY_CONTENT)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'writes the bytes unchanged when Encoding.default_internal is set' do
|
|
246
|
+
output = File.join(temp_dir, 'result.docx')
|
|
247
|
+
stub_request(:post, url('/v2/document/doc-1/result'))
|
|
248
|
+
.to_return(status: 200, body: Fixtures::BINARY_CONTENT)
|
|
249
|
+
previous = Encoding.default_internal
|
|
250
|
+
Encoding.default_internal = Encoding::UTF_8
|
|
251
|
+
|
|
252
|
+
documents.download(handle, output)
|
|
253
|
+
|
|
254
|
+
expect(File.binread(output)).to eq(Fixtures::BINARY_CONTENT)
|
|
255
|
+
ensure
|
|
256
|
+
Encoding.default_internal = previous
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'URL-encodes the document ID in the path' do
|
|
260
|
+
stub_request(:post, url('/v2/document/a%20b%2Fc/result')).to_return(status: 200, body: 'x')
|
|
261
|
+
|
|
262
|
+
documents.download(DeepL::Resources::DocumentHandle.new('a b/c', 'key-1', nil, nil),
|
|
263
|
+
File.join(temp_dir, 'result.txt'))
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'raises DocumentNotReadyError, a ServerError, for a 503 reply' do
|
|
267
|
+
stub = stub_request(:post, url('/v2/document/doc-1/result'))
|
|
268
|
+
.to_return(reply({ message: 'not ready' }, 503))
|
|
269
|
+
DeepL.configuration.max_network_retries = 0
|
|
270
|
+
|
|
271
|
+
expect { documents.download(handle, File.join(temp_dir, 'result.txt')) }
|
|
272
|
+
.to raise_error(DeepL::Exceptions::DocumentNotReadyError, /not ready/) { |e|
|
|
273
|
+
expect([e, e.http_status_code]).to match([be_a(DeepL::Exceptions::ServerError), 503])
|
|
274
|
+
}
|
|
275
|
+
expect(stub).to have_been_requested.once
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe DeepL::Resources::DocumentHandle, '#wait_until_document_translation_finished' do
|
|
280
|
+
it 'polls the status until the translation is done' do
|
|
281
|
+
stub = stub_status(status_reply('queued'), status_reply('done'))
|
|
282
|
+
|
|
283
|
+
status = handle.wait_until_document_translation_finished
|
|
284
|
+
|
|
285
|
+
expect(stub).to have_been_requested.twice
|
|
286
|
+
expect(status.done?).to be(true)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it 'polls through the connection and configuration the handle was created with' do
|
|
290
|
+
configuration = DeepL::Configuration.new(auth_key: 'other', host: 'https://other.example.test',
|
|
291
|
+
max_doc_status_queries: 0)
|
|
292
|
+
other = DeepL::DocumentApi.new(DeepL::API.new(configuration))
|
|
293
|
+
stub_request(:post, 'https://other.example.test/v2/document').to_return(upload_reply)
|
|
294
|
+
stub = stub_request(:post, 'https://other.example.test/v2/document/doc-1')
|
|
295
|
+
.with(headers: { 'Authorization' => 'DeepL-Auth-Key other' })
|
|
296
|
+
.to_return(status_reply('queued'))
|
|
297
|
+
input, = create_text_document(temp_dir)
|
|
298
|
+
|
|
299
|
+
other.upload(input, 'EN', 'DE').wait_until_document_translation_finished
|
|
300
|
+
|
|
301
|
+
expect(stub).to have_been_requested.once
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'does not show the auth key in inspect' do
|
|
305
|
+
configuration = DeepL::Configuration.new(auth_key: 'sdktest-secret-key')
|
|
306
|
+
other = described_class.new('doc-1', 'key-1', nil, nil,
|
|
307
|
+
api: DeepL::API.new(configuration))
|
|
308
|
+
|
|
309
|
+
expect(other.inspect).not_to include('sdktest-secret-key')
|
|
310
|
+
expect(configuration.inspect).not_to include('sdktest-secret-key')
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'stops after max_doc_status_queries polls' do
|
|
314
|
+
DeepL.configuration.max_doc_status_queries = 0
|
|
315
|
+
stub = stub_status(status_reply('queued'))
|
|
316
|
+
|
|
317
|
+
status = handle.wait_until_document_translation_finished
|
|
318
|
+
|
|
319
|
+
expect(stub).to have_been_requested.once
|
|
320
|
+
expect(status.finished?).to be(false)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
describe '#translate_document' do
|
|
325
|
+
it 'uploads, waits and writes the translated document' do
|
|
326
|
+
input, output = create_text_document(temp_dir)
|
|
327
|
+
stub_upload.to_return(upload_reply)
|
|
328
|
+
stub_status(status_reply('done'))
|
|
329
|
+
stub_request(:post, url('/v2/document/doc-1/result'))
|
|
330
|
+
.with(body: document_key_body).to_return(status: 200, body: 'Protonenstrahl')
|
|
331
|
+
|
|
332
|
+
documents.translate_document(input, output, 'EN', 'DE')
|
|
333
|
+
|
|
334
|
+
expect(File.read(output)).to eq('Protonenstrahl')
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'passes enable_watermark on to the upload' do
|
|
338
|
+
input, output = create_text_document(temp_dir)
|
|
339
|
+
stub_upload.to_return(upload_reply)
|
|
340
|
+
stub_status(status_reply('done'))
|
|
341
|
+
stub_request(:post, url('/v2/document/doc-1/result')).to_return(status: 200, body: 'x')
|
|
342
|
+
forms = capture_forms
|
|
343
|
+
|
|
344
|
+
documents.translate_document(input, output, 'EN', 'DE', nil, enable_watermark: true)
|
|
345
|
+
|
|
346
|
+
expect(form_fields(forms.first)).to include('enable_watermark' => 'true')
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it 'returns the number of bytes written' do
|
|
350
|
+
input, output = create_text_document(temp_dir)
|
|
351
|
+
stub_upload.to_return(upload_reply)
|
|
352
|
+
stub_status(status_reply('done', billed_characters: 11))
|
|
353
|
+
stub_request(:post, url('/v2/document/doc-1/result')).to_return(status: 200, body: 'x')
|
|
354
|
+
|
|
355
|
+
expect(documents.translate_document(input, output, 'EN', 'DE')).to eq(1)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it 'polls until the translation is done, beyond the former limit of 11 status requests' do
|
|
359
|
+
allow(DeepL::Utils::DocumentPoller).to receive(:sleep)
|
|
360
|
+
input, output = create_text_document(temp_dir)
|
|
361
|
+
stub_upload.to_return(upload_reply)
|
|
362
|
+
replies = Array.new(12) { status_reply('translating') } << status_reply('done')
|
|
363
|
+
stub = stub_status(*replies)
|
|
364
|
+
stub_request(:post, url('/v2/document/doc-1/result')).to_return(status: 200, body: 'x')
|
|
365
|
+
|
|
366
|
+
documents.translate_document(input, output, 'EN', 'DE')
|
|
367
|
+
|
|
368
|
+
expect(stub).to have_been_requested.times(13)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
it 'raises DocumentTranslationError instead of downloading after max_doc_status_queries' do
|
|
372
|
+
DeepL.configuration.max_doc_status_queries = 0
|
|
373
|
+
input, output = create_text_document(temp_dir)
|
|
374
|
+
stub_upload.to_return(upload_reply)
|
|
375
|
+
stub_status(status_reply('translating'))
|
|
376
|
+
|
|
377
|
+
expect { documents.translate_document(input, output, 'EN', 'DE') }
|
|
378
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError, /not done/)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it 'raises DocumentTranslationError and keeps the file when the output file already exists' do
|
|
382
|
+
input, output = create_text_document(temp_dir)
|
|
383
|
+
File.write(output, 'keep me')
|
|
384
|
+
|
|
385
|
+
expect { documents.translate_document(input, output, 'EN', 'DE') }
|
|
386
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError,
|
|
387
|
+
'File already exists at output path') { |e|
|
|
388
|
+
expect(e.document_handle).to be_nil
|
|
389
|
+
}
|
|
390
|
+
expect(File.read(output)).to eq('keep me')
|
|
391
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
it 'raises DocumentTranslationError for an unreadable input without other libraries loaded' do
|
|
395
|
+
script = <<~RUBY
|
|
396
|
+
DeepL.configure { |config| config.auth_key = 'k' }
|
|
397
|
+
begin
|
|
398
|
+
DeepL.document.translate_document('/nonexistent/in.txt', '/nonexistent/out.txt', 'EN', 'DE')
|
|
399
|
+
rescue StandardError => e
|
|
400
|
+
puts e.class
|
|
401
|
+
end
|
|
402
|
+
RUBY
|
|
403
|
+
lib = File.expand_path('../../lib', __dir__)
|
|
404
|
+
|
|
405
|
+
# stdout only: stderr carries the deprecation warning of the module API.
|
|
406
|
+
output, = Open3.capture3({ 'PATH' => ENV.fetch('PATH') }, RbConfig.ruby, '-I', lib, '-r',
|
|
407
|
+
'deepl', '-e', script, unsetenv_others: true)
|
|
408
|
+
|
|
409
|
+
expect(output.strip).to eq('DeepL::Exceptions::DocumentTranslationError')
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it 'raises DocumentTranslationError with the API message when the translation fails' do
|
|
413
|
+
input, output = create_text_document(temp_dir)
|
|
414
|
+
stub_upload.to_return(upload_reply)
|
|
415
|
+
stub_status(status_reply('error', error_message: 'Unsupported file'))
|
|
416
|
+
|
|
417
|
+
expect { documents.translate_document(input, output, 'EN', 'DE') }
|
|
418
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError, /Unsupported file/) { |e|
|
|
419
|
+
expect(e.document_handle.document_id).to eq('doc-1')
|
|
420
|
+
}
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'raises DocumentTranslationError and leaves no output file when the download fails' do
|
|
424
|
+
input, output = create_text_document(temp_dir)
|
|
425
|
+
stub_upload.to_return(upload_reply)
|
|
426
|
+
stub_status(status_reply('done'))
|
|
427
|
+
stub_request(:post,
|
|
428
|
+
url('/v2/document/doc-1/result')).to_return(reply({ message: 'gone' }, 404))
|
|
429
|
+
|
|
430
|
+
expect { documents.translate_document(input, output, 'EN', 'DE') }
|
|
431
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError, /gone/)
|
|
432
|
+
expect(File.exist?(output)).to be(false)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# The client has its own key and server, different from the module configuration, so a
|
|
437
|
+
# request that falls back to module state hits no stub.
|
|
438
|
+
describe 'DeepL::Client document translation' do
|
|
439
|
+
let(:client) { DeepL::Client.new('client-key', server_url: 'https://client.example.test') }
|
|
440
|
+
let(:poller) { DeepL::Utils::DocumentPoller }
|
|
441
|
+
|
|
442
|
+
def url(path)
|
|
443
|
+
"https://client.example.test#{path}"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def json_headers
|
|
447
|
+
{ 'Authorization' => 'DeepL-Auth-Key client-key', 'Content-Type' => 'application/json' }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def stub_download(body = 'Protonenstrahl')
|
|
451
|
+
stub_request(:post, url('/v2/document/doc-1/result'))
|
|
452
|
+
.with(body: document_key_body, headers: json_headers).to_return(status: 200, body: body)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
describe '#translate_document_upload' do
|
|
456
|
+
it 'sends the filename option as the filename field and returns the handle' do
|
|
457
|
+
input, = create_text_document(temp_dir)
|
|
458
|
+
stub_upload.to_return(upload_reply)
|
|
459
|
+
forms = capture_forms
|
|
460
|
+
|
|
461
|
+
result = client.translate_document_upload(input, 'EN', 'DE', filename: 'name.docx',
|
|
462
|
+
formality: 'more')
|
|
463
|
+
|
|
464
|
+
expect(form_fields(forms.first)).to eq('source_lang' => 'EN', 'target_lang' => 'DE',
|
|
465
|
+
'filename' => 'name.docx', 'formality' => 'more')
|
|
466
|
+
expect([result.document_id, result.document_key]).to eq(%w[doc-1 key-1])
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it 'raises ArgumentError for a missing input file before any request' do
|
|
470
|
+
expect { client.translate_document_upload(File.join(temp_dir, 'missing.txt'), nil, 'DE') }
|
|
471
|
+
.to raise_error(ArgumentError, /Input file does not exist/)
|
|
472
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
describe '#translate_document_get_status' do
|
|
477
|
+
it 'sends the document key and parses the status' do
|
|
478
|
+
stub_status(status_reply('translating', seconds_remaining: 3, billed_characters: 11))
|
|
479
|
+
|
|
480
|
+
status = client.translate_document_get_status(handle)
|
|
481
|
+
|
|
482
|
+
expect([status.status, status.seconds_remaining, status.billed_characters, status.ok?])
|
|
483
|
+
.to eq(['translating', 3, 11, true])
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
describe '#translate_document_wait_until_done' do
|
|
488
|
+
before { allow(poller).to receive(:sleep) }
|
|
489
|
+
|
|
490
|
+
it 'polls every 5 seconds, past unknown statuses, until done and returns the status' do
|
|
491
|
+
stub = stub_status(status_reply('queued'), status_reply('new_status'),
|
|
492
|
+
status_reply('done', billed_characters: 11))
|
|
493
|
+
expect(poller).to receive(:sleep).with(5).twice
|
|
494
|
+
|
|
495
|
+
status = client.translate_document_wait_until_done(handle)
|
|
496
|
+
|
|
497
|
+
expect(stub).to have_been_requested.times(3)
|
|
498
|
+
expect([status.status, status.billed_characters]).to eq(['done', 11])
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
it 'raises Error with the API message when the translation fails' do
|
|
502
|
+
stub_status(status_reply('error', error_message: 'Unsupported file'))
|
|
503
|
+
|
|
504
|
+
expect { client.translate_document_wait_until_done(handle) }
|
|
505
|
+
.to raise_error(DeepL::Exceptions::Error, /Unsupported file/)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
it 'raises Error when timeout_s expires, sleeping no longer than the time left' do
|
|
509
|
+
allow(poller).to receive(:monotonic_now).and_return(0, 3, 8)
|
|
510
|
+
stub = stub_status(status_reply('translating'))
|
|
511
|
+
expect(poller).to receive(:sleep).with(4).once
|
|
512
|
+
|
|
513
|
+
expect { client.translate_document_wait_until_done(handle, timeout_s: 7) }
|
|
514
|
+
.to raise_error(DeepL::Exceptions::Error, /within 7 seconds/)
|
|
515
|
+
expect(stub).to have_been_requested.twice
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
describe '#translate_document_download' do
|
|
520
|
+
it 'writes the translated document and returns nil' do
|
|
521
|
+
output = File.join(temp_dir, 'result.docx')
|
|
522
|
+
stub_download(Fixtures::BINARY_CONTENT)
|
|
523
|
+
|
|
524
|
+
expect(client.translate_document_download(handle, output)).to be_nil
|
|
525
|
+
expect(File.binread(output)).to eq(Fixtures::BINARY_CONTENT)
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
describe '#translate_document' do
|
|
530
|
+
before { allow(poller).to receive(:sleep) }
|
|
531
|
+
|
|
532
|
+
it 'uploads with the options, waits, downloads and returns the final status' do # rubocop:disable RSpec/ExampleLength
|
|
533
|
+
input, output = create_text_document(temp_dir)
|
|
534
|
+
stub_upload.to_return(upload_reply)
|
|
535
|
+
stub_status(status_reply('translating'), status_reply('done', billed_characters: 11))
|
|
536
|
+
stub_download
|
|
537
|
+
forms = capture_forms
|
|
538
|
+
|
|
539
|
+
status = client.translate_document(input, output, 'EN', 'DE', filename: 'name.txt',
|
|
540
|
+
glossary: 'g-1')
|
|
541
|
+
|
|
542
|
+
expect(form_fields(forms.first))
|
|
543
|
+
.to include('filename' => 'name.txt', 'glossary_id' => 'g-1')
|
|
544
|
+
expect([status.status, status.billed_characters]).to eq(['done', 11])
|
|
545
|
+
expect(File.read(output)).to eq('Protonenstrahl')
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
it 'raises DocumentTranslationError with the handle when timeout_s expires' do
|
|
549
|
+
allow(poller).to receive(:monotonic_now).and_return(0, 2)
|
|
550
|
+
input, output = create_text_document(temp_dir)
|
|
551
|
+
stub_upload.to_return(upload_reply)
|
|
552
|
+
stub_status(status_reply('queued'))
|
|
553
|
+
|
|
554
|
+
expect { client.translate_document(input, output, 'EN', 'DE', timeout_s: 1) }
|
|
555
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError, /within 1 seconds/) { |e|
|
|
556
|
+
expect(e.document_handle.document_id).to eq('doc-1')
|
|
557
|
+
}
|
|
558
|
+
expect(File.exist?(output)).to be(false)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
it 'raises DocumentTranslationError without a handle when the upload fails' do
|
|
562
|
+
input, output = create_text_document(temp_dir)
|
|
563
|
+
stub_upload.to_return(reply({ message: 'bad' }, 400))
|
|
564
|
+
|
|
565
|
+
expect { client.translate_document(input, output, 'EN', 'DE') }
|
|
566
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError, /bad/) { |e|
|
|
567
|
+
expect(e.document_handle).to be_nil
|
|
568
|
+
}
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
it 'copies the HTTP status and retryability of the DeepL error it wraps' do
|
|
572
|
+
client = DeepL::Client.new('client-key', server_url: 'https://client.example.test',
|
|
573
|
+
max_retries: 0)
|
|
574
|
+
input, output = create_text_document(temp_dir)
|
|
575
|
+
stub_upload.to_return(reply({ message: 'unavailable' }, 503))
|
|
576
|
+
|
|
577
|
+
expect { client.translate_document(input, output, 'EN', 'DE') }
|
|
578
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError) { |e|
|
|
579
|
+
expect([e.http_status_code, e.should_retry?]).to eq([503, true])
|
|
580
|
+
}
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
it 'raises DocumentTranslationError and keeps the file when the output file already exists' do
|
|
584
|
+
input, output = create_text_document(temp_dir)
|
|
585
|
+
File.write(output, 'keep me')
|
|
586
|
+
|
|
587
|
+
expect { client.translate_document(input, output, 'EN', 'DE') }
|
|
588
|
+
.to raise_error(DeepL::Exceptions::DocumentTranslationError,
|
|
589
|
+
'File already exists at output path') { |e|
|
|
590
|
+
expect([e.document_handle, e.http_status_code, e.should_retry?])
|
|
591
|
+
.to eq([nil, nil, false])
|
|
592
|
+
}
|
|
593
|
+
expect(File.read(output)).to eq('keep me')
|
|
594
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
it 'raises ArgumentError for a missing input file before any request' do
|
|
598
|
+
output = File.join(temp_dir, 'out.txt')
|
|
599
|
+
|
|
600
|
+
expect { client.translate_document(File.join(temp_dir, 'missing.txt'), output, 'EN', 'DE') }
|
|
601
|
+
.to raise_error(ArgumentError, /Input file does not exist/)
|
|
602
|
+
expect(a_request(:any, /.*/)).not_to have_been_made
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|