deepl-rb 3.7.0 → 3.9.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 +4 -2
- data/CHANGELOG.md +39 -1
- data/Gemfile +0 -2
- data/README.md +225 -16
- data/VERSION +1 -1
- data/deepl-rb.gemspec +52 -19
- data/lib/deepl/document_api.rb +11 -1
- data/lib/deepl/requests/base.rb +55 -3
- data/lib/deepl/requests/document/upload.rb +50 -1
- data/lib/deepl/requests/rephrase.rb +3 -2
- data/lib/deepl/requests/translate.rb +6 -3
- data/lib/deepl/requests/translation_memory/base.rb +47 -0
- data/lib/deepl/requests/translation_memory/create_export.rb +41 -0
- data/lib/deepl/requests/translation_memory/create_import.rb +53 -0
- data/lib/deepl/requests/translation_memory/destroy.rb +37 -0
- data/lib/deepl/requests/translation_memory/download_export.rb +35 -0
- data/lib/deepl/requests/translation_memory/find.rb +38 -0
- data/lib/deepl/requests/translation_memory/find_job.rb +38 -0
- data/lib/deepl/requests/translation_memory/list.rb +5 -18
- data/lib/deepl/requests/translation_memory/segments.rb +46 -0
- data/lib/deepl/requests/translation_memory/storage_base.rb +47 -0
- data/lib/deepl/requests/translation_memory/upload_file.rb +47 -0
- data/lib/deepl/resources/translation_memory.rb +4 -1
- data/lib/deepl/resources/translation_memory_export.rb +37 -0
- data/lib/deepl/resources/translation_memory_import.rb +29 -0
- data/lib/deepl/resources/translation_memory_job.rb +142 -0
- data/lib/deepl/resources/translation_memory_segments.rb +78 -0
- data/lib/deepl/translation_memory_api.rb +293 -1
- data/lib/deepl/utils/time_parser.rb +26 -0
- data/lib/deepl.rb +38 -4
- data/lib/version.rb +1 -1
- data/spec/api/deepl_spec.rb +134 -332
- data/spec/integration_tests/document_api_spec.rb +51 -18
- data/spec/integration_tests/document_error_paths_spec.rb +33 -0
- data/spec/integration_tests/glossary_api_spec.rb +114 -0
- data/spec/integration_tests/glossary_error_paths_spec.rb +107 -0
- data/spec/integration_tests/integration_test_utils.rb +8 -0
- data/spec/integration_tests/languages_api_spec.rb +54 -0
- data/spec/integration_tests/languages_error_paths_spec.rb +25 -0
- data/spec/integration_tests/rephrase_api_spec.rb +90 -0
- data/spec/integration_tests/rephrase_error_paths_spec.rb +53 -0
- data/spec/integration_tests/smoke_test_spec.rb +24 -0
- data/spec/integration_tests/style_rule_api_spec.rb +1 -19
- data/spec/integration_tests/style_rule_error_paths_spec.rb +45 -0
- data/spec/integration_tests/translate_api_spec.rb +117 -0
- data/spec/integration_tests/translate_error_paths_spec.rb +48 -0
- data/spec/integration_tests/translation_memory_api_spec.rb +191 -17
- data/spec/integration_tests/translation_memory_error_paths_spec.rb +87 -0
- data/spec/integration_tests/usage_api_spec.rb +29 -0
- data/spec/integration_tests/usage_error_paths_spec.rb +18 -0
- data/spec/requests/document/upload_spec.rb +130 -0
- data/spec/requests/glossary/create_spec.rb +0 -21
- data/spec/requests/glossary/destroy_spec.rb +0 -39
- data/spec/requests/glossary/entries_spec.rb +0 -35
- data/spec/requests/glossary/find_spec.rb +0 -40
- data/spec/requests/glossary/language_pairs_spec.rb +0 -13
- data/spec/requests/glossary/list_spec.rb +0 -27
- data/spec/requests/languages_spec.rb +0 -41
- data/spec/requests/rephrase_spec.rb +13 -139
- data/spec/requests/style_rule/create_custom_instruction_spec.rb +0 -24
- data/spec/requests/style_rule/create_spec.rb +0 -16
- data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +0 -26
- data/spec/requests/style_rule/destroy_spec.rb +0 -27
- data/spec/requests/style_rule/find_custom_instruction_spec.rb +0 -27
- data/spec/requests/style_rule/find_spec.rb +0 -28
- data/spec/requests/style_rule/list_spec.rb +0 -31
- data/spec/requests/style_rule/update_configured_rules_spec.rb +0 -21
- data/spec/requests/style_rule/update_custom_instruction_spec.rb +0 -26
- data/spec/requests/style_rule/update_spec.rb +0 -19
- data/spec/requests/translate_spec.rb +63 -217
- data/spec/requests/translation_memory/create_export_spec.rb +37 -0
- data/spec/requests/translation_memory/create_import_spec.rb +61 -0
- data/spec/requests/translation_memory/destroy_spec.rb +36 -0
- data/spec/requests/translation_memory/download_export_spec.rb +54 -0
- data/spec/requests/translation_memory/find_job_spec.rb +37 -0
- data/spec/requests/translation_memory/find_spec.rb +36 -0
- data/spec/requests/translation_memory/list_spec.rb +0 -34
- data/spec/requests/translation_memory/segments_spec.rb +58 -0
- data/spec/requests/translation_memory/upload_file_spec.rb +55 -0
- data/spec/requests/usage_spec.rb +0 -16
- data/spec/resources/custom_instruction_spec.rb +32 -0
- data/spec/resources/style_rule_spec.rb +68 -0
- data/spec/resources/translation_memory_export_spec.rb +38 -0
- data/spec/resources/translation_memory_import_spec.rb +30 -0
- data/spec/resources/translation_memory_job_spec.rb +109 -0
- data/spec/resources/translation_memory_segments_spec.rb +78 -0
- data/spec/resources/translation_memory_spec.rb +18 -1
- data/spec/spec_helper.rb +15 -45
- data/spec/support/live_mock_server.rb +12 -0
- data/spec/support/managed_glossary.rb +65 -0
- data/spec/support/managed_style_rule.rb +17 -0
- data/spec/support/managed_translation_memory.rb +55 -0
- metadata +50 -17
- data/spec/fixtures/vcr_cassettes/deepl_document.yml +0 -95
- data/spec/fixtures/vcr_cassettes/deepl_document_download.yml +0 -1214
- data/spec/fixtures/vcr_cassettes/deepl_glossaries.yml +0 -1163
- data/spec/fixtures/vcr_cassettes/deepl_languages.yml +0 -54
- data/spec/fixtures/vcr_cassettes/deepl_rephrase.yml +0 -87
- data/spec/fixtures/vcr_cassettes/deepl_translate.yml +0 -358
- data/spec/fixtures/vcr_cassettes/deepl_usage.yml +0 -129
- data/spec/fixtures/vcr_cassettes/glossaries.yml +0 -1702
- data/spec/fixtures/vcr_cassettes/languages.yml +0 -229
- data/spec/fixtures/vcr_cassettes/rephrase_texts.yml +0 -401
- data/spec/fixtures/vcr_cassettes/style_rules.yml +0 -92
- data/spec/fixtures/vcr_cassettes/style_rules_crud.yml +0 -926
- data/spec/fixtures/vcr_cassettes/translate_texts.yml +0 -10630
- data/spec/fixtures/vcr_cassettes/translation_memories.yml +0 -74
- data/spec/fixtures/vcr_cassettes/usage.yml +0 -171
|
@@ -0,0 +1,58 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Requests::TranslationMemory::Segments do
|
|
9
|
+
subject(:translation_memory_segments) { described_class.new(api, id, options) }
|
|
10
|
+
|
|
11
|
+
around do |tests|
|
|
12
|
+
tmp_env = replace_env_preserving_deepl_vars_except_mock_server
|
|
13
|
+
tests.call
|
|
14
|
+
ENV.replace(tmp_env)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:api) { build_deepl_api }
|
|
18
|
+
let(:id) { 'a74d88fb-ed2a-4943-a664-a4512398b994' }
|
|
19
|
+
let(:options) { {} }
|
|
20
|
+
|
|
21
|
+
describe '#initialize' do
|
|
22
|
+
context 'when building a request' do
|
|
23
|
+
it 'creates a request object' do
|
|
24
|
+
expect(translation_memory_segments).to be_a(described_class)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#to_s' do
|
|
30
|
+
context 'when no options are given' do
|
|
31
|
+
it 'requests the segments below the v3 prefix without a query string' do
|
|
32
|
+
expect(translation_memory_segments.to_s)
|
|
33
|
+
.to eq("GET /v3/translation_memories/#{id}/segments")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'when the supported options are given' do
|
|
38
|
+
let(:options) { { page_size: 10, page_cursor: 'NQ', filter_case_sensitive: true } }
|
|
39
|
+
|
|
40
|
+
it 'adds the options to the query string' do
|
|
41
|
+
expect(translation_memory_segments.to_s)
|
|
42
|
+
.to eq("GET /v3/translation_memories/#{id}/segments" \
|
|
43
|
+
'?page_size=10&page_cursor=NQ&filter_case_sensitive=true')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'when the text filter contains characters that must be escaped' do
|
|
48
|
+
let(:options) { { filter_text: 'a b+c' } }
|
|
49
|
+
|
|
50
|
+
# A space must be percent-encoded in a URI query string; `+` only means a space in a form
|
|
51
|
+
# body, and a literal `+` has to survive as %2B.
|
|
52
|
+
it 'percent-encodes the spaces instead of writing them as a plus' do
|
|
53
|
+
expect(translation_memory_segments.to_s)
|
|
54
|
+
.to eq("GET /v3/translation_memories/#{id}/segments?filter_text=a%20b%2Bc")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Requests::TranslationMemory::UploadFile do
|
|
9
|
+
subject(:upload_file) { described_class.new(api, upload_url, file_content) }
|
|
10
|
+
|
|
11
|
+
around do |tests|
|
|
12
|
+
tmp_env = replace_env_preserving_deepl_vars_except_mock_server
|
|
13
|
+
tests.call
|
|
14
|
+
ENV.replace(tmp_env)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:api) { build_deepl_api }
|
|
18
|
+
let(:upload_url) { 'https://storage.example.com/upload/tm?signature=abc' }
|
|
19
|
+
let(:file_content) { '<tmx version="1.4"/>' }
|
|
20
|
+
|
|
21
|
+
describe '#initialize' do
|
|
22
|
+
context 'when building a request' do
|
|
23
|
+
it 'creates a request object' do
|
|
24
|
+
expect(upload_file).to be_a(described_class)
|
|
25
|
+
expect(upload_file.content_type).to eq(described_class::DEFAULT_CONTENT_TYPE)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when no upload URL is given' do
|
|
30
|
+
let(:upload_url) { '' }
|
|
31
|
+
|
|
32
|
+
it 'raises an error' do
|
|
33
|
+
expect { upload_file }.to raise_error(DeepL::Exceptions::Error, /must not be empty/)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#to_s' do
|
|
39
|
+
context 'when building a request' do
|
|
40
|
+
it 'puts to the pre-signed storage URL' do
|
|
41
|
+
expect(upload_file.to_s).to eq('PUT /upload/tm?signature=abc')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#details' do
|
|
47
|
+
context 'when building a request' do
|
|
48
|
+
# The upload URL points outside of the DeepL API, so the auth key must not be sent there.
|
|
49
|
+
it 'does not send the DeepL authorization header' do
|
|
50
|
+
expect(upload_file.details).not_to include('Authorization')
|
|
51
|
+
expect(upload_file.details).to include('User-Agent')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/spec/requests/usage_spec.rb
CHANGED
|
@@ -24,20 +24,4 @@ describe DeepL::Requests::Usage do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
describe '#request' do
|
|
29
|
-
around do |example|
|
|
30
|
-
VCR.use_cassette('usage') { example.call }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
context 'when performing a valid request' do
|
|
34
|
-
it 'returns an usage object' do
|
|
35
|
-
usage = usage_req.request
|
|
36
|
-
|
|
37
|
-
expect(usage).to be_a(DeepL::Resources::Usage)
|
|
38
|
-
expect(usage.character_count).to be_a(Numeric)
|
|
39
|
-
expect(usage.character_limit).to be_a(Numeric)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
27
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::CustomInstruction do
|
|
9
|
+
subject(:custom_instruction) do
|
|
10
|
+
described_class.new({
|
|
11
|
+
'id' => 'ci-1',
|
|
12
|
+
'label' => 'Formal',
|
|
13
|
+
'prompt' => 'Always use formal language',
|
|
14
|
+
'source_language' => 'en'
|
|
15
|
+
})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#initialize' do
|
|
19
|
+
context 'when building a basic object' do
|
|
20
|
+
it 'creates a resource' do
|
|
21
|
+
expect(custom_instruction).to be_a(described_class)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'assigns the attributes' do
|
|
25
|
+
expect(custom_instruction.id).to eq('ci-1')
|
|
26
|
+
expect(custom_instruction.label).to eq('Formal')
|
|
27
|
+
expect(custom_instruction.prompt).to eq('Always use formal language')
|
|
28
|
+
expect(custom_instruction.source_language).to eq('en')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::StyleRule do
|
|
9
|
+
subject(:style_rule) do
|
|
10
|
+
described_class.new({
|
|
11
|
+
'style_id' => 'dca2e053-8ae5-45e6-a0d2-881156e7f4e4',
|
|
12
|
+
'name' => 'Default Style Rule',
|
|
13
|
+
'creation_time' => '2025-01-01T00:00:00Z',
|
|
14
|
+
'updated_time' => '2025-01-02T00:00:00Z',
|
|
15
|
+
'language' => 'en',
|
|
16
|
+
'version' => 1
|
|
17
|
+
}, nil, nil)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#initialize' do
|
|
21
|
+
context 'when building a basic object' do
|
|
22
|
+
it 'creates a resource' do
|
|
23
|
+
expect(style_rule).to be_a(described_class)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'assigns the attributes' do
|
|
27
|
+
expect(style_rule.style_id).to eq('dca2e053-8ae5-45e6-a0d2-881156e7f4e4')
|
|
28
|
+
expect(style_rule.name).to eq('Default Style Rule')
|
|
29
|
+
expect(style_rule.language).to eq('en')
|
|
30
|
+
expect(style_rule.version).to eq(1)
|
|
31
|
+
expect(style_rule.creation_time).to eq(Time.parse('2025-01-01T00:00:00Z'))
|
|
32
|
+
expect(style_rule.updated_time).to eq(Time.parse('2025-01-02T00:00:00Z'))
|
|
33
|
+
expect(style_rule.configured_rules).to be_nil
|
|
34
|
+
expect(style_rule.custom_instructions).to be_nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when building an object with configured rules and custom instructions' do
|
|
39
|
+
subject(:style_rule) do
|
|
40
|
+
described_class.new({
|
|
41
|
+
'style_id' => 'dca2e053-8ae5-45e6-a0d2-881156e7f4e4',
|
|
42
|
+
'name' => 'Detailed Style Rule',
|
|
43
|
+
'language' => 'en',
|
|
44
|
+
'version' => 1,
|
|
45
|
+
'configured_rules' => {
|
|
46
|
+
'dates_and_times' => { 'calendar_era' => 'use_bc_and_ad' }
|
|
47
|
+
},
|
|
48
|
+
'custom_instructions' => [
|
|
49
|
+
{ 'id' => 'ci-1', 'label' => 'Test', 'prompt' => 'Be formal' }
|
|
50
|
+
]
|
|
51
|
+
}, nil, nil)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'maps configured_rules into a ConfiguredRules resource' do
|
|
55
|
+
expect(style_rule.configured_rules).to be_a(DeepL::Resources::ConfiguredRules)
|
|
56
|
+
expect(style_rule.configured_rules.dates_and_times)
|
|
57
|
+
.to eq({ 'calendar_era' => 'use_bc_and_ad' })
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'maps custom_instructions into an array of CustomInstruction resources' do
|
|
61
|
+
expect(style_rule.custom_instructions).to all(be_a(DeepL::Resources::CustomInstruction))
|
|
62
|
+
expect(style_rule.custom_instructions.first.id).to eq('ci-1')
|
|
63
|
+
expect(style_rule.custom_instructions.first.label).to eq('Test')
|
|
64
|
+
expect(style_rule.custom_instructions.first.prompt).to eq('Be formal')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::TranslationMemoryExport do
|
|
9
|
+
subject(:translation_memory_export) { described_class.new(response, false, nil, nil) }
|
|
10
|
+
|
|
11
|
+
let(:response) do
|
|
12
|
+
{
|
|
13
|
+
'job_id' => '5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d',
|
|
14
|
+
'parameters' => { 'translation_memory_id' => 'a74d88fb-ed2a-4943-a664-a4512398b994' }
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#initialize' do
|
|
19
|
+
it 'creates a resource' do
|
|
20
|
+
expect(translation_memory_export).to be_a(described_class)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'assigns the attributes of a newly created export' do
|
|
24
|
+
expect(translation_memory_export.job_id).to eq('5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d')
|
|
25
|
+
expect(translation_memory_export.translation_memory_id)
|
|
26
|
+
.to eq('a74d88fb-ed2a-4943-a664-a4512398b994')
|
|
27
|
+
expect(translation_memory_export.reused_existing?).to be(false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when the API reused a previously completed export' do
|
|
31
|
+
subject(:translation_memory_export) { described_class.new(response, true, nil, nil) }
|
|
32
|
+
|
|
33
|
+
it 'reports the export as reused' do
|
|
34
|
+
expect(translation_memory_export.reused_existing?).to be(true)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::TranslationMemoryImport do
|
|
9
|
+
subject(:translation_memory_import) do
|
|
10
|
+
described_class.new({
|
|
11
|
+
'job_id' => '5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d',
|
|
12
|
+
'upload_url' => 'https://storage.example.com/upload',
|
|
13
|
+
'expires_at' => '2026-01-01T00:00:00Z'
|
|
14
|
+
}, nil, nil)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#initialize' do
|
|
18
|
+
it 'creates a resource' do
|
|
19
|
+
expect(translation_memory_import).to be_a(described_class)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'assigns the attributes' do
|
|
23
|
+
expect(translation_memory_import.job_id).to eq('5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d')
|
|
24
|
+
expect(translation_memory_import.upload_url).to eq('https://storage.example.com/upload')
|
|
25
|
+
expect(translation_memory_import.expires_at).to eq(Time.parse('2026-01-01T00:00:00Z'))
|
|
26
|
+
expect(translation_memory_import.to_s)
|
|
27
|
+
.to eq('TranslationMemoryImport: ID: 5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::TranslationMemoryJob do
|
|
9
|
+
subject(:job) { described_class.new(response, nil, nil) }
|
|
10
|
+
|
|
11
|
+
let(:response) do
|
|
12
|
+
{
|
|
13
|
+
'job_id' => '5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d',
|
|
14
|
+
'product' => 'translation_memory',
|
|
15
|
+
'operation' => 'import',
|
|
16
|
+
'creation_time' => '2026-01-01T00:00:00Z',
|
|
17
|
+
'updated_time' => '2026-01-02T00:00:00Z',
|
|
18
|
+
'source_file' => { 'content_type' => 'application/xml', 'content_length' => 128 },
|
|
19
|
+
'parameters' => { 'display_name' => 'Legal' },
|
|
20
|
+
'results' => [
|
|
21
|
+
{ 'status' => 'completed', 'translation_memory_id' => 'tm-1',
|
|
22
|
+
'skipped_segment_count' => 2 }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#initialize' do
|
|
28
|
+
it 'creates a resource' do
|
|
29
|
+
expect(job).to be_a(described_class)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'assigns the job level attributes' do
|
|
33
|
+
expect(job.job_id).to eq('5b1a0a52-1f3b-4b7f-9f6b-9d4d3f5b6c7d')
|
|
34
|
+
expect(job.product).to eq('translation_memory')
|
|
35
|
+
expect(job.operation).to eq('import')
|
|
36
|
+
expect(job.creation_time).to eq(Time.parse('2026-01-01T00:00:00Z'))
|
|
37
|
+
expect(job.updated_time).to eq(Time.parse('2026-01-02T00:00:00Z'))
|
|
38
|
+
expect(job.display_name).to eq('Legal')
|
|
39
|
+
expect(job.source_content_type).to eq('application/xml')
|
|
40
|
+
expect(job.source_content_length).to eq(128)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'maps the single result and delegates the status helpers to it' do
|
|
44
|
+
expect(job.result).to be_a(DeepL::Resources::TranslationMemoryJobResult)
|
|
45
|
+
expect(job.status).to eq('completed')
|
|
46
|
+
expect(job.finished?).to be(true)
|
|
47
|
+
expect(job.awaiting_input?).to be(false)
|
|
48
|
+
expect(job.error?).to be(false)
|
|
49
|
+
expect(job.result.translation_memory_id).to eq('tm-1')
|
|
50
|
+
expect(job.result.skipped_segment_count).to eq(2)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when the job is awaiting its file upload' do
|
|
55
|
+
let(:response) do
|
|
56
|
+
{ 'job_id' => 'job-1', 'operation' => 'import',
|
|
57
|
+
'results' => [{ 'status' => 'awaiting_input',
|
|
58
|
+
'status_metadata' => { 'required_action' => 'Waiting for upload' } }] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'exposes the required action and does not report the job as finished' do
|
|
62
|
+
expect(job.awaiting_input?).to be(true)
|
|
63
|
+
expect(job.finished?).to be(false)
|
|
64
|
+
expect(job.result.required_action).to eq('Waiting for upload')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'when the export job completed' do
|
|
69
|
+
let(:response) do
|
|
70
|
+
{ 'job_id' => 'job-2', 'operation' => 'export',
|
|
71
|
+
'parameters' => { 'translation_memory_id' => 'tm-1' },
|
|
72
|
+
'results' => [{ 'status' => 'completed', 'download_url' => 'https://example.com/tmx',
|
|
73
|
+
'expires_at' => '2026-01-03T00:00:00Z' }] }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'exposes the download URL and its expiry' do
|
|
77
|
+
expect(job.translation_memory_id).to eq('tm-1')
|
|
78
|
+
expect(job.result.download_url).to eq('https://example.com/tmx')
|
|
79
|
+
expect(job.result.expires_at).to eq(Time.parse('2026-01-03T00:00:00Z'))
|
|
80
|
+
expect(job.to_s).to eq('TranslationMemoryJob: export - ID: job-2 - Status: completed')
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when the job failed' do
|
|
85
|
+
let(:response) do
|
|
86
|
+
{ 'job_id' => 'job-3', 'operation' => 'import',
|
|
87
|
+
'results' => [{ 'status' => 'failed', 'error' => { 'message' => 'Invalid TMX' } }] }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'reports the error and treats the job as finished' do
|
|
91
|
+
expect(job.error?).to be(true)
|
|
92
|
+
expect(job.finished?).to be(true)
|
|
93
|
+
expect(job.error_message).to eq('Invalid TMX')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'when the API returned no result' do
|
|
98
|
+
let(:response) { { 'job_id' => 'job-4', 'operation' => 'import' } }
|
|
99
|
+
|
|
100
|
+
it 'reports neither a status nor an error' do
|
|
101
|
+
expect(job.result).to be_nil
|
|
102
|
+
expect(job.status).to be_nil
|
|
103
|
+
expect(job.finished?).to be(false)
|
|
104
|
+
expect(job.awaiting_input?).to be(false)
|
|
105
|
+
expect(job.error?).to be(false)
|
|
106
|
+
expect(job.error_message).to be_nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
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.md file.
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
describe DeepL::Resources::TranslationMemorySegments do
|
|
9
|
+
subject(:segments_page) { described_class.new(response, nil, nil) }
|
|
10
|
+
|
|
11
|
+
let(:response) do
|
|
12
|
+
{
|
|
13
|
+
'segments' => [
|
|
14
|
+
{
|
|
15
|
+
'source_segment_id' => 'tm-1-source-0',
|
|
16
|
+
'source_text' => 'Quelltext Nummer 0',
|
|
17
|
+
'creation_time' => '2026-01-01T00:00:00Z',
|
|
18
|
+
'updated_time' => '2026-01-02T00:00:00Z',
|
|
19
|
+
'last_used_time' => '2026-01-03T00:00:00Z',
|
|
20
|
+
'targets' => [
|
|
21
|
+
{
|
|
22
|
+
'target_segment_id' => 'tm-1-target-en-0',
|
|
23
|
+
'target_language' => 'en',
|
|
24
|
+
'target_text' => 'Source text number 0',
|
|
25
|
+
'creation_time' => '2026-01-01T00:00:00Z',
|
|
26
|
+
'updated_time' => '2026-01-02T00:00:00Z',
|
|
27
|
+
'last_used_time' => '2026-01-03T00:00:00Z'
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
'segment_count' => 12,
|
|
33
|
+
'next_page_cursor' => 'NQ'
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#initialize' do
|
|
38
|
+
it 'creates a resource' do
|
|
39
|
+
expect(segments_page).to be_a(described_class)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'assigns the page level attributes' do
|
|
43
|
+
expect(segments_page.segment_count).to eq(12)
|
|
44
|
+
expect(segments_page.next_page_cursor).to eq('NQ')
|
|
45
|
+
expect(segments_page.next_page?).to be(true)
|
|
46
|
+
expect(segments_page.to_s).to eq('1 of 12 segment(s)')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'maps the segments into resources' do
|
|
50
|
+
segment = segments_page.segments.first
|
|
51
|
+
expect(segment).to be_a(DeepL::Resources::TranslationMemorySegment)
|
|
52
|
+
expect(segment.source_segment_id).to eq('tm-1-source-0')
|
|
53
|
+
expect(segment.source_text).to eq('Quelltext Nummer 0')
|
|
54
|
+
expect(segment.creation_time).to eq(Time.parse('2026-01-01T00:00:00Z'))
|
|
55
|
+
expect(segment.updated_time).to eq(Time.parse('2026-01-02T00:00:00Z'))
|
|
56
|
+
expect(segment.last_used_time).to eq(Time.parse('2026-01-03T00:00:00Z'))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'maps the target segments into resources' do
|
|
60
|
+
target = segments_page.segments.first.targets.first
|
|
61
|
+
expect(target).to be_a(DeepL::Resources::TranslationMemoryTargetSegment)
|
|
62
|
+
expect(target.target_segment_id).to eq('tm-1-target-en-0')
|
|
63
|
+
expect(target.target_language).to eq('en')
|
|
64
|
+
expect(target.target_text).to eq('Source text number 0')
|
|
65
|
+
expect(target.to_s).to eq('en: Source text number 0')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when the response is the last page' do
|
|
70
|
+
let(:response) { { 'segments' => [], 'segment_count' => 12 } }
|
|
71
|
+
|
|
72
|
+
it 'reports no further page' do
|
|
73
|
+
expect(segments_page.segments).to eq([])
|
|
74
|
+
expect(segments_page.next_page_cursor).to be_nil
|
|
75
|
+
expect(segments_page.next_page?).to be(false)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -12,7 +12,9 @@ describe DeepL::Resources::TranslationMemory do
|
|
|
12
12
|
'name' => 'Legal',
|
|
13
13
|
'source_language' => 'en',
|
|
14
14
|
'target_languages' => %w[es de],
|
|
15
|
-
'segment_count' => 3542
|
|
15
|
+
'segment_count' => 3542,
|
|
16
|
+
'creation_time' => '2026-01-01T00:00:00Z',
|
|
17
|
+
'updated_time' => '2026-01-02T00:00:00Z'
|
|
16
18
|
}, nil, nil)
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -29,6 +31,21 @@ describe DeepL::Resources::TranslationMemory do
|
|
|
29
31
|
expect(translation_memory.source_language).to eq('en')
|
|
30
32
|
expect(translation_memory.target_languages).to eq(%w[es de])
|
|
31
33
|
expect(translation_memory.segment_count).to eq(3542)
|
|
34
|
+
expect(translation_memory.creation_time).to eq(Time.parse('2026-01-01T00:00:00Z'))
|
|
35
|
+
expect(translation_memory.updated_time).to eq(Time.parse('2026-01-02T00:00:00Z'))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'when the optional timestamps are missing' do
|
|
40
|
+
subject(:translation_memory) do
|
|
41
|
+
described_class.new({ 'translation_memory_id' => 'tm-1', 'name' => 'Legal' }, nil, nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'leaves the timestamps nil and defaults the collections' do
|
|
45
|
+
expect(translation_memory.creation_time).to be_nil
|
|
46
|
+
expect(translation_memory.updated_time).to be_nil
|
|
47
|
+
expect(translation_memory.target_languages).to eq([])
|
|
48
|
+
expect(translation_memory.segment_count).to eq(0)
|
|
32
49
|
end
|
|
33
50
|
end
|
|
34
51
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -14,59 +14,29 @@ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
|
|
14
14
|
require_relative '../lib/deepl'
|
|
15
15
|
require_relative 'integration_tests/integration_test_utils'
|
|
16
16
|
|
|
17
|
+
# Auto-load test helpers under spec/support
|
|
18
|
+
Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f }
|
|
19
|
+
|
|
17
20
|
# Lib config
|
|
18
21
|
ENV['DEEPL_AUTH_KEY'] ||= 'TEST-TOKEN'
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
RSpec.configure do |config|
|
|
24
|
+
config.include ManagedGlossary
|
|
25
|
+
config.include ManagedStyleRule
|
|
26
|
+
config.include ManagedTranslationMemory
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
uri1 = request1.uri
|
|
28
|
-
uri2 = request2.uri
|
|
29
|
-
uri1_match = uri1.match(deepl_api_regexp)
|
|
30
|
-
uri2_match = uri2.match(deepl_api_regexp)
|
|
31
|
-
if uri1_match && uri2_match
|
|
32
|
-
uri1_without_deepl_domain = uri1.gsub(uri1_match[0], '')
|
|
33
|
-
uri2_without_deepl_domain = uri2.gsub(uri2_match[0], '')
|
|
34
|
-
uri1_without_deepl_domain == uri2_without_deepl_domain
|
|
35
|
-
else
|
|
36
|
-
uri1 == uri2
|
|
28
|
+
config.before(:each, :mock_server_only) do
|
|
29
|
+
skip 'Only runs on mock server' unless ENV.key?('DEEPL_MOCK_SERVER_PORT')
|
|
37
30
|
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
headers_ignoring_user_agent = lambda do |request1, request2|
|
|
41
|
-
user_agent_key = 'User-Agent'
|
|
42
|
-
# Pass by reference, so we need to use a copy of the headers
|
|
43
|
-
headers1 = request1.headers.dup
|
|
44
|
-
headers2 = request2.headers.dup
|
|
45
|
-
headers1_has_user_agent = headers1.key?(user_agent_key)
|
|
46
|
-
headers2_has_user_agent = headers2.key?(user_agent_key)
|
|
47
|
-
return false if headers1_has_user_agent != headers2_has_user_agent
|
|
48
31
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
config.before(:each, :real_server_only) do
|
|
33
|
+
skip 'Only runs on real server' if ENV.key?('DEEPL_MOCK_SERVER_PORT')
|
|
34
|
+
end
|
|
52
35
|
end
|
|
53
36
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
|
58
|
-
config.hook_into :webmock
|
|
59
|
-
config.filter_sensitive_data('VALID_TOKEN') { ENV.fetch('DEEPL_AUTH_KEY', nil) }
|
|
60
|
-
|
|
61
|
-
# to record new or missing VCR cassettes, call rspec like this:
|
|
62
|
-
# $ VCR_RECORD_MODE=new_episodes bundle exec rspec
|
|
63
|
-
|
|
64
|
-
record_mode = ENV['VCR_RECORD_MODE'] ? ENV['VCR_RECORD_MODE'].to_sym : :none
|
|
65
|
-
config.default_cassette_options = {
|
|
66
|
-
record: record_mode,
|
|
67
|
-
match_requests_on: [:method, uri_ignoring_deepl_api_subdomain, :body,
|
|
68
|
-
headers_ignoring_user_agent]
|
|
69
|
-
}
|
|
37
|
+
# General helpers
|
|
38
|
+
def build_deepl_api
|
|
39
|
+
DeepL::API.new(DeepL::Configuration.new)
|
|
70
40
|
end
|
|
71
41
|
|
|
72
42
|
# Test helpers
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
# Shared setup for integration specs that hit the live mock server: expose an
|
|
7
|
+
# API the server rejects as unauthorized without mutating global DeepL state.
|
|
8
|
+
RSpec.shared_context 'with a live mock server' do
|
|
9
|
+
let(:unauthorized_api) do
|
|
10
|
+
DeepL::API.new(DeepL::Configuration.new(auth_key: 'invalid'))
|
|
11
|
+
end
|
|
12
|
+
end
|